From 58ce849223667f77dc0d6d7658870ca3f815e17f Mon Sep 17 00:00:00 2001 From: Akash Satheesan Date: Fri, 30 Apr 2021 20:25:16 +0530 Subject: [PATCH] Squashed 'lib/vscode/' changes from 3c4e3df9e89..631dbe250bc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 631dbe250bc Merge pull request #122730 from microsoft/tyriar/121282 166d7fe44d9 Ensure inline action applies to terminal out of selection 921203d98a5 Revert "Use a menu instead of hard coding actions in tabsWidget (#122461)" 1ed6862c3ea more insights when execution integration tests fail e1a46ca69b5 fix #122699 (#122712) 6449b6b7253 Fixes #122714: Guard against negative numbers around indentation f2878212f72 Listen for workspace trust when upgrading tasks Fixes #122535 42cf2286c7a fixes microsoft/vscode-remote-release#4970 de6424abd01 remove workbench-web*html a348d103d12 debug: update companion version 2a9f903edef Fix #122257 c67711fdde3 Fix #120112 f375f7e0aa2 Bumps RemoteHub version 74454818fe9 Use single quotes instead of double quotes (#122703) 78577aa18da Merge branch 'notebook/dev' into main dd58b1fe2bf fix #121647 502ebdb1930 bump builtin modules a7dcfd1e2c3 fix #122608 (#122616) 14a3cec0d4a Fix #122360 33a9b2adb7d Add missing scope to settings 24ea8408c3d testing: propoagate timeout option for unit tests 8dcc489fa1b Re-enables Git extension for virtual workspaces 03393445797 pass configuration in from processManager -> remoteTerminalService e7c0af1137f Revert "Revert "Merge pull request #122540 from microsoft/tyriar/122104"" 024251c2be2 fix #120848. f0394ca9663 Updates endgame notebook repos 160b5509d2b re #120545 f9901cea702 Fix markdown cell folding issue Fix #122675 bdb2324c9da revert change to hide condition 5d5d7389888 fix #122334 ed52d777ef6 fix integration tests 1846aeaf8c7 testing: fix welcome view being shown if tests were synchronously available 62b609670ed Clear lastRunSuccess when clearing output Fix #122645 f5bef964648 change wording back to kernel, https://github.com/microsoft/vscode/issues/122415#issuecomment-829329529 6102a7d2636 Fix running 19c6d913ca9 Add noop command (fixes #122518) df7aae61657 Add flag on whether a contribution can support a resource (#122658) de6057e397e fixes #122656 439ec79e6be Fix #122637 3d6f88eb925 fixes #120828 2eb378324d0 fixes #121392 b2d6cab9985 Revert "chore: bump electron@12.0.5" 4b06c752c6a fixes #121425 ae82935e448 fixes #121503 7d51e0b1eb6 fixes #122289 d295d140849 fixes #122353 83c7bc3dbf1 allow controller picker with single item, https://github.com/microsoft/vscode/issues/122415 1cc7abafb0d Drive users to picker when having multiple potential notebook controllers, https://github.com/microsoft/vscode/issues/122415 ed5d4a1329a Revert "do not auto open pasted file" 3bb2d0c3a21 fixes #122354 7fa8cdf44ef fixes #122079 677be5b44b0 change vs select wording tweaks, https://github.com/microsoft/vscode/issues/122415 3162eac1f9b update distro e6b871d094d show placeholder text for kernel picker, https://github.com/microsoft/vscode/issues/122415 eeceecef81e update doc 780eb163b7a Improve capabilities.untrustedWorkspaces contribution (#122603) 5682823e663 improve virtual workspace disablement wording 0123d26aaa3 tweak setting config 2f915a24b40 Don't label custom editors are readonly if the editor is not editable 28a1bc2f35e Revert "Merge pull request #122540 from microsoft/tyriar/122104" 3915fcfe493 Add some links and small cleanup for JS/TS settings e8eb44523ef Improve auto-fetch setting display a4fdb2c3ad0 Make adding overrides an preferred quick fix 9064c909d70 chore: bump electron@12.0.5 7adfba6262f Remove no implicit override task 60f46f2f7a8 Adding JS and PY Azure SDK packages (#122468) f15ac1527ff fix unit tests for web 75f4c87a854 fix #122548. c56f5ea118b fix #122359. 2858d5f3a75 remove trust from web 6a841dd8d5a Remove extra padding on h1s in markdown cells 40e0a578a80 Merge pull request #122505 from joyceerhl/dev/joyceerhl/markdown-cell-padding 033f6aac860 Fix missed Previewer.plain call due to adjacent @ts-expect-error (#121975) f17e1ce19d0 Update distro f8033ad59a2 xterm@4.12.0-beta26 3719898cb81 Merge branch 'notebook/dev' into main bf7d8da5f0f Fix #122360 2f5e355bea7 fix #122329 (#122576) 968d6702663 testing: fix welcome view being shown incorrectly 7770708545c Reapply markup renderers change c8c96aae9d5 Fixes # 122567: regex included .git in capture 5082e72921d Avoid cell execution icon flickering due to rerendering spinner 201063aa2ef Fix missing cell execution icon d89a35b91fb API TODO 107351bc206 debug: use category instead of tag for search 82399e71c2d Even more port forwarding logging 29b49a03928 fixes #122520 fixes #122393 26ce0255947 fixes #122310 1bd250ca513 Set tunnel factory earlier (#122557) a8a1b0f6db1 fix #122452 3b1b002b039 testing: mark runs complete if trust is not given 606bf316c73 Fix #122544 6f6b8156506 Call cell statusbar providers after changes are sent to the EH Fix #122341 99f8682e29a Fix inherited split cwd 5728f84b8c3 Merge pull request #122399 from solomatov/ext-host-hang 24faa3733fb Resolve profile environment when launching default 13d3c0e7575 Add explicit dispose calls for `DiskFileSystemProvider` in tests (#121857) e2eb2ec0d75 Add enum descriptions to tabs settings 398a23805b3 Fix #122311 06694a8e577 Merge pull request #122540 from microsoft/tyriar/122104 36fe84236dc Rename hideForSingle to hideCondition 1e9c0a643e4 Fix notebook cell collapse action Fix #122318 935fb2b33c1 Merge remote-tracking branch 'origin/main' into tyriar/122104 1f913a81b16 Revert "better fix for https://github.com/microsoft/vscode/issues/119943" 134f551f210 Revert "remove unused code after changes for https://github.com/microsoft/vscode/issues/119943" 3ea6e11841a requiresTrust -> restricted a9ad5d9eacc Reinstall sash listener after view swap b3292e6fff7 Merge branch 'main' into tyriar/122104 85ed0a54d12 Fix #122543 aa1b82bf134 Fix #122477 05658fe91ea remove unused code after changes for https://github.com/microsoft/vscode/issues/119943 0877dc2131f better fix for https://github.com/microsoft/vscode/issues/119943 01a6c000ce4 Fix compile 10a5077e0b9 Bring back workspace shells via setting c76579365d1 Fix #122533 5f9336443fb Task upgrade requires trust Fixes #122535 d0fceb64b42 fix disposal of extension-triggered document references when file is deleted, https://github.com/microsoft/vscode/issues/119943 8f45bda14f1 Enable some upgrade of tasks with global os config Fixes #122435 0be4e2958f9 Fix #122488 b98c1dd7eb6 Remove no longer relevant comment d0d615d1e4c Improve plural language in task upgrade notification Fixes #122437 dc5219000dc Fixes #122333 636583b1cd7 Use schema version as task upgrade trigger Fixes #122442 a613882bbdb Task upgrade shouldn't stop if tasks.json.old exists Fixes #122439 e2044db595d Don't include empty arrays in task upgrade Fixes #122445 8420e94d8d2 fix https://github.com/microsoft/vscode/issues/122270 5688d75ef95 update jsdoc for NotebookController#viewType 232b10fb540 more jsdoc for supportedLanguages, fixes https://github.com/microsoft/vscode/issues/122372 5ba42fcf94d fix #122510 811416a13c1 Merge branch 'main' into notebook/dev f608a090a6a make sure to return options from override handler (or existing options) https://github.com/microsoft/vscode/issues/122480 a6ec48d1597 fix #118423 84ef3183736 Add missing padding-bottom 08605e882e2 Fix #122256 38831ba3626 Fix typo 13f1afffab0 Make sure empty cells clear previous rendered contents 8d1794e91d5 Register config 5ce4e7ca8f3 Bumps RemoteHub version f727e50c1a8 Use a menu instead of hard coding actions in tabsWidget (#122461) 1f165beac40 Fix unreliable status bar indicator Fix #121194 27b39a13a00 fixes #122310 75615b76275 Fix potential cell statusbar issue 779434d2d11 fixes #122306 ae1c315aa7b tweak wording for disabled extensions fixes #122394 bf7c1f53d94 use label service to provide tooltip fixes #122411 df3ebcc5ef2 use authors always fixes #122382 bea80413c2e add setting to configure trust prompt fixes #122304 782ec1356c0 Add build extension media tasks b08c521660a Fix #122385 e8c5b7bb8bf Fix #122429 04e57de7248 Fix #122425 eb6a45ae94f Align single tab status size with tabs aa454714b70 Fix status icon blurriness db5a53958e0 Fix error message in case of activation failure 051c38cf0f1 testing: trigger relayout on welcome view visibility change a50793147e6 Fix #122440 d93abcd3d97 Fix #122430 e106db06194 fix #122218 541063725ce update distro f1c66b6f9bf Set aria label and include split number/total 40983b6a142 fix #122340 283c3035a49 fixes #121148 f4ab7e822f9 Fixes #122361 and 122360 19cda32aafc Another asWebviewUri fix, use the authority of the root that we found the file in. c193a1ca383 Ensure an invalid icon ID is not used f637c8fda56 fix #122150. 138be779f34 Terminate in case of ext host IPC timeout a065a9458d0 Task name updates 912e8aff9c4 fix https://github.com/microsoft/vscode/issues/122328 3a8aefc8006 Use markdownDeprecationMessage d3d2d113917 Add deprecation message to shell and shellArgs settings 0d5eb683ab7 Ensure tab rerender happens after layout 724172d1349 Show keybindings in inline actions 11a7f9565ea text files - automatically save when reopening with encoding (#122301) b26b993dafa Make inline actions multi-select aware 87a33e998d8 disable edu telemetry on nocors b9147d80e5a Set active and focus new terminal on double click c50ab1e89f6 adopt editorOverrideService for compare (#121668) 3e1a7dc6551 Don't cache the text editor as it doesn't require the ext host 83581ff2b9e Merge pull request #121186 from microsoft/tyriar/ptyhost f28947fb35f Fix compile 33be8b82f70 Merge remote-tracking branch 'origin/main' into tyriar/ptyhost a6591d45e04 Add telemetry events for pty host 40c385c4498 web: use tmp as inmemory scheme f92821bfa34 update distro aac4cce13e6 Set focus/selection to new term on dbl click 4c24caf5d90 Add ... to rename and change icon faf54e77e61 Remove console.log in test dbb1a584992 Hide terminal tab hover on mousein, unless actions eb141a80257 Use the latest LSIF version for indexing f247d234d7c Use terminal groups for build Fixes https://github.com/microsoft/vscode/issues/122287 4550c3b454d can change enablement when disabled by trust c9e8dff24c4 window.title ${remoteName} seems to be "vscode-remote" when not in a remote (fix #122213) 6ef53c31d9c shared process - also log lifecycle state when window errors d417ef3e79f update distro d74baa17185 log when using in memory data provider e2e0d915729 Merge branch 'main' into notebook/dev 1f8e11079ea Revert "Comment out failing integration tests (#122269)" 0317e056bbb Merge branch 'main' into notebook/dev 26cf2a5d624 fix https://github.com/microsoft/vscode/issues/122269 2250b7aa3c0 :chore: allow to use node.js 16 for developing vscode 5346ab1bc3a Merge branch 'main' into notebook/dev 9a4d524f909 Comment out failing integration tests (#122269) 0cbc7217096 Bumps RemoteHub version f88202aa46f Fix missing cell execution icon 8b22dff166d Updates endgame query e3476ac1865 API TODO fe7c5aba015 Revert "Better notebook markup renderer api (#121882)" 4a0a1f2f6f1 Fix replacement for script after it has been optimized e95ce0a810b do not block webview creation for fetch errors. fef89de135f remove open editor on startup 9b328b27396 NotebookKernel test, another attempt 98fdb8a9a06 add skipped test for lcs diff. b1569995f9d testing: allow waiting for server before running unit tests fc90674374e use parent folder instead of folder name 4cb27d2ec76 Better notebook markup renderer api (#121882) 495d162a4ef minheight 4f6b0a1b7c7 swap button and text 2be7cd82b0d cleanup styling and wording in editor 5c8ab73c2fb Build using newer TS nightly bf339ba787e testing: allow contributing to test welcome view 6014c7781e3 testing: allow following running test 480f3c0b351 Return undefined if dialog is cancelled (#122234) 8b15a01d8eb [json] update service 5d4454709a3 Fix #122218 82520584efd remove unneeded logic b466147ca4b fix #122161 e1845803d30 Add telemetry event for failed extension activation 2f946f7d803 fix test. @jrieken. bf94540cc88 Merge branch 'notebook/dev' into main 893d29c487e Bumps RemoteHub version 553d92817ba Word selection is incomplete for C++ identifiers containing certain allowed characters. Fixes #121343 4f37102dc0a Fix issue with cancelling the dialog ba7c9491645 web: drop authority usage 51b2521eef6 use prominent status bar theme color fixes #120455 cb7b2e8c375 fixes #120500 37d967e30d9 testing: rename testRunTask to testRun b9b6a11be76 fix #118073 f6f81128192 Merge pull request #121938 from microsoft/alex/change-default-enable-builtin-extensions 5080d21c0b5 Fix typo 7b0a8177fac Implement workaround for jupyter in codespaces for asWebviewUri issue #121981 aed088f7016 fix #119678 Co-authored-by: Daniel Imms 7971891fa17 Merge pull request #122100 from microsoft/ben/search-editor-backups c15cb13a383 Add js doc comments 7329a207e09 Merge branch 'main' into notebook/dev 8fa05403b6b Add a cache to the editor override service to allow awaiting ext host (#122067) 08e29e6e890 fix #121317 141c4274d90 update distro b7e57b93f8f No longer force showing input box for screenreader (#122054) 205a366f1c1 Fix file location pattern in eslint-stylish problem matcher (#117576) 693e3a4aebe browser: register inmemory fs provider f3cb718a800 more renamed 9a43ef93f70 extract INotebookKernelMatchInfo db7b52abdc1 Undo task queue change 7135d9d5ea6 Bump ssri from 8.0.0 to 8.0.1 efa9e692e34 Fix narrow terminal tab alignment 25b4a53f67b Fix bad tilda handling in simple file picker Part of #121280 289d46f960d update distro 4212c6f9234 Remove unnecessary entries c95aaf71a8c Add keybinding for split in tabs 91c41b04db2 Add keybinding for delete terminal tab b57a9d16c93 Clean up IEditorInput#copy 25787c9584f Add focusMode tab setting 8fac1535178 :lipstick: renames 7a5031487a6 Remove short description for terminal split/kill 5bf77b350f1 Set selection to what is right clicked if outside selection 50b10894ce4 Merge pull request #122179 from microsoft/sandy081/custommHover acc4bcebcff Adjust active instance in tab on dispose 5d77401e475 debug: restart should be sent to parent session 61472da358d Render tabs on tab widget create 42754baf3a0 relax uniqueness requirement for controller ids, must only be unique per extension, not globally, https://github.com/microsoft/vscode/issues/121902 57f1aa9ae66 update distro 0489b546826 OSS Tool d90814dab4e make sure alternative goto-command is also a goto-command 5623111a12e correct remoteAuthority description in workspace files. Fixes #119858 0c4663383db Merge remote-tracking branch 'origin/main' into alex/change-default-enable-builtin-extensions dcd5eaa0b56 remove todo 9129f217050 Merge branch 'main' into notebook/dev ad372e2f94a more test fixture adoption 6c5613e6972 add test and fixes for https://github.com/microsoft/vscode/issues/121904 16423fad0b5 file working copy - actually use elevated file service a2656e12606 add doc ad16e7c2123 round pointer poistion 7c10c7e1e28 Fix remote explorer dropdown alignment Fixes microsoft/vscode-remote-release#4945 975e8ba2845 web api - move log level into new dev options f52952332d1 :chore: some ReadOnlyArray => readonly 974d87948b4 Merge branch 'main' into sandy081/custommHover 7d13ce88e7f improve hover position logic b8c9fbe5918 store associated notebook by uri _and_ type, send unselect event on kernel- or notebook-remove but keep the memento untouched, https://github.com/microsoft/vscode/issues/121904 f85e69f55d8 file working copy - write elevated support 594b7b07a6a make sure "Open With..." actually works..., maybe https://github.com/microsoft/vscode/issues/121974 or maybe https://github.com/microsoft/vscode/issues/121904 94daaf715a4 win/linux: alt+f4 to close window (#54492) 2cfe0d79121 fix notebook type bindings 5053c82843e adopt api changes a15a6237c2f Merge branch 'main' into ben/search-editor-backups d505f37758f file working copy - provide "Save As" support and address some issues 633562da380 Remove some finniky logic causing rendering glitches 2023dd3ce83 Fix #122090 0eccb45395c Fix #122091 7950d3e6210 Support multi select split/ kill in terminal tabs (#122148) 9e9ad5b905f notebook.navigation.allowNavigateToSurroundingCells 08d37a85a3f turn markdown cell into preview mode after hiding find. 27388ab7cf4 fix output edit sequence. 25d716f9006 [html/css/json] update dependencies cb6e7b383bb fix #122099 6c8875d8bb8 working copy backup - adress some issues before endgame c2041ceed35 :lipstick: editor extension export 1a46de24eab Improve terminal text shifting when icon is resolved late fd610ca54bb Improve rendering of very long collapesed titles Ref #119766 c67ea42a683 Restrict space that can be taken up by extremely long contributed items Closes #119765 Closes #119766 6ef0f2f1bd4 tabs.enable -> enabled 42851ddb80e Hide description in narrow tab view 7aab1c1714b Update tab sash reset to play nicely with new changes 8251c67121e Merge pull request #122076 from microsoft/tyriar/122010 cec1401b9b9 Fix compile d7df6a7be04 Support new description in tasks 2c7604e4ad9 Add terminal description to single tab 14919db24dc Remove local title appending 2e191b55ae4 Add terminal local description/initial text 8a426385aef Close #120122 51ea16cd700 No more mr nice guy 47b7fd91d6b [Getting Started] Naming: tasks/items => steps Ref #120049 49ea5ccc13b Fixes #121873 (#121874) a1d60dcb3bd return terminal icon if one isn't resolved a87db274dac Make sure we build extension media for integration tests d690b2d7e21 Fix notebook tests 3faf47d16ee debug: bump js-debug version b126d45af96 Allow x64 macOS builds to be triggered alone af58ea10fc1 Show trust dialog when running a notebook cell #118584 013521dd1e6 Accept uid=501(daimms) gid=20(staff) groups=20(staff),12(everyone),61(localaccounts),79(_appserverusr),80(admin),81(_appserveradm),98(_lpadmin),701(com.apple.sharepoint.group.1),33(_appstore),100(_lpoperator),204(_developer),250(_analyticsusers),395(com.apple.access_ftp),398(com.apple.access_screensharing),399(com.apple.access_ssh),400(com.apple.access_remote_ae) icon format only be42b4b655a Don't autofocus search box in settings on ipad - #122044 3efe6bb4f94 Allow markdown content in getting started (#121960) afaa4944045 remote indicator menu: cache action groups aa219c8eb0a Merge branch 'notebook/dev' into main a0804b66d3a fix #122049 (#122056) b2c250faeb3 fix build f0c8fea098d Tweak "switch to insiders" message 6d56e69ba93 fix #121954 b272df7ade8 fix #122051 a729f709a32 Refresh tabs on hideForSingle change 1fcbe3d71d2 update edit mode with source 9c6f4c194f7 notebook find with match count. 5c7d17bf267 Move service registration 3418b95ac34 Introduce copy concept to editor inputs Fix #121429 3b18aed03e7 fix #122039 6c747e75dad Workspace Trust Prop Changes (#121779) ee3812f51cd fix #122038 d879960af3d send event when affinities change so that status bar can update, fixes https://github.com/microsoft/vscode/issues/122028 80b5eca3c1b add unit test to assert, https://github.com/microsoft/vscode/issues/122028 558b08e7905 fix #122031 fc19fba5ae1 Have single tab act like a dropdown 3cba3d9c4df Merge pull request #122032 from microsoft/tyriar/121601 838b1131985 Have single tab focus the terminal b9f5e85bc07 Add context menu for single terminal tab ef64d205b95 Fix missing cell statusbar icon color 89451c134d0 Remove todo 760b84e09f8 Namespace tabs settings 31c946b97ab Catch showTabs = true legacy case c8807577d4c Hook up new showTabs enum 876dd2782a2 Don't allow multiple tasks runs to start at once Fixes #118238 803d0d62464 Add new settings to control visibility of active terminal 2a384630932 Merge remote-tracking branch 'origin/main' into tyriar/121601 9a978d2f31b working copy - implement backup restore in tracker and keep non restored backups (#117873) 338afacb02d Add additional height to term tabs for dbl click 25cecbe4be9 Fix launching terminal profile via dropdown 4827b266951 Initial single tab view 64f72f290a1 Bump distro 66f0c29ddb3 Fix spelling error a8755553a71 show selected controller in picker, don't say kernel in the UI 0ee8f4efeda Update my endgame notebook 912259262bd fix build d95f4c4d8b5 Workspace trust - multi-root workspace file (#121765) 5685e22ba3e Remove soft notification from extension enablement e51f5015b29 fixes #111350 18c4196f82f Remove soft notification badge 122eac1d743 naming :lipstick: 118c41150c5 Move extension settings for workspace trust fa0da8322c7 Further improve pasting into simple file picker Fixes #121280 a14e4d060a5 working copy - first cut of a backup restorer that works on new working copy editor service (#117873) 6a16dcf5867 Ensure tabs don't get another split added f46cebe4666 sandbox - fix running in sandbox mode cfb866ac533 Show tab actions on focus/selected d40139dd638 Make typescript problem matcher even more general Fixes #121672 f37cda267a9 show in tooltip if controller was auto picked or not 4106ff45187 Localize empty tabs context menu labels 88e2f94a9d7 Add show tabs dropdown entry, remove others 43247e86159 Add the globe back to the Ports view b8e2b1b7889 Merge branch 'main' into notebook/dev 68ddcf50e86 Remove bad } from terminal title 97814d10877 node-pty@0.10.1 cf603f67f05 Remove tasks from workbench.desktop.main.ts Part of https://github.com/microsoft/vscode/issues/118201 ef521cc5403 remove bad instanceof check 2437313ca46 Fixes #121535 ca36916a61a update distro 8e0e2199b16 working copy - drop TestWorkingCopyService abe5e414e70 add onDidCreateNotebookDocument in addition to onDidAdd. d1b0a469b12 remove kernelProviderAssociations but add a notebook-type 2 kernel association as shared/sync'd memento 05e0899ffd1 working copy - more readonly arrays 52ef3b65268 Pass correct URI to opener service fallback Fixes microsoft/vscode-remote-release#4920 31536dd690c editors - make findEditors more powerful c32f1cc4632 update ref viewlet ac151b40a57 enable virtual workspace capability 77ac0dc1a5c Merge pull request #119040 from jeanp413/fix-103631 9701c057ef7 refine prompts: using warning when file is too large for formatting, use model for installing formatter, https://github.com/microsoft/vscode/issues/119463#issuecomment-825434820 a668bed63ae add skipped/failing test for https://github.com/microsoft/vscode/issues/121994 abc6cf94729 text file tracker - fix regression with untitled not being tracked 666f3ac3e06 file working copy - provide some basic save error handling (#117873) 0310f02dc5d reset key on focus out and set focus if there was one 4853ea69eb4 Add command and keybinding to focus terminal tabs view (#121978) 66fd0cba91b Enable noImplicitOverride 36591a96a02 Rename cd9a6a48201 Add telemetry for rendering of markdown path 1ea93521feb fix #121816 (#121957) afdd217b3bd Create new terminal from profile in multi-root workspace (#121958) 7bf7774d8a6 Add custom editor telemetry b0b4b814d0a Fix #121793 1b1fa411580 :lipstick: test 274565432ce Merge branch 'notebook/dev' into main 94f28728f76 remoteIndicator menu point: double digit order number. For #121180 c141ca66619 testing: show load errors in tree view 214a1581b7e Make window indicator menu contribution point stable. Fixes #121180 484c5c87338 Fix tests 8e51485f45f Debounce cell output edits from EH Fix #119832 ddbc369b115 Fix cell output append edits showing up in reverse #119832 2f077172cb0 Add `php.validate.executablePath` to restrictedConfigurations dfd1b65fce9 debug config manager: dynamic providers fix 894f50d5443 debug: make sure to not show dialog when launch config content is being created b1ca80cf60a re #121140. 2d12c84be75 fix #121723. 1f75a0331bf Make narrow tabs show status color icon aebef8cf5b7 Fix statusbar items with wrong width in new cell 7c17ed4680f Fix #121927 Remove new inner span in statusbar items, also switch to just codicon syntax ab9a916272c some :lipstick: and a tricky todo 426f1b7d80c Fix layer issue b6ff982121d Merge branch 'main' into sandy081/custommHover 9f43daf9028 use DAP 1.47.0 df42794a6c8 node-debug@1.44.27 5460fcfc4ea Support relaunch hover action 2130042ca4c disable github extension in virtual workspace as it depends on git e1bfeec6b1a Show relaunch needed details in hover f38f90dfe16 Improve package.json file check Fixes #120279 f68a7e77788 add NotebookDocument to executeHandler 690c76c1051 Fix statuses getting stuck, integrate decoration title 5fc245f2d74 refs #120675 a217409e38f include silent for notifications:actionExecuted f6c4607367b Incorporate task upgrade feedback 68bed019705 adopt virtual workspaces - not supported d4033de1f1d update distro ed16789225e localise workspace trust strings for exts 97344537a59 Use affinity over priority 1305c61d823 Merge remote-tracking branch 'origin/main' into alex/change-default-enable-builtin-extensions a385ea15bdd todos about affinity vs priority 5390ec0e7c3 remove isPreferred as instance property, replace selector with only viewType, allow controller to set a notebook priority instead 5c39e705cc7 Add tooltips to terminal statuses b14455b318e Provide label formatter for terminal URIs daab26c2e71 Merge pull request #121853 from microsoft/capabilities 70c5e1e00c5 fix compilation 07505af8c9e update distro 793cdaea1f7 Removed unused code 7ac3721a4d0 Hook visual bell up to enableBell setting e90519d1cce Rever awaiting for ext host on open 1eb45d72c8a Delay terminal bell shortly after creation 3ebd601face Change default for enable builtin extensions 0cca69109c4 Ensure primary status event fires when status is removed 436725c584e Use Application scope for `keyboard.dispatch` c95c0cbae49 Add tab view empty context menu 6779f853214 add comment 371b3de4367 Fix right click pasting in tab view 03a3abf6daa Use constant for dblclick b64f1aa2358 Support double click to create new term 522062daf57 Expand terminal tabs to whole width 9fb92f0f705 mark property readonly dab0e063bcd mark supportedLanguages as optional, some todos 166efae3664 Fix `--noImplicitOverride` problems (#120675) 6a781878370 Remove deprecated sync methods from configuration resolver Part of #108804 87eac45476d Support defaultProfile in selection default profile command 253fee71631 assert that executing a cell send selection event, https://github.com/microsoft/vscode/issues/121904 90c5c7a0d2f Merge branch 'main' into capabilities 56acb0b1d25 Throttle calls to spawn/kill under conpty bceab04077b debug issues should get auto assigned to me 12741ed7d88 add notebook instance to interrupt handler, https://github.com/microsoft/vscode/issues/121912 b96d65b9be4 Finish adoption of IShellEnvironmentService in terminal 5c904937545 Fix case issue on Windows with variable resolving Fixes #121847 2a6703463d9 Update distro 5a78d3377f3 Merge pull request #121918 from microsoft/tyriar/120328 7bd176faa1b Adopt bash terminal icon 4ae4ca97598 Move dropdown with primary to base 1df9d8902c1 send controller selection event when selection knowledge is restored from memento, https://github.com/microsoft/vscode/issues/121904 e86fdc6728e Merge pull request #121732 from microsoft/tyriar/121657 22d2f01347b Fix compile fd709cc79fa Reduce calls to getShellEnvironment eaf3a5dcda4 Move off deprecated resolve function ae50493e894 Only dispose on middle click 29ade591f55 Remove old prompt from tasks service Part of #119463 dc589f540ab Merge remote-tracking branch 'origin/main' into tyriar/121657 00e8e6459d2 fix UntrustedWorkspaces type 5d8f09d2708 adopt to new capabilities api proposal f5b31e9a331 use UriComponents for extensionTestsPath. For #121734 af76d0cf137 export IDevelopmentOptions 79033ef7a19 Adds code lens on vfs files 46a25f25e87 update distro 8ce16487193 Merge pull request #121734 from microsoft/aeschli/testsInWebEH 71b23eb39d8 Merge branch 'main' into aeschli/testsInWebEH b3151c10ce7 fix compile error, fyi @rebornix a18d061b009 Merge branch 'main' into notebook/dev 2e0214b3645 rename ExecutionHandler to ExecuteHandler 7ce1499d954 More removed unused 60af02d7582 fix #121807. 27675a99be1 Remove unused services 49924bbab74 Remove notification ref #119463 4a8e9493472 consolidate tabActions code f547ada6eeb Allow text search providers to give messages (#121528) 159d5d2799b move separator inside a536ece09f7 add hide tabs panel to context menu 807ae1e167d fix #121744 79a5cd28cae re #121252. Add actions to toggle cell position. c638290c09a add setting for specifying cell toolbar position per view type. fix #121252. 6c48db1730a testing: fix test failures 883424c4e37 testing: betterify view 57d4e39aebc Fix issue with tabbing out of outputs with iframe webviews enabled #119727 7c83a16e724 Fix tabbing into statusbar, #119727 a689b7ec842 Remove underline from h1s in notebooks 15110d66f0d Bump fallback version for webview contents 7e8193bee0d adjust hover position for panel d2b7b09d716 show panel hover on top fbfe0154989 remove NotebookTextModel._mapping. 9e29a9caf53 align left and right positions 61364b04926 introduce hover position and align hover and pointer 306f32b7ce5 re #121869. dd2aefcd7cc fix #121818 and make terminalService manager/updater of available profiles (#121837) 264ccd16d4f Merge branch 'notebook/dev' into main 2a718949839 update distro 0867628480e Merge pull request #121836 from microsoft/tyriar/plus_style 2ae89029880 Update Codicons: update "copy" 951612b9339 Remove deps from dropdown with primary 6344624354d Await extension registration fixes #116259 cc66860c669 Add styles to dropdown.css 39c0b817706 Update Codicons - Add "wand" - Add "debug-line-by-line" a509be4c994 Update Codicons: Add "filter-filled" https://github.com/microsoft/vscode-codicons/commit/cdceb9f5fc38387c5c7dc3535bc6b528cd9cd4e1 dc0b17b5bfd typo eaec4b3f2cd workbench embedder API: developmentOptions 731402dce22 async override -> override async 190e902afe6 Move editor associations to override service 685147c008f show hover pointer 9d0add77827 Update Codicons - Add "swap" - Add "copy" - Add "person-add" 2e5ca55a78b Update Codicons: add `terminal-bash` https://github.com/microsoft/vscode-codicons/commit/678b2158feae3d346916fd0a5db883c499376e5a 74cba9e0038 Move IEditorOverrideService to common d86ba38b9eb Clean up cell margins 10afe5984ae Fix dragging cells near the execute button Fix #119535 07ebcc8d0ac remove virtual workspaces until onboarded f9fc28d47db update ext manifest schema validation and built-in exts 0d29a8722c1 Revert "Move editorOverrideService to common" 6009275300c Don't lose cell toolbar when expanding the dropdown c3265df7cc1 Show cell toolbar when hovering it, when it's invisible Fix #117618 84809f41cd7 Move editorOverrideService to common 4eee5c6ee5d testing: fix test wrapper not including ranges and not dealing with sync discovered root 3c4b64079d3 Logging categoryDescriptor.id instead of [object Object] (#121850) 301d4e9a50f fix broken test aec0921e5c2 first pass to update workspace trust to capabilities 43d2efaab63 describe the context key for workspace trust a9f70f7919e finalize basic workspace trust api d97790d1624 Fixes #59 https://github.com/microsoft/vscode-codicons/commit/6c518bd9af3935deecf24ef31a55194598e98349 fa96e5566b0 Fix setting sync button in Getting Started. aa6e570a848 Fix typo 0aad5f2390a fix #121841 3fe2d3b5433 [remote] Allow to open remote files through vscode://vscode-remote/. Fixes #108257 ce63fe5e18d correct name of initial dropdown 4af696b7892 Improve new terminal dropdown style dbac9179a0b Merge pull request #121831 from microsoft/tyriar/121827 7623d09e3ae Remove tasks v1 Part of #118201 4a5c0bd88ae Don't expose change icon instance in command palette 27d236fec1c Inline menu entries 128c666879b Don't override keybinding for split 827d9284bfe Fix change icon/rename ctx menu 84e63b1d13f Create new command IDs for kill/split instance f9737367372 Revert "fix #109765." fd88b56ab91 improve messages, web: showSaveDialog f80d55121f5 web: pickFileToSave 091b1f5a6ef web: showOpenDialog 9784c7a0efa fix compile errors e2c97039958 prefix api object with api-prefix, like apiCell e468d47ed84 some more API todos 415a43f04dc Remove has text log 37789e46950 Remove logs 28fbb99921c Merge pull request #121774 from microsoft/tyriar/121483 13302253b56 editors - respect override option in openEditors 034157dea81 gh auth: back to prod auth e6a70813331 Action feedback 5b7a28c43fb text file tracker - open editors without going through override a165ee92c50 Remove old new terminal panel action when showTabs is false 1efbb32036a editor service - cleanup isOpened() 14720cbc524 Remove configure term, replace with change icon+rename 74fc96f8491 rename IMainNotebookController to INotebookContentProvider 61baba1f583 remove some more kernel leftovers from notebook service 010267d7376 Handle cwd inside _convertProfileToShellLaunchConfig 16921f35e34 notebook editor status must listen to kernel instance updates e09f0ecfcfc editors - require typeId for some methods that are otherwise unspecific e3df15418ef relax NotebookCellOutputItem and NotebookCellOutput, e.g. don't enforce the member to be readonly c1549ef877e sktech NotebookEdit ideas 7194c24dad9 use serializer in NB integration test 9af75c08c5b remove ability to alter notebook output via WorkspaceEdit or NotebookEditorEdit 4f9ac638ffc extension contributed kernels fall back to all languages when supportedLanguages isn't set, https://github.com/microsoft/vscode/issues/121329 832a699c00c move keybinding label computation to activity action item 89684626f23 editor service - :lipstick: API e350c258dbe fix build d60dbbf646a gh auth: fetch json 186897d756e distro 0c723c1bf37 tweak kernel sorting 9e8377d8cd3 TestWorkspaceTrustRequestService 3668a573adf gh auth: fix staging server selection 479a2247416 disable compare action when no active file, no need for notification 853f9171901 notification updates: #119463 141ecfb7981 mac specific actions should not respect web 820e70a460a gh auth: don't reach out to product.json 3236b8dd707 Revert "Enable the notebook renderer on web" 9bacf617b24 Use clearer variable names 5c3383251a8 unique working copy type id, stricter view types 625dcc34122 add enum-set for alternative commands and check the command type is a symbol navigation command 07013ef4a33 update ref viewlet fc191749834 Move quick pick into editor override service (#121666) 7095f4706c0 unit tests (electron) - avoid sending circular structures over 2e79d9e362b Merge branch 'main' into notebook/dev 23a2409675b Update debug-auto-attach ext icon (refs #81760) a72b1574b33 Double click on the middle part of a collapsed cell should expand it Fix #119777 65d1d29f7b4 Remove unused statusbar styles e815d287851 fix #121655 27cf6a38076 Implement workspace trust for php-language-features 643756788f7 Allow running builtin php validation with a relative path configured to the php binary. This was previously allowed, but the fix to use 'which' was too strict 609e717d750 Merge pull request #121704 from microsoft/merogge/menus 21ae0ca0d45 fix #121736 91ee63fd0a0 Fix missing run buttons Update kernel context when editor model changes 64282aa6b7a Use new cell statusbar for execution items. Support color and icon info on cell statusbar items. bc8c79c8f2c Fix missing run buttons Update kernel context when editor model changes e23c988571b remove custom metadata internally 1315dbe4951 remove breakpointMargin internally. b7d27a057d1 remove cellEditable internally. 7947c4d3f88 tweak split view width work 158ba0de72d fix typings. 1eb2a93ef39 fix #121631 e986377a625 move NotebookRange. 17fa3235965 more jsdoc 1d951484ed4 testing: bump distro and make document polyfill work again 8711b2dc92d remove deprecated transientMetadata. 2da00961559 remove deprecated custom metadata. c67ac5d0830 Remove path from github-authentication 1d54cdf54a1 Avoid extra call to reveal if the editor is already active 9b8d52ded35 Fix calls to reveal for webviews that are inside of diff editors 24c6f586ddc Add requireTrust to comments.openPanel setting 7c486f3a693 Merge pull request #121758 from benmccann/patch-1 7d9dcaca452 Enable the notebook renderer on web 7d45a4503a2 Allow ThemeIcons to be used as decorations 3b20d95b3ad Consider updateOpen as signalling that project loading as completed 0642a7c299d Update debug server ready ext icon 851613fe478 fix unit test ce91f3a00ff fix unit test 61f646674b0 Merge branch 'notebook/dev' into main 7b23ff442e1 more staging detection (#121769) ad3e7ea1c06 gh auth: look at staging query param (#121768) efe612365f1 feat: add support for additional surrounding pairs for markdown (#119982) (#121698) ac48f78dd64 Add icons for built-in extensions (fixes #81760) c32f4d0afd1 Make collapse/expanse cell actions apply to all selected cells 1764fc81a8c Add requiredForConfigurations for TS ae24966cce1 Localize workspaceTrust b9c647ab3f8 get rid of instantiation service 3737f9eacff fix #119866. a818bda1aaf fix #121288. 3b3ec496f60 Adopt terminal ubuntu profile icon 3a862c2f7dc update wording 704f85a9db3 split, configure, kill for the focused instance 0772410c7d5 initialize installed extensions before starting extension host 3099237dbaa Max item width, style fixes for status bar items cc850ade50c Cell statusbar test b286729f356 Test cleanup 637403c1d57 Fix issue number in comment 4418386c13a Remove 'requireTrust' from search.exclude. Searching without excludes in an untrusted workspace would not be useful 1e6315f7059 Update Codicons: Add `terminal-ubuntu` (refs #121737) https://github.com/microsoft/vscode-codicons/commit/98b2fc81360d59ca195f00c16ff984121d4fe255 1c726074ec0 add jsdoc for NotebookSerializer 66c8a845474 Revert agent pool name b62af979828 Update build agent pool 6473cab2d34 add some api todos 94589129a2f Add experimentation service to github auth extension 008c20865ec Intermediate content for Getting Started (#120602) eb45f2bd17e Override original agent again (#117054) e324e19e96e add a comment 390d4388c6c only fetch the profiles on window reload or if config has changed 666a6e8896a pass cwd to convertToShellLaunchConfig d3245fbf024 remove unused, make convertProfileToShellLaunchConfig public, rename MenuId 9809e3f12ff WIP style updates bdf2ef774e0 #118731 faec962a3e9 more removal of unused interfaces 01b1689c1e9 fix comparison bug when updating kernel bindings d9e4b98387c simplify notebook kernel service f7b69ef7045 Add keybindings for navigating terminal groups 61017718099 #118731 82357d5d4c0 #119110 0922dbb475a Move vscode-windows-ca-certs dependency (#120546) e838389be00 Update distro 03a096326f1 Fix binary mouse events on remote 5fa2193ba78 add extensionTestsPath to IWorkbenchConstructionOptions 454b741257f restore integration test 45e013b704f Merge remote-tracking branch 'origin/main' into merogge/menus e791f6b410b Merge remote-tracking branch 'origin/main' into tyriar/121657 2447e78e687 Implement sash reset in terminal tabs b2b45e1005f execute by cell-handle, kernel manager has only execute/cancel cells, also "fix" isNotebookCellData fyi @rebornix 74bbba9aa66 Fix compile e9b2d57825b Middle click on terminal tab to kill terminal 6170715c03f Fix fallback profile using --login on macOS d4a6400596f Merge branch 'main' into aeschli/testsInWebEH 064ead50123 notifications - properly hash resource 0b46219a2d6 move "execution" of markdown cells closer to command 514d136bd02 adopt typeId for simple notebook working copies 79d748989e0 some jsdoc for NotebookController 438cdf9232d remove unused NotebookCommunication interface 47b406c8227 Merge pull request #120359 from solomatov/optimized-memento be6a9027041 notifications - introduce ID property to help detect duplicates better 3abadabac87 Merge branch 'main' into notebook/dev b06388f501e Merge branch 'joh/clean-kernel' into notebook/dev fe7951dd7ab remove kernel provider from core, adopt kernel service, merge kernel2 and kernel interface, extract context keys from kernel manager, make context key manager only for execution/cancellation 40c99293c86 Try another fix for tooltip on safari Part of #111756 Fixes #121684 3a7cf0c9dc0 debug: update wording for safari dialog 1a74d20b2a4 file service - add test for `getProvider` 10b35b6aa2c #118077 remove hover background a5b19b6f641 add supportsVirtualWorkspace to schema b4816cfd1f4 fix tests e74712bda17 fixes #118731 319d1511382 #118077 move custom hovers behind experiment flag fa602ecdc37 extension tests - stop automated opening of devtools when running out of sources 1f9e039a11e :lipstick: ab27efcc9f9 use new types for browser fs a09354329b8 exclude htmlfsp from monaco eaabe284856 Revert "Revert "use dom api to resolve file schema on web"" 193d5d2c5e6 revert unneeded change e2d0668ffad get rid of tabsWidget buttons bd0d9341399 improve styling 6491f003c7d stylize 53ca9ec8afa refine conditional 0db78a7d563 only re-render dropdown 29a726f400e add comma c0d4b45306e Merge branch 'main' into merogge/menus fa9e3a81c17 resolve disposable error 6de1a8b7b8d get rid of register 1292b973d09 Revert "use dom api to resolve file schema on web" a4b13e3fcf7 Bump distro 909f03eb3e2 Adding a few more override specifiers 65273f89bc1 fix output max height update for diff editor. 8fa6582b834 fix diff editor output width/margin. e5c466f587b markdown-language-features: Fix Windows Preview Link Uri (#120090) 25b47bb9571 Use ctrl for add to selection on non-mac platforms e62f21f40cc Remove extra await f47e207f915 style buttons 1dde9302af4 get profiles to appear f26ca76fb62 fix #121623. 297ac839dca replaceNotebookCells with range. 87c10dba596 Protect against exception when initializing terminal tree e382db30892 Improve terminal tab setting descriptions ef0942ba058 Don't throw exceptions to the extension when an outputId has been removed Be sure to emit events from applyEdits even when an error occurs Fix #121687 427ab0079f4 clean up 891683014c8 Allow svgs inside iframes inside webviews 1845ee08b84 Allow specific command uri in extension editor 180f95fa222 Merge branch 'notebook/dev' into main ea5434ca544 node-debug@1.44.26 41d0782edec resize when tab location changes d6ddd5866c0 use dom api to resolve file schema on web f6b293d318f IFileService.getProvider 288368cdc05 add @types/wicg-file-system-access 8215a110a94 fix: build 6df31c584a1 trust: make workspace trusted during unit tests b0eff48b18c testing: adopt workspace trust for testing 94894dfe7f3 Close #121395 caaa44b7aa7 Update aria label to include codicon names and wrap in spaces (#121496) 8f8407812bb add CombinedButtonActionViewItem 9dff5e0e014 debug: workaround to open new debugee window in Safari 3a3864174e2 trust: onboard debug land to workspace trust f937a28dd0b Move scheduler initialization to the ctor 7635c6504d0 resolve -> reject 6330a7a728e Addressed code review feedback b4b2d40adf9 Addressed code review feedback 03666a06804 Cleanup redundant code c25aa77e336 Fixing linter 59ce58884eb Fix linter error a502d34d923 More effecient handling of changes d415fc0d0e3 adopt notebookSelector in status bar API. 9e323dacff3 fixes #121621 68362616637 Adds workspace trust setting 86157f0d557 api resolves immediately trust state in non-modal f9db59cc755 update jsdocs. dec167076bf Remove code related to dangling files d7f19966a49 Fix #121289 3b8bfe8cee6 setup menus 1cafce9f5be DAP: new option suspendDebuggee on disconnect request cdb8d62f088 Ben/workingcopy typeid (#121646) 4dde01fe34f backups - some more :lipstick: 500b051842e Fix #121506 @joaomoreno - Seems typo 11c3ba58af9 remove NotebookKernelProvider API and its implementation 05fa4efaec7 show disabled reason for extension disabled in virtualws 8f67a43aa28 web api - remove types that are not exposed in any API 5efce0f7e03 fix tests 5dbb0cc89aa working copy backup - more tests 7e22b12c4d7 fix build 2ea7d9c65f9 Merge branch 'joao/fix-120696' into main 3606317e4bf add test for whenTextEditorClosed b89cb3dccd2 Format code 92b4126f276 Move workspace trust code into ExtensionManifestPropertiesService 401554bc451 change property to supportVirtualWorkspace bfe28fa0962 implement workspace requirements for extensions 47231d090d3 adopt trust in github f9fec24b452 make INotebookKernel2 extends INotebookKernel b0ac820e347 working copy - do not allow VSBuffer for backups 1268f86ac7d add common.platformDetail in telemetry (#121264) a8f860beecc remove window.sessionId 12aeeabdb5e more working copy backup cleanup 9da5986f515 Set default tab location to right 030e33ca8eb working copy backup cleanup ef569a6e08c enable noImplicitOverride in our codebase 693c0e3e1da text files - add a getDecodedStream method 14981ce89d2 tests - enable colors for windows (CI supports it and the new Windows terminal too) 6b9bd78998a add NotebookController#detail 058552963b7 Merge branch 'main' into ben/workingcopy-typeid cc778d679fb Merge pull request #121396 from limitedmage/suggestrace 0595da6b543 Merge pull request #121511 from gjsjohnmurray/fix-121509 b0d5ab6aabd :lipstick: 8aff878db25 Fix typo in map.ts (#121279) 2288da484db Bump ssri from 6.0.1 to 6.0.2 (#121576) 200d4c77619 tests in web extension host 73c85159e72 change extension kind controller to a service 367c11f77b7 File gets garbled characters when restored from backup (fix #121347) e5d6ff4c06e jschardet 2.2.1 -> 2.3.0 (#121575) e877b8062de backup - change to a buffer format (away from text snapshots) 2b44820d337 windows - fix tests 8e43ae3087e Workspace trust - merge storage and management services (#121540) 085df87b3bf working copy - extract a workingCopy.ts file e1727abb411 backups - rename to be working copy related 99075a46ec3 Merge branch 'connor4312/test-api-refactor-application' into main 6360146a419 backups - introduce backup identifiers and adopt 92b5ca6d968 testing: fix error for synchronnously discovered tests 5624d860cc5 Further tweaks to grid styling db221b816d5 Revert "add description to statusList" 34bdd983e21 Update renderings of getting started items based on overrides d7f55753675 skip failing test 45e308fb286 add description to statusList fa0cd78f7d6 Dont clip steps unless needed bb3ea733def testing: rework running side to new apis 5b5320b7d5a Remove onDidAddTask - categories now immutable 522b60a1979 Fix bug where tasks could not be opened e5295350a09 Close #119964 0767f255d2d comment out tooltip causing build issues c0e29b7d2dd show separator in filtered quick pick (#121075) a9109869988 fix #121509 documentation error for two API command results 1845b9d2a52 Merge branch 'notebook/dev' into main 85ca912f72c extract NotebookDocumentContentOptions viewOptions. c657cdbef30 avoid stripping custom data. bbcb45cdcdd use label instead of a custo name in terminalDecorationsProvider 95cc902f2e1 try to fix the build a37a8c517a3 Revert "Revert "Don't block svg loading inside of iframe based webviews"" f8bc55fd267 Remove unused 8706813cdf3 Set max tab view size of 500 23ac9b5184f Fix showTabs NPE a3ac715bfb5 Set correct layout when turning showTabs on 29b621b47e8 Fix exception when loading with showTabs = false 1f544741220 Hide dropdown when tabs are showing 557ac0b24bf debugAdapterManager: update wording 186bced5aa1 Remove Task prefix from tabs only 2102600c561 Reduce min tab size d2961b4c081 Remove extrenuous injection 8217e20a706 Make resizing smooth 03aad7bb2db Prevent wide tabs from being centered 8f70a2d6ac1 use language name for labels 8fec10e2516 Focus newly created group ref #121395 4dbad29ad41 Change configure terminal to pencil 8eb52dce857 Fix warning status showing up when env var widget is on 8a67dade9ea Make status decoration padding consistent b97977ff55a Fix exception a3d61cb0251 Add change icon/configure terminal action 0b9c11a49cf more binary editor cleanup 0e2d23ec434 Remove * as platform from terminal b46bca09d52 binary editor - remove file size check (fix #121301) 19b577164e8 Update distro 8d234a4dc9d Merge pull request #121176 from microsoft/tyriar/shell_resolve_refactor 9d72785a2a6 Merge branch 'main' into tyriar/shell_resolve_refactor 067f60cc403 Merge pull request #121330 from microsoft/merogge/terminal-status 8bcc74944c2 require trust for cfg vals 27539a2ef78 tweak conditional 59963d0328f Make error message clearer (#121339) f206da596e0 modify conditional 7c7c83e5644 Update replacing editors in editorOverride service 5138a81c177 🧹 3fb483e7fff chore: bump electron@12.0.4 880363524f6 Fix #120202 972978848e3 Merge remote-tracking branch 'origin/main' into tyriar/shell_resolve_refactor 93cf9f231e2 Clean up 6b7a55444a4 update configuration scope for tel,exp cfg 3595082e7d9 Address comments 5971cb68edb Fix race condition in suggestWidget 2c631534503 Revert "Don't allow multiple tasks runs to start at once" b4dd497b275 add scheme Co-authored-by: Daniel Imms 30ae7bde852 Respect resolver's trust options 78872ce6113 Smooth the flow of installing new debuggers d66d3929174 only register workspace trust request handler when trust is enabled e4428f18335 revert change to a line 3e8b31b3947 Bump distro 7c7c0b7ee62 remove unneeded css bbc5a53ac5b Try out another possible fix for safari tooltip Part of #111756 fe6850d4e73 Change default port setting value instead of setting Fixes #117189 5523c825ad2 Disable pre-resolving icon in remote windows 5029f1b18b4 Update distro 328937e7b64 Use OperatingSystem over Platform in terminal f48845c4451 untitledHint: respect editor font 4430569ad07 debug session: pass configuration as args for restart f4b60835ea7 Fix tests c7d26be9a5c Disable terminal workspace settings until workspace trust is on e077071ade6 Update distro a79c0b09492 Update distro 5d72ea7c196 Add more top/bottom spacing to features boxes f31f60b7aeb Pass remote authority to context methods 93dbc7ac44c remove NotebookControllerOptions and simplify createNotebookController-signature aa6ae6b4193 Pass remote authority into resolver 5619daacc25 Merge remote-tracking branch 'origin/main' into tyriar/shell_resolve_refactor 27add7e2ac3 Merge branch 'lszomoru/workspace-trust-loose-file' 9e77384443c Resolve default profile in remote slc 3bddb9636c3 Fix compilation a76a1efe043 Merge remote-tracking branch 'origin/main' into tyriar/shell_resolve_refactor e49f95b5315 More removing workspace shell permissions 61556099dd6 Remove todos 96065290c8c Update distro 3ae9ec00516 Adopt requireTrust in terminal 3c0e4897460 Don't allow multiple tasks runs to start at once Fixes #118238 95932045c7d check that notebook controller are unique per id 949639b9424 use default some delay 17cec8c0089 Fix ~ with fill path pasting in simple file picker Fixes #121280 1f941f612fe Fix #121456 aeb9b2051c2 Remove only 0358f147137 Fix unit tests ba5e81ab093 Fix import 31016ade1e9 Merge remote-tracking branch 'origin/main' into tyriar/shell_resolve_refactor bdd072e51ba Add trust prompt 217359a80d2 some jsdoc and relaxed NotebookRange ce791d27dc9 Fix #121362 564bcfe4cd9 render tree on trust / trust settings change ba531de1988 fix ExtensionRecommendationsService test bb43ee61909 Fix #121297 98f69856ea6 extract types for execute/interrupt handler, interrupt in not per document but per controller 9699bd793ce Smoke test fix 1f385f70e6e reduce usage of extensionDevelopmentLocationURI c074bf897c0 make preload a creation argument, no editor needed when calling asWebviewUri 5ef190e98a4 Fix port log line and add regex check fd7cedae033 :lipstick: 7668affd16b Merge branch 'main' into lszomoru/workspace-trust-loose-file 8877d8ca941 hook up execution task cancellation from controllers cfdce942776 #116731 644e1d0bc42 :lipstick: in notebook service, also make sure to clean-up editor contributions when disabling/enabling extensions 42e271dd2e7 do not show dialog if workspace trust is not enabled @lszomoru FYI ddbc8ae408c fix remote integration tests - do not show trust dialog when change is coming from cache @lszomoru fyi a387125e199 Merge branch 'main' into lszomoru/workspace-trust-loose-file 3ac5ff11b6a Merge branch 'main' into notebook/dev 3c31f0903e4 move workspace trust service out of config service b7ba430dcee Begin to allow extensions to contribute start items 55b2ab7fa4b re #121329. NotebookCellExecutionSummary readonly properties. 4db99a10b86 transientDocumentMetadata 8629b48030e rename transientMetadata to transientCellMetadata. 881a304efe9 Add some NotebookCellStatusBarItemProvider jsdoc a7c67442b97 Clean up cell statusbar keybinding tip placeholder 7193b08f344 Remove old cell language picker item CSS 402c9bee093 always show intro modal e7004428b7e update todos. 7cccfd1d43a Merge remote-tracking branch 'origin/main' into notebook/dev d85b11a2ac4 use terminal service's instance status changed instead of instance.statusList.changed d0a23bb0667 revert some changes dedc2d1150c Add other platform settings 8859256f866 Merge remote-tracking branch 'origin/main' into tyriar/shell_resolve_refactor 03333c995e7 Resolve icon in terminal asap da16f9bef17 Move cell language picker to statusbar API d87ab38a497 only show widget for manual relaunch bd493ff734d Reduces validation timeout ba4954af208 Ensures that the validation message shows up 0f890eeb173 Calls focus method 155df27d8dc :lipstick: 9f027210ce3 Update distro a1a9adb9176 Merge remote-tracking branch 'origin/main' into tyriar/shell_resolve_refactor a6b849a8f9c Use icon of default profile 28b2a0f19fe Fix #121193 8894f893da6 Add platform override to default system shell 05bd596a618 Move new service to sandbox 6e84c224f44 allow for markdown dialogs and custom icons 77f067f0652 Merge remote-tracking branch 'origin/main' into tyriar/shell_resolve_refactor d531f3b0547 Merge branch 'notebook/dev' into main 3d434efcc01 Progress on single file scenarios b7d0ff397d3 always fire ptyReconnect after relaunch de1c13e539c mostly working 662ea12128d NotebookCellRange is now NotebookRange. df1214ddee9 Rename and move combined service ebb0a7165bc update integration test. 25ea3341e0b Guess synthetic profile icon dbbf3325212 Simplify service context by moving into browser 40ed9160e1c Implement browser profile resolver 2459b4fab9d :lipstick: 4edba25ca9c Central service for notebooks and custom editors (#120426) d9a371b9fd4 update comments. ae5aeabcd7d flatten custom* metadata. 271189bbabe isReadOnly contributed by the file service. a3b1c9550dd remove cellEditable. 060d4175057 remove editable and breakpointMargin. bcecbeb2ad9 change to async cba995310b3 Merge branch 'main' into lszomoru/workspace-trust-loose-file dd4350388f2 adopt to workspace trust storage service - create the service during initialization @lszomoru FYI 0688b5a7bd5 Revert "custom dialog for introducing workspace trust (#121326)" 35dcde89ee3 Revert "adopt custom and shield icon for trusted workspaces" 43295fde7a6 Remove statusMessage from constructor fb4b3e82ed8 Adopt shell env service in TerminalProcessManager 00ae987ad04 Remove getDefaultShellAndArgs c780db5fe26 Use new service to resolve local process paths 348a8fa1fe7 Partially implement resolve shell launch config 2436eeee72d adopt custom and shield icon for trusted workspaces 6590d3e0f78 custom dialog for introducing workspace trust (#121326) 3bb83e95562 Skip failing tests temporarily 8cf96ed6454 pass icon directly bbf148b0c60 merge renderer IPC into notebook controller, remove global renderer IPC 337e14744c0 implement decorationsProvider 296c417c07f Add workspace trust property to auth extensions 3f4e9a0ebdf node-debug@1.44.25 150af9a47a7 Use workspace trust signal when adding a folder to a workspace (#121197) de246aa544e Turn on showTabs by default in Insiders a49404e6443 Resolve todos in profile resolver 68d9e023384 add support for isPreferred for notebook controllers b2103c79e45 add preloads to notebook controller, fyi @connor4312 NotebookKernel will go out soon 16663d23bc1 Register the new service e5ab815d0c3 Fix collapse all tree view focus Fixes #120990 151c7d5dff0 Recognise commonly sourced files as shellscript (#121373) 9a0441e9cb8 Inject last active workspace c079d216f0e actually remove fileName from the notebook document implementation, https://github.com/microsoft/vscode/issues/121329 45097cfcc9e remove NotebookDocument.fileName, https://github.com/microsoft/vscode/issues/121329 b6cee1ca22f add cancellation to NotebookSerializer, https://github.com/microsoft/vscode/issues/121329 afa4a2df2c3 rename to serializeNotebook and deserializeNotebook, https://github.com/microsoft/vscode/issues/121329 0287003973e workspace is virtual if all folders are virtual b27d796dfc1 Inject context, convert functions to async 255f82dbece editors - move some code out of common/editor.ts 495ad394f46 Fix layering issues 8681ecba619 editors - move some code over to browser/editor.ts fdca487fd41 Implement resolving default/synthetic profile 679c373828e editors - remove whenClosed from editor service and make a standalone helper specific for text files 12c93777db4 Merge branch 'main' into lszomoru/workspace-trust-loose-file d13686a8607 Extract isWorkspaceTrustEnabled into a function (#121385) 9f4eceb52cb Merge remote-tracking branch 'origin/main' into tyriar/shell_resolve_refactor 91f8c81acbc Reveal active terminal on change e24c03de151 Center +v when collapsed 520462074de working copies - first cut typeId support 6f9917d81fb no more need to dispose ExtHostCell f8cf56ae9e7 Merge pull request #121272 from akosyakov/akosyakov/remote-race-between-terminal-121270 f1d0c30f1e0 pass cells and controller to execute handler 0aabafd35f9 NotebookControllerOptions instead of NotebookKernelOptions f5bfd64982b Merge branch 'main' into notebook/dev 57b8d811da1 print cycle of dependencies when instantiation service alerts for loop, fyi @lszomoru 220560dd404 Switched to using Restored instead of Eventually for the extension enablement soft notification c91c21bab07 Tweak dialog based on feedback a4d95b35c4e Merge branch 'main' into lszomoru/workspace-trust-loose-file f9ca74adc43 Merge branch 'main' into notebook/dev e51173f3b6f startup code :lipstick: 92d5997b1df "Help us improve.." notification is displaying language IDs (fix #121216) 25785b97e53 Adopt `requireTrust` for my configs e870a6d7004 Update js/ts grammar a5d656cd3f7 Configure workspace trust for simple browser and image preview 293061802c2 Add workspace trust for markdown extension be2026c324c Only try to scroll to element when the target path exactly matches the base path of the current page 2ea7c77bf88 Remove statusMessage metadata edbdd6fe092 Fix test, fix cell data startTime and duration visibility 0a6309be1ee Support markdown styling in task descriptions. (#121338) 2881355f372 Use es2020 as the default target for js/ts implicit projects 3e5c20b7626 Change execution duration to startTime/endTime fe7f4c0241e Do not run code actions on auto save bec017d389c testing: rework discovery side of testing to new apis 063bc4b7b11 Support seeding selected search text from embedded editors Fix #121060 1329659d841 Remove "please" from messages Fix #121312 7f2bcc15304 Fixes #119032 - changes to support settings UI 88361f24e07 DAP: add a missing comment f065ba8d8a4 DAP: pass launch config to restart request 81fb5b7accf Merge pull request #121328 from microsoft/sandy081/settingsEditor/trust 263b76c8f00 add colors to css e660d60f84c Merge branch 'main' into merogge/terminal-status 2ed7f15346f adjust opacity bce7ea241ec incorporate UX feedback 7a72e4e95be fix styling in wt badge 36fcc538bda Merge branch 'notebook/dev' into main 6d468a59f69 Fixes #121325 - fixes notification deduplication a9c869d4208 add statusIcons to terminalTabsWidget labels dfc8f1fd037 Move back to min-width for hover hr 9a2b9a79f4a Add a very basic hover for the terminal tabs 22dd9030841 terminal: fix swapped cols/rows d27fa1df47e #118077 introduce delay option c4f59fa1e01 only show workspace scoped configurations 54beeaada6c Remove TerminalTabs from tabs, polish selection/focus b7157be21e6 Fix compile 3e40e14bc41 Cell statusbar API (#121215) 93754ff5f0f Fix terminal tab actions to work on the hovered instance ab15aa4d393 fix build error a32d03dad99 modal false by default 040cfa078b1 Add override in notebook code 5b4ee563a1d Add override modifiers in a few more places 6299e56d22f debug: start debugging requires trust f590a4901c0 add statusIcons to terminalTabsWidget labels 4e6b46a15f3 Implement notebook selection status bar item Fix #121194 279bdca34a2 Set title when reused terminal slc has name 6d81daa07c0 fix #121140. 3636a058e58 Fix launching profile from PATH var 08f9bbc2394 typo fa56f6ea3db Add telemetry for import statement completions (#121243) b42484b6871 add status icons and listeners 9c0300872ed simpler storing of ExtHostNotebookEditor 9afc7348000 no nested DOM.schedule. 361ba1a8231 update scrolling. d3394d21773 set maxheight and overflow hidden for output container. 02f0f4a0c8e make createNotebookRendererCommunication independent of controller, make it renderer first 304200343eb Comment out unused prop 071b4b3b872 Add override to tasks and tunnels Part of #120675 1351e8c6e40 Add padding to terminal plus button 5c74225ab18 Workspace trust transition improvements (#121173) 4cf3e89b67f Fix tab widget layout height af1b5066622 Merge pull request #121291 from microsoft/sandy081/activitybar/hover 388d40ffc3a Disable escape sequence logging by default 94f637a4965 Add new terminal with profile button ec579a2c217 Mark server ready extension trusted 6c2ccf8be54 Implement custom hover for activity bar and panel b2d002de4bb Fix centering, use action bar for + fd640b0b32a Fix tunnel privacy context 20784c13a10 fixes #120981 85bf18a6d8d Correctly align plus button on load ab0809ce864 Clean up terminal tabs widget ctor 0ea92219740 Improve plus icon position, style c9b2c542316 rename NotebookKernel2 to NotebookController d6ae97cf976 add Kernel2#createNotebookRendererCommunication cb8b3ce97b6 polish remote menu separators 5371ac58c40 Include virtual workspace check for task type enablment 38066c2802c node-debug@1.44.24 7dd3678adf7 Change action order in tunnel view order in tunnel view f15bbef705d Do some magic to pick best label action for ports Part of microsoft/vscode-remote-release#4826 94d369e27d1 backups - explicitly wait for editor group to signal restored when deciding to drop backups or not on shutdown d9008be9ab5 Empty workspace is untrusted 5b66335738f perf - change lifecycle phase restored to work differently 4453232aee6 fix #121270: race between terminal create and initial resize 3d276ed5cc7 vscode API - env. Fixes #121261 907ab317dee fixes #120637 3ba684ae3aa Fix styling issue in the workspace trust editor e7934ec243a Moved telemetry into a contribution 1317cdbc5dc remote: unnecessary separator f157a3d2019 Merge branch 'main' into notebook/dev 02f2f886985 fixes #121248 f9490e715c4 fixes #121162 bb19564a844 fixes #121156 c2c5b7eaeb9 fixes #121117 7968e137fe3 fixes #120840 4ea838bcce9 perf - fix issues with how layout restore is handled 08226f4678c editors - whenCreated => whenReady a09d6403113 github auth: improve test env detection b5dfdbe00f1 editors - some :lipstick: renames for editor group model b7b1315457f prefix terminal CSS rules (#121258) 730d57ed65e fix #120485 hide `Open Timeline` from Explorer context menu when there are no timeline providers c8d5c905bfd Fixes git rename on windows a11959e28cc Fix tests 66d0f975442 replace vars to fix build in terminalActions 44b3df962fb more todos 485d6c36463 Fix double localization 509c20c9b13 some API todos. f59a77842e6 Use markdown for link contributions. Ref #120050 7a2f9014556 Only await for worker ready when setting content 1d22f863622 Don't wait until getting content to register service worker a9216fb38c0 Remove __leftMargin__ c188a51edef Use clientHeight instead of computing the size ourselves 4ffd6a8d742 Merge pull request #120878 from microsoft/merogge/tabs-button 066dae222ff Merge remote-tracking branch 'origin/main' into merogge/tabs-button 72b09f10193 Compensate for out of sync clocks in cell timer Fix #117460 084668c6270 remove workspace trust grant/deny actions from gear e143ce4a8c1 remove confusing elements from workspace trust editor 776ab3d6e5e Only enable pointer events for the folding indicator itself 5276ebf9509 Make sure we focus the markdown cell on contextmenu 3bf5b37fcfc Don't trigger notebook.find when focus is in another editor Partial fix for #121242 7f805ea0d93 Simplify redundant context keys 9cd8ff410e5 🧹 294e3eb7ef7 make private readonly in constructor 388a0c80dfd change type of icon to string | undefined 6e5a0a58032 Update src/vs/workbench/contrib/terminal/browser/media/terminal.css 3357bf274be fix hideText d13a914bd6f Merge branch 'notebook/dev' into main 4dfa06cd356 snap right or left based on midpt c5fab4faa37 Boolean Trust State (#121141) 75f2ce9735c Adopt workspaceTrust 40a6563f9b0 Remove git-ui extension 51451d8355f get rid of unused menu a0099970e94 align button 71230441fa4 get button to stay on the page 090554355df notebook: cache preferred mimetypes for renderers abf4835fd28 use tab instead of terminals ab650745458 add instance connectors a3444b12123 Theming for keybinding label (#120727) 9d1bfa6a6aa update distro 1d5f53777cd finalize #77423 (#121213) 23e346ab55a Add allowCommands option (#121211) c826e9aabd2 Explicit remote CLI commands (#121212) b3ff207039a get text to show or hide appropriately ceda963326c Change proxy scope based on `useHostProxy` flag (#121207) 2e165500a6d Use CSS variables (#121208) 71043537525 php: use which (#121210) f0a344a76e2 npm: use which (#121209) bacfb76d909 localize action names cb6a3cc3775 add rename Co-Authored-By: Daniel Imms 80dba2b8782 set min width Co-Authored-By: Daniel Imms 75bce498f9e fix css ed139c10361 hide label text when min view Co-Authored-By: Daniel Imms e0a85f94c59 Move windows mitigation comment into terminal process fa852ac3f16 Remove redundant call 36c68a5d6dd Speculative fix for unresponsive pty host 83f13c70ff5 Merge pull request #121181 from microsoft/tyriar/reconnect_fix f2b1e94fe78 api todos update 7c946e118a6 add onDidChangeNotebookAssociation-API and wire everything up using the glue adapter dd14addcbd1 adopt to trusted configurations fc15ccef8d5 Fix terminal reconnect, remove writeSync completely ade32e71e59 add ability to bind a notebook to a kernel, add events and forward to ext-host 14b07fe6e75 Disable flushing of output 3986914a040 cleaner setup ed545b8a43a add missing block 10bf5cfd61a align kernel and kernel2 a little more 6725bd39334 [json] fix for workspaceTrust ed7d5c0d0e7 move all views under removed view container into default view container fcd005ce8c3 fix compile errors, evolve internal API about kernel selection 8e8621c137b [css/json/html] add workspaceTrust property b90e3e7786a Fix spacing issue in feature list 25366914862 rollback distro temporarily 89032503e31 wip 8c18565aae5 bump distro 1aa4efcd7ae :lipstick: e66dbdeb459 default supported languages is plaintext bb55852fc2b relax NotebookDocumentContentOptions 2c123cc1a8d always show notebook status bar entry 394a2e4779f update distro 49796d31cb3 virtualWorkspace context key to detect virtual workspaces. Fixes #121165 38f2172759e editors - do not expose raw group from editor group view 5e33a56d232 editors - cleanup editor restore logic 0788b3fd68c Merge branch 'main' into notebook/dev 800ac2f7fac add doc 0c555419feb Extension workspace trust request using product/settings files (#121021) 601dec633b0 Reduce callers of createEditorInput when not needed (#121098) (#121099) 48387dfc3d6 Fix loading of webview resources that depend on query params eac3821fbf9 Hook up basic context menu for markdown cells 50c310b6d7a fix #120955. f69d2cc6dd4 fix #121032 ee0a183de19 fix #121115 60a7c6a9253 notebooks: add initial kernel/renderer constraints f0cb4fe60ce Don't check cancel token after it has ended its lifetime Fix #120939 f7e5a67039b NotebookDocumentShowOptions#selections. 119581fc2ad Use more standard name for shared extension tsconfig 98bbcddec35 Enable noImplicitOverride for extensions d5f4e119e05 Use `Array.from`'s map directly instead of a second call to `map` 32a7858a637 Remove override 9a00c870b9b remove NotebookEditor#selection. aa1c694664c Add override ead5639bbe1 Pick up new TS nightly for building VS Code ab4c0f1e2e4 Remove duplicated code 2637da856a0 Use URL instead of regexp for getting webview id 069f8f39591 Load webview scripts async 62c62103fcd Use javascript modules for webview host script b4d42b14241 Use ?. for accessing webview 1bf5e8f15c2 Remove `getInnerWebview` 48e7505c677 reformat 8f7df5e9c92 Merge branch 'notebook/dev' into main 1132a8a7d34 only show rows on hover 7dd9fe7e963 only show actions on terminal isntances e963ace70ff Bump vscode-ripgrep 964ad75846c fix build 52c11b376ae Merge branch 'sandy081/settings/trusted' 27f3fc1f157 Merge branch 'connor4312/test-output' into main 1f3acad6320 testing: polish up test output terminal handling 9de25518855 add actions to items a546db679a2 re #119599. Avoid flickering when outputs are cleared. 21806b884b9 debug: display error response from invalid exception filter conditions e8325e7f073 review feedback e5bef439287 Merge pull request #121027 from jeanp413/fix-121026 8e183f0d2ed terminal: standardize on customPtyImplementation 2962e6ade1f update untitledHint, remove button f1ca325298b always allow split cells in cell toolbar. 2864eba6b06 get rid of twisties 5c99b8c11e3 Merge pull request #120925 from microsoft/joh/kernel-push 31da9f54b86 some jsdoc 88006b84373 add (bring back) NotebookKernel#createNotebookCellExecutionTask f88027607dd fix #120906 panel issues 7f728230675 DAP: setExceptionBreakpoint optionally returns breakpoint array; related to #117789 51ff6b5e4e2 untitled hint: do not render keybinding 08a9de6e3e0 add rename to context menu d38cfa11d8d Merge branch 'notebook/dev' into joh/kernel-push 82a5fe0aeef test 2 d837f9a26c1 Merge pull request #120503 from conwnet/main 8e41cc0471b test commit signing 79e47e06717 try to make monaco ESM happy 95fe64c807b api todos, remove unused type 4358b0c9f53 change to warning 765ce34320b vscode.openFolder: forceLocalWindow 5e49fc945f4 fix web dev builtin extensions b7d7aaca741 add NotebookTextModel#reset which allows to re-init a notebook, use when reverting notebook editor models, https://github.com/microsoft/vscode/issues/120771 b36bcfe1b4f vscode.openFolder: allow to open a local window 318fe9ff80a improve TestResolver command names 219270a10c7 adopt to workspace trust 4c1946a2ecf #120675 adopt override 905a9016720 editors - supportsSplitEditor => canSplit 4ea02ea5e27 split workbench-web from dev 973f5d00b43 code-web: /builtin 53ec27a6c64 editor input getTypeId() => typeId 29aedf93830 move defining configurations requiring trust to workspace trust property 52ee0bd562b some more "no implicit override" work 8988e849bad Merge pull request #120891 from timmaffett/main 25d61e6f137 editor serializers - only allow 1 factory per type id 1b26a6d3224 Merge pull request #120938 from bourdakos1/update-seti-icons b0b9ac30870 editors - :lipstick: a few assumptions around editor resources 1994b301513 Color Theme: add button.border. Fixes #120868 e6e2249f09e Expose stop/start methods and have start wait for all pending delta extensions to finish fb5ea7962a1 fix yarn lock 66c78c9031d Merge pull request #120841 from CanadaHonk/patch-3 d2b6aa211d4 Problems in no-implici-override-watch 1bc121e0b80 Add all valid hosts to simple browser (#121080) e70d5b0aee3 task for no-implict-override-watch 08037076738 fix #120911 b2a29c09430 Slim down the rendering of the remote command palette. Fixes microsoft/vscode-remote-release#4831 d6d57945d81 autodetect settings: add reference to preferred colors 932742fb26a Resolve unix shell env when default shell is PowerShell (#120769) af0d76ffa98 Merge branch 'notebook/dev' into joh/kernel-push 4b719f95d8b add to suggest list 2dc222a3ae3 add code actions for untrusted settings 2278ddfa233 - update schema in non trusted workspace - decorate settings requiring trust 99524b3229a Fixes #121026 0dce221fa2e fix when there are no untrusted workspace settings 2e204caf72d fix compilation issues 48bdbd87453 Merge branch 'main' into sandy081/settings/trusted d6cf66331cd update trust label 2331507e97a - show settings requiring trusted targets as readonly with action to manage trust - command to filter settings require trusted workspace - Show the number of settings requiring trusted workspace in workspace trust editor 01466cf692c Remove IWorkspaceTrustRequestModel c41460cba5b Split WorkspaceTrustService (#120974) 3b4a74297c9 Getting Started: Allow click to toggle completion Closes #120996 148029c5357 Make find expand widget button themeable Fix #120899 0b305965c90 Close #119964 5d85fe4fa0b #119964 8047d350150 Clean up details view for small windows. 6b666279b8e wip 4c78efda3db add snap when in no-man's land 41861dc05b2 switch tabs when instance is clicked on in tabs widget a5af80b4bc9 Getting started details page scrolling 6e978a960c4 Fix centering 8a8e2336697 move + button to the top 2134666ed13 fixup scrolling of categories page a7e080af027 Revert "Don't block svg loading inside of iframe based webviews" f95b7e935f0 Revert "chore: bump electron@12.0.2" c5dd09e2f57 Update to use new webview url for permissions check a7a14ff215d Don't block svg loading inside of iframe based webviews d5ecfd54f1f Merge branch 'notebook/dev' into main 751d8deef06 load show more action only when necessary. ee4bfad688b use custom icons on reconnect/reload 😄 6229e7a57f5 change to async 8df884882c9 #120860 Improve untrusted settings shape - define requireTrustedTarget on property - add tests d993d5d5557 Adds proposed api checks and splits proposal b8afc6415ba Simplify the trust editor (#120964) 4020b1a1e3a ci: disable exploration branch sync 532a0b3a99b Onboard search-result to workspace trust. dfef0f6e43e Jackson/getting started grid (#120958) c15d1c342c5 remove one command via consolidation ded8b935cac fix typo 46feb94438d Bump windows-process-tree, fixes #120570 5c7879c29fc Merge branch 'main' into merogge/tabs-button 261862e2be8 make private readonly vars 6ba185b5952 removed font classes with hard-coded font stacks c6d0c1139ef add check for show tabs and return min_width isntead of 0 6fe8914f37c remember split across sessions and set sane default 18c0f5ae8df add context menu groups 1820f177611 removed mention of user modifying CSS with extensions, added further explaination of the font support and font stacks to allow developer flexiblity in logging to the debug console. 1936c34a331 Fixed and extended Debug Console support for most ANSI SGR codes 58faca759ab custom editors - implement save retry properly f7d9b934d34 Bump distro 2894338f388 Add all valid hosts to simple browser (#120929) 84043ab3b8f fixes #120934 243f4660ba1 Update seti icons 957d8d2e74a Ensure tab widget doesn't resize on workbench resize 1ed73408cd2 file working copy - prolong shutdown for pending saves fd315204d7f :lipstick: d8e1c802f74 add some override https://github.com/microsoft/vscode/issues/120675 ec8c7257eda remove/disable selected and onDidChangeSelection from new kernel world a1139fd83c9 :lipstick: 760ca343734 add some deprecation marks 5cadd1240b3 Merge remote-tracking branch 'origin/main' into merogge/tabs-button ead08c9b518 update notebook editor status when kernel "instance" changes 50fe76ba290 Onboard task extension to workspace trust b2b90e970c8 Improve type safety around terminalTabbedView 611bda0dbd4 Consolidate tab tree creation acb172752a5 backups - move tracker and restorer out of contrib into service 2d2696a6d6d Adopt workspace trust in the tasks service 72b99e07bb5 backup restorer code :lipstick: f103814a2b2 Add Windows pwsh detected profile, remove cygwin source 43f58dbe1c6 Merge branch 'notebook/dev' into joh/kernel-push e200749f888 remove NotebookEditor#onDidDispose aa4285b8b5a remove hasExecutionOrder from notebook metadata edd36fd64cc add and adopt implementsExecutionOrder 79a429d92d2 chore: bump electron@12.0.2 5bf85d71d89 perf - warm up canvas in idle callback (#120916) c82eb7d4980 bring back accessibilityPageSize from deprecation, it is still needed due to upstream issues b0733b55b98 layout code :lipstick: 845efe5d8e3 debug: Variable do not override ExpressionContainer type 239bc433cb7 Debugger: Add option to allow disconnect and stop/terminate UI elements fb5cc92c38b Update no commandline message in ports view ac4f97790f6 fix quick input title 3af3fe66aaf Improve logging (#4813) 41985fd83b3 Update distro commit 53746f5cae7 fixes #120905 fb102278354 fixes #120871 9f9f73d704e perf - more fine grained window counters 4cacb3dce4c make executeHandler readonly 4c3becc0bbe Merge branch 'notebook/dev' into joh/kernel-push 9c40ada6157 Merge branch 'main' into notebook/dev 65a8d0905e3 update ref viewlet f8c7dd64a4c tests - ensure preload promises are all resolved ec972e8acc0 ts - fix some override errors (#120675) 479d3fcbed4 Remove log 13944bac07e Pick up new katex version 062e89fa198 Don't scroll to top when removing entries from recently opened (#120404) 08c3c088ba8 Polish getting started layout d5e4bdc814b Remove unused import 3c89afbbc43 Pull in newer markdown-it katex version 9708fb15ff9 Fix markdown cells being re-rendered twice on edit 989c39e178f Remove commented code ecb45f5207c Move shared webview focus implementation into base class ed655c2d2ed Fix split json editor underline Fix #120876 4b5ef688725 Use cloneNode to inject default styles 02e5ed5280f Avoid calling `applyStyle` again if the style has not changed 6b5122bb067 Add typings 3fc15fb34aa Remove unused function 923fbc8c2a3 Cancel resource load when the webview is disposed of d5b46d6e56c Use transfer for sending resource buffer from webview to service worker fbf86d3709a on create, focus active instance 595c14a8d30 Inline `rewriteUri` into `loadLocalResource` 2d7ddf5a381 Remove extra indirection around `loadLocalResource` 8a918399897 Move webview resource loading from `platform` to `workbench` de9887d9e0e Remove vscode-webview-resource path normalization logic 9c9e188aa0b Don't use 'self' for image editor csp 044a99585c3 Make sure we use the webviewResourceRoot on for computing cspSource 84391165c30 Pass in correct project root for notebook cells f5ea03079ab turn off logging for notebook. 4d1131b7b32 revert a change 3622dafdc01 Adds new proposed api for scm input validation 4c02536030d get context menu to work 99868a33bf6 get profiles to appear in context menu, not working yet a8563509738 april. 9bcf5a56253 try to modify tabsMenu actions 7c2007b9259 Merge branch 'main' into merogge/tabs-button 0203f8e25a6 add plus button and terminalTabsContext 852375649cd Remove extra whitespace 614dd5174f9 testing: fix explorer view 618705cc574 Merge branch 'notebook/dev' into main 93934cacd0f precalcuate cell editor height: guess if the editor will render scrollbar. bc506127abf Temporarily restore vscode-webview-resource constant 1c27a285175 Fix rewriting old-school vscode-resource uris 7c26f27d906 #120860 Implement trusted workspace settings f689971195a Mark Emmet trusted bd815e462db Don't warmup markdown preview when input is collapsed 437f2383b92 Add icon to terminal tabs 618f06314e6 change terminal tab height for uniformity 2f26adffbe2 fixes #120786 8ec2bceec99 fix #120776 remove stray words from `contextualTitle` description. (#120777) 13f4f052582 Fix spacing ae1452eea67 Add script to run build with noImplicitOverride e29e1f908a4 Adding a few more overrides 86bf0279201 Add a few more overrides in codebase 5904b7b686e chore: info.plist => Info.plist e1f0f8f5139 Add override keyword in codebase (#120755) 604b950f0da Reapply badclient commit 37025381399 fix #120824 e956e0a0eec 🐛 FIX: Typo (#120816) 464e51e24f0 Merge pull request #120054 from stoyannk/fix_extensions_profile 13452d7fd68 Fix JSON formatting in High Contrast Black default theme (3) f4449876e4a file working copy - getAlternativeVersionId() => versionId 354c914f677 add NotebookKernelOptions so that createNotebookKernel is simpler 015d5565b71 remove createNotebookCellExecutionTask from NotebookKernel2 because exec-handler is called with executions be198294e41 execute handler gets notebook cell execution directly 6062a5458a0 Merge pull request #120591 from microsoft/merogge/tabs2 7a1e1fd2af7 Fix ctx menu, clean up DI a4fed64a16e fixes #120188 ffde5985b22 Update color for port wtih running process Part of microsoft/vscode-remote-release#4826 37a44ac7dbb Types, polish 29d772fc9e0 Merge remote-tracking branch 'origin/main' into merogge/tabs2 c14109e5ec5 perf - wait for dom ready when waiting for services 61dacc2f813 Add hover underline to port address Part of microsoft/vscode-remote-release#4826 e9b4e01a4b2 process explorer - fix window title ffe10c858b7 Add ctrl+click message to address cell in ports view Part of microsoft/vscode-remote-release#4826 c111a4caf2b perf - delay notifications for extensions disabled to a later point 10c17f374e5 fix speling error 28c734595ac perf - add marks for crash reporter and window creation 965f86890dd Merge pull request #120815 from CanadaHonk/patch-2 936219d314f Fix JSON formatting in High Contrast Black default theme (2) eadff843e8b Merge pull request #120761 from CanadaHonk/patch-1 969af665349 node-debug@1.44.23; fixes #120762 2214484ee44 startup perf - load NLS module conditionally; add perf mark for main IPC server 11d3a8027d9 debug: better launch schema updating 7c3f60f86e6 multiple action hover feedback fixes 5ffc27fb629 log error when vscode.workspace.applyEdit fails, https://github.com/microsoft/vscode/issues/120760#issuecomment-815602556 461f09ff789 add extension identifier to kernel 2 6abe3beb6a5 Merge branch 'notebook/dev' into joh/kernel-push 38fed243558 make auto-referencing of dirty notebook models simpler and lighter 9a8d55525d0 fix: skip sha comparison for info.plist d10367cee83 Merge branch 'notebook/dev' into joh/kernel-push a41711cab5a Windows: --user-data-dir argument no longer supports relative paths (fix #120269) 44207598e9d sandbox - properly await fs creation 75da2b7add1 sandbox - wire in paths from main side de3aca7a531 Revert "Add badclient telemetry property" 1a141423308 remove empty line 71d57e05dcc copy directly from master 966eea39277 🧹 2a9e136e1b5 revert more changes to settings 77d5d2c7c2e revert some changes 494536b12e7 find widget 0b89eae87f7 add a bunch of stuff from terminalView 2c77e72f8b3 add or remove view depending on showTabs e6c32c3d743 create issue to track indent guides todo d228dbc70a3 use TerminalTab e5ef9895b6e Add badclient telemetry property 513f2072960 Don't overwrite perf value 5207b984029 Update JS/TS grammars 74a43ce1498 Merge branch 'notebook/dev' into main f593c515674 better perf marks. 3241e1ad249 Improve tabbed view size ranges c492147a5b2 Simplify instance node 5b6f939eb2a Don't remove the textmodel when closing the notebook editor, and cleanup how the textmodel ref is created Fix #117936 more 5bcbaa2d825 Rerender tabs on title change event 3222bd6698d revert to node-debug@1.44.20 6846fee42fa Prevent disposable store exception e6bf0c58615 feedback d05ded6d3b6 Use service workers for loading webview resources on desktop (#120654) b4fe86328a9 add some more checks 4f55d8181b5 Fix JSON formatting in High Contrast Black default theme c19bae2dfc2 Pick up TS 4.2.4 fe106399c1a Make splitting respect supportMultipleDocument (#120263) bb6757bfc6a Pick up electron 11.4.2 (#120670) 73ae56b82b0 Build with latest TS nightly (#120690) ca0a583a1b4 🧹 b116dceb478 Merge branch 'main' into merogge/tabs2 8d235ef07ca Fix #120614 c2075cd2a98 revert some changes aecaf0a6ac6 get widget to work 95c63b68e94 Support env in terminal profiles c2269e6f64c get terminal container to show up 967a33b6e23 fix code cell output offset flickering caused by wrong font update. 28298795b13 debug: update debuggers, fix node not activating 5c12763dbe0 adopt toggled.title. 1db8a625c2c fix #120603. 74351e8f09e Remove processBinary from localTerminalService 970b14808c1 Update distro b3ce5af360d Remove optional reduceGraceTime arg 17220763b62 fix title for toggle line numbers. f8771f455c1 add Tabs Location fe6e081a259 extensionEditor: focus only on enabled items 264ca56fe1f Rename initialText to message 72d772b1a15 make execution and interrupt callback instead of commands 16d8f35b261 Fix incorrect welcomView calculation for ports 07d0bba09d0 status bar: focus improvements 55b1c5bca85 sash working! 76a230d4b47 wire push-kernels into internal kernel provider API 08508fd2d51 call TerminalTabbedView.layout 17d52861097 styler - optional chaining ftw aee12f9812c Add green to running process circle in ports view Part of microsoft/vscode-remote-release#4826 37abf64afd3 Add command line regex for port attributes provider Part of #115616 50d89df33cd css is awful 84f30546240 add local resource root and cancelCells so that Kernel2 is very similar to Kernel 4075475d310 accessibiiltyPageSize default down to 500 9221eb96970 don't forward changes for disposed kernels a4270b8b6fd Merge branch 'notebook/dev' into joh/kernel-push cf1634602a4 fix compile error due to missing mode service, fyi @roblourens bb90ba3d5a5 sandbox - delete "Code Cache" folder on startup for now (workaround for #120655) 5dd5dd81004 extensions: more CSS fixes f76b1ae3409 add test for extHostNotebookKernels b1523b435b0 Update port attributes api to not use array Par of #115616 38e4243e2d7 add kernel push to proposed API 30dcb112b46 fixes #120698 d1505792171 fixes #120706 eec393e0d47 chore: use bundled node-gyp (#120683) fefe5f324a6 extension view: CSS layout tweaks b854effee99 Fix #120700 3cbbe4d2494 window - make sure all perf entries are there 5aef07536d9 window - more aligned window config handling 269025c30b0 hmm 0791b3d49c9 Merge branch 'notebook/dev' into joh/kernel-push 155f187b816 Merge branch 'main' into notebook/dev 219509dc2d7 fix yarn lock check 28a55539bb9 window - create a proper window config 04b6291eed8 build - push missing compile artifacts c1db1db7b46 Set new language on TextModel, and listen to mode changes on existing cell text models #120284 6beb1b43af5 more splitView work 05b52df98a5 Update JS/TS grammar 28a246d9ec5 Don't call node-gyp anymore (#120673) 0e15648f9fc clean up a bit 3546e73c682 take 2, sash still disabled but otherwise working d2f39824975 update markdown cell heights synchronously on init. 7ed402d3676 Remove nextTick usages 8117fec7dfb Fixes #120036 (#120275) 4cd60098755 Avoid measuring outputs unnecessarily Fix #120282 bbcaac9693c Fix output.selfClosingStyle, fixes #120417 b7989dddc59 Goto line number does not work when --remote and -g are used at the same time. Fixes #120487 3efdebb1e7f Merge branch 'notebook/dev' into main 331015eaf0c track notebook file open perf. 408d3561117 Fix #120657 d3d7966f8cf Merge pull request #120382 from LuisPeregrinaIBM2/main 0e34376ed38 Hook up actual icons 1b70c818a17 fix error, sign off for now 070e7087094 revert some changes b8b23d85200 bump distro 4209e2cc125 Workspace trust not enabled in web 6b1d3bfb949 delete comment 2b6df1bba95 sandbox - disable vscode-file:// again (#120655) ab4f455f850 more migration 56b96bc997b The Great Migration of code from terminalView to tabsView ffb7adc71e0 Revert "fixes #120166" 551daf412ee Polish and fix #120245 d520e940afe Update Codicons: Fix typo for Debian e7e15984bcb package.json completion: fetch on every key (always incomplete) 0e7d1610f3f Update Codicons: Add terminal icons https://github.com/microsoft/vscode-codicons/commit/27e4b9b87ef8b558f301834f09ac2abdea513597 383a22e924a try more stuff 76ec94285b9 a little closer c7faaf0ea08 update wt static declarations (#120443) 3d6f3002545 fix #120641 c3f72be4959 add createNotebookCellExecutionTask to kernel object baeae379657 correctly use outputs c1236953128 fix syntax error 4790696ccca bring back task output 99d9bf26974 use this. 07dab53ae14 more yarn lock changes dfafa70dce0 add debug to yarn lock task 639a32f9093 Make sure yarn.lock changes are allowed for committer PRs (#120642) d52fbadeb20 more cleanup be2e72af102 clean up 110be45dcc1 Move icon to side in term quick access 05de36649f2 Merge pull request #120317 from Jolg42/npm-ext-use-npm-only ac35a5b937e better fix for #117095 and also fixes https://github.com/microsoft/vscode/issues/119086 0a9caa8efba Use tools icon for task terminals 2f62eb09067 ignore focus dependent test also on GHA, fixes https://github.com/microsoft/vscode/issues/120355 962933e8be8 fix actions margins across workbench 1603dc79ad2 Hook up bell status 33d5a743555 xterm@4.12.0-beta.20 8843da37a91 :lipstick: 40a16b61a16 Merge shellEnv into process.env (#120332) b47fb7a2756 add kernel service and add extension host logic for it 92281913a1b shell env - change to ipc invoke/handle to speed up resolution d491ec5c728 Merge pull request #120581 from gjsjohnmurray/fix-120578 6d9ca4a7a28 Merge pull request #120457 from lf-/config-error-reporting ffd7a242f5b fixes #120166 f250472adc4 lifecycle - onShutdown => onDidShutdown ee4b092ef2a Merge pull request #120362 from andrewbranch/ts-4.3-update 23d1a967cfd :lipstick: a1b33b14853 Fix #120340 6d097be455c fix test d06df602de8 Add resolveWithEnvironment to config resovler Part of #120328 fef62f8d80d Fix #120486 b655f698806 update cssnano 4d5c6269454 fix logging zip path 65b8dfc686f #120545 remove usages 6a1404a6f80 fixes #120518 fdbcc90b4f0 Fix removal of port label 52d2d096049 Trim port label Fixes microsoft/vscode-remote-release#4832 1e03dc25374 :lipstick: 4202acc9f76 node-debug@1.44.21 accd84def60 node-debug@1.44.20 5d94a7c9550 Refactor worksspace trust setting ce275c6c0c7 remove obsolete todo-tag 58186680eb9 sandbox - enable vscode-file// protocol by default (#98682) fca4457adaa Better action hover feedback (#120247) def8fc7b0d5 integration tests - revert changes for commands test fca1144d009 remove NotebookDocument#cells in favor of cellsAt, getCells, and cellCount e848d708990 tests - increase leak warning threshold for #119968 e637ff1bf7f Update perl grammar Fixes https://github.com/microsoft/vscode/issues/120466 0215117ec6e Make easy adoptions of async configuation resolver service (#120326) 82c180bf346 tests - easy on the timeout 8de37914778 integration tests - use async test (#120225) 6f8b983dfdf fix tests 8684456b1e3 Merge pull request #119021 from qchateau/fix-format-modified 30f0d188c71 sandbox - prefer sandbox types over node.js types 6b67774b756 use NotebookEditorInput to work around https://github.com/microsoft/vscode/issues/120284 584d0acaf1e Fix #119369 ff0f7f3cf24 Make product configuration available to non node.js environments (fix #120243) 0c14397b64f Merge branch 'main' into notebook/dev 224127ce54e add splitTabsPane dd37fc58cad fix #120513. 1722d6e243e Merge pull request #120590 from microsoft/tyriar/status 440a4415ade Merge branch 'main' into merogge/tabs2 6d55039842d prompt user for reload with wt tree changes be56a9ec0c2 disable step* buttons rather than hiding them 329804e4cb3 Remove only e757e4e25ad still not working 96e5868749c Move to quick pick for configuring welcome page item visibility 617b7c0067b take a break 04d8568000b cleanup unused var b7b1e0cf8a6 Add unit tests 1e674d98414 Expose on did change primary status event eaf1c8c6e9d Hook up disconnect and relaunch needed statuses 0f696464e69 get instances to focus on click afebedaf0ee Show line number for current cell. 4377f6f19de Docs, remove old timeout 7d6c5a0174e Create status list 2633dd01e3b :lipstick: fb824ca76f1 fix using ref before initialization. 8d57595fe8c testing: store results in separate files to fix ui slowness with large results 0821b6fcbfd Prototype side by side commands for getting started (#120436) 37d72096b2a context key checks for cell line numbers. 92ff8a92093 rearrange code 30287791f85 Merge pull request #120562 from microsoft/tyriar/terminal_icon 04fb146b0a5 fix-120578 don't offer callstack step-in/over/out when not stopped 59c22b33530 more improvements 192e36293c4 Remove icon from title a6754b2bce2 Merge remote-tracking branch 'origin/main' into tyriar/terminal_icon 9e86b50fe71 Remove todo e4300049a4c Create terminals using only profile a2056a83584 Remove unused "walkthroughs" section. Closes #119946 c097d4c618c move show line numbers into editor title f2bdb4f37ab Remove task icon, maybe status is enough? 36cc8dd6611 Hardcode js-debug using id 606a68fa2a5 Fix typings in preferences 76736a879b3 add showTabs setting 782830d29e1 skip failing test, delete console.log, remove reduceGracetime call for local terminals 18548e7af05 fixes #120462 80f11080c1e fix get embedded css util (#120480) 218e702bbb4 children now work 6861fc9480c support action through keybindings. e6678afeb76 toggle line numbers for notebook or active cell. 804c1fc594d notebook line numbers. 65be3acf599 cleanup access to process.env; fixes #108804 1c04d9f2df3 Update PHP grammar (#120339) b3c6ef20394 Correct detected separator condition 060ac497525 Add contributed to create with profile 909f1ac17fa Support icons in terminal type contribution abe140a32d2 Add a placeholder icon for tasks 83ef138eb37 Add source/profile icon schema a5bcb9f3b59 Support icon in source 6359fa608b4 Add icon to create with profile ac7338b4eab Add terminal icon to quick access f8810116c6f Add terminal icon ext api 0e7fc5faf54 Add icon to launch config 76acf1ef644 working pretty well 027f6293107 add to terminal view 17ae3eea6a1 Update notebook.inactiveSelectedCellBorder for HC (refs #117705) e8b3426f9b8 Fix #117705 3edb33fc0c3 push terminalTabsWidget e7805ded7e0 Merge pull request #119468 from anthony-c-martin/update_vscode_icons c86fff70459 Update distro 14470bff05d fix: only add readonly attribute to textarea dom when editor is readonly 874d19930e3 Calls new fork repo command in a codespace 3de15ba87ce Improve reporting of extension config schema validation errors 94c9ea46838 Merge pull request #120419 from microsoft/tyriar/profile_test 96eaf9dd9b8 update distro 60e19db154f rename channel action processBinary c61393b0c17 void -> Promise 731802f9452 Merge branch 'notebook/dev' into main 5364c0f9c92 update distro e6cce4722c3 make reduceConnectionGraceTime and processBinary return promises fc71fbf4905 render outputs for viewport. 83cf9f1503a wrarmup outputs. c5974ffde6e :lipstick: bb4b451c17b Merge remote-tracking branch 'origin/main' into notebook/dev b88f1ede0ec Emmet polish and updateExtensionsPath changes 7cd615ca4c3 Restructure outputs in notebooks to use relative offsets 638e801dae7 Remove extra not-null assertions 040454db280 update distro to include ptyService async method processBinary 7dc766dabc1 make processBinary async 215fe3d92be fall back to dom if webgl loses context (#120400) 15af2df708b tweak api based on conversations (#120397) 99ffa6db260 update distro c247af6a237 remote processBinary 8fcdb2035ad Improve descriptions, fixes #117516, fixes #117518 d0061c5db63 Update distro cd7499c1677 Ignore shutdown pty host messages when already shutdown 8be3c7391c8 Correct Windows PowerShell path 6b9e16f22f6 Remove only, describe 76f61e9183b Use profilesEqual helper 3f843c863a6 Test pwsh source fallbacks 6082284b318 Improve fs provider in windows tests 75b9018255b Validate /etc/shells and findExecutable in unix tests 8c315552d13 Merge mac/linux tests, clean up c159ad6fb4e Add create with profile to term quick access 3ef964364cb Fix issue with getting extension trust requirement 55003580840 avoid flush cached focus mode. 4f46f64b437 Add slight right padding to markdown cells b831938a47e Update distro cd4ef67520e xterm@4.12.0-beta.15 b62d4a06bba Use a single resize observer instead of one per cell f6b67b6cc32 Batch together a few more notebook change events 81f2d352a35 update distro again 50a900f51eb more 🧹 ce9c754d743 markdown editor model might be disposed. 4dc3a84c60e Flatted structure of preview nodes in markdown back layer webview 0a2a57cfc37 Clean up interface for dimension update apis 93b7b60aaf8 update distro 962e1afa634 🧹and store isBinary in writeBuffer a99d2735ff8 notebookEditorWidget.layoutNotebook is still the better place. 68207be49ca compilation error 9b3eaf003c3 move layout cell into cell list. b0a7f84a178 move layout cell into cell list. ade3e6e6831 testing: some api polishing 2a3c39017a3 wsl -l -q, removes header and default mark 788d62f10f8 Merge branch 'notebook/dev' into main e3d59bf3a55 fix #119661. a3a61f467e1 Fix webview search, fixes #120158 (#120373) ef1e9bfa2ca Merge remote-tracking branch 'origin/main' into notebook/dev c8ccd07d0f7 Add initialText proposed API 062ed0700d2 bump distro a816fcfb6b5 Hide outputs with visibility instead of display 470e37c3d85 Merge the two scroll events 0e5ecf116f0 Reduce the number of times we fire updateSelectedMarkdownPreviews ee7c454339b Fix potentially null reference while updating scroll tops fcc12d58d21 Batch dimension updates from backlayer webview when possible 5f445eae300 Avoid running content height update multiple times per frame 50b58c4868c Use for/of loop 003c7d95e4f Remove unused values from webview messaging 2557840bce4 Remove extra conditional 4bc5ab886f1 Use for/of loop 000e90ec385 Use single call to add/remove for classlist c131f155684 Add guards to updateMarkdownScrollTop b3df72fe764 Skip sending updateViewScrollTop if there is nothing to update 04492b42b04 Use for/of loop 4b20016f0df Use for of loop 79011a0fe1f Extract duplicated code 0691553200f pull reduce grace time into ptyService method (#120195) 3ab41807e91 fix compile error. e2a0fe60b27 cut aligns with delete cells. ae6d1a7328a fix #120187. 890372404a8 fix #120348. 1fd558ab2fa Fixes #117513 - deals with very old git 8426ff6475c Update language service call for 4.3 protocol f3f8d3d870a Fix tests for #120356 083bf902040 JSX self-closing style should default to XHTML, fixes #120356 7a263003017 debug: update js-debug 4cd94d0299f `ILanguageSelection` should not be `IDisposable` (#119968) abff1971b9f Update to vscode-proxy-agent 0.8.2 (#120354) 8e93733fb9a Revert to vscode-proxy-agent 0.8.1 (#120354) c7be0b52229 Adopt `assert.deepStrictEqual` (#118667) 1ed36fae345 Fix #117881 a56afa9033c Emmet: Select next/previous item does not work with script tag. (#118842) 595a894b21b Add emmet self closing abbrev support (#120311) 3e22994cad3 Adopt latest loader (fixes #120150) e664085b70e Use optional chaining in MainThreadTerminalService 11e96b2da8c Fixes #95843 49a164e8862 Workspace trust API polish 648ea3d1e03 Refactor requireWorkspaceTrust to requestWorkspaceTrust be5170cc19a Fix #120343 5dc3db6ca24 Factor in workspace trust when installing/enabling an extension (#119069) ee2fd0ea637 Set up scroll width even when width has to be detected by the list (#120281) 50ce1902037 Update PHP grammar 1e6d85953d9 Debounce port change events since they can be in batches Fixes microsoft/vscode-remote-release#4759 65a763bdf8b Remove long cpp colorizer test Fixes #120095 f427914f064 Add telemetry for task engine version Part of https://github.com/microsoft/vscode/issues/118201 fd14474d344 Add IShellEnvironmentService to configuration resolver Part of #108804 d05d8ca4c25 Adopt async configuration resolver in Tasks Part of #108804 13e5c93740d refactor npm extension to only use npm 0b8a7ca21a9 :up: distro 1f30107d9e6 product - move the interfaces into base layer for reuse in sandbox 2dab9c80c1e bootstrap - some more cleanup around types 1db94dbb102 sandbox - add additional arguments also to test runner 0640cdeb8ba revert addition of events 4155993b36d add onProcessBinary to ptyService c81420bb379 add onProcessbinary to ptyhostService a06fd62f351 :lipstick: 78a974700f6 cellAt can actually return undefined. c65082a28d5 avoid offsetHeight check on monaco-editor output/ 7a98d1fd3d5 fix #119732 b41213c5735 Enable setTextDocumentLanguage (#119429) 0631bbc801b first resize of the builtin renderer can clear the minHeight. 90747e9da6c switch mimetype with minheight. 3fbe95a1cb5 bump distro d5cf4ac0eb9 Fix terminal mouse reporting via binary events (#120145) 7b11e6519e0 debug: call to native console methods in web worker ext host development 5fa18996fc5 Revert "Fix #119369" c4ceba7cb7b Add events for when the active webview changes 0e8edff8533 Avoid extra casts fc691ebd333 :lipstick: d9ab9acf160 Merge remote-tracking branch 'origin/main' into notebook/dev 85ad1676518 update coreActions getContextFromArgs. c0299f4948e :lipstick: aefafa019df Merge remote-tracking branch 'origin/notebook/dev' into main 7c8891965b6 replace slice with getCells. f16c14f484a getCells in range. 7617fa43ae1 adopt cellAt in tests. cc094ec8972 cellAt. 6d0ffa2a345 Update distro commit to include REST Book enabled for insiders (#120265) 6d115ef2fdd Fixes #119738: In case of an extension being installed on both sides, prefer the side which has the extension under development 2754be9605a use viewModel.length. f35264bfc61 fetch focus from view model other than list. eb36432c6b4 use existing func for wid 8094bd9c60c auto show mgmt editor in unknown state f1c4a8676e5 fix #119205. cdab314713b check if type === createInstance a49d15d70de Profiles via command pallette (#120141) 2a611bc6821 update trust parent language 1b934932765 Hides context key ca980ecfac3 update notebook editor view column when moving editors, https://github.com/microsoft/vscode/issues/115704 407a0e37235 assert that we have static view columns 9e8699e844b change the label f7753783ca1 Merge pull request #113155 from plainerman/fix-76879 388eaa34735 simplify auto update options to boolean + onlyEnabledExtensions introduce configure auto update action e715199ccc7 extract createTestNotebookEditor to testing outside of with-util 36ef468d4dd Make async versions fo configuration resolver methods and deprecate the sync versions Part of #108804 1372233695c remove unused service 10a1caa55f8 Remove depreated assert.equals from configurationResolverService.test.ts Part of #118667 c1209ae4e01 extract separte interfaces from ExtHostNotebookShape but not change anything yet 48e11a0cab4 rename fa48622fdf9 split mainThreadNotebook up into logical pieces a851a2612f4 Add openwith entry for search editor. Fixes 119185 (#120198) eece236bbd6 Fix #119369 5457db8aac5 Revert mkdirp resolution 96ae5e83d28 Add availableFilesSystems to Add Folder to Workspace Fixes #120232 5a673f33499 Revert node-pty update 4c76edff2b3 Revert "skip failing terminal tests." 141ac31cdcb sandbox - ensure to validate vscode-window-config ipc call 203f1cb56b1 add some logging to ipc object channels 1d883f8af1d fix issue with finding required node handles (#118091) 0f7d20c3b4e Revert "Add resolution for mkdirp" 7dab1460c16 Add resolution for mkdirp e9047f01dcb Merge branch 'lszomoru/mkdirp' dd360b25da3 add notebookEditorModelResolverService.isDirty and use that when forwarding events 65fbf884a8f :lipstick: bootstrap types 6b5f2532d9f cellCount and cellAt API proposal so that notebook aligns better with text document 5ec2f69fb30 sandbox config - add forceEnableDeveloperKeybindings 5b15a3c2636 Add resolution for mkdirp 8a4d271f43c :lipstick: bootstrap types 970f7998901 preload - apply zoom level after resolving config 5c795716107 Add next/prev conflicts (#97613) 96d0dc4154d Add next/prev conflicts (#97613) bbdc0e4c79c add NotebookSelector which is like DocumentSelector d0d89c94c38 IPC object URLs - make it generic and use ISandboxConfiguration in all windows for proper typing 335bc0196c3 Remove unused dependency d35df4a3efe window - use IPC to transport window config and drop URL query param (#120096) 45f061b9c82 Swap cpx for cpx2 f04401cfa48 #118667 adopt to deep strict equal 367c5e2dd6b add some todo-tags 4fd610fc717 try bigger timeout awaiting events 38abcf09ad7 Merge branch 'main' into notebook/dev 325bea69a74 chore: add media and apple event entitlements 7a9151a29d9 do not block svg for webview resources (#120156) c18893214c2 Fix #120215 2d5f0e49534 skip failing terminal tests. 00c1ca5afcb fix browser integration test timeout. cd52cd7c8d0 Fix clicking on markdown cell to add/remove selection 9ab80019bf2 Use forked markdown-it-katex version 16cff9cff93 Finalize CodeActionTriggerKind caa03cd52fc testing: remove total tests indicator 4bd5a83b86f testing: clean up 0% label running tests d6448cfd3ad testing: fix test welcome being shown at inappropriate times c79109bff16 testing: fix active style overriding checked in filter 8a7228ee7d9 search: use strict equals 665cc92c7e6 check if localPtyService is defined to avoid throwing in web playground (#120144) 63fbe4473f2 Remove vscode-webview-test from electron csp f122b1096cf Don't use `dataTransfer` for cell drag and drop d4412e708ae Render markdown preview cells inside a shadow dom (#120137) 268ac6e3df2 Bump documented min version for new postMessage behavior 795f44ea6a0 Build VS Code with latest TS nightly 7bbc15a60e0 Replace -1 constant in keycode with enum value (#120126) c0ed513633d Add output.inlineBreak parameter, fixes #119088 1f5ff0f17a0 Bump dependencies, affects #119088 708b353f052 Remove notDeepEqual, part of #118667 6e07fb24ee8 Remove unnecessary condition, fixes #119120 588c2f6c783 skip showNotebookDocment. 9b091c7d228 :lipstick: Emmet tests 9503e7dce7a :up: distro 78d3cc3feaa Remove use of assert.equal from the code (#118667) 1968bda7e67 platform#IProcessEnvironment is badly typed (fix #119046) 4ccad80121a fix #119322 (#120143) d5a670eaeb0 Switch to deepStrictEqual part of #118667 33cc87e1c4e re #118108. separate selection and focus. 88c58b011c0 re #117623. 4bb08160c70 fix #117670. 48cc8a84fd3 fix powershell profile source (#120138) 0cea28a5ded Adopt ITerminalEnvironment in external terminal d3da22dfc41 node-pty@0.11.0-beta4 dddbfa61652 Remove process.env from tasks Part of #108804 1f06361cd36 Debug view should show when a breakpoint is hit 770ba2b0c0c Debugger: Add option to allow disconnect and stop/terminate UI elements 1988caa6272 Merge pull request #119160 from alexmyczko/patch-1 1a4f9ebf8b9 Update distro 01e935eac97 xterm@4.12.0-beta.11 298b3bb7b99 show "no elements" for empty breadcrumb case, https://github.com/microsoft/vscode/issues/56327 e3853050254 Remove notEqual part of #118667 e30424ae617 Merge pull request #108397 from awilkins/feature/selection-paste-in-terminal 29d59497cd3 Update Github issue for workspace trust proposed api 149c304c8cf Merge branch 'main' into feature/selection-paste-in-terminal 142cbdbaad8 Increase timeout for browser integration tests 54bd78ffbec Revert "Increase timeout for browser integration tests" 849a2db25b5 Update markdown-it (#120161) 291cc7c9d55 Fix #120159 9dbdcc4a0cc Increase timeout for browser integration tests 7c498ec3a85 Update tests to use consistent version of mkdirp 26dba7aab5a better working copy uri for complex notebooks, https://github.com/microsoft/vscode/issues/117899 095d06955ca Fix table list height. Fixes #120062 (#120102) 1f43f5ffcff use INotebookEditorModelResolverService#onDidChangeDirty to drive notebook file tracker 29a1cfddcd2 make sure dirty state of notebooks from simple content provider is reflected in extension host, add integration test 5cf75096a60 :lipstick: 95420b39f28 Add cuda-cpp lang id, grammar and problem matchers (#119444) 3287cedd343 no more usage of assert.equal, https://github.com/microsoft/vscode/issues/118667 c3aa80c57b4 Merge branch 'main' into notebook/dev 2b04ebaa114 Remove unused static functions in grid (#120123) eb7ccce154d update y18n 452b685b997 use assert.strictEqual 3499f63dc1c Exploration: Better transferring of TypedArrays used in Webview.postMessage (#115664) 242bea8c8f0 zoom - always set it early on (fix #108920) de4b1cf9dca Add placeholder for empty markdown cells eb17cca0d54 Don't call updateOutputRendering if dom not has not been set f908389c0b1 fix #117670. 97044c900a2 Adopt workbench.hover.delay in the terminal (#120134) 768ea662541 Handle error in getting wsl distros (#120124) 9fd5f25ec58 rendererType -> gpuAcceleration e09adb28ff4 add trusted parent button 2a1f20afc7c fix #115432. 0ff1928a3b9 Merge pull request #120002 from microsoft/tyriar/terminal_sandbox cbed5812a3d testing: fix name of view container 469cd185a90 testing: add run/debug actions to context menu 9a27f0e2cc4 testing: s/re-run/rerun/ ca43526452b testing: be more consistent about focus actions, add context menu to reveal 3aff04dcae2 testing: improve test item labels 20fc2a175a4 debug: fix rare(?) cannot read property of null error when debugging 214acf0d65c fix: don't open peeks when tests are retired a565ba12a38 testing: remove peek if showing for removed test 48e8463a897 testing: tear down app if renderer crashes b6fc5df1d4b testing: toggle all visibility when reselecting the same test filter 412f45fa80a re #111663. 368f44e07bc Enable iframe based webviews on desktop for webviews that don't need search c310e402216 Enable webview developer tools command for iframe based webviews da681f2fef5 Merge pull request #120131 from microsoft/tyriar/gpuaccel 182d40c07c6 Fix test 6bf2a7eb960 Merge remote-tracking branch 'origin/main' into tyriar/terminal_sandbox da94e122d1f Merge remote-tracking branch 'origin/main' into tyriar/gpuaccel 06ce7bd2977 Fix hygiene 06a451d8154 Merge branch 'notebook/dev' into main 34354cd5a5b Enable useMarkdownRenderer by default again and document this experimental setting 878c3fb7f45 Merge remote-tracking branch 'origin/main' into tyriar/terminal_sandbox 7d67641ef31 Merge remote-tracking branch 'origin/main' into tyriar/gpuaccel dcb7f7c853a Skip environmentVariableCollection suite 30ccdf6b6cd feat: add typographer option for markdown preview (#119641) f02e4853c37 Change rendererType to gpuAcceleration bfe889d3b96 chore: bump electron@11.4.1 21fca08c978 Revisit webviewExternalEndpoint in environment (#118950) (#119780) 4dc2d399c75 Fix TS 4.3 compile errors 47256436d8c Remove explicit assignment 755424b11f8 Add type for constant 14373a5fc59 Merge remote-tracking branch 'origin/main' into notebook/dev 08e1302c980 Update telemtry extractor cca122c4337 :lipstick: 54910b1970f cut cell without selection update selections correctly. 31fd94b6606 First round of getting rid of deprecated asserts (#120125) 5da392fdf03 allow authority in wt (#120059) ca37086d9ae unify copy/paste cell from ui and kb. 29ca249959f [typescript-language-features] Support import statement completions for TypeScript 4.3 (#119009) 0854a0a6231 Add include for ms-auth extension (#119219) 12c4596ce31 Try fixing leak of backlayer webviews for notebook diffs (#119372) a39120ddd11 Add support for JSDoc link tags (#119358) 1c3c96b4b9a fix(notebook): redo cell creation does not re-select the cell a573d500d08 Merge pull request #120086 from arnobl/fix-notebook-redo-selection a5f7945b3bf Update git test for integration tests, fixes #120116 2111c113802 remove (inline) INotebookEditorModel#isUntitled d80301ca05e Update to vscode-proxy-agent 0.9.0 50d1ce4ad17 remove unused NotebookTextModelSnapshot 79e0e2737a1 remove default cell for empty, untitled notebooks fe154b40607 Merge branch 'joh/nb/next' into notebook/dev e2d3e9f9255 :lipstick: throw if cancelled 030cb3c75cf Moves extension prop out of proposed 7164efa38e5 Open Editor refactors (#119274) 3e16e9151b9 update typescript-vscode-sh-plugin. For #120103 7c8da70fd02 Respect viewtype for reopen with in notebooks (#120101) 3e0f2f1c6ee allow wt description in widget (#119291) 848e3301a18 also :up: jschardet for web 34fa8941743 :up: nsfw 9c2b0537785 :up: sudo-prompt f89406f2da7 :up: native-is-elevated 13bbe762f5d Inline values provider isn't called with new view port when scrolling editor content 2bf76afac69 :up: jschardet 17e7a257ffc Update grammars c318085f383 Merge pull request #119817 from yannickowow/master e09a24028ed Automatically show inline values if an inline values provider is registered 0d2b550ab1d Show current DataBreakpoint access type in "Breakpoints" view - Append debugViewlet.css for "access-type" element - extend IBaseBreakpointTemplateData when rendering 3a2a24e452c cli - set VSCODE_CLI earlier (#119660) 456651f2868 zen mode: do not keep pointer to editor. Just reset line number visibility for all visible editors ddd480046df debug: update inline values when variable is set ce184d9ec72 fixes #119825 d13f3b4a2ff remove NotebookContentProvider#resolveNotebook e5b2742fff8 debt - provide common method to load renderer with config param 19f6f310360 :lipstick: remove extra comment slashes e96e70d59ef remove overflow action when there are no overflows 44f89fc13c3 editors - do not wait for editor to resolve to signal restored phase (fix #119059) f554a745505 debug: always show top call stack column 01e844e75df Merge pull request #119992 from microsoft/tyriar/119046 df6d78a1695 debt - align editor model closer to file working copy 7031abadeaf debt - less explicit any ec1def6ca81 fixes #120066 576ef958f1a don't save outputs anymore 7f36af1c924 debt - improve emmet integration tests babbec1a0b1 file working copy - better cancellation support 82647af99ac debt - onDispose => onWillDispose e8388ab7401 chore: allow vmodule switch for debugging d2db91f2c68 Fix issue with bottom border for focus 8f7392d0788 Add confirmation for workspace trust state transition 7979a7b5166 Workspace trust - extension enablement (#120028) 5d2e7aceda5 Remove code that is not needed (#120005) 89571337d18 Fixes #115327 with HC images for GS setup/beginners. 21051a1d73a Recommend Quick Open when a workspace is open. d99caf58bb9 Allow 100ms for terminal container to become available before spawn 2a60d7f8b00 Enable terminal event batching from ptyHost (#117268) 3b10a59cfc3 replace one additional displayDetectedWslProfiles caa9478d80a allow terminal profiles w a source to have args (#119967) f88275a0834 iPad OS ctrl + c in terminal (#119979) f3d2dea9f5d displayDetectedWslProfiles -> useWslProfiles 307f57385b9 Workspace trust - multi-window improvements (#119483) 8bd15af0cba Reload window on workspace trust state transition 2aa503b9ece debug: double check that launch is defined 5e711f49fdb Remove quick launch terminology from code dafbda4cd2b new file => new untitled file rather than explorer's new file ref #119882 7647d09883e debug: fix stop on entry not working after launch.json change ceac3391bb6 Bump version to 1.56.0 e54d67868ca fix optional service typing 62e5b935091 Use ThrottledDelayer per review feedback. 183818196f2 ces: use ThrottledDelayer 8b642ee2953 Applied review feedback and tweaked heuristics e847db90611 RunOneScheduler and code cleanup for CES 7f2dc82f044 Fix localization strings and typo d662c36c8d6 More CES heuristics work a49a633a042 Prompt within 1-8hrs heuristics 09ea4bcdbc3 CES experiment treatment 9c681aeb9be Draft for ces survey 74c15f1159b Ignore extension that is being installed 958e1f7cecb Update distro ed74a5301c2 Adopt writeSync maxSubsequentCalls 05778206b4b xterm@4.12.0-beta.7 2a3acf9fd89 LocalizationService.update no longer needed. Fixes https://github.com/microsoft/vssaas-planning/issues/3290 4f6069c99a4 don't save outputs anymore b19f50e0160 debt - improve emmet integration tests 7f9835ac433 file working copy - better cancellation support 5e3df4ce301 debt - onDispose => onWillDispose 03b35958bfd fix #119885. f8436bd10ab :lipstick: 7fbddb5522d fix(notebook): redo cell creation does not re-select the cell dc9bdc2fc7b Fixed wrong variable name shadowing that caused Profiles for extensions not to appear. c0fa3dfd3e2 chore: allow vmodule switch for debugging 720c1e74ec6 Change auto update extensions behavior in UI f453ed8e382 Fix issue with bottom border for focus c4eb9272319 Add confirmation for workspace trust state transition 908b5bfdf8b Show update all extensions only if auto updating is disabled c2b5d3d4710 Workspace trust - extension enablement (#120028) 6aa528024bc Remove code that is not needed (#120005) 364923c06b6 Fixes #115327 with HC images for GS setup/beginners. 80a74dde2ef Recommend Quick Open when a workspace is open. ff254794f5d re #119561. 1733f27dfb3 Move terminalRemote and parts of native contribution to sandbox eb0f111c58d Remove unneeded impl from browser/terminalInstanceService 8ac3d73848e :guard: folding+cut a802d1f9dff Allow 100ms for terminal container to become available before spawn 02d38098249 Allow undefined in terminal env map, unify interfaces e7f329a8d8f re #119771. a05c63db4fd Enable terminal event batching from ptyHost (#117268) 778d622ca16 replace one additional displayDetectedWslProfiles 76a7bfce680 tests for #119773. db36fa61f1a extract copy/cut/paste actions. 31f8118486d allow terminal profiles w a source to have args (#119967) cc077af2d6f iPad OS ctrl + c in terminal (#119979) 6bbfedaa7d7 displayDetectedWslProfiles -> useWslProfiles b45ab9da567 Workspace trust - multi-window improvements (#119483) 3f511d294fb Reload window on workspace trust state transition aad588a535f debug: double check that launch is defined d684db7ba9a Merge pull request #119970 from microsoft/tyriar/quickLaunch b32eca63091 add tests for undo/redo with invalid ranges. 16721ed9e02 out of boundary replace should not throw. f39716e0151 undo/redo tests. 8804de8eb70 new file => new untitled file rather than explorer's new file ref #119882 91d308b13d0 debug: fix stop on entry not working after launch.json change e0be2784123 Bump version to 1.56.0 d02a4aacbaf Remove quick launch terminology from code 5f30215a0f8 add `NotebookDocument#isClosed` 13c66745802 add `NotebookDocument#getCells(range?)`, https://github.com/microsoft/vscode/issues/119602 c0bb10ac324 always (re)load notebook editor model resolving notebook input 3a8a44f64bf implement saveAs and better resolving/loading, move some files around, split some files up 0d44c987c61 Change auto update key to allow not updating disabled extension fecf3b3e3bd Update VSCode icons 3ef1a3394dd Check for extension updates on extension enabled cb813a6ed18 Add change update disabled extensions behavior in extensions context menu 2fbe092c9c5 fix long description 50% bfac4892e5f Fix #103631 f977d7f0663 format modified: format lines with whitespace changes a0fecbd327f Merge remote-tracking branch 'upstream/main' into feature/selection-paste-in-terminal f075b241438 Improve filtering of disabled extensions c8ced34cdeb Add configuration key for updating disabled extensions e7b681dc9ac fix: 76879 abc7ec3ad3b Merge branch 'feature/selection-paste-in-terminal' of github.com:awilkins/vscode into feature/selection-paste-in-terminal ab34fa90e99 Merge branch 'master' into feature/selection-paste-in-terminal 426a04c8cc1 Merge branch 'master' into feature/selection-paste-in-terminal db2809fe443 Merge branch 'master' into feature/selection-paste-in-terminal ebf33532c61 Fixed a lint 39585898d12 Register command 441dfcd718b Fix a lint 65f3889331f Implement a selection paste action in terminal REVERT: 3c4e3df9e89 Merge pull request #121167 from microsoft/alex/1-55-2-fix REVERT: c01cbae1127 update distro REVERT: 0320c595d05 Merge pull request #120967 from microsoft/roblou/bumpDistro REVERT: 3f9cf547f7d Bump distro for release/1.55 REVERT: ead2c2ab0f5 Merge pull request #120858 from microsoft/alex/1-55-2 REVERT: bfb2654224e Pick up new distro version and bump version REVERT: 08a217c4d27 Merge pull request #120733 from microsoft/joao/release/1.55/fix-build REVERT: 6b89f1aaa2e Don't call node-gyp anymore (#120673) REVERT: 6e3220a31a9 Merge shellEnv into process.env (#120332) REVERT: 5dee7d559ff Update to vscode-proxy-agent 0.8.2 (#120354) REVERT: 91fa7f030d2 Merge pull request #120519 from microsoft/misolori/icon-updates REVERT: c5e030273bf Merge branch 'release/1.55' into misolori/icon-updates REVERT: 8be12407ad0 bump version to 1.55.1 (#120379) REVERT: d6e72b493ce debug: update js-debug (#120358) REVERT: b7d152d5b2f Update distro REVERT: c185983a683 Fix table list height. Fixes #120062 (#120102) (#120168) REVERT: c71be15869a fix powershell profile source (#120138) REVERT: 4c2a1fa6306 handle error when wsl is not installed (#120128) REVERT: e3297826776 Merge pull request #120107 from microsoft/aeschli/120103 REVERT: c7f963cb27d Moves extension prop out of proposed REVERT: 55f4c2ec1e2 update typescript-vscode-sh-plugin REVERT: 26a60dbecac Merge pull request #120081 from microsoft/sandy081/fix119975 REVERT: 5753889f2d1 remove overflow action when there are no overflows REVERT: be363080a36 fixes #120066 (#120080) REVERT: 554774c21d6 Merge pull request #119983 from microsoft/lramos15/notebookViewType REVERT: 428daa3650c Merge pull request #119990 from microsoft/tyriar/r155_wsl_compile REVERT: 64876cb88e2 Fix test compilation REVERT: 00296950306 [Getting Started] new file => new untitled file rather than explorer's new file (#119981) REVERT: 2a8113f7231 Enable terminal event batching from ptyHost (#117268) REVERT: aefdd723bd0 Merge pull request #119987 from microsoft/tyriar/r155_wsl_setting REVERT: 98aeda8479a Remove RemoteHub from desktop stable (#119972) REVERT: 981a2b53abc Merge pull request #119977 from microsoft/digitarald/fix-115327 REVERT: a3514cd1481 allow terminal profiles w a source to have args (#119967) REVERT: 78636f2c133 Fixes #115327 with HC images for GS setup/beginners. REVERT: b72964aaace iPad OS ctrl + c in terminal (#119979) REVERT: a84f17acbbf displayDetectedWslProfiles -> useWslProfiles REVERT: 1b0b1c67f35 Merge pull request #119973 from microsoft/digitarald-ces-distro-update REVERT: c97d55a2131 debug: fix stop on entry not working after launch.json change (#119980) REVERT: c7068ae9331 Respect viewtype for reopen with in notebooks REVERT: 59b2869bdb1 Distro update git-subtree-dir: lib/vscode git-subtree-split: 631dbe250bcee37fcba8c4a6888284426943b48c --- .eslintrc.json | 1 + .github/ISSUE_TEMPLATE/bug_report.md | 2 +- .github/classifier.json | 4 +- .github/workflows/ci.yml | 12 +- .github/workflows/no-yarn-lock-changes.yml | 24 +- .github/workflows/rich-navigation.yml | 1 + .vscode/launch.json | 7 + .vscode/notebooks/api.github-issues | 2485 +- .vscode/notebooks/endgame.github-issues | 63 +- .vscode/notebooks/inbox.github-issues | 24 +- .vscode/notebooks/my-endgame.github-issues | 144 +- .vscode/notebooks/my-work.github-issues | 2 +- .vscode/notebooks/papercuts.github-issues | 21 +- .vscode/settings.json | 4 +- .vscode/tasks.json | 76 +- .yarnrc | 2 +- ThirdPartyNotices.txt | 67 +- build/.cachesalt | 2 +- .../darwin/app-entitlements.plist | 6 + .../darwin/product-build-darwin-sign.yml | 2 +- .../darwin/product-build-darwin.yml | 19 +- build/azure-pipelines/distro-build.yml | 2 +- build/azure-pipelines/exploration-build.yml | 6 +- .../linux/product-build-alpine.yml | 2 +- .../linux/product-build-linux.yml | 12 +- .../linux/snap-build-linux.yml | 2 +- build/azure-pipelines/product-build.yml | 2 +- build/azure-pipelines/product-compile.yml | 2 +- .../publish-types/publish-types.yml | 2 +- build/azure-pipelines/release.yml | 2 +- build/azure-pipelines/sync-mooncake.yml | 2 +- .../azure-pipelines/web/product-build-web.yml | 2 +- .../win32/product-build-win32.yml | 5 +- build/builtin/main.js | 12 +- build/darwin/create-universal-app.js | 1 + build/darwin/create-universal-app.ts | 1 + build/darwin/sign.js | 11 + build/darwin/sign.ts | 12 + build/gulpfile.extensions.js | 69 +- build/gulpfile.vscode.js | 2 +- build/lib/i18n.resources.json | 12 +- build/lib/test/i18n.test.js | 18 +- build/lib/test/i18n.test.ts | 24 +- build/lib/treeshaking.js | 2 +- build/lib/treeshaking.ts | 2 +- build/monaco/monaco.d.ts.recipe | 1 + build/npm/dirs.js | 1 - build/npm/preinstall.js | 4 +- build/package.json | 4 +- build/yarn.lock | 18 +- cglicenses.json | 345 +- cgmanifest.json | 12 +- .../configuration-editing/build/tsconfig.json | 2 +- .../configuration-editing/images/icon.png | Bin 0 -> 2825 bytes extensions/configuration-editing/package.json | 7 + .../src/extensionsProposals.ts | 25 + .../src/settingsDocumentHelper.ts | 11 +- .../configuration-editing/tsconfig.json | 4 +- extensions/cpp/build/update-grammars.js | 2 + extensions/cpp/cgmanifest.json | 13 + extensions/cpp/language-configuration.json | 1 + extensions/cpp/package.json | 38 + .../cpp/syntaxes/cuda-cpp.tmLanguage.json | 19818 ++++++++++++++++ extensions/csharp/cgmanifest.json | 2 +- .../csharp/syntaxes/csharp.tmLanguage.json | 276 +- .../client/tsconfig.json | 4 +- extensions/css-language-features/package.json | 6 + .../css-language-features/server/package.json | 2 +- .../server/test/index.js | 2 +- .../server/tsconfig.json | 4 +- .../css-language-features/server/yarn.lock | 8 +- extensions/debug-auto-launch/media/icon.png | Bin 0 -> 7191 bytes extensions/debug-auto-launch/package.json | 7 + extensions/debug-auto-launch/tsconfig.json | 4 +- extensions/debug-server-ready/media/icon.png | Bin 0 -> 8309 bytes extensions/debug-server-ready/package.json | 7 + extensions/debug-server-ready/tsconfig.json | 4 +- extensions/emmet/package.json | 70 +- extensions/emmet/package.nls.json | 37 +- extensions/emmet/src/abbreviationActions.ts | 6 +- .../emmet/src/defaultCompletionProvider.ts | 3 +- extensions/emmet/src/emmetCommon.ts | 4 +- extensions/emmet/src/parseDocument.ts | 4 + extensions/emmet/src/selectItemHTML.ts | 2 +- .../emmet/src/test/abbreviationAction.test.ts | 9 +- extensions/emmet/src/test/completion.test.ts | 13 +- .../src/test/cssAbbreviationAction.test.ts | 119 +- .../test/editPointSelectItemBalance.test.ts | 13 +- .../emmet/src/test/incrementDecrement.test.ts | 14 +- extensions/emmet/src/test/index.ts | 2 +- .../src/test/partialParsingStylesheet.test.ts | 29 +- .../emmet/src/test/reflectCssValue.test.ts | 10 +- extensions/emmet/src/test/tagActions.test.ts | 47 +- .../emmet/src/test/test-fixtures/marker.txt | 1 - extensions/emmet/src/test/testUtils.ts | 3 +- .../emmet/src/test/toggleComment.test.ts | 72 +- .../emmet/src/test/updateImageSize.test.ts | 6 +- .../src/test/wrapWithAbbreviation.test.ts | 8 +- extensions/emmet/src/util.ts | 86 +- .../.vscode/settings.json | 0 extensions/emmet/tsconfig.json | 4 +- extensions/emmet/yarn.lock | 12 +- extensions/extension-editing/images/icon.png | Bin 0 -> 2007 bytes extensions/extension-editing/package.json | 9 +- extensions/extension-editing/tsconfig.json | 4 +- extensions/extension-editing/yarn.lock | 54 +- extensions/git-ui/.vscodeignore | 8 - extensions/git-ui/README.md | 7 - extensions/git-ui/cgmanifest.json | 4 - extensions/git-ui/extension.webpack.config.js | 17 - extensions/git-ui/package.json | 35 - extensions/git-ui/package.nls.json | 4 - extensions/git-ui/resources/icons/git.png | Bin 2383 -> 0 bytes extensions/git-ui/src/main.ts | 57 - extensions/git-ui/src/typings/refs.d.ts | 8 - extensions/git-ui/tsconfig.json | 13 - extensions/git-ui/yarn.lock | 8 - extensions/git/package.json | 25 +- extensions/git/package.nls.json | 2 +- extensions/git/src/commands.ts | 8 +- extensions/git/src/git.ts | 33 +- extensions/git/src/repository.ts | 5 +- extensions/git/src/test/index.ts | 2 +- extensions/git/src/test/smoke.test.ts | 10 +- extensions/git/src/util.ts | 53 + extensions/git/tsconfig.json | 4 +- extensions/git/yarn.lock | 8 +- .../github-authentication/images/icon.png | Bin 0 -> 3658 bytes extensions/github-authentication/package.json | 10 +- .../src/experimentationService.ts | 73 + .../github-authentication/src/extension.ts | 6 +- .../github-authentication/src/github.ts | 4 +- .../github-authentication/src/githubServer.ts | 65 +- .../github-authentication/tsconfig.json | 2 +- extensions/github-authentication/yarn.lock | 26 + extensions/github/images/icon.png | Bin 0 -> 3657 bytes extensions/github/package.json | 7 + extensions/github/src/pushErrorHandler.ts | 50 +- extensions/github/src/remoteSourceProvider.ts | 2 +- extensions/github/tsconfig.json | 2 +- extensions/grunt/package.json | 9 +- extensions/grunt/tsconfig.json | 4 +- extensions/gulp/package.json | 9 +- extensions/gulp/tsconfig.json | 4 +- .../client/tsconfig.json | 4 +- .../html-language-features/package.json | 6 + .../server/package.json | 6 +- .../server/src/modes/javascriptMode.ts | 3 +- .../server/test/index.js | 2 +- .../server/tsconfig.json | 4 +- .../html-language-features/server/yarn.lock | 16 +- extensions/image-preview/icon.png | Bin 1979 -> 1889 bytes extensions/image-preview/package.json | 6 + extensions/image-preview/src/preview.ts | 3 +- extensions/image-preview/tsconfig.json | 2 +- extensions/jake/package.json | 9 +- extensions/jake/tsconfig.json | 4 +- .../syntaxes/JavaScript.tmLanguage.json | 65 +- .../syntaxes/JavaScriptReact.tmLanguage.json | 65 +- .../client/tsconfig.json | 4 +- .../json-language-features/package.json | 6 + .../server/package.json | 2 +- .../server/tsconfig.json | 4 +- .../json-language-features/server/yarn.lock | 34 +- extensions/julia/cgmanifest.json | 2 +- .../julia/syntaxes/julia.tmLanguage.json | 8 +- .../language-configuration.json | 5 +- .../markdown-language-features/esbuild.js | 32 + .../notebook/index.ts | 45 +- .../notebook/tsconfig.json | 13 + .../markdown-language-features/package.json | 25 +- .../package.nls.json | 4 +- .../preview-src/index.ts | 2 +- .../preview-src/tsconfig.json | 2 +- .../src/features/preview.ts | 16 +- .../src/features/previewManager.ts | 2 +- .../src/features/workspaceSymbolProvider.ts | 2 +- .../src/markdownEngine.ts | 4 +- .../src/test/index.ts | 2 +- .../markdown-language-features/tsconfig.json | 4 +- .../webpack.notebook.js | 28 - extensions/merge-conflict/media/icon.png | Bin 0 -> 2439 bytes extensions/merge-conflict/package.json | 26 +- .../resources/icons/merge-conflict.png | Bin 2149 -> 0 bytes .../merge-conflict/src/codelensProvider.ts | 5 +- .../merge-conflict/src/commandHandler.ts | 5 +- extensions/merge-conflict/tsconfig.json | 4 +- .../microsoft-authentication/media/icon.png | Bin 0 -> 3818 bytes .../microsoft-authentication/package.json | 7 + .../notebook-markdown-extensions/esbuild.js | 41 + .../notebook/emoji.ts | 17 +- .../notebook/katex.ts | 19 +- .../notebook/tsconfig.json | 3 +- .../notebook-markdown-extensions/package.json | 15 +- .../webpack.notebook.js | 50 - .../notebook-markdown-extensions/yarn.lock | 25 +- .../npm/extension-browser.webpack.config.js | 3 +- extensions/npm/package.json | 11 +- extensions/npm/package.nls.json | 1 + .../npm/src/features/jsonContributions.ts | 6 +- .../src/features/packageJSONContribution.ts | 49 +- extensions/npm/src/npmBrowserMain.ts | 2 +- extensions/npm/src/npmMain.ts | 16 +- extensions/npm/src/tasks.ts | 18 +- extensions/npm/tsconfig.json | 2 +- extensions/npm/yarn.lock | 17 + extensions/package.json | 3 +- extensions/perl/cgmanifest.json | 2 +- extensions/perl/syntaxes/perl.tmLanguage.json | 1798 +- extensions/php-language-features/package.json | 17 +- .../php-language-features/package.nls.json | 3 +- .../src/features/utils/async.ts | 2 +- .../src/features/validationProvider.ts | 205 +- .../src/typings/refs.d.ts | 1 + .../php-language-features/tsconfig.json | 4 +- extensions/php-language-features/yarn.lock | 17 + extensions/php/syntaxes/php.tmLanguage.json | 9 +- extensions/powershell/cgmanifest.json | 2 +- .../syntaxes/powershell.tmLanguage.json | 114 +- extensions/ruby/cgmanifest.json | 2 +- extensions/ruby/syntaxes/ruby.tmLanguage.json | 4 +- extensions/scss/cgmanifest.json | 4 +- extensions/search-result/package.json | 6 + extensions/search-result/tsconfig.json | 2 +- extensions/shellscript/package.json | 4 + extensions/simple-browser/media/icon.png | Bin 0 -> 1787 bytes extensions/simple-browser/package.json | 8 +- .../simple-browser/preview-src/tsconfig.json | 2 +- extensions/simple-browser/src/extension.ts | 11 +- .../simple-browser/src/simpleBrowserView.ts | 5 +- extensions/simple-browser/tsconfig.json | 2 +- .../media/icon.png | Bin 0 -> 2286 bytes .../testing-editor-contributions/package.json | 7 + .../tsconfig.json | 2 +- .../theme-abyss/themes/abyss-color-theme.json | 2 + extensions/theme-defaults/themes/dark_vs.json | 1 + .../theme-defaults/themes/hc_black.json | 7 +- .../theme-defaults/themes/light_vs.json | 1 + .../themes/kimbie-dark-color-theme.json | 1 + .../themes/dimmed-monokai-color-theme.json | 1 + .../themes/monokai-color-theme.json | 1 + .../themes/quietlight-color-theme.json | 1 + .../theme-red/themes/Red-color-theme.json | 1 + .../theme-seti/build/update-icon-theme.js | 1 + extensions/theme-seti/cgmanifest.json | 2 +- extensions/theme-seti/icons/seti.woff | Bin 36168 -> 36472 bytes .../theme-seti/icons/vs-seti-icon-theme.json | 748 +- .../themes/solarized-dark-color-theme.json | 1 + .../themes/solarized-light-color-theme.json | 1 + .../tomorrow-night-blue-color-theme.json | 1 + ...hared.tsconfig.json => tsconfig.base.json} | 1 + extensions/typescript-basics/cgmanifest.json | 2 +- .../syntaxes/TypeScript.tmLanguage.json | 65 +- .../syntaxes/TypeScriptReact.tmLanguage.json | 65 +- .../typescript-language-features/icon.png | Bin 998 -> 0 bytes .../media/icon.png | Bin 0 -> 2181 bytes .../typescript-language-features/package.json | 46 +- .../package.nls.json | 13 +- .../codeLens/referencesCodeLens.ts | 2 +- .../src/languageFeatures/completions.ts | 65 +- .../src/languageFeatures/diagnostics.ts | 2 +- .../fileConfigurationManager.ts | 6 +- .../src/languageFeatures/hover.ts | 20 +- .../src/languageFeatures/organizeImports.ts | 4 +- .../src/languageFeatures/quickFix.ts | 1 + .../src/languageFeatures/signatureHelp.ts | 12 +- .../src/languageFeatures/tagClosing.ts | 2 +- .../src/languageProvider.ts | 2 +- .../src/protocol.d.ts | 4 + .../src/test-all.ts | 2 +- .../src/test/index.ts | 2 +- .../src/test/smoke/index.ts | 2 +- .../src/test/unit/index.ts | 2 +- .../src/test/unit/previewer.test.ts | 37 +- .../src/tsServer/server.ts | 2 +- .../src/tsServer/serverError.ts | 4 +- .../src/tsServer/versionManager.ts | 29 +- .../src/typescriptServiceClient.ts | 33 +- .../src/utils/api.ts | 1 + .../src/utils/fixNames.ts | 1 + .../src/utils/previewer.ts | 128 +- .../src/utils/resourceMap.ts | 2 +- .../src/utils/telemetry.ts | 2 +- .../src/utils/tsconfig.ts | 2 +- .../src/utils/typingsStatus.ts | 4 +- .../tsconfig.json | 2 +- extensions/vscode-api-tests/media/icon.png | Bin 0 -> 2210 bytes extensions/vscode-api-tests/package.json | 15 +- .../src/singlefolder-tests/env.test.ts | 10 +- .../src/singlefolder-tests/index.ts | 2 +- .../notebook.document.test.ts | 203 +- .../notebook.editor.test.ts | 34 +- .../src/singlefolder-tests/notebook.test.ts | 781 +- .../src/singlefolder-tests/terminal.test.ts | 9 +- .../src/singlefolder-tests/webview.test.ts | 112 + .../src/singlefolder-tests/window.test.ts | 2 +- .../workspace.tasks.test.ts | 8 +- .../src/singlefolder-tests/workspace.test.ts | 2 +- extensions/vscode-api-tests/src/utils.ts | 2 +- .../src/workspace-tests/index.ts | 2 +- extensions/vscode-api-tests/tsconfig.json | 4 +- .../vscode-colorize-tests/media/icon.png | Bin 0 -> 2210 bytes extensions/vscode-colorize-tests/package.json | 1 + extensions/vscode-colorize-tests/src/index.ts | 2 +- .../test/colorize-fixtures/test-92369.cpp | 3 - .../test/colorize-fixtures/test.cu | 149 + .../test/colorize-results/test-92369_cpp.json | 24 - .../test/colorize-results/test_cshtml.json | 2 +- .../test/colorize-results/test_cu.json | 12047 ++++++++++ .../test/colorize-results/test_ps1.json | 174 +- .../vscode-colorize-tests/tsconfig.json | 4 +- .../vscode-custom-editor-tests/media/icon.png | Bin 0 -> 2210 bytes .../vscode-custom-editor-tests/package.json | 1 + .../src/customTextEditor.ts | 2 +- .../src/test/customEditor.test.ts | 3 +- .../src/test/index.ts | 4 +- .../vscode-custom-editor-tests/tsconfig.json | 4 +- .../vscode-notebook-tests/media/icon.png | Bin 0 -> 2210 bytes extensions/vscode-notebook-tests/package.json | 1 + .../vscode-notebook-tests/src/extension.ts | 27 +- extensions/vscode-notebook-tests/src/index.ts | 2 +- .../vscode-notebook-tests/tsconfig.json | 4 +- .../vscode-test-resolver/media/icon.png | Bin 0 -> 2210 bytes extensions/vscode-test-resolver/package.json | 19 +- .../vscode-test-resolver/src/extension.ts | 3 +- extensions/vscode-test-resolver/tsconfig.json | 4 +- extensions/yarn.lock | 13 +- package.json | 49 +- product.json | 29 +- remote/.yarnrc | 2 +- remote/package.json | 19 +- remote/web/package.json | 10 +- remote/web/yarn.lock | 40 +- remote/yarn.lock | 272 +- resources/linux/debian/control.template | 5 +- resources/web/code-web.js | 14 + scripts/test-integration.bat | 3 +- scripts/test-integration.sh | 5 +- src/bootstrap-node.js | 2 +- src/bootstrap-window.js | 139 +- src/bootstrap.js | 18 +- src/cli.js | 3 + src/main.js | 48 +- src/tsconfig.base.json | 1 + src/tsconfig.json | 3 +- src/tsconfig.monaco.json | 7 +- src/vs/base/browser/contextmenu.ts | 2 +- src/vs/base/browser/dnd.ts | 4 +- src/vs/base/browser/dom.ts | 40 +- src/vs/base/browser/event.ts | 2 +- src/vs/base/browser/formattedTextRenderer.ts | 26 +- src/vs/base/browser/touch.ts | 2 +- .../browser/ui/actionbar/actionViewItems.ts | 42 +- .../base/browser/ui/actionbar/actionbar.css | 25 +- src/vs/base/browser/ui/actionbar/actionbar.ts | 14 +- .../ui/breadcrumbs/breadcrumbsWidget.css | 3 + .../ui/breadcrumbs/breadcrumbsWidget.ts | 11 +- src/vs/base/browser/ui/button/button.css | 15 +- src/vs/base/browser/ui/button/button.ts | 211 + src/vs/base/browser/ui/checkbox/checkbox.ts | 14 +- .../ui/codicons/codicon/codicon-modifiers.css | 5 +- .../browser/ui/codicons/codicon/codicon.ttf | Bin 66136 -> 70964 bytes .../browser/ui/contextview/contextview.ts | 6 +- src/vs/base/browser/ui/dialog/dialog.css | 17 +- src/vs/base/browser/ui/dialog/dialog.ts | 108 +- src/vs/base/browser/ui/dropdown/dropdown.css | 21 + src/vs/base/browser/ui/dropdown/dropdown.ts | 10 +- .../ui/dropdown/dropdownActionViewItem.ts | 48 +- .../dropdownWithPrimaryActionViewItem.ts | 106 + .../base/browser/ui/findinput/replaceInput.ts | 2 +- src/vs/base/browser/ui/grid/grid.ts | 47 +- src/vs/base/browser/ui/hover/hover.css | 3 +- src/vs/base/browser/ui/hover/hoverWidget.ts | 4 + .../browser/ui/iconLabel/iconHoverDelegate.ts | 4 +- src/vs/base/browser/ui/iconLabel/iconLabel.ts | 12 +- src/vs/base/browser/ui/inputbox/inputBox.ts | 2 +- .../ui/keybindingLabel/keybindingLabel.css | 18 +- .../ui/keybindingLabel/keybindingLabel.ts | 89 +- src/vs/base/browser/ui/list/listView.ts | 16 +- src/vs/base/browser/ui/list/listWidget.ts | 10 +- src/vs/base/browser/ui/menu/menu.ts | 34 +- src/vs/base/browser/ui/menu/menubar.ts | 2 +- src/vs/base/browser/ui/sash/sash.ts | 8 +- .../browser/ui/scrollbar/scrollableElement.ts | 13 +- .../browser/ui/selectBox/selectBoxCustom.ts | 2 +- src/vs/base/browser/ui/splitview/paneview.css | 24 +- src/vs/base/browser/ui/splitview/paneview.ts | 2 +- src/vs/base/browser/ui/splitview/splitview.ts | 2 +- src/vs/base/browser/ui/toolbar/toolbar.css | 4 + src/vs/base/browser/ui/toolbar/toolbar.ts | 4 +- src/vs/base/browser/ui/tree/abstractTree.ts | 18 +- src/vs/base/browser/ui/tree/asyncDataTree.ts | 18 +- src/vs/base/browser/ui/tree/dataTree.ts | 2 +- src/vs/base/browser/ui/tree/indexTree.ts | 2 +- src/vs/base/browser/ui/tree/objectTree.ts | 12 +- src/vs/base/browser/ui/tree/treeDefaults.ts | 4 +- src/vs/base/common/actions.ts | 51 +- src/vs/base/common/async.ts | 16 +- src/vs/base/common/buffer.ts | 8 + src/vs/base/common/codicons.ts | 26 +- src/vs/base/common/collections.ts | 18 + src/vs/base/common/errors.ts | 4 +- src/vs/base/common/event.ts | 2 +- src/vs/base/common/keyCodes.ts | 2 + src/vs/base/common/map.ts | 52 +- src/vs/base/common/network.ts | 14 +- src/vs/base/common/platform.ts | 12 +- src/vs/base/common/process.ts | 22 +- src/vs/base/common/product.ts | 168 + src/vs/base/common/scanCode.ts | 5 +- src/vs/base/common/scrollable.ts | 2 +- src/vs/base/common/severity.ts | 10 + src/vs/base/common/stream.ts | 92 +- src/vs/base/common/types.ts | 22 +- src/vs/base/common/uri.ts | 6 +- src/vs/base/node/decoder.ts | 2 +- src/vs/base/node/id.ts | 5 +- src/vs/base/node/macAddress.ts | 11 +- src/vs/base/node/processes.ts | 2 +- src/vs/base/node/shell.ts | 24 +- src/vs/base/parts/ipc/common/ipc.mp.ts | 2 +- src/vs/base/parts/ipc/common/ipc.net.ts | 4 +- .../ipc/electron-sandbox/ipc.electron.ts | 2 +- src/vs/base/parts/ipc/node/ipc.net.ts | 4 +- src/vs/base/parts/ipc/test/common/ipc.test.ts | 36 +- .../base/parts/ipc/test/node/ipc.cp.test.ts | 18 +- .../quickinput/browser/media/quickInput.css | 23 +- .../parts/quickinput/browser/quickInput.ts | 67 +- .../quickinput/browser/quickInputList.ts | 14 +- .../parts/quickinput/common/quickInput.ts | 2 +- .../base/parts/sandbox/common/sandboxTypes.ts | 52 + .../parts/sandbox/electron-browser/preload.js | 253 +- .../parts/sandbox/electron-sandbox/globals.ts | 32 +- .../test/electron-sandbox/globals.test.ts | 8 +- src/vs/base/parts/storage/common/storage.ts | 2 +- .../parts/storage/test/node/storage.test.ts | 2 +- src/vs/base/test/browser/comparers.test.ts | 4 +- .../browser/formattedTextRenderer.test.ts | 30 +- .../test/browser/markdownRenderer.test.ts | 2 +- src/vs/base/test/browser/ui/grid/grid.test.ts | 546 +- .../test/browser/ui/grid/gridview.test.ts | 126 +- .../test/browser/ui/list/listView.test.ts | 8 +- .../test/browser/ui/list/rangeMap.test.ts | 236 +- .../base/test/browser/ui/menu/menubar.test.ts | 4 +- .../browser/ui/splitview/splitview.test.ts | 188 +- .../browser/ui/tree/asyncDataTree.test.ts | 36 +- .../ui/tree/compressedObjectTreeModel.test.ts | 88 +- .../test/browser/ui/tree/dataTree.test.ts | 74 +- .../browser/ui/tree/indexTreeModel.test.ts | 412 +- .../browser/ui/tree/objectTreeModel.test.ts | 84 +- src/vs/base/test/common/async.test.ts | 11 +- src/vs/base/test/common/cache.test.ts | 32 +- src/vs/base/test/common/cancellation.test.ts | 7 +- src/vs/base/test/common/codicons.test.ts | 28 + src/vs/base/test/common/collections.test.ts | 38 +- src/vs/base/test/common/color.test.ts | 290 +- src/vs/base/test/common/console.test.ts | 30 +- src/vs/base/test/common/decorators.test.ts | 116 +- src/vs/base/test/common/event.test.ts | 6 +- src/vs/base/test/common/filters.test.ts | 14 +- src/vs/base/test/common/glob.test.ts | 16 +- src/vs/base/test/common/history.test.ts | 58 +- src/vs/base/test/common/iconLabels.test.ts | 2 +- src/vs/base/test/common/iterator.test.ts | 8 +- src/vs/base/test/common/json.test.ts | 18 +- src/vs/base/test/common/jsonEdit.test.ts | 2 +- src/vs/base/test/common/jsonFormatter.test.ts | 4 +- src/vs/base/test/common/labels.test.ts | 4 +- src/vs/base/test/common/lazy.test.ts | 4 +- src/vs/base/test/common/marshalling.test.ts | 22 +- src/vs/base/test/common/normalization.test.ts | 88 +- src/vs/base/test/common/objects.test.ts | 50 +- src/vs/base/test/common/paging.test.ts | 14 +- src/vs/base/test/common/path.test.ts | 48 +- src/vs/base/test/common/processes.test.ts | 4 +- src/vs/base/test/common/resourceTree.test.ts | 40 +- src/vs/base/test/common/stream.test.ts | 49 +- .../test/node/processes/processes.test.ts | 6 +- .../code/browser/workbench/workbench-web.html | 80 - src/vs/code/browser/workbench/workbench.ts | 25 +- .../sharedProcess/sharedProcess.js | 34 +- .../sharedProcess/sharedProcessMain.ts | 41 +- .../electron-browser/workbench/workbench.html | 2 +- .../electron-browser/workbench/workbench.js | 48 +- src/vs/code/electron-main/app.ts | 157 +- src/vs/code/electron-main/main.ts | 33 +- .../electron-sandbox/issue/issueReporter.js | 37 +- .../issue/issueReporterMain.ts | 45 +- .../issue/test/testReporterModel.test.ts | 18 +- .../processExplorer/processExplorer.js | 34 +- .../processExplorer/processExplorerMain.ts | 97 +- .../electron-sandbox/workbench/workbench.html | 2 +- .../electron-sandbox/workbench/workbench.js | 47 +- src/vs/code/node/cli.ts | 5 +- src/vs/code/node/cliProcessMain.ts | 10 +- src/vs/editor/browser/config/configuration.ts | 6 +- .../browser/config/elementSizeObserver.ts | 2 +- .../editor/browser/controller/mouseHandler.ts | 12 +- .../editor/browser/controller/mouseTarget.ts | 2 +- .../browser/controller/pointerHandler.ts | 2 +- .../browser/controller/textAreaHandler.ts | 30 +- .../browser/controller/textAreaInput.ts | 2 +- src/vs/editor/browser/core/editorState.ts | 6 +- .../browser/core/keybindingCancellation.ts | 2 +- .../editor/browser/core/markdownRenderer.ts | 6 +- .../browser/services/bulkEditService.ts | 4 +- .../browser/services/codeEditorServiceImpl.ts | 4 - .../editor/browser/services/openerService.ts | 7 +- src/vs/editor/browser/view/viewImpl.ts | 12 +- src/vs/editor/browser/view/viewOverlays.ts | 34 +- src/vs/editor/browser/view/viewPart.ts | 2 +- .../contentWidgets/contentWidgets.ts | 20 +- .../currentLineHighlight.ts | 20 +- .../viewParts/decorations/decorations.ts | 18 +- .../editorScrollbar/editorScrollbar.ts | 8 +- .../viewParts/glyphMargin/glyphMargin.ts | 18 +- .../viewParts/indentGuides/indentGuides.ts | 22 +- .../viewParts/lineNumbers/lineNumbers.ts | 18 +- .../browser/viewParts/lines/viewLine.ts | 2 +- .../browser/viewParts/lines/viewLines.ts | 26 +- .../linesDecorations/linesDecorations.ts | 18 +- .../editor/browser/viewParts/margin/margin.ts | 6 +- .../marginDecorations/marginDecorations.ts | 18 +- .../browser/viewParts/minimap/minimap.ts | 28 +- .../overlayWidgets/overlayWidgets.ts | 4 +- .../overviewRuler/decorationsOverviewRuler.ts | 16 +- .../viewParts/overviewRuler/overviewRuler.ts | 10 +- .../editor/browser/viewParts/rulers/rulers.ts | 6 +- .../scrollDecoration/scrollDecoration.ts | 6 +- .../viewParts/selections/selections.ts | 20 +- .../viewParts/viewCursors/viewCursors.ts | 28 +- .../browser/viewParts/viewZones/viewZones.ts | 14 +- .../editor/browser/widget/codeEditorWidget.ts | 4 +- .../editor/browser/widget/diffEditorWidget.ts | 4 +- src/vs/editor/browser/widget/diffNavigator.ts | 2 +- src/vs/editor/browser/widget/diffReview.ts | 5 +- .../widget/embeddedCodeEditorWidget.ts | 4 +- src/vs/editor/common/commands/shiftCommand.ts | 3 + src/vs/editor/common/config/editorOptions.ts | 31 +- src/vs/editor/common/controller/cursor.ts | 2 +- .../editor/common/controller/cursorCommon.ts | 4 +- .../common/controller/cursorTypeOperations.ts | 2 +- src/vs/editor/common/core/selection.ts | 8 +- src/vs/editor/common/model/mirrorTextModel.ts | 6 +- src/vs/editor/common/model/textModel.ts | 84 +- src/vs/editor/common/model/textModelTokens.ts | 2 +- .../common/modes/supports/tokenization.ts | 20 +- .../common/services/editorSimpleWorker.ts | 8 +- .../services/editorWorkerServiceImpl.ts | 8 +- .../services/markerDecorationsServiceImpl.ts | 2 +- src/vs/editor/common/services/modeService.ts | 3 +- .../editor/common/services/modeServiceImpl.ts | 24 +- .../common/services/modelServiceImpl.ts | 6 +- .../editor/common/services/resolverService.ts | 5 - src/vs/editor/common/services/webWorker.ts | 2 +- .../common/standalone/standaloneEnums.ts | 1 + src/vs/editor/common/viewLayout/viewLayout.ts | 2 +- .../viewModel/monospaceLineBreaksComputer.ts | 2 +- .../editor/common/viewModel/viewModelImpl.ts | 20 +- .../contrib/codeAction/codeActionModel.ts | 2 +- .../editor/contrib/codeAction/codeActionUi.ts | 2 +- .../contrib/codeAction/lightBulbWidget.ts | 2 +- .../codeAction/test/codeAction.test.ts | 20 +- .../test/codeActionKeybindingResolver.test.ts | 14 +- .../codeAction/test/codeActionModel.test.ts | 21 +- .../contrib/codelens/codelensController.ts | 4 +- .../contrib/colorPicker/colorContributions.ts | 2 +- .../contrib/colorPicker/colorDetector.ts | 2 +- src/vs/editor/contrib/dnd/dnd.ts | 2 +- .../documentSymbols/test/outlineModel.test.ts | 50 +- src/vs/editor/contrib/find/findController.ts | 6 +- .../editor/contrib/find/findOptionsWidget.ts | 2 +- src/vs/editor/contrib/find/findWidget.ts | 4 +- .../contrib/find/test/findController.test.ts | 2 +- src/vs/editor/contrib/folding/folding.ts | 2 +- .../contrib/folding/test/foldingModel.test.ts | 16 +- .../folding/test/foldingRanges.test.ts | 38 +- .../folding/test/hiddenRangeModel.test.ts | 70 +- .../contrib/folding/test/indentFold.test.ts | 2 +- .../folding/test/indentRangeProvider.test.ts | 2 +- .../contrib/folding/test/syntaxFold.test.ts | 2 +- .../contrib/gotoError/gotoErrorWidget.ts | 18 +- .../editor/contrib/gotoSymbol/goToCommands.ts | 40 +- .../editor/contrib/gotoSymbol/goToSymbol.ts | 42 +- .../gotoSymbol/peek/referencesWidget.ts | 10 +- .../contrib/gotoSymbol/referencesModel.ts | 44 +- .../gotoSymbol/test/referencesModel.test.ts | 8 +- src/vs/editor/contrib/hover/hoverWidgets.ts | 2 +- .../contrib/hover/markerHoverParticipant.ts | 2 +- .../editor/contrib/hover/modesContentHover.ts | 2 +- .../editor/contrib/hover/modesGlyphHover.ts | 4 +- .../inlineHints/inlineHintsController.ts | 5 +- .../contrib/linkedEditing/linkedEditing.ts | 4 +- .../linkedEditing/test/linkedEditing.test..ts | 4 +- src/vs/editor/contrib/links/links.ts | 2 +- .../editor/contrib/multicursor/multicursor.ts | 4 +- .../parameterHints/parameterHintsModel.ts | 2 +- .../contrib/peekView/media/peekViewWidget.css | 17 - src/vs/editor/contrib/peekView/peekView.ts | 8 +- .../quickAccess/gotoSymbolQuickAccess.ts | 7 +- src/vs/editor/contrib/rename/rename.ts | 2 +- .../smartSelect/test/smartSelect.test.ts | 4 +- .../editor/contrib/snippet/snippetParser.ts | 16 +- .../snippet/test/snippetParser.test.ts | 351 +- .../snippet/test/snippetVariables.test.ts | 7 +- src/vs/editor/contrib/suggest/resizable.ts | 7 + .../editor/contrib/suggest/suggestMemory.ts | 4 +- .../editor/contrib/suggest/suggestWidget.ts | 2 + .../contrib/suggest/suggestWidgetDetails.ts | 2 + .../contrib/suggest/suggestWidgetStatus.ts | 2 +- .../suggest/test/completionModel.test.ts | 82 +- .../contrib/suggest/test/suggest.test.ts | 40 +- .../suggest/test/suggestController.test.ts | 50 +- .../suggest/test/suggestMemory.test.ts | 46 +- .../contrib/suggest/test/suggestModel.test.ts | 162 +- .../contrib/suggest/test/wordDistance.test.ts | 6 +- .../wordHighlighter/wordHighlighter.ts | 4 +- .../contrib/wordOperations/wordOperations.ts | 8 +- .../accessibilityHelp/accessibilityHelp.ts | 2 +- .../iPadShowKeyboard/iPadShowKeyboard.ts | 4 +- .../browser/inspectTokens/inspectTokens.ts | 4 +- .../quickInput/standaloneQuickInput.css | 23 + .../standaloneQuickInputServiceImpl.ts | 2 +- .../standalone/browser/simpleServices.ts | 19 +- .../browser/standaloneCodeEditor.ts | 18 +- .../test/browser/standaloneLanguages.test.ts | 2 +- .../test/browser/controller/cursor.test.ts | 23 + .../services/decorationRenderOptions.test.ts | 4 +- .../browser/services/openerService.test.ts | 117 +- src/vs/editor/test/browser/testCodeEditor.ts | 8 +- .../common/config/commonEditorConfig.test.ts | 2 +- src/vs/editor/test/common/mocks/mockMode.ts | 1 - .../common/model/textModelWithTokens.test.ts | 93 +- .../test/common/model/tokensStore.test.ts | 4 +- .../modes/supports/tokenization.test.ts | 32 +- .../textResourceConfigurationService.test.ts | 46 +- .../common/viewModel/viewModelImpl.test.ts | 2 +- src/vs/loader.js | 3 + src/vs/monaco.d.ts | 7 +- .../browser/menuEntryActionViewItem.css | 7 + .../browser/menuEntryActionViewItem.ts | 42 +- src/vs/platform/actions/common/actions.ts | 19 +- .../actions/test/common/menuService.test.ts | 54 +- .../electron-main/backupMainService.test.ts | 2 +- .../test/node/checksumService.test.ts | 4 +- src/vs/platform/commands/common/commands.ts | 2 + .../commands/test/common/commands.test.ts | 2 +- .../common/configurationModels.ts | 95 +- .../common/configurationRegistry.ts | 34 +- .../test/common/configuration.test.ts | 40 +- .../test/common/configurationModels.test.ts | 203 +- .../test/common/configurationRegistry.test.ts | 8 +- .../test/common/configurationService.test.ts | 18 +- .../contextkey/browser/contextKeyService.ts | 16 +- .../platform/contextkey/common/contextkey.ts | 4 +- .../contextview/browser/contextMenuHandler.ts | 7 +- .../debug/common/extensionHostDebug.ts | 11 +- .../debug/common/extensionHostDebugIpc.ts | 7 +- .../electron-main/extensionHostDebugIpc.ts | 33 +- src/vs/platform/dialogs/common/dialogs.ts | 18 +- src/vs/platform/driver/common/driver.ts | 9 + src/vs/platform/driver/common/driverIpc.ts | 96 + .../platform/driver/electron-main/driver.ts | 5 +- .../driver.ts | 2 +- src/vs/platform/driver/node/driver.ts | 99 +- src/vs/platform/editor/common/editor.ts | 30 +- src/vs/platform/environment/common/argv.ts | 3 +- .../electron-main/environmentMainService.ts | 8 +- src/vs/platform/environment/node/argv.ts | 1 + .../platform/environment/node/argvHelper.ts | 3 +- src/vs/platform/environment/node/shellEnv.ts | 53 +- .../platform/environment/node/userDataPath.js | 37 +- .../environment/test/node/argv.test.ts | 20 +- .../common/extensionManagementCLIService.ts | 6 +- .../common/extensionTipsService.ts | 3 +- .../electron-sandbox/extensionTipsService.ts | 6 +- .../node/extensionManagementService.ts | 5 +- .../common/extensionGalleryService.test.ts | 6 +- .../test/common/extensionManagement.test.ts | 32 +- .../platform/extensions/common/extensions.ts | 33 +- .../files/browser/htmlFileSystemProvider.ts | 210 + src/vs/platform/files/common/fileService.ts | 6 +- src/vs/platform/files/common/files.ts | 82 +- src/vs/platform/files/common/io.ts | 3 +- .../diskFileSystemProvider.ts | 4 +- .../files/node/diskFileSystemProvider.ts | 2 +- .../node/watcher/nodejs/watcherService.ts | 2 +- .../files/node/watcher/nsfw/watcherService.ts | 2 +- .../files/node/watcher/unix/watcherService.ts | 2 +- .../files/test/browser/fileService.test.ts | 10 +- .../electron-browser/diskFileService.test.ts | 10 +- src/vs/platform/instantiation/common/graph.ts | 30 + .../common/instantiationService.ts | 6 +- .../instantiation/test/common/graph.test.ts | 22 +- .../test/common/instantiationService.test.ts | 38 +- src/vs/platform/issue/common/issue.ts | 19 +- .../issue/electron-main/issueMainService.ts | 390 +- .../common/abstractKeybindingService.ts | 2 +- .../test/common/mockKeybindingService.ts | 2 +- src/vs/platform/list/browser/listService.ts | 40 +- src/vs/platform/log/common/bufferLog.ts | 2 +- src/vs/platform/log/common/fileLog.ts | 34 +- src/vs/platform/log/common/log.ts | 50 +- src/vs/platform/log/common/logIpc.ts | 32 +- src/vs/platform/log/node/loggerService.ts | 49 +- src/vs/platform/log/node/spdlogLog.ts | 4 +- .../markers/test/common/markerService.test.ts | 80 +- .../platform/menubar/electron-main/menubar.ts | 7 +- .../electron-main/nativeHostMainService.ts | 2 +- .../notification/common/notification.ts | 11 +- src/vs/platform/opener/browser/link.ts | 2 +- src/vs/platform/opener/common/opener.ts | 5 + src/vs/platform/product/common/product.ts | 72 +- .../platform/product/common/productService.ts | 158 +- src/vs/platform/progress/common/progress.ts | 4 +- .../protocol/electron-main/protocol.ts | 51 + .../electron-main/protocolMainService.ts} | 94 +- .../quickinput/browser/commandsQuickAccess.ts | 6 +- .../quickinput/browser/pickerQuickAccess.ts | 10 +- .../platform/quickinput/browser/quickInput.ts | 11 +- .../registry/test/common/platform.test.ts | 2 +- .../remote/common/remoteAgentEnvironment.ts | 1 + .../remote/common/remoteAuthorityResolver.ts | 7 + src/vs/platform/remote/common/remoteHosts.ts | 18 + src/vs/platform/remote/common/tunnel.ts | 4 +- src/vs/platform/remote/node/tunnelService.ts | 2 +- src/vs/platform/request/common/request.ts | 40 +- .../electron-main/requestMainService.ts | 2 +- .../electron-main/sharedProcess.ts | 28 +- .../sharedProcess/node/sharedProcess.ts | 9 +- .../storage/browser/storageService.ts | 2 +- .../storage/electron-main/storageMain.ts | 2 +- .../electron-main/storageMainService.test.ts | 2 +- .../telemetry/browser/errorTelemetry.ts | 2 +- .../telemetry/common/commonProperties.ts | 18 +- .../telemetry/common/telemetryService.ts | 4 +- .../platform/telemetry/node/errorTelemetry.ts | 2 +- .../test/browser/telemetryService.test.ts | 290 +- .../test/common/telemetryLogAppender.test.ts | 6 +- .../appInsightsAppender.test.ts | 54 +- src/vs/platform/terminal/common/terminal.ts | 56 +- .../terminal/common/terminalProcess.ts | 10 +- .../platform/terminal/node/ptyHostService.ts | 55 +- src/vs/platform/terminal/node/ptyService.ts | 45 +- .../terminal/node/terminalEnvironment.ts | 4 +- .../platform/terminal/node/terminalProcess.ts | 95 +- .../terminal/node/windowsShellHelper.ts | 10 +- src/vs/platform/theme/common/colorRegistry.ts | 16 + src/vs/platform/theme/common/styler.ts | 187 +- .../undoRedo/common/undoRedoService.ts | 12 +- .../test/common/undoRedoService.test.ts | 2 +- src/vs/platform/update/common/update.ts | 6 +- src/vs/platform/update/common/updateIpc.ts | 4 +- .../electron-main/abstractUpdateService.ts | 8 +- .../electron-main/updateService.darwin.ts | 10 +- .../electron-main/updateService.linux.ts | 4 +- .../electron-main/updateService.snap.ts | 24 +- .../electron-main/updateService.win32.ts | 12 +- .../userDataSync/common/extensionsSync.ts | 106 +- .../userDataSync/common/globalStateSync.ts | 4 +- .../userDataSync/common/keybindingsSync.ts | 2 +- .../userDataSync/common/settingsSync.ts | 4 +- .../userDataSync/common/snippetsSync.ts | 2 +- .../userDataSync/common/userDataSync.ts | 2 +- .../common/userDataSyncService.ts | 2 +- .../common/userDataSyncServiceIpc.ts | 2 +- .../common/userDataSyncStoreService.ts | 3 +- .../test/common/extensionsMerge.test.ts | 224 +- .../test/common/globalStateMerge.test.ts | 200 +- .../test/common/globalStateSync.test.ts | 82 +- .../test/common/keybindingsMerge.test.ts | 62 +- .../test/common/keybindingsSync.test.ts | 54 +- .../test/common/settingsMerge.test.ts | 290 +- .../test/common/settingsSync.test.ts | 62 +- .../test/common/snippetsMerge.test.ts | 294 +- .../test/common/snippetsSync.test.ts | 266 +- .../test/common/synchronizer.test.ts | 282 +- .../common/userDataAutoSyncService.test.ts | 40 +- .../test/common/userDataSyncService.test.ts | 34 +- .../common/userDataSyncStoreService.test.ts | 117 +- .../webview/common/webviewManagerService.ts | 30 +- .../electron-main/webviewMainService.ts | 62 +- .../webviewPortMappingProvider.ts | 103 - .../electron-main/webviewProtocolProvider.ts | 274 +- src/vs/platform/windows/common/windows.ts | 19 +- .../platform/windows/electron-main/window.ts | 336 +- .../electron-main/windowsMainService.ts | 157 +- src/vs/platform/workspace/common/workspace.ts | 11 + .../workspace/common/workspaceTrust.ts | 109 +- .../workspacesManagementMainService.test.ts | 2 +- src/vs/vscode.d.ts | 50 +- src/vs/vscode.proposed.d.ts | 1349 +- src/vs/workbench/api/browser/apiCommands.ts | 20 + .../api/browser/extensionHost.contribution.ts | 3 + .../api/browser/mainThreadBulkEdits.ts | 7 +- .../api/browser/mainThreadCLICommands.ts | 30 +- .../api/browser/mainThreadCodeInsets.ts | 2 +- .../api/browser/mainThreadComments.ts | 2 +- .../api/browser/mainThreadCustomEditors.ts | 67 +- .../api/browser/mainThreadDocuments.ts | 12 +- .../workbench/api/browser/mainThreadEditor.ts | 2 +- .../api/browser/mainThreadExtensionService.ts | 47 +- .../api/browser/mainThreadMessageService.ts | 2 +- .../api/browser/mainThreadNotebook.ts | 588 +- .../browser/mainThreadNotebookDocuments.ts | 151 + .../mainThreadNotebookDocumentsAndEditors.ts | 254 + .../api/browser/mainThreadNotebookEditors.ts | 191 + .../api/browser/mainThreadNotebookKernels.ts | 225 + src/vs/workbench/api/browser/mainThreadSCM.ts | 20 +- .../workbench/api/browser/mainThreadSearch.ts | 2 +- .../api/browser/mainThreadStatusBar.ts | 4 +- .../workbench/api/browser/mainThreadTask.ts | 2 +- .../api/browser/mainThreadTerminalService.ts | 82 +- .../api/browser/mainThreadTesting.ts | 85 +- .../api/browser/mainThreadTreeViews.ts | 2 +- .../api/browser/mainThreadTunnelService.ts | 25 +- .../api/browser/mainThreadUriOpeners.ts | 6 +- .../api/browser/mainThreadWebviewPanels.ts | 48 +- .../api/browser/mainThreadWebviewViews.ts | 6 +- .../api/browser/mainThreadWebviews.ts | 23 +- .../api/browser/mainThreadWorkspace.ts | 23 +- .../api/browser/viewsExtensionPoint.ts | 6 +- .../api/common/configurationExtensionPoint.ts | 12 +- .../workbench/api/common/extHost.api.impl.ts | 79 +- .../workbench/api/common/extHost.protocol.ts | 221 +- .../api/common/extHostApiCommands.ts | 18 +- .../api/common/extHostCustomEditors.ts | 6 +- .../api/common/extHostDebugService.ts | 4 +- .../api/common/extHostDiagnostics.ts | 2 +- .../api/common/extHostDocumentData.ts | 2 +- .../api/common/extHostExtensionActivator.ts | 53 +- .../api/common/extHostExtensionService.ts | 20 +- src/vs/workbench/api/common/extHostMemento.ts | 36 +- .../workbench/api/common/extHostNotebook.ts | 760 +- .../common/extHostNotebookConcatDocument.ts | 2 +- .../api/common/extHostNotebookDocument.ts | 120 +- .../api/common/extHostNotebookEditor.ts | 69 +- .../api/common/extHostNotebookKernels.ts | 259 + src/vs/workbench/api/common/extHostOutput.ts | 6 +- src/vs/workbench/api/common/extHostSCM.ts | 16 + .../api/common/extHostTerminalService.ts | 26 +- src/vs/workbench/api/common/extHostTesting.ts | 464 +- .../api/common/extHostTestingPrivateApi.ts | 44 + .../workbench/api/common/extHostTreeViews.ts | 10 +- .../api/common/extHostTunnelService.ts | 2 +- .../api/common/extHostTypeConverters.ts | 175 +- src/vs/workbench/api/common/extHostTypes.ts | 436 +- src/vs/workbench/api/common/extHostWebview.ts | 56 +- .../api/common/extHostWebviewMessaging.ts | 122 + .../api/common/extHostWebviewPanels.ts | 10 +- .../api/common/extHostWebviewView.ts | 7 +- .../workbench/api/common/extHostWorkspace.ts | 30 +- .../api/common/menusExtensionPoint.ts | 11 + src/vs/workbench/api/node/extHostCLIServer.ts | 38 +- .../workbench/api/node/extHostDebugService.ts | 10 +- .../api/node/extHostOutputService.ts | 12 +- src/vs/workbench/api/node/extHostSearch.ts | 6 +- src/vs/workbench/api/node/extHostTask.ts | 14 +- .../api/node/extHostTerminalService.ts | 66 +- .../api/node/extHostTunnelService.ts | 24 +- .../api/worker/extHostExtensionService.ts | 8 +- .../browser/actions/developerActions.ts | 20 +- .../browser/actions/layoutActions.ts | 20 +- .../browser/actions/navigationActions.ts | 24 +- .../browser/actions/textInputActions.ts | 2 +- .../browser/actions/windowActions.ts | 12 +- .../browser/actions/workspaceActions.ts | 23 +- .../browser/actions/workspaceCommands.ts | 39 +- src/vs/workbench/browser/codeeditor.ts | 13 +- src/vs/workbench/browser/composite.ts | 8 +- src/vs/workbench/browser/contextkeys.ts | 21 +- src/vs/workbench/browser/dnd.ts | 22 +- src/vs/workbench/browser/editor.ts | 217 +- src/vs/workbench/browser/labels.ts | 5 +- src/vs/workbench/browser/layout.ts | 125 +- src/vs/workbench/browser/media/part.css | 13 +- src/vs/workbench/browser/panecomposite.ts | 20 +- src/vs/workbench/browser/panel.ts | 8 +- src/vs/workbench/browser/part.ts | 4 +- .../parts/activitybar/activitybarActions.ts | 35 +- .../parts/activitybar/activitybarPart.ts | 29 +- .../activitybar/media/activityaction.css | 4 +- .../workbench/browser/parts/compositeBar.ts | 13 +- .../browser/parts/compositeBarActions.ts | 210 +- .../workbench/browser/parts/compositePart.ts | 16 +- .../parts/dialogs/dialog.web.contribution.ts | 4 +- .../browser/parts/dialogs/dialogHandler.ts | 30 +- .../browser/parts/editor/binaryDiffEditor.ts | 2 +- .../browser/parts/editor/binaryEditor.ts | 171 +- .../parts/editor/breadcrumbsControl.ts | 22 +- .../browser/parts/editor/breadcrumbsPicker.ts | 2 +- .../parts/editor/editor.contribution.ts | 56 +- .../workbench/browser/parts/editor/editor.ts | 40 +- .../browser/parts/editor/editorActions.ts | 96 +- .../browser/parts/editor/editorAutoSave.ts | 9 +- .../browser/parts/editor/editorCommands.ts | 10 +- .../browser/parts/editor/editorControl.ts | 6 +- .../browser/parts/editor/editorDropTarget.ts | 24 +- .../browser/parts/editor/editorGroupView.ts | 337 +- .../browser/parts/editor/editorPane.ts | 10 +- .../browser/parts/editor/editorPart.ts | 99 +- .../browser/parts/editor/editorQuickAccess.ts | 4 +- .../browser/parts/editor/editorStatus.ts | 24 +- .../browser/parts/editor/editorsObserver.ts | 72 +- .../parts/editor/media/editorgroupview.css | 15 +- .../parts/editor/media/notabstitlecontrol.css | 4 + .../parts/editor/media/tabstitlecontrol.css | 23 +- .../parts/editor/media/titlecontrol.css | 33 - .../parts/editor/noTabsTitleControl.ts | 6 +- .../browser/parts/editor/sideBySideEditor.ts | 38 +- .../browser/parts/editor/tabsTitleControl.ts | 11 +- .../browser/parts/editor/textDiffEditor.ts | 30 +- .../browser/parts/editor/textEditor.ts | 19 +- .../parts/editor/textResourceEditor.ts | 10 +- .../browser/parts/editor/titleControl.ts | 13 +- .../browser/parts/editor/untitledHint.ts | 62 +- .../media/notificationsActions.css | 17 +- .../media/notificationsCenter.css | 6 +- .../notifications/notificationsActions.ts | 23 +- .../notifications/notificationsCenter.ts | 2 +- .../parts/notifications/notificationsList.ts | 4 +- .../notifications/notificationsToasts.ts | 22 +- .../notifications/notificationsViewer.ts | 6 +- .../browser/parts/panel/media/panelpart.css | 20 +- .../browser/parts/panel/panelActions.ts | 14 +- .../browser/parts/panel/panelPart.ts | 22 +- .../parts/sidebar/media/sidebarpart.css | 8 + .../browser/parts/sidebar/sidebarPart.ts | 10 +- .../browser/parts/statusbar/statusbarPart.ts | 54 +- .../browser/parts/titlebar/menubarControl.ts | 23 +- .../browser/parts/titlebar/titlebarPart.ts | 21 +- .../browser/parts/views/media/paneviewlet.css | 5 +- .../browser/parts/views/media/views.css | 26 +- .../workbench/browser/parts/views/treeView.ts | 21 +- .../workbench/browser/parts/views/viewPane.ts | 22 +- .../browser/parts/views/viewPaneContainer.ts | 16 +- .../browser/parts/views/viewsViewlet.ts | 4 +- src/vs/workbench/browser/style.ts | 35 +- src/vs/workbench/browser/viewlet.ts | 10 +- src/vs/workbench/browser/web.main.ts | 32 +- src/vs/workbench/browser/window.ts | 15 +- .../browser/workbench.contribution.ts | 28 +- src/vs/workbench/browser/workbench.ts | 107 +- src/vs/workbench/common/composite.ts | 16 - src/vs/workbench/common/editor.ts | 299 +- .../common/editor/binaryEditorModel.ts | 4 +- .../common/editor/diffEditorInput.ts | 26 +- .../common/editor/diffEditorModel.ts | 14 +- .../{editorGroup.ts => editorGroupModel.ts} | 70 +- .../common/editor/resourceEditorInput.ts | 23 +- .../common/editor/resourceEditorModel.ts | 2 +- .../common/editor/textDiffEditorModel.ts | 19 +- .../common/editor/textEditorModel.ts | 7 +- .../common/editor/textResourceEditorInput.ts | 18 +- src/vs/workbench/common/notifications.ts | 16 +- .../backup/browser/backup.web.contribution.ts | 12 - .../backup/common/backup.contribution.ts | 12 - .../contrib/backup/common/backupRestorer.ts | 122 - .../electron-sandbox/backup.contribution.ts | 12 - .../backup/test/browser/backupTracker.test.ts | 153 - .../contrib/bulkEdit/browser/bulkCellEdits.ts | 2 +- .../contrib/bulkEdit/browser/bulkFileEdits.ts | 10 +- .../contrib/bulkEdit/browser/bulkTextEdits.ts | 4 +- .../browser/preview/bulkEdit.contribution.ts | 12 +- .../bulkEdit/browser/preview/bulkEditPane.ts | 6 +- .../test/browser/bulkEditPreview.test.ts | 36 +- .../browser/callHierarchyPeek.ts | 8 +- .../browser/accessibility/accessibility.ts | 2 +- .../codeEditor/browser/diffEditorHelper.ts | 2 +- .../browser/find/simpleFindReplaceWidget.ts | 32 +- .../browser/find/simpleFindWidget.ts | 2 +- .../inspectEditorTokens.ts | 4 +- .../codeEditor/browser/inspectKeybindings.ts | 2 +- .../quickaccess/gotoLineQuickAccess.ts | 2 +- .../quickaccess/gotoSymbolQuickAccess.ts | 8 +- .../codeEditor/browser/saveParticipants.ts | 4 +- .../suggestEnabledInput.ts | 8 +- .../browser/toggleColumnSelection.ts | 2 +- .../codeEditor/browser/toggleMinimap.ts | 2 +- .../browser/toggleMultiCursorModifier.ts | 2 +- .../browser/toggleRenderControlCharacter.ts | 2 +- .../browser/toggleRenderWhitespace.ts | 2 +- .../electron-sandbox/selectionClipboard.ts | 2 +- .../test/browser/saveParticipant.test.ts | 10 +- .../comments/browser/commentThreadWidget.ts | 25 +- .../comments/browser/comments.contribution.ts | 3 +- .../comments/browser/commentsTreeViewer.ts | 2 +- .../contrib/comments/browser/commentsView.ts | 8 +- .../contrib/comments/browser/media/review.css | 4 - .../comments/browser/reactionsAction.ts | 4 +- .../browser/customEditor.contribution.ts | 23 +- .../customEditor/browser/customEditorInput.ts | 77 +- .../browser/customEditorInputFactory.ts | 21 +- .../customEditor/browser/customEditors.ts | 446 +- .../common/contributedCustomEditors.ts | 17 +- .../customEditor/common/customEditor.ts | 52 +- .../contrib/debug/browser/breakpointWidget.ts | 6 +- .../contrib/debug/browser/breakpointsView.ts | 28 +- .../browser/callStackEditorContribution.ts | 10 +- .../contrib/debug/browser/callStackView.ts | 25 +- .../debug/browser/debug.contribution.ts | 23 +- .../debug/browser/debugANSIHandling.ts | 189 +- .../debug/browser/debugActionViewItems.ts | 16 +- .../debug/browser/debugAdapterManager.ts | 50 +- .../contrib/debug/browser/debugCommands.ts | 33 +- .../browser/debugConfigurationManager.ts | 15 +- .../debug/browser/debugEditorContribution.ts | 16 +- .../contrib/debug/browser/debugService.ts | 95 +- .../contrib/debug/browser/debugSession.ts | 47 +- .../contrib/debug/browser/debugToolBar.ts | 19 +- .../contrib/debug/browser/debugViewlet.ts | 10 +- .../contrib/debug/browser/exceptionWidget.ts | 4 +- .../browser/extensionHostDebugService.ts | 11 +- .../debug/browser/loadedScriptsView.ts | 18 +- .../debug/browser/media/debugToolBar.css | 5 +- .../debug/browser/media/debugViewlet.css | 34 +- .../contrib/debug/browser/media/repl.css | 28 +- .../contrib/debug/browser/rawDebugSession.ts | 64 +- .../workbench/contrib/debug/browser/repl.ts | 31 +- .../contrib/debug/browser/replFilter.ts | 10 +- .../contrib/debug/browser/replViewer.ts | 2 +- .../debug/browser/statusbarColorProvider.ts | 2 +- .../contrib/debug/browser/variablesView.ts | 6 +- .../debug/browser/watchExpressionsView.ts | 6 +- .../contrib/debug/browser/welcomeView.ts | 8 +- .../workbench/contrib/debug/common/debug.ts | 10 +- .../contrib/debug/common/debugModel.ts | 53 +- .../contrib/debug/common/debugProtocol.d.ts | 22 +- .../contrib/debug/common/debugViewModel.ts | 5 +- .../contrib/debug/common/replModel.ts | 4 +- .../debug/test/browser/callStack.test.ts | 36 +- .../test/browser/debugANSIHandling.test.ts | 565 +- .../debug/test/browser/debugHover.test.ts | 8 +- .../contrib/debug/test/browser/mockDebug.ts | 14 +- .../contrib/debug/test/browser/repl.test.ts | 2 +- .../emmet/test/browser/emmetAction.test.ts | 9 +- .../browser/experiments.contribution.ts | 4 +- .../experimentService.test.ts | 220 +- .../experimentalPrompts.test.ts | 30 +- .../abstractRuntimeExtensionsEditor.ts | 8 +- .../extensions/browser/extensionEditor.ts | 191 +- ...onEnablementByWorkspaceTrustRequirement.ts | 36 + .../extensionRecommendationsService.ts | 2 +- .../browser/extensions.contribution.ts | 136 +- .../browser/extensions.web.contribution.ts | 3 +- .../extensions/browser/extensionsActions.ts | 151 +- .../extensions/browser/extensionsList.ts | 20 +- .../extensions/browser/extensionsViewer.ts | 17 +- .../extensions/browser/extensionsViewlet.ts | 55 +- .../extensions/browser/extensionsViews.ts | 100 +- .../browser/extensionsWorkbenchService.ts | 33 +- .../browser/fileBasedRecommendations.ts | 3 +- .../extensions/browser/media/extension.css | 14 + .../browser/media/extensionActions.css | 15 +- .../browser/media/extensionEditor.css | 35 +- .../browser/media/extensionsViewlet.css | 10 + .../extensions/common/extensionQuery.ts | 2 +- .../extensions/common/extensionsInput.ts | 16 +- .../extensions/common/extensionsUtils.ts | 2 +- .../common/runtimeExtensionsInput.ts | 14 +- .../debugExtensionHostAction.ts | 2 +- .../extensions.contribution.ts | 8 +- .../electron-browser/extensionsSlowActions.ts | 6 +- .../reportExtensionIssueAction.ts | 2 +- .../runtimeExtensionsEditor.ts | 6 +- .../electron-sandbox/extensionsActions.ts | 2 +- .../test/common/extensionQuery.test.ts | 78 +- .../extensionRecommendationsService.test.ts | 16 +- .../extensionsActions.test.ts | 146 +- .../electron-browser/extensionsViews.test.ts | 168 +- .../extensionsWorkbenchService.test.ts | 350 +- .../common/externalTerminal.ts | 3 +- .../node/externalTerminalService.ts | 9 +- .../common/externalUriOpenerService.ts | 2 +- .../contrib/feedback/browser/feedback.ts | 12 +- .../files/browser/editors/binaryFileEditor.ts | 31 +- .../files/browser/editors/textFileEditor.ts | 18 +- .../browser/editors/textFileEditorTracker.ts | 7 +- .../editors/textFileSaveErrorHandler.ts | 64 +- .../contrib/files/browser/explorerViewlet.ts | 14 +- .../files/browser/fileActions.contribution.ts | 8 +- .../contrib/files/browser/fileActions.ts | 101 +- .../contrib/files/browser/fileCommands.ts | 18 +- .../files/browser/files.contribution.ts | 14 +- .../workbench/contrib/files/browser/files.ts | 8 +- .../files/browser/files.web.contribution.ts | 4 +- .../contrib/files/browser/views/emptyView.ts | 4 +- .../files/browser/views/explorerView.ts | 20 +- .../files/browser/views/explorerViewer.ts | 2 +- .../files/browser/views/media/openeditors.css | 10 +- .../files/browser/views/openEditorsView.ts | 15 +- .../files/common/dirtyFilesIndicator.ts | 5 +- .../files/common/editors/fileEditorInput.ts | 51 +- .../contrib/files/common/workspaceWatcher.ts | 2 +- .../fileActions.contribution.ts | 5 +- .../files/electron-sandbox/fileCommands.ts | 6 +- .../electron-sandbox/files.contribution.ts | 4 +- .../files/electron-sandbox/textFileEditor.ts | 8 +- .../files/test/browser/editorAutoSave.test.ts | 2 +- .../files/test/browser/explorerModel.test.ts | 2 +- .../test/browser/fileEditorInput.test.ts | 30 +- .../browser/textFileEditorTracker.test.ts | 25 +- .../format/browser/formatActionsNone.ts | 20 +- .../contrib/format/browser/formatModified.ts | 2 +- .../issue/electron-sandbox/issueActions.ts | 8 +- .../browser/localizationsActions.ts | 2 +- .../contrib/logs/common/logsActions.ts | 4 +- .../logs/electron-sandbox/logsActions.ts | 4 +- .../common/markdownDocumentRenderer.ts | 168 + .../markers/browser/markersFilterOptions.ts | 9 + .../markers/browser/markersTreeViewer.ts | 8 +- .../contrib/markers/browser/markersView.ts | 14 +- .../markers/browser/markersViewActions.ts | 20 +- .../contrib/markers/browser/media/markers.css | 42 +- .../markers/test/browser/markersModel.test.ts | 70 +- .../contrib/notebook/browser/constants.ts | 11 +- .../contrib/cellOperations/cellOperations.ts | 56 +- .../test/cellOperations.test.ts | 249 +- .../contrib/clipboard/notebookClipboard.ts | 571 +- .../clipboard/test/notebookClipboard.test.ts | 304 + .../notebook/browser/contrib/coreActions.ts | 588 +- .../browser/contrib/find/findController.ts | 91 +- .../contrib/find/media/notebookFind.css | 4 + .../notebook/browser/contrib/fold/folding.ts | 3 +- .../browser/contrib/fold/foldingModel.ts | 3 +- .../contrib/fold/test/notebookFolding.test.ts | 86 +- .../browser/contrib/layout/layoutActions.ts | 74 + .../contrib/layout/test/layoutActions.test.ts | 61 + .../browser/contrib/navigation/arrow.ts | 234 + .../contrib/outline/notebookOutline.ts | 6 +- .../outline/test/notebookOutline.test.ts | 4 +- .../browser/contrib/status/editorStatus.ts | 356 +- .../contrib/statusBar/cellStatusBar.ts | 142 + .../contributedStatusBarItemController.ts | 126 + .../executionStatusBarItemController.ts | 225 + .../statusBar/notebookVisibleCellObserver.ts | 83 + .../contrib/statusBar/statusBarProviders.ts | 119 + .../browser/contrib/troubleshoot/layout.ts | 131 + .../contrib/undoRedo/notebookUndoRedo.ts | 4 +- .../undoRedo/test/notebookUndoRedo.test.ts | 132 + .../viewportCustomMarkdown.ts | 45 +- .../notebook/browser/diff/diffComponents.ts | 150 +- .../browser/diff/diffElementOutputs.ts | 4 +- .../browser/diff/diffElementViewModel.ts | 36 +- .../browser/diff/diffNestedCellViewModel.ts | 11 + .../browser/diff/notebookDiffActions.ts | 4 +- .../browser/diff/notebookTextDiffEditor.ts | 254 +- .../browser/diff/notebookTextDiffList.ts | 2 +- .../notebook/browser/extensionPoint.ts | 59 +- .../notebook/browser/media/notebook.css | 101 +- .../notebook/browser/notebook.contribution.ts | 459 +- .../notebook/browser/notebookBrowser.ts | 132 +- .../notebookCellStatusBarServiceImpl.ts | 68 +- .../browser/notebookDiffEditorInput.ts | 29 +- .../notebook/browser/notebookEditor.ts | 121 +- .../browser/notebookEditorKernelManager.ts | 514 +- .../notebook/browser/notebookEditorService.ts | 2 +- .../browser/notebookEditorServiceImpl.ts | 55 +- .../notebook/browser/notebookEditorWidget.ts | 770 +- .../notebookEditorWidgetContextKeys.ts | 98 + .../contrib/notebook/browser/notebookIcons.ts | 2 + .../browser/notebookKernelAssociation.ts | 76 - .../browser/notebookKernelServiceImpl.ts | 235 + .../notebook/browser/notebookServiceImpl.ts | 302 +- .../notebook/browser/view/notebookCellList.ts | 99 +- .../browser/view/output/outputRenderer.ts | 8 + .../view/output/transforms/richTransform.ts | 43 +- .../view/renderers/backLayerWebView.ts | 816 +- .../browser/view/renderers/cellActionView.ts | 6 +- .../browser/view/renderers/cellContextKeys.ts | 18 +- .../browser/view/renderers/cellDnd.ts | 3 +- .../view/renderers/cellEditorOptions.ts | 218 + .../browser/view/renderers/cellOutput.ts | 125 +- .../browser/view/renderers/cellRenderer.ts | 224 +- .../browser/view/renderers/cellWidgets.ts | 316 +- .../browser/view/renderers/codeCell.ts | 105 +- .../browser/view/renderers/markdownCell.ts | 71 +- .../browser/view/renderers/webviewPreloads.ts | 538 +- .../browser/viewModel/baseCellViewModel.ts | 187 +- .../browser/viewModel/cellOutputViewModel.ts | 4 +- .../viewModel/cellSelectionCollection.ts | 2 +- .../browser/viewModel/codeCellViewModel.ts | 125 +- .../viewModel/markdownCellViewModel.ts | 62 +- .../browser/viewModel/notebookViewModel.ts | 152 +- .../common/model/notebookCellTextModel.ts | 117 +- .../common/model/notebookTextModel.ts | 395 +- .../common/notebookCellStatusBarService.ts | 11 +- .../contrib/notebook/common/notebookCommon.ts | 230 +- .../notebookEditorInput.ts | 60 +- .../notebook/common/notebookEditorModel.ts | 188 +- .../notebookEditorModelResolverService.ts | 147 +- .../notebookEditorModelResolverServiceImpl.ts | 183 + .../notebook/common/notebookKernelService.ts | 57 + .../common/notebookMarkdownRenderer.ts | 10 +- .../notebook/common/notebookOutputRenderer.ts | 63 +- .../notebook/common/notebookPerformance.ts | 41 + .../notebook/common/notebookProvider.ts | 12 +- .../contrib/notebook/common/notebookRange.ts | 83 + .../notebook/common/notebookSelector.ts | 31 + .../notebook/common/notebookService.ts | 33 +- .../common/services/notebookSimpleWorker.ts | 5 +- .../electron-browser/notebook.contribution.ts | 61 - .../notebook/test/notebookCellList.test.ts | 88 +- .../notebook/test/notebookCommon.test.ts | 21 +- .../notebook/test/notebookDiff.test.ts | 177 + .../notebook/test/notebookEditor.test.ts | 28 +- .../test/notebookEditorKernelManager.test.ts | 123 +- .../notebook/test/notebookEditorModel.test.ts | 27 +- .../test/notebookKernelService.test.ts | 183 + .../notebook/test/notebookSelection.test.ts | 8 +- .../notebook/test/notebookServiceImpl.test.ts | 25 +- .../notebook/test/notebookTextModel.test.ts | 372 +- .../notebook/test/notebookViewModel.test.ts | 325 +- .../notebook/test/testNotebookEditor.ts | 126 +- .../contrib/outline/browser/outlinePane.ts | 8 +- .../contrib/output/browser/logViewer.ts | 12 +- .../output/browser/output.contribution.ts | 5 +- .../contrib/output/browser/outputServices.ts | 2 +- .../contrib/output/browser/outputView.ts | 24 +- .../output/common/outputChannelModel.ts | 18 +- .../outputChannelModelService.ts | 6 +- .../browser/performance.contribution.ts | 6 +- .../performance/browser/perfviewEditor.ts | 15 +- .../preferences/browser/keybindingWidgets.ts | 22 +- .../preferences/browser/keybindingsEditor.ts | 84 +- .../browser/keybindingsEditorContribution.ts | 2 +- .../browser/keyboardLayoutPicker.ts | 10 +- .../preferences/browser/media/preferences.css | 7 +- .../browser/media/settingsEditor2.css | 39 +- .../browser/preferences.contribution.ts | 43 +- .../preferences/browser/preferencesActions.ts | 4 +- .../preferences/browser/preferencesEditor.ts | 101 +- .../browser/preferencesRenderers.ts | 117 +- .../preferences/browser/preferencesWidgets.ts | 25 +- .../preferences/browser/settingsEditor2.ts | 159 +- .../preferences/browser/settingsLayout.ts | 11 + .../preferences/browser/settingsTree.ts | 88 +- .../preferences/browser/settingsTreeModels.ts | 77 +- .../preferences/browser/settingsWidgets.ts | 28 +- .../contrib/preferences/common/preferences.ts | 1 + .../common/preferencesContribution.ts | 2 +- .../keybindingsEditorContribution.test.ts | 2 +- .../test/browser/settingsTreeModels.test.ts | 36 +- .../test/common/smartSnippetInserter.test.ts | 2 +- .../browser/relauncher.contribution.ts | 8 + .../remote/browser/explorerViewItems.ts | 4 +- .../remote/browser/media/remoteViewlet.css | 1 - .../remote/browser/media/tunnelView.css | 15 +- .../contrib/remote/browser/remote.ts | 20 +- .../contrib/remote/browser/remoteExplorer.ts | 16 +- .../contrib/remote/browser/remoteIndicator.ts | 104 +- .../contrib/remote/browser/tunnelView.ts | 185 +- .../contrib/remote/browser/urlFinder.ts | 2 +- .../remote/common/remote.contribution.ts | 26 +- .../contrib/scm/browser/dirtydiffDecorator.ts | 25 +- .../contrib/scm/browser/media/scm.css | 15 +- .../scm/browser/scmRepositoriesViewPane.ts | 10 +- .../contrib/scm/browser/scmViewPane.ts | 41 +- .../scm/browser/scmViewPaneContainer.ts | 6 +- src/vs/workbench/contrib/scm/browser/util.ts | 4 +- src/vs/workbench/contrib/scm/common/scm.ts | 6 + .../contrib/scm/common/scmService.ts | 20 +- .../search/browser/anythingQuickAccess.ts | 2 +- .../search/browser/media/searchview.css | 43 +- .../search/browser/patternInputWidget.ts | 14 +- .../search/browser/search.contribution.ts | 4 +- .../contrib/search/browser/searchActions.ts | 26 +- .../contrib/search/browser/searchView.ts | 144 +- .../contrib/search/browser/searchWidget.ts | 6 +- .../contrib/search/common/searchModel.ts | 12 +- .../search/test/browser/queryBuilder.test.ts | 20 +- .../search/test/browser/searchActions.test.ts | 14 +- .../search/test/browser/searchViewlet.test.ts | 4 +- .../search/test/common/extractRange.test.ts | 24 +- .../search/test/common/searchModel.test.ts | 30 +- .../search/test/common/searchResult.test.ts | 80 +- .../contrib/searchEditor/browser/constants.ts | 4 +- .../browser/searchEditor.contribution.ts | 60 +- .../searchEditor/browser/searchEditor.ts | 100 +- .../browser/searchEditorActions.ts | 10 +- .../searchEditor/browser/searchEditorInput.ts | 52 +- .../searchEditor/browser/searchEditorModel.ts | 40 +- .../browser/searchEditorSerialization.ts | 2 +- .../browser/languageSurveys.contribution.ts | 23 +- .../electron-sandbox/workspaceTagsService.ts | 232 +- .../tasks/browser/abstractTaskService.ts | 337 +- .../tasks/browser/runAutomaticTasks.ts | 11 +- .../contrib/tasks/browser/taskService.ts | 12 +- .../tasks/browser/terminalTaskSystem.ts | 190 +- .../contrib/tasks/common/problemCollectors.ts | 2 +- .../contrib/tasks/common/problemMatcher.ts | 10 +- .../contrib/tasks/common/taskService.ts | 10 - .../workbench/contrib/tasks/common/tasks.ts | 62 +- .../taskService.ts | 111 +- .../tasks/node/processRunnerDetector.ts | 390 - .../contrib/tasks/node/processTaskSystem.ts | 495 - .../tasks/test/common/configuration.test.ts | 8 +- .../tasks/test/common/problemMatcher.test.ts | 50 +- .../browser/telemetry.contribution.ts | 8 +- .../browser/environmentVariableInfo.ts | 3 +- .../terminal/browser/links/terminalLink.ts | 7 +- .../terminalValidatedLocalLinkProvider.ts | 2 +- .../terminal/browser/media/terminal.css | 87 + .../contrib/terminal/browser/remotePty.ts | 9 +- .../terminal/browser/remoteTerminalService.ts | 39 +- .../terminal/browser/terminal.contribution.ts | 6 +- .../contrib/terminal/browser/terminal.ts | 68 +- .../browser/terminal.web.contribution.ts | 6 +- .../terminal/browser/terminalActions.ts | 553 +- .../terminal/browser/terminalConfigHelper.ts | 93 +- .../browser/terminalDecorationsProvider.ts | 60 + .../terminal/browser/terminalFindWidget.ts | 2 +- .../terminal/browser/terminalInstance.ts | 230 +- .../browser/terminalInstanceService.ts | 37 +- .../browser/terminalProcessExtHostProxy.ts | 14 +- .../browser/terminalProcessManager.ts | 198 +- .../browser/terminalProfileResolverService.ts | 336 + .../terminal/browser/terminalQuickAccess.ts | 12 +- .../terminal/browser/terminalService.ts | 339 +- .../terminal/browser/terminalStatusList.ts | 144 + .../contrib/terminal/browser/terminalTab.ts | 33 +- .../terminal/browser/terminalTabbedView.ts | 534 + .../terminal/browser/terminalTabsWidget.ts | 378 + .../browser/terminalTypeAheadAddon.ts | 8 +- .../contrib/terminal/browser/terminalView.ts | 425 +- .../widgets/environmentVariableInfoWidget.ts | 5 +- .../browser/widgets/terminalHoverWidget.ts | 4 +- .../terminal/browser/xterm-private.d.ts | 2 - .../terminal/common/environmentVariable.ts | 7 +- .../terminal/common/remoteTerminalChannel.ts | 98 +- .../contrib/terminal/common/terminal.ts | 97 +- .../terminal/common/terminalConfiguration.ts | 182 +- .../terminal/common/terminalEnvironment.ts | 89 +- .../common/terminalExtensionPoints.ts | 17 +- .../terminal/common/terminalStrings.ts | 4 +- .../electron-browser/terminal.contribution.ts | 7 +- .../terminalInstanceService.ts | 57 +- .../terminalNativeContribution.ts | 50 - .../terminal/electron-sandbox/localPty.ts | 12 +- .../electron-sandbox/localTerminalService.ts | 26 +- .../electron-sandbox/terminal.contribution.ts | 7 + .../terminalNativeContribution.ts | 74 + .../terminalProfileResolverService.ts | 50 + .../terminalRemote.ts | 24 +- .../contrib/terminal/node/terminal.ts | 4 +- .../terminal/node/terminalEnvironment.ts | 2 +- .../contrib/terminal/node/terminalProfiles.ts | 136 +- .../browser/links/terminalLinkHelpers.test.ts | 24 +- .../terminalProtocolLinkProvider.test.ts | 4 +- ...terminalValidatedLocalLinkProvider.test.ts | 4 +- .../links/terminalWordLinkProvider.test.ts | 4 +- .../browser/terminalCommandTracker.test.ts | 110 +- .../test/browser/terminalConfigHelper.test.ts | 72 +- .../browser/terminalProcessManager.test.ts | 4 +- .../test/browser/terminalStatusList.test.ts | 142 + .../test/common/terminalColorRegistry.test.ts | 6 +- .../test/common/terminalDataBuffering.test.ts | 60 +- .../test/common/terminalEnvironment.test.ts | 188 +- .../test/node/terminalProfiles.test.ts | 322 +- .../hierarchalByLocation.ts | 155 +- .../explorerProjections/hierarchalByName.ts | 66 +- .../explorerProjections/hierarchalNodes.ts | 115 +- .../browser/explorerProjections/index.ts | 201 +- .../browser/explorerProjections/nodeHelper.ts | 53 +- .../contrib/testing/browser/icons.ts | 18 +- .../contrib/testing/browser/media/testing.css | 11 +- .../testing/browser/testExplorerActions.ts | 130 +- .../testing/browser/testing.contribution.ts | 48 +- .../testing/browser/testingDecorations.ts | 57 +- .../testing/browser/testingExplorerFilter.ts | 36 +- .../testing/browser/testingExplorerView.ts | 535 +- .../testing/browser/testingOutputPeek.ts | 87 +- .../browser/testingOutputTerminalService.ts | 204 + .../browser/testingProgressUiService.ts | 25 +- .../browser/testingViewPaneContainer.ts | 6 +- .../contrib/testing/browser/theme.ts | 22 +- .../contrib/testing/common/configuration.ts | 11 +- .../contrib/testing/common/constants.ts | 18 +- .../testing/common/getComputedState.ts | 16 +- .../contrib/testing/common/observableValue.ts | 16 +- .../testing/common/ownedTestCollection.ts | 151 +- .../contrib/testing/common/testCollection.ts | 100 +- .../contrib/testing/common/testResult.ts | 596 + .../testing/common/testResultService.ts | 550 +- .../testing/common/testResultStorage.ts | 283 + .../contrib/testing/common/testService.ts | 6 +- .../contrib/testing/common/testServiceImpl.ts | 71 +- .../contrib/testing/common/testStubs.ts | 47 +- .../contrib/testing/common/testingAutoRun.ts | 17 +- .../testing/common/testingContentProvider.ts | 6 +- .../contrib/testing/common/testingProgress.ts | 58 - .../contrib/testing/common/testingStates.ts | 53 +- .../contrib/testing/common/testingUri.ts | 22 +- .../hierarchalByLocation.test.ts | 8 +- .../hierarchalByName.test.ts | 20 +- .../testing/test/browser/testObjectTree.ts | 22 +- .../test/common/ownedTestCollection.ts | 2 +- .../test/common/testResultService.test.ts | 175 +- .../test/common/testResultStorage.test.ts | 96 + .../testing/test/common/testingUri.test.ts | 6 +- .../themes/browser/themes.contribution.ts | 8 +- .../timeline/browser/timeline.contribution.ts | 2 +- .../contrib/timeline/browser/timelinePane.ts | 26 +- .../update/browser/releaseNotesEditor.ts | 141 +- .../contrib/update/browser/update.ts | 20 +- .../contrib/url/browser/trustedDomains.ts | 8 +- .../trustedDomainsFileSystemProvider.ts | 9 +- .../url/browser/trustedDomainsValidator.ts | 4 - .../url/test/browser/trustedDomains.test.ts | 2 +- .../userDataSync/browser/userDataSync.ts | 8 +- .../browser/userDataSyncMergesView.ts | 6 +- .../userDataSync/browser/userDataSyncViews.ts | 4 +- .../contrib/watermark/browser/watermark.ts | 15 +- .../webview/browser/baseWebviewElement.ts | 229 +- .../browser/dynamicWebviewEditorOverlay.ts | 46 +- .../contrib/webview/browser/pre/host.js | 266 +- .../contrib/webview/browser/pre/index.html | 3 +- .../contrib/webview/browser/pre/main.js | 1180 +- .../webview/browser/pre/service-worker.js | 27 +- .../webview/browser/resourceLoading.ts} | 128 +- .../contrib/webview/browser/webview.ts | 18 +- .../contrib/webview/browser/webviewElement.ts | 236 +- .../webview/browser/webviewFindWidget.ts | 2 +- .../contrib/webview/browser/webviewService.ts | 20 +- .../electron-browser/pre/electron-index.js | 49 +- .../webview/electron-browser/pre/index.html | 43 +- .../electron-browser/webviewCommands.ts | 17 +- .../electron-browser/webviewElement.ts | 144 +- .../electron-browser/webviewService.ts | 7 +- .../electron-sandbox/iframeWebviewElement.ts | 64 +- .../electron-sandbox/resourceLoading.ts | 178 - .../webviewPanel/browser/webviewEditor.ts | 16 +- .../browser/webviewEditorInput.ts | 20 +- ...ory.ts => webviewEditorInputSerializer.ts} | 4 +- .../browser/webviewPanel.contribution.ts | 18 +- .../browser/webviewWorkbenchService.ts | 71 +- .../webviewView/browser/webviewViewPane.ts | 12 +- .../common/viewsWelcomeExtensionPoint.ts | 1 + .../browser/gettingStarted.contribution.ts | 198 +- .../gettingStarted/browser/gettingStarted.css | 362 +- .../gettingStarted/browser/gettingStarted.ts | 1068 +- .../browser/gettingStartedExtensionPoint.ts | 184 + .../browser/gettingStartedIcons.ts | 4 +- .../browser/gettingStartedInput.ts | 19 +- .../browser/gettingStartedService.ts | 541 +- .../common/gettingStartedContent.ts | 290 +- .../common/media/dark/debug.png | Bin 0 -> 167788 bytes .../common/media/dark/playground.png | Bin 0 -> 47530 bytes .../gettingStarted/common/media/dark/scm.png | Bin 0 -> 45247 bytes .../common/media/dark/shortcuts.png | Bin 0 -> 29589 bytes .../common/media/dark/splitview.png | Bin 0 -> 41860 bytes .../common/media/dark/tasks.png | Bin 0 -> 11001 bytes .../common/media/light/debug.png | Bin 0 -> 171717 bytes .../common/media/light/playground.png | Bin 0 -> 35846 bytes .../gettingStarted/common/media/light/scm.png | Bin 0 -> 52177 bytes .../common/media/light/shortcuts.png | Bin 0 -> 33089 bytes .../common/media/light/splitview.png | Bin 0 -> 49154 bytes .../common/media/light/tasks.png | Bin 0 -> 12232 bytes .../welcome/overlay/browser/welcomeOverlay.ts | 4 +- .../page/browser/welcomePage.contribution.ts | 6 +- .../welcome/page/browser/welcomePage.ts | 18 +- .../browser/editor/editorWalkThrough.ts | 6 +- .../browser/walkThrough.contribution.ts | 8 +- .../walkThrough/browser/walkThroughInput.ts | 20 +- .../walkThrough/browser/walkThroughPart.ts | 19 +- .../browser/walkthroughs.contribution.ts | 129 - .../walkthroughs/browser/walkthroughs.css | 348 - .../walkthroughs/browser/walkthroughs.ts | 721 - .../workspace/browser/media/trusted-badge.png | Bin 0 -> 4330 bytes .../browser/media/untrusted-status.png | Bin 0 -> 1361 bytes .../browser/workspace.contribution.ts | 603 +- .../workspace/browser/workspaceTrustColors.ts | 5 +- .../browser/workspaceTrustEditor.css | 256 +- .../workspace/browser/workspaceTrustEditor.ts | 490 +- .../workspace/browser/workspaceTrustTree.ts | 260 +- .../electron-browser/desktop.main.ts | 20 +- .../electron-sandbox/actions/windowActions.ts | 10 +- .../electron-sandbox/desktop.contribution.ts | 13 +- .../electron-sandbox/desktop.main.ts | 14 +- .../parts/dialogs/dialog.contribution.ts | 6 +- .../parts/titlebar/menubarControl.ts | 2 +- .../parts/titlebar/titlebarPart.ts | 24 +- .../sandbox.simpleservices.ts | 116 +- .../electron-sandbox/shared.desktop.main.ts | 85 +- src/vs/workbench/electron-sandbox/window.ts | 14 +- .../electron-sandbox/accessibilityService.ts | 6 +- .../activity/browser/activityService.ts | 2 +- .../services/activity/common/activity.ts | 2 +- .../services/backup/common/backup.ts | 78 - .../backup/common/backupFileService.ts | 511 - .../backupFileService.test.ts | 722 - .../clipboard/browser/clipboardService.ts | 2 +- .../test/common/commandService.test.ts | 26 +- .../configuration/browser/configuration.ts | 197 +- .../browser/configurationCache.ts | 2 +- .../browser/configurationService.ts | 473 +- .../configuration/common/configuration.ts | 35 +- .../common/configurationModels.ts | 29 +- .../configurationEditingService.test.ts | 74 +- .../test/browser/configurationService.test.ts | 744 +- .../test/common/configurationModels.test.ts | 66 +- .../browser/configurationResolverService.ts | 20 +- .../common/configurationResolver.ts | 11 +- .../common/variableResolver.ts | 77 +- .../configurationResolverService.ts | 7 +- .../configurationResolverService.test.ts | 213 +- .../decorations/browser/decorations.ts | 4 +- .../decorations/browser/decorationsService.ts | 57 +- .../test/browser/decorationsService.test.ts | 50 +- .../browser/abstractFileDialogService.ts | 8 +- .../dialogs/browser/fileDialogService.ts | 82 +- .../dialogs/browser/simpleFileDialog.ts | 77 +- .../electron-sandbox/fileDialogService.ts | 2 - .../editor/browser/editorOverrideService.ts | 564 + .../services/editor/browser/editorService.ts | 523 +- .../editor/common/editorGroupsService.ts | 96 +- .../editor/common/editorOverrideService.ts | 266 + .../services/editor/common/editorService.ts | 51 +- .../test/browser/editorGroupsService.test.ts | 193 +- .../editor/test/browser/editorService.test.ts | 251 +- .../test/browser/editorsObserver.test.ts | 323 +- .../environment/browser/environmentService.ts | 22 +- .../environment/common/environmentService.ts | 2 - .../electron-sandbox/environmentService.ts | 29 +- .../shellEnvironmentService.ts | 2 +- .../builtinExtensionsScannerService.ts | 3 +- .../browser/extensionEnablementService.ts | 99 +- .../common/extensionManagement.ts | 11 +- .../extensionManagementServerService.ts | 4 +- .../common/extensionManagementService.ts | 35 +- .../remoteExtensionManagementService.ts | 13 +- .../common/webExtensionsScannerService.ts | 35 +- .../extensionManagementService.ts | 10 +- .../electron-sandbox/extensionTipsService.ts | 10 +- .../remoteExtensionManagementService.ts | 12 +- .../extensionEnablementService.test.ts | 181 +- .../extensions/browser/extensionService.ts | 46 +- .../browser/webWorkerExtensionHost.ts | 2 +- .../common/abstractExtensionService.ts | 142 +- .../extensions/common/extensionHostManager.ts | 4 +- .../extensionManifestPropertiesService.ts | 312 + .../services/extensions/common/extensions.ts | 33 +- .../extensions/common/extensionsRegistry.ts | 42 + .../extensions/common/extensionsUtil.ts | 3 +- .../extensions/common/remoteExtensionHost.ts | 4 +- .../services/extensions/common/rpcProtocol.ts | 2 +- .../electron-browser/extensionService.ts | 99 +- .../localProcessExtensionHost.ts | 2 +- .../node/extensionHostProcessSetup.ts | 2 +- .../services/extensions/node/proxyResolver.ts | 6 +- .../test/browser/extensionService.test.ts | 130 +- ...extensionManifestPropertiesService.test.ts | 155 + .../test/common/extensionsUtil.test.ts | 43 - .../files/browser/elevatedFileService.ts | 28 + .../files/common/elevatedFileService.ts | 27 + .../electron-sandbox/elevatedFileService.ts | 52 + .../services/history/browser/history.ts | 52 +- .../services/history/common/history.ts | 2 +- .../history/test/browser/history.test.ts | 35 +- .../host/browser/browserHostService.ts | 27 +- .../workbench/services/hover/browser/hover.ts | 24 +- .../services/hover/browser/hoverService.ts | 4 + .../services/hover/browser/hoverWidget.ts | 247 +- .../services/hover/browser/media/hover.css | 57 + .../keybinding/browser/keybindingService.ts | 19 +- .../browser/keyboardLayoutService.ts | 2 +- .../common/macLinuxFallbackKeyboardMapper.ts | 2 +- .../common/macLinuxKeyboardMapper.ts | 26 +- .../common/windowsKeyboardMapper.ts | 6 +- .../test/browser/keybindingEditing.test.ts | 59 +- .../services/label/common/labelService.ts | 2 +- .../services/label/test/browser/label.test.ts | 34 +- .../services/layout/browser/layoutService.ts | 7 +- .../lifecycle/browser/lifecycleService.ts | 10 +- .../services/lifecycle/common/lifecycle.ts | 29 +- .../lifecycle/common/lifecycleService.ts | 4 +- .../electron-sandbox/lifecycleService.ts | 8 +- .../log/electron-sandbox/logService.ts | 8 +- .../mode/common/workbenchModeService.ts | 2 +- .../services/path/browser/pathService.ts | 2 +- .../browser/keybindingsEditorModel.ts | 3 +- .../browser/preferencesEditorInput.ts | 32 +- .../preferences/browser/preferencesService.ts | 26 +- .../preferences/common/preferences.ts | 3 +- .../preferences/common/preferencesModels.ts | 17 +- .../browser/keybindingsEditorModel.test.ts | 218 +- .../test/browser/preferencesService.test.ts | 2 +- .../test/common/preferencesValidation.test.ts | 8 +- .../progress/browser/progressIndicator.ts | 8 +- .../progress/browser/progressService.ts | 16 +- .../test/browser/progressIndicator.test.ts | 4 - .../quickinput/browser/quickInputService.ts | 4 +- .../remote/browser/tunnelServiceImpl.ts | 2 +- .../common/remoteAgentEnvironmentChannel.ts | 6 +- .../remote/common/remoteExplorerService.ts | 8 +- .../electron-browser/tunnelServiceImpl.ts | 2 +- .../request/browser/requestService.ts | 2 +- .../electron-sandbox/requestService.ts | 2 +- .../search/common/fileSearchManager.ts | 105 +- .../services/search/common/search.ts | 6 + .../services/search/common/searchExtTypes.ts | 17 + .../services/search/common/searchService.ts | 6 +- .../search/common/textSearchManager.ts | 20 +- .../search/electron-browser/searchService.ts | 3 +- .../services/search/node/fileSearch.ts | 11 +- .../services/search/node/rawSearchService.ts | 1 + .../search/node/ripgrepTextSearchEngine.ts | 6 +- .../search/test/common/replace.test.ts | 86 +- .../search/test/common/search.test.ts | 40 +- .../search/test/common/searchHelpers.test.ts | 20 +- .../electron-browser/rawSearchService.test.ts | 6 +- .../test/node/fileSearch.integrationTest.ts | 2 +- .../test/node/ripgrepFileSearch.test.ts | 2 +- .../test/node/ripgrepTextSearchEngine.test.ts | 34 +- .../services/search/test/node/search.test.ts | 94 +- .../test/node/textSearch.integrationTest.ts | 42 +- .../services/statusbar/common/statusbar.ts | 5 + .../electron-sandbox/telemetryService.ts | 2 +- .../workbenchCommonProperties.ts | 3 +- .../test/browser/commonProperties.test.ts | 6 +- .../electron-browser/commonProperties.test.ts | 14 +- .../electron-sandbox/textMateService.ts | 8 +- .../electron-sandbox/textMateWorker.ts | 4 +- .../browser/browserTextFileService.ts | 2 +- .../textfile/browser/textFileService.ts | 60 +- .../textfile/common/textFileEditorModel.ts | 173 +- .../common/textFileEditorModelManager.ts | 94 +- .../common/textFileSaveParticipant.ts | 2 +- .../services/textfile/common/textfiles.ts | 204 +- .../electron-sandbox/nativeTextFileService.ts | 55 +- .../browser/browserTextFileService.io.test.ts | 4 +- .../test/browser/textFileEditorModel.test.ts | 159 +- .../textFileEditorModelManager.test.ts | 12 +- .../test/browser/textFileService.test.ts | 14 +- .../test/common/textFileService.io.test.ts | 22 +- .../nativeTextFileService.io.test.ts | 8 +- .../nativeTextFileService.test.ts | 6 +- .../common/textModelResolverService.ts | 4 +- .../browser/textModelResolverService.test.ts | 22 +- .../themes/browser/workbenchThemeService.ts | 6 +- .../themes/common/themeConfiguration.ts | 4 +- .../tokenStyleResolving.test.ts | 17 +- .../services/timer/browser/timerService.ts | 94 +- .../timer/electron-sandbox/timerService.ts | 4 +- .../common/untitledTextEditorInput.ts | 43 +- .../common/untitledTextEditorModel.ts | 100 +- .../common/untitledTextEditorService.ts | 26 +- .../test/browser/untitledTextEditor.test.ts | 93 +- .../services/update/browser/updateService.ts | 12 +- .../test/common/uriIdentityService.test.ts | 18 +- .../services/url/browser/urlService.ts | 2 - .../url/electron-sandbox/urlService.ts | 4 +- .../services/userData/browser/userDataInit.ts | 198 +- .../test/browser/fileUserDataProvider.test.ts | 72 +- .../userDataAutoSyncEnablementService.ts | 6 +- .../userDataSyncResourceEnablementService.ts | 2 +- .../test/browser/viewContainerModel.test.ts | 228 +- .../browser/viewDescriptorService.test.ts | 48 +- .../browser/workingCopyBackupService.ts} | 18 +- .../browser/workingCopyBackupTracker.ts} | 18 +- .../workingCopy/common/fileWorkingCopy.ts | 318 +- .../common/fileWorkingCopyManager.ts | 30 +- .../common/legacyBackupRestorer.ts | 136 + .../workingCopy/common/workingCopy.ts | 174 + .../workingCopy/common/workingCopyBackup.ts | 83 + .../common/workingCopyBackupService.ts | 596 + .../common/workingCopyBackupTracker.ts} | 187 +- .../common/workingCopyEditorService.ts | 99 + .../workingCopyFileOperationParticipant.ts | 6 +- .../common/workingCopyFileService.ts | 59 +- .../workingCopy/common/workingCopyService.ts | 213 +- .../workingCopyBackupService.ts} | 16 +- .../workingCopyBackupTracker.ts} | 103 +- .../test/browser/fileWorkingCopy.test.ts | 52 +- .../browser/fileWorkingCopyManager.test.ts | 69 +- .../browser/legacyBackupRestorer.test.ts} | 59 +- .../browser/workingCopyBackupTracker.test.ts | 386 + .../browser/workingCopyEditorService.test.ts | 82 + .../browser/workingCopyFileService.test.ts | 84 +- .../test/common/workingCopyService.test.ts | 74 +- .../test/electron-browser/fixtures/binary.txt | Bin 0 -> 274 bytes .../workingCopyBackupService.test.ts | 1186 + .../workingCopyBackupTracker.test.ts} | 139 +- .../abstractWorkspaceEditingService.ts | 15 +- .../browser/workspaceEditingService.ts | 8 +- .../browser/workspaceTrustEditorInput.ts | 24 +- .../workspaces/common/workspaceTrust.ts | 563 +- .../workspaceEditingService.ts | 24 +- .../test/browser/workspaces.test.ts | 28 +- .../test/common/testWorkspaceTrustService.ts | 84 +- .../test/browser/api/extHost.api.impl.test.ts | 8 +- .../browser/api/extHostApiCommands.test.ts | 48 +- .../test/browser/api/extHostBulkEdits.test.ts | 2 +- .../test/browser/api/extHostCommands.test.ts | 12 +- .../browser/api/extHostConfiguration.test.ts | 17 +- .../browser/api/extHostDecorations.test.ts | 4 +- .../browser/api/extHostDiagnostics.test.ts | 14 +- .../browser/api/extHostDocumentData.test.ts | 10 +- .../extHostDocumentSaveParticipant.test.ts | 2 +- .../api/extHostLanguageFeatures.test.ts | 14 +- .../api/extHostMessagerService.test.ts | 6 +- .../test/browser/api/extHostNotebook.test.ts | 58 +- .../api/extHostNotebookConcatDocument.test.ts | 150 +- .../browser/api/extHostNotebookKernel.test.ts | 109 - .../api/extHostNotebookKernel2.test.ts | 96 + .../test/browser/api/extHostTesting.test.ts | 495 +- .../browser/api/extHostTextEditor.test.ts | 2 +- .../test/browser/api/extHostTreeViews.test.ts | 6 +- .../test/browser/api/extHostTypes.test.ts | 44 +- .../test/browser/api/extHostWorkspace.test.ts | 27 +- .../browser/api/mainThreadCommands.test.ts | 4 +- .../browser/api/mainThreadDiagnostics.test.ts | 2 +- ...mainThreadDocumentContentProviders.test.ts | 4 +- .../api/mainThreadDocumentsAndEditors.test.ts | 21 +- .../browser/api/mainThreadEditors.test.ts | 38 +- .../browser/api/mainThreadTreeViews.test.ts | 6 +- .../workbench/test/browser/codeeditor.test.ts | 46 +- src/vs/workbench/test/browser/part.test.ts | 18 +- .../parts/editor/breadcrumbModel.test.ts | 42 +- .../test/browser/parts/editor/editor.test.ts | 77 +- ...roups.test.ts => editorGroupModel.test.ts} | 158 +- .../browser/parts/editor/editorInput.test.ts | 14 +- .../browser/parts/editor/editorModel.test.ts | 18 +- .../browser/parts/editor/editorPane.test.ts | 102 +- .../parts/editor/resourceEditorInput.test.ts | 4 +- src/vs/workbench/test/browser/viewlet.test.ts | 2 +- .../test/browser/workbenchTestServices.ts | 304 +- .../test/common/notifications.test.ts | 6 + .../test/common/workbenchTestServices.ts | 36 +- .../api/extHostSearch.test.ts | 70 +- .../api/mainThreadWorkspace.test.ts | 26 +- .../colorRegistry.releaseTest.ts | 4 +- .../textsearch.perf.integrationTest.ts | 8 +- .../electron-browser/workbenchTestServices.ts | 38 +- src/vs/workbench/workbench.common.main.ts | 11 +- src/vs/workbench/workbench.desktop.main.ts | 6 - src/vs/workbench/workbench.sandbox.main.ts | 9 +- src/vs/workbench/workbench.web.api.ts | 44 +- src/vs/workbench/workbench.web.main.ts | 6 +- test/automation/package.json | 6 +- test/automation/yarn.lock | 1387 +- test/integration/browser/package.json | 2 +- test/integration/browser/yarn.lock | 8 +- test/monaco/monaco.test.ts | 8 +- test/smoke/package.json | 5 +- test/smoke/yarn.lock | 1019 +- test/unit/electron/index.js | 82 +- test/unit/electron/renderer.js | 37 +- yarn.lock | 673 +- 1749 files changed, 87866 insertions(+), 43771 deletions(-) create mode 100644 extensions/configuration-editing/images/icon.png create mode 100644 extensions/cpp/syntaxes/cuda-cpp.tmLanguage.json create mode 100644 extensions/debug-auto-launch/media/icon.png create mode 100644 extensions/debug-server-ready/media/icon.png delete mode 100644 extensions/emmet/src/test/test-fixtures/marker.txt rename extensions/emmet/{src/test/test-fixtures => test-workspace}/.vscode/settings.json (100%) create mode 100644 extensions/extension-editing/images/icon.png delete mode 100644 extensions/git-ui/.vscodeignore delete mode 100644 extensions/git-ui/README.md delete mode 100644 extensions/git-ui/cgmanifest.json delete mode 100644 extensions/git-ui/extension.webpack.config.js delete mode 100644 extensions/git-ui/package.json delete mode 100644 extensions/git-ui/package.nls.json delete mode 100644 extensions/git-ui/resources/icons/git.png delete mode 100644 extensions/git-ui/src/main.ts delete mode 100644 extensions/git-ui/src/typings/refs.d.ts delete mode 100644 extensions/git-ui/tsconfig.json delete mode 100644 extensions/git-ui/yarn.lock create mode 100644 extensions/github-authentication/images/icon.png create mode 100644 extensions/github-authentication/src/experimentationService.ts create mode 100644 extensions/github/images/icon.png create mode 100644 extensions/markdown-language-features/esbuild.js create mode 100644 extensions/markdown-language-features/notebook/tsconfig.json delete mode 100644 extensions/markdown-language-features/webpack.notebook.js create mode 100644 extensions/merge-conflict/media/icon.png delete mode 100644 extensions/merge-conflict/resources/icons/merge-conflict.png create mode 100644 extensions/microsoft-authentication/media/icon.png create mode 100644 extensions/notebook-markdown-extensions/esbuild.js delete mode 100644 extensions/notebook-markdown-extensions/webpack.notebook.js create mode 100644 extensions/simple-browser/media/icon.png create mode 100644 extensions/testing-editor-contributions/media/icon.png rename extensions/{shared.tsconfig.json => tsconfig.base.json} (90%) delete mode 100644 extensions/typescript-language-features/icon.png create mode 100644 extensions/typescript-language-features/media/icon.png create mode 100644 extensions/vscode-api-tests/media/icon.png create mode 100644 extensions/vscode-colorize-tests/media/icon.png delete mode 100644 extensions/vscode-colorize-tests/test/colorize-fixtures/test-92369.cpp create mode 100644 extensions/vscode-colorize-tests/test/colorize-fixtures/test.cu delete mode 100644 extensions/vscode-colorize-tests/test/colorize-results/test-92369_cpp.json create mode 100644 extensions/vscode-colorize-tests/test/colorize-results/test_cu.json create mode 100644 extensions/vscode-custom-editor-tests/media/icon.png create mode 100644 extensions/vscode-notebook-tests/media/icon.png create mode 100644 extensions/vscode-test-resolver/media/icon.png create mode 100644 src/vs/base/browser/ui/dropdown/dropdownWithPrimaryActionViewItem.ts create mode 100644 src/vs/base/common/product.ts create mode 100644 src/vs/base/parts/sandbox/common/sandboxTypes.ts create mode 100644 src/vs/base/test/common/codicons.test.ts delete mode 100644 src/vs/code/browser/workbench/workbench-web.html create mode 100644 src/vs/platform/driver/common/driverIpc.ts rename src/vs/platform/driver/{electron-browser => electron-sandbox}/driver.ts (98%) create mode 100644 src/vs/platform/files/browser/htmlFileSystemProvider.ts create mode 100644 src/vs/platform/protocol/electron-main/protocol.ts rename src/vs/{code/electron-main/protocol.ts => platform/protocol/electron-main/protocolMainService.ts} (69%) delete mode 100644 src/vs/platform/webview/electron-main/webviewPortMappingProvider.ts create mode 100644 src/vs/workbench/api/browser/apiCommands.ts create mode 100644 src/vs/workbench/api/browser/mainThreadNotebookDocuments.ts create mode 100644 src/vs/workbench/api/browser/mainThreadNotebookDocumentsAndEditors.ts create mode 100644 src/vs/workbench/api/browser/mainThreadNotebookEditors.ts create mode 100644 src/vs/workbench/api/browser/mainThreadNotebookKernels.ts create mode 100644 src/vs/workbench/api/common/extHostNotebookKernels.ts create mode 100644 src/vs/workbench/api/common/extHostTestingPrivateApi.ts create mode 100644 src/vs/workbench/api/common/extHostWebviewMessaging.ts rename src/vs/workbench/common/editor/{editorGroup.ts => editorGroupModel.ts} (90%) delete mode 100644 src/vs/workbench/contrib/backup/browser/backup.web.contribution.ts delete mode 100644 src/vs/workbench/contrib/backup/common/backup.contribution.ts delete mode 100644 src/vs/workbench/contrib/backup/common/backupRestorer.ts delete mode 100644 src/vs/workbench/contrib/backup/electron-sandbox/backup.contribution.ts delete mode 100644 src/vs/workbench/contrib/backup/test/browser/backupTracker.test.ts create mode 100644 src/vs/workbench/contrib/extensions/browser/extensionEnablementByWorkspaceTrustRequirement.ts create mode 100644 src/vs/workbench/contrib/notebook/browser/contrib/clipboard/test/notebookClipboard.test.ts create mode 100644 src/vs/workbench/contrib/notebook/browser/contrib/layout/layoutActions.ts create mode 100644 src/vs/workbench/contrib/notebook/browser/contrib/layout/test/layoutActions.test.ts create mode 100644 src/vs/workbench/contrib/notebook/browser/contrib/navigation/arrow.ts create mode 100644 src/vs/workbench/contrib/notebook/browser/contrib/statusBar/cellStatusBar.ts create mode 100644 src/vs/workbench/contrib/notebook/browser/contrib/statusBar/contributedStatusBarItemController.ts create mode 100644 src/vs/workbench/contrib/notebook/browser/contrib/statusBar/executionStatusBarItemController.ts create mode 100644 src/vs/workbench/contrib/notebook/browser/contrib/statusBar/notebookVisibleCellObserver.ts create mode 100644 src/vs/workbench/contrib/notebook/browser/contrib/statusBar/statusBarProviders.ts create mode 100644 src/vs/workbench/contrib/notebook/browser/contrib/troubleshoot/layout.ts create mode 100644 src/vs/workbench/contrib/notebook/browser/contrib/undoRedo/test/notebookUndoRedo.test.ts create mode 100644 src/vs/workbench/contrib/notebook/browser/notebookEditorWidgetContextKeys.ts delete mode 100644 src/vs/workbench/contrib/notebook/browser/notebookKernelAssociation.ts create mode 100644 src/vs/workbench/contrib/notebook/browser/notebookKernelServiceImpl.ts create mode 100644 src/vs/workbench/contrib/notebook/browser/view/renderers/cellEditorOptions.ts rename src/vs/workbench/contrib/notebook/{browser => common}/notebookEditorInput.ts (81%) create mode 100644 src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverServiceImpl.ts create mode 100644 src/vs/workbench/contrib/notebook/common/notebookKernelService.ts create mode 100644 src/vs/workbench/contrib/notebook/common/notebookPerformance.ts create mode 100644 src/vs/workbench/contrib/notebook/common/notebookRange.ts create mode 100644 src/vs/workbench/contrib/notebook/common/notebookSelector.ts delete mode 100644 src/vs/workbench/contrib/notebook/electron-browser/notebook.contribution.ts create mode 100644 src/vs/workbench/contrib/notebook/test/notebookDiff.test.ts create mode 100644 src/vs/workbench/contrib/notebook/test/notebookKernelService.test.ts rename src/vs/workbench/contrib/tasks/{electron-browser => electron-sandbox}/taskService.ts (68%) delete mode 100644 src/vs/workbench/contrib/tasks/node/processRunnerDetector.ts delete mode 100644 src/vs/workbench/contrib/tasks/node/processTaskSystem.ts create mode 100644 src/vs/workbench/contrib/terminal/browser/terminalDecorationsProvider.ts create mode 100644 src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts create mode 100644 src/vs/workbench/contrib/terminal/browser/terminalStatusList.ts create mode 100644 src/vs/workbench/contrib/terminal/browser/terminalTabbedView.ts create mode 100644 src/vs/workbench/contrib/terminal/browser/terminalTabsWidget.ts create mode 100644 src/vs/workbench/contrib/terminal/electron-sandbox/terminalNativeContribution.ts create mode 100644 src/vs/workbench/contrib/terminal/electron-sandbox/terminalProfileResolverService.ts rename src/vs/workbench/contrib/terminal/{electron-browser => electron-sandbox}/terminalRemote.ts (64%) create mode 100644 src/vs/workbench/contrib/terminal/test/browser/terminalStatusList.test.ts create mode 100644 src/vs/workbench/contrib/testing/browser/testingOutputTerminalService.ts create mode 100644 src/vs/workbench/contrib/testing/common/testResult.ts create mode 100644 src/vs/workbench/contrib/testing/common/testResultStorage.ts delete mode 100644 src/vs/workbench/contrib/testing/common/testingProgress.ts create mode 100644 src/vs/workbench/contrib/testing/test/common/testResultStorage.test.ts rename src/vs/{platform/webview/common/resourceLoader.ts => workbench/contrib/webview/browser/resourceLoading.ts} (58%) delete mode 100644 src/vs/workbench/contrib/webview/electron-sandbox/resourceLoading.ts rename src/vs/workbench/contrib/webviewPanel/browser/{webviewEditorInputFactory.ts => webviewEditorInputSerializer.ts} (97%) create mode 100644 src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStartedExtensionPoint.ts create mode 100644 src/vs/workbench/contrib/welcome/gettingStarted/common/media/dark/debug.png create mode 100644 src/vs/workbench/contrib/welcome/gettingStarted/common/media/dark/playground.png create mode 100644 src/vs/workbench/contrib/welcome/gettingStarted/common/media/dark/scm.png create mode 100644 src/vs/workbench/contrib/welcome/gettingStarted/common/media/dark/shortcuts.png create mode 100644 src/vs/workbench/contrib/welcome/gettingStarted/common/media/dark/splitview.png create mode 100644 src/vs/workbench/contrib/welcome/gettingStarted/common/media/dark/tasks.png create mode 100644 src/vs/workbench/contrib/welcome/gettingStarted/common/media/light/debug.png create mode 100644 src/vs/workbench/contrib/welcome/gettingStarted/common/media/light/playground.png create mode 100644 src/vs/workbench/contrib/welcome/gettingStarted/common/media/light/scm.png create mode 100644 src/vs/workbench/contrib/welcome/gettingStarted/common/media/light/shortcuts.png create mode 100644 src/vs/workbench/contrib/welcome/gettingStarted/common/media/light/splitview.png create mode 100644 src/vs/workbench/contrib/welcome/gettingStarted/common/media/light/tasks.png delete mode 100644 src/vs/workbench/contrib/welcome/walkthroughs/browser/walkthroughs.contribution.ts delete mode 100644 src/vs/workbench/contrib/welcome/walkthroughs/browser/walkthroughs.css delete mode 100644 src/vs/workbench/contrib/welcome/walkthroughs/browser/walkthroughs.ts create mode 100644 src/vs/workbench/contrib/workspace/browser/media/trusted-badge.png create mode 100644 src/vs/workbench/contrib/workspace/browser/media/untrusted-status.png delete mode 100644 src/vs/workbench/services/backup/common/backup.ts delete mode 100644 src/vs/workbench/services/backup/common/backupFileService.ts delete mode 100644 src/vs/workbench/services/backup/test/electron-browser/backupFileService.test.ts create mode 100644 src/vs/workbench/services/editor/browser/editorOverrideService.ts create mode 100644 src/vs/workbench/services/editor/common/editorOverrideService.ts create mode 100644 src/vs/workbench/services/extensions/common/extensionManifestPropertiesService.ts create mode 100644 src/vs/workbench/services/extensions/test/common/extensionManifestPropertiesService.test.ts delete mode 100644 src/vs/workbench/services/extensions/test/common/extensionsUtil.test.ts create mode 100644 src/vs/workbench/services/files/browser/elevatedFileService.ts create mode 100644 src/vs/workbench/services/files/common/elevatedFileService.ts create mode 100644 src/vs/workbench/services/files/electron-sandbox/elevatedFileService.ts rename src/vs/workbench/services/{backup/browser/backupFileService.ts => workingCopy/browser/workingCopyBackupService.ts} (55%) rename src/vs/workbench/{contrib/backup/browser/backupTracker.ts => services/workingCopy/browser/workingCopyBackupTracker.ts} (63%) create mode 100644 src/vs/workbench/services/workingCopy/common/legacyBackupRestorer.ts create mode 100644 src/vs/workbench/services/workingCopy/common/workingCopy.ts create mode 100644 src/vs/workbench/services/workingCopy/common/workingCopyBackup.ts create mode 100644 src/vs/workbench/services/workingCopy/common/workingCopyBackupService.ts rename src/vs/workbench/{contrib/backup/common/backupTracker.ts => services/workingCopy/common/workingCopyBackupTracker.ts} (54%) create mode 100644 src/vs/workbench/services/workingCopy/common/workingCopyEditorService.ts rename src/vs/workbench/services/{backup/electron-sandbox/backupFileService.ts => workingCopy/electron-sandbox/workingCopyBackupService.ts} (53%) rename src/vs/workbench/{contrib/backup/electron-sandbox/backupTracker.ts => services/workingCopy/electron-sandbox/workingCopyBackupTracker.ts} (76%) rename src/vs/workbench/{contrib/backup/test/browser/backupRestorer.test.ts => services/workingCopy/test/browser/legacyBackupRestorer.test.ts} (58%) create mode 100644 src/vs/workbench/services/workingCopy/test/browser/workingCopyBackupTracker.test.ts create mode 100644 src/vs/workbench/services/workingCopy/test/browser/workingCopyEditorService.test.ts create mode 100644 src/vs/workbench/services/workingCopy/test/electron-browser/fixtures/binary.txt create mode 100644 src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyBackupService.test.ts rename src/vs/workbench/{contrib/backup/test/electron-browser/backupTracker.test.ts => services/workingCopy/test/electron-browser/workingCopyBackupTracker.test.ts} (78%) delete mode 100644 src/vs/workbench/test/browser/api/extHostNotebookKernel.test.ts create mode 100644 src/vs/workbench/test/browser/api/extHostNotebookKernel2.test.ts rename src/vs/workbench/test/browser/parts/editor/{editorGroups.test.ts => editorGroupModel.test.ts} (93%) diff --git a/.eslintrc.json b/.eslintrc.json index 47731490..fb2e489f 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1010,6 +1010,7 @@ "edit", "end", "expand", + "grant", "hide", "invalidate", "open", diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index de7ad30b..b40ba5dd 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -20,4 +20,4 @@ Steps to Reproduce: Does this issue occur when all extensions are disabled?: Yes/No - + diff --git a/.github/classifier.json b/.github/classifier.json index 96b58771..5adabb9a 100644 --- a/.github/classifier.json +++ b/.github/classifier.json @@ -20,8 +20,8 @@ "context-keys": {"assign": []}, "css-less-scss": {"assign": ["aeschli"]}, "custom-editors": {"assign": ["mjbvz"]}, - "debug": {"assign": ["weinand"]}, - "debug-console": {"assign": ["weinand"]}, + "debug": {"assign": ["isidorn"]}, + "debug-console": {"assign": ["isidorn"]}, "dialogs": {"assign": ["sbatten"]}, "diff-editor": {"assign": []}, "dropdown": {"assign": []}, diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f1324b59..d08dac3c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -109,8 +109,8 @@ jobs: uses: actions/cache@v2 with: path: "**/node_modules" - key: ${{ runner.os }}-cacheNodeModules11-${{ steps.nodeModulesCacheKey.outputs.value }} - restore-keys: ${{ runner.os }}-cacheNodeModules11- + key: ${{ runner.os }}-cacheNodeModules13-${{ steps.nodeModulesCacheKey.outputs.value }} + restore-keys: ${{ runner.os }}-cacheNodeModules13- - name: Get yarn cache directory path id: yarnCacheDirPath if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }} @@ -165,8 +165,8 @@ jobs: uses: actions/cache@v2 with: path: "**/node_modules" - key: ${{ runner.os }}-cacheNodeModules11-${{ steps.nodeModulesCacheKey.outputs.value }} - restore-keys: ${{ runner.os }}-cacheNodeModules11- + key: ${{ runner.os }}-cacheNodeModules13-${{ steps.nodeModulesCacheKey.outputs.value }} + restore-keys: ${{ runner.os }}-cacheNodeModules13- - name: Get yarn cache directory path id: yarnCacheDirPath if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }} @@ -218,8 +218,8 @@ jobs: uses: actions/cache@v2 with: path: "**/node_modules" - key: ${{ runner.os }}-cacheNodeModules11-${{ steps.nodeModulesCacheKey.outputs.value }} - restore-keys: ${{ runner.os }}-cacheNodeModules11- + key: ${{ runner.os }}-cacheNodeModules13-${{ steps.nodeModulesCacheKey.outputs.value }} + restore-keys: ${{ runner.os }}-cacheNodeModules13- - name: Get yarn cache directory path id: yarnCacheDirPath if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }} diff --git a/.github/workflows/no-yarn-lock-changes.yml b/.github/workflows/no-yarn-lock-changes.yml index ac37eaa6..ebd735bf 100644 --- a/.github/workflows/no-yarn-lock-changes.yml +++ b/.github/workflows/no-yarn-lock-changes.yml @@ -6,9 +6,25 @@ jobs: name: Prevent yarn.lock changes in PRs runs-on: ubuntu-latest steps: - - id: file_changes - uses: trilom/file-changes-action@ce38c8ce2459ca3c303415eec8cb0409857b4272 - - name: Check for yarn.lock changes + - uses: octokit/request-action@v2.x + id: get_permissions + with: + route: GET /repos/microsoft/vscode/collaborators/{username}/permission + username: ${{ github.event.pull_request.user.login }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Set control output variable + id: control run: | - cat $HOME/files.json | jq -e '.[] | test("yarn\\.lock$") | not' \ + echo "user: ${{ github.event.pull_request.user.login }}" + echo "role: ${{ fromJson(steps.get_permissions.outputs.data).permission }}" + echo "should_run: ${{ !contains(fromJson('["admin", "write"]'), fromJson(steps.get_permissions.outputs.data).permission) }}" + echo "::set-output name=should_run::${{ !contains(fromJson('["admin", "write"]'), fromJson(steps.get_permissions.outputs.data).permission) }}" + - name: Get file changes + uses: trilom/file-changes-action@ce38c8ce2459ca3c303415eec8cb0409857b4272 + if: ${{ steps.control.outputs.should_run == 'true' }} + - name: Check for yarn.lock changes + if: ${{ steps.control.outputs.should_run == 'true' }} + run: | + cat $HOME/files.json | jq -e 'any(test("yarn\\.lock$")) | not' \ || (echo "Changes to yarn.lock files aren't allowed in PRs." && exit 1) diff --git a/.github/workflows/rich-navigation.yml b/.github/workflows/rich-navigation.yml index 71824cab..07a175d9 100644 --- a/.github/workflows/rich-navigation.yml +++ b/.github/workflows/rich-navigation.yml @@ -33,4 +33,5 @@ jobs: with: languages: typescript repo-token: ${{ secrets.GITHUB_TOKEN }} + typescriptVersion: 0.6.0-next.8 continue-on-error: true diff --git a/.vscode/launch.json b/.vscode/launch.json index b1db49c2..3189af06 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -199,6 +199,13 @@ "name": "Attach to VS Code", "browserAttachLocation": "workspace", "port": 9222, + "trace": true, + "outFiles": [ + "${workspaceFolder}/out/**/*.js" + ], + "resolveSourceMapLocations": [ + "${workspaceFolder}/out/**/*.js" + ], "perScriptSourcemaps": "yes" }, { diff --git a/.vscode/notebooks/api.github-issues b/.vscode/notebooks/api.github-issues index 95ae0023..b9e25a7c 100644 --- a/.vscode/notebooks/api.github-issues +++ b/.vscode/notebooks/api.github-issues @@ -3,2507 +3,36 @@ "kind": 1, "language": "markdown", "value": "#### Config", - "editable": true, - "outputs": [] + "editable": true }, { "kind": 2, "language": "github-issues", - "value": "$repo=repo:microsoft/vscode\n$milestone=milestone:\"March 2021\"", - "editable": true, - "outputs": [] + "value": "$repo=repo:microsoft/vscode\n$milestone=milestone:\"April 2021\"", + "editable": true }, { "kind": 1, "language": "markdown", "value": "### Finalization", - "editable": true, - "outputs": [] + "editable": true }, { "kind": 2, "language": "github-issues", "value": "$repo $milestone label:api-finalization", - "editable": true, - "outputs": [ - { - "mime": "x-application/github-issues", - "value": [ - { - "url": "https://api.github.com/repos/microsoft/vscode/issues/117058", - "repository_url": "https://api.github.com/repos/microsoft/vscode", - "labels_url": "https://api.github.com/repos/microsoft/vscode/issues/117058/labels{/name}", - "comments_url": "https://api.github.com/repos/microsoft/vscode/issues/117058/comments", - "events_url": "https://api.github.com/repos/microsoft/vscode/issues/117058/events", - "html_url": "https://github.com/microsoft/vscode/issues/117058", - "id": 812164158, - "node_id": "MDU6SXNzdWU4MTIxNjQxNTg=", - "number": 117058, - "title": "add property to extension api for new install", - "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": "" - } - ], - "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": 1, - "created_at": "2021-02-19T16:33:09Z", - "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```", - "performed_via_github_app": null, - "score": 1 - }, - { - "url": "https://api.github.com/repos/microsoft/vscode/issues/116906", - "repository_url": "https://api.github.com/repos/microsoft/vscode", - "labels_url": "https://api.github.com/repos/microsoft/vscode/issues/116906/labels{/name}", - "comments_url": "https://api.github.com/repos/microsoft/vscode/issues/116906/comments", - "events_url": "https://api.github.com/repos/microsoft/vscode/issues/116906/events", - "html_url": "https://github.com/microsoft/vscode/issues/116906", - "id": 810668744, - "node_id": "MDU6SXNzdWU4MTA2Njg3NDQ=", - "number": 116906, - "title": "Add extension ID and version to ExtensionContext", - "user": { - "login": "eamodio", - "id": 641685, - "node_id": "MDQ6VXNlcjY0MTY4NQ==", - "avatar_url": "https://avatars.githubusercontent.com/u/641685?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/eamodio", - "html_url": "https://github.com/eamodio", - "followers_url": "https://api.github.com/users/eamodio/followers", - "following_url": "https://api.github.com/users/eamodio/following{/other_user}", - "gists_url": "https://api.github.com/users/eamodio/gists{/gist_id}", - "starred_url": "https://api.github.com/users/eamodio/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/eamodio/subscriptions", - "organizations_url": "https://api.github.com/users/eamodio/orgs", - "repos_url": "https://api.github.com/users/eamodio/repos", - "events_url": "https://api.github.com/users/eamodio/events{/privacy}", - "received_events_url": "https://api.github.com/users/eamodio/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": 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" - } - ], - "state": "open", - "locked": false, - "assignee": { - "login": "eamodio", - "id": 641685, - "node_id": "MDQ6VXNlcjY0MTY4NQ==", - "avatar_url": "https://avatars.githubusercontent.com/u/641685?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/eamodio", - "html_url": "https://github.com/eamodio", - "followers_url": "https://api.github.com/users/eamodio/followers", - "following_url": "https://api.github.com/users/eamodio/following{/other_user}", - "gists_url": "https://api.github.com/users/eamodio/gists{/gist_id}", - "starred_url": "https://api.github.com/users/eamodio/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/eamodio/subscriptions", - "organizations_url": "https://api.github.com/users/eamodio/orgs", - "repos_url": "https://api.github.com/users/eamodio/repos", - "events_url": "https://api.github.com/users/eamodio/events{/privacy}", - "received_events_url": "https://api.github.com/users/eamodio/received_events", - "type": "User", - "site_admin": false - }, - "assignees": [ - { - "login": "eamodio", - "id": 641685, - "node_id": "MDQ6VXNlcjY0MTY4NQ==", - "avatar_url": "https://avatars.githubusercontent.com/u/641685?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/eamodio", - "html_url": "https://github.com/eamodio", - "followers_url": "https://api.github.com/users/eamodio/followers", - "following_url": "https://api.github.com/users/eamodio/following{/other_user}", - "gists_url": "https://api.github.com/users/eamodio/gists{/gist_id}", - "starred_url": "https://api.github.com/users/eamodio/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/eamodio/subscriptions", - "organizations_url": "https://api.github.com/users/eamodio/orgs", - "repos_url": "https://api.github.com/users/eamodio/repos", - "events_url": "https://api.github.com/users/eamodio/events{/privacy}", - "received_events_url": "https://api.github.com/users/eamodio/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": 11, - "created_at": "2021-02-18T01:20:24Z", - "updated_at": "2021-03-15T15:26:39Z", - "closed_at": null, - "author_association": "MEMBER", - "active_lock_reason": null, - "body": "It would be great to expose both `extensionId` and `extensionVersion` on our `ExtensionContext`. I've seen extensions (too many times) actually `require('./package.json)' to get at either the extension id or version. We were (still are until a PR lands) even doing this in our GHPR extension. In the best case, this wastes cycles, causes I/O contention, slows activations, and in the worse (e.g. bundling) actually bundles the `package.json` into your JS bundle.\r\n\r\nBy at least providing access to the `id` (and the mostly commonly accessed `package.json` property `version`), extensions can then use the `extensions.getExtension()` api to get at the other `package.json` properties that we already cache.\r\n\r\n```ts\r\n\texport interface ExtensionContext {\r\n\t\treadonly extensionId: string;\r\n\t\treadonly extensionVersion: string;\r\n\t}\r\n```\r\n\r\n/cc @jrieken @sandy081 ", - "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": 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": 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" - } - ] + "editable": true }, { "kind": 1, "language": "markdown", "value": "### Proposals", - "editable": true, - "outputs": [] + "editable": true }, { "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 - }, - "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/115626", - "repository_url": "https://api.github.com/repos/microsoft/vscode", - "labels_url": "https://api.github.com/repos/microsoft/vscode/issues/115626/labels{/name}", - "comments_url": "https://api.github.com/repos/microsoft/vscode/issues/115626/comments", - "events_url": "https://api.github.com/repos/microsoft/vscode/issues/115626/events", - "html_url": "https://github.com/microsoft/vscode/issues/115626", - "id": 799566516, - "node_id": "MDU6SXNzdWU3OTk1NjY1MTY=", - "number": 115626, - "title": "Microsoft Auth Provider should support overriding client id and tenant id", - "user": { - "login": "TylerLeonhardt", - "id": 2644648, - "node_id": "MDQ6VXNlcjI2NDQ2NDg=", - "avatar_url": "https://avatars.githubusercontent.com/u/2644648?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/TylerLeonhardt", - "html_url": "https://github.com/TylerLeonhardt", - "followers_url": "https://api.github.com/users/TylerLeonhardt/followers", - "following_url": "https://api.github.com/users/TylerLeonhardt/following{/other_user}", - "gists_url": "https://api.github.com/users/TylerLeonhardt/gists{/gist_id}", - "starred_url": "https://api.github.com/users/TylerLeonhardt/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/TylerLeonhardt/subscriptions", - "organizations_url": "https://api.github.com/users/TylerLeonhardt/orgs", - "repos_url": "https://api.github.com/users/TylerLeonhardt/repos", - "events_url": "https://api.github.com/users/TylerLeonhardt/events{/privacy}", - "received_events_url": "https://api.github.com/users/TylerLeonhardt/received_events", - "type": "User", - "site_admin": false - }, - "labels": [ - { - "id": 869332220, - "node_id": "MDU6TGFiZWw4NjkzMzIyMjA=", - "url": "https://api.github.com/repos/microsoft/vscode/labels/api-proposal", - "name": "api-proposal", - "color": "c5def5", - "default": false, - "description": "" - }, - { - "id": 1702048079, - "node_id": "MDU6TGFiZWwxNzAyMDQ4MDc5", - "url": "https://api.github.com/repos/microsoft/vscode/labels/authentication", - "name": "authentication", - "color": "c5def5", - "default": false, - "description": "Authentication issues" - } - ], - "state": "open", - "locked": false, - "assignee": { - "login": "TylerLeonhardt", - "id": 2644648, - "node_id": "MDQ6VXNlcjI2NDQ2NDg=", - "avatar_url": "https://avatars.githubusercontent.com/u/2644648?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/TylerLeonhardt", - "html_url": "https://github.com/TylerLeonhardt", - "followers_url": "https://api.github.com/users/TylerLeonhardt/followers", - "following_url": "https://api.github.com/users/TylerLeonhardt/following{/other_user}", - "gists_url": "https://api.github.com/users/TylerLeonhardt/gists{/gist_id}", - "starred_url": "https://api.github.com/users/TylerLeonhardt/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/TylerLeonhardt/subscriptions", - "organizations_url": "https://api.github.com/users/TylerLeonhardt/orgs", - "repos_url": "https://api.github.com/users/TylerLeonhardt/repos", - "events_url": "https://api.github.com/users/TylerLeonhardt/events{/privacy}", - "received_events_url": "https://api.github.com/users/TylerLeonhardt/received_events", - "type": "User", - "site_admin": false - }, - "assignees": [ - { - "login": "TylerLeonhardt", - "id": 2644648, - "node_id": "MDQ6VXNlcjI2NDQ2NDg=", - "avatar_url": "https://avatars.githubusercontent.com/u/2644648?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/TylerLeonhardt", - "html_url": "https://github.com/TylerLeonhardt", - "followers_url": "https://api.github.com/users/TylerLeonhardt/followers", - "following_url": "https://api.github.com/users/TylerLeonhardt/following{/other_user}", - "gists_url": "https://api.github.com/users/TylerLeonhardt/gists{/gist_id}", - "starred_url": "https://api.github.com/users/TylerLeonhardt/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/TylerLeonhardt/subscriptions", - "organizations_url": "https://api.github.com/users/TylerLeonhardt/orgs", - "repos_url": "https://api.github.com/users/TylerLeonhardt/repos", - "events_url": "https://api.github.com/users/TylerLeonhardt/events{/privacy}", - "received_events_url": "https://api.github.com/users/TylerLeonhardt/received_events", - "type": "User", - "site_admin": false - }, - { - "login": "RMacfarlane", - "id": 3672607, - "node_id": "MDQ6VXNlcjM2NzI2MDc=", - "avatar_url": "https://avatars.githubusercontent.com/u/3672607?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/RMacfarlane", - "html_url": "https://github.com/RMacfarlane", - "followers_url": "https://api.github.com/users/RMacfarlane/followers", - "following_url": "https://api.github.com/users/RMacfarlane/following{/other_user}", - "gists_url": "https://api.github.com/users/RMacfarlane/gists{/gist_id}", - "starred_url": "https://api.github.com/users/RMacfarlane/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/RMacfarlane/subscriptions", - "organizations_url": "https://api.github.com/users/RMacfarlane/orgs", - "repos_url": "https://api.github.com/users/RMacfarlane/repos", - "events_url": "https://api.github.com/users/RMacfarlane/events{/privacy}", - "received_events_url": "https://api.github.com/users/RMacfarlane/received_events", - "type": "User", - "site_admin": false - } - ], - "milestone": { - "url": "https://api.github.com/repos/microsoft/vscode/milestones/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-02-02T18:42:12Z", - "updated_at": "2021-02-22T23:20:49Z", - "closed_at": null, - "author_association": "MEMBER", - "active_lock_reason": null, - "body": "\r\n\r\n\r\n\r\n\r\n\r\nThe Microsoft Auth Provider uses a specific AAD application with client id hardcoded here:\r\nhttps://github.com/microsoft/vscode/blob/582ea371c2bf785d88458dab95828387ad94a63d/extensions/microsoft-authentication/src/AADHelper.ts#L25-L26\r\n\r\nHowever, this application only has access to a handful of scopes, and to add _allowed_ scopes to this client id is a manual process (which for an external extension author means opening an issue here and then having one of us add that scope to the _allowed_ scopes for the application)\r\n\r\nAs an extension author, I should easily be able to create my own AAD application (in the Azure Portal for example) and use that client id instead of the one vscode uses so that I can have control over the scopes I care about and, if this exists, I can get telemetry when my client id is used.\r\n\r\nSince we have abstracted auth providers, I think it's fitting to be able to pass additional auth provider specific options down to an auth provider. For example, the Microsoft auth provider would take a client id and tenant that would replace the hard coded string above.\r\n\r\nProposal:\r\n\r\n```ts\r\n /**\r\n\t * Options to be used when getting an [AuthenticationSession](#AuthenticationSession) from an [AuthenticationProvider](#AuthenticationProvider).\r\n\t */\r\n\texport interface AuthenticationGetSessionOptions {\r\n\t\t/**\r\n\t\t * Whether login should be performed if there is no matching session.\r\n\t\t *\r\n\t\t * If true, a modal dialog will be shown asking the user to sign in. If false, a numbered badge will be shown\r\n\t\t * on the accounts activity bar icon. An entry for the extension will be added under the menu to sign in. This\r\n\t\t * allows quietly prompting the user to sign in.\r\n\t\t *\r\n\t\t * Defaults to false.\r\n\t\t */\r\n\t\tcreateIfNone?: boolean;\r\n\r\n\t\t/**\r\n\t\t * Whether the existing user session preference should be cleared.\r\n\t\t *\r\n\t\t * For authentication providers that support being signed into multiple accounts at once, the user will be\r\n\t\t * prompted to select an account to use when [getSession](#authentication.getSession) is called. This preference\r\n\t\t * is remembered until [getSession](#authentication.getSession) is called with this flag.\r\n\t\t *\r\n\t\t * Defaults to false.\r\n\t\t */\r\n\t\tclearSessionPreference?: boolean;\r\n\r\n\t\t/*************/\r\n\t\t/*** NEW ***/\r\n\t\t/*************/\r\n /**\r\n * Provider specific options for getting this session (i.e. client id, tenant)\r\n */\r\n\t\tproviderOptions?: { [key: string]: any; }\r\n\t}\r\n```\r\n\r\nThe Auth Provider would then need to be responsible for deciding if it already has created a session with these options or if it needs to create a new session based on these options.", - "performed_via_github_app": null, - "score": 1 - }, - { - "url": "https://api.github.com/repos/microsoft/vscode/issues/115616", - "repository_url": "https://api.github.com/repos/microsoft/vscode", - "labels_url": "https://api.github.com/repos/microsoft/vscode/issues/115616/labels{/name}", - "comments_url": "https://api.github.com/repos/microsoft/vscode/issues/115616/comments", - "events_url": "https://api.github.com/repos/microsoft/vscode/issues/115616/events", - "html_url": "https://github.com/microsoft/vscode/issues/115616", - "id": 799392757, - "node_id": "MDU6SXNzdWU3OTkzOTI3NTc=", - "number": 115616, - "title": "Provide extension API to exclude ports from forwarding", - "user": { - "login": "alexr00", - "id": 38270282, - "node_id": "MDQ6VXNlcjM4MjcwMjgy", - "avatar_url": "https://avatars.githubusercontent.com/u/38270282?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/alexr00", - "html_url": "https://github.com/alexr00", - "followers_url": "https://api.github.com/users/alexr00/followers", - "following_url": "https://api.github.com/users/alexr00/following{/other_user}", - "gists_url": "https://api.github.com/users/alexr00/gists{/gist_id}", - "starred_url": "https://api.github.com/users/alexr00/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/alexr00/subscriptions", - "organizations_url": "https://api.github.com/users/alexr00/orgs", - "repos_url": "https://api.github.com/users/alexr00/repos", - "events_url": "https://api.github.com/users/alexr00/events{/privacy}", - "received_events_url": "https://api.github.com/users/alexr00/received_events", - "type": "User", - "site_admin": false - }, - "labels": [ - { - "id": 290465400, - "node_id": "MDU6TGFiZWwyOTA0NjU0MDA=", - "url": "https://api.github.com/repos/microsoft/vscode/labels/api", - "name": "api", - "color": "1d76db", - "default": false, - "description": "" - }, - { - "id": 869332220, - "node_id": "MDU6TGFiZWw4NjkzMzIyMjA=", - "url": "https://api.github.com/repos/microsoft/vscode/labels/api-proposal", - "name": "api-proposal", - "color": "c5def5", - "default": false, - "description": "" - }, - { - "id": 272689392, - "node_id": "MDU6TGFiZWwyNzI2ODkzOTI=", - "url": "https://api.github.com/repos/microsoft/vscode/labels/feature-request", - "name": "feature-request", - "color": "dcdcdc", - "default": false, - "description": "Request for new features or functionality" - }, - { - "id": 2426856573, - "node_id": "MDU6TGFiZWwyNDI2ODU2NTcz", - "url": "https://api.github.com/repos/microsoft/vscode/labels/ghcs-in-progress", - "name": "ghcs-in-progress", - "color": "000000", - "default": false, - "description": "" - }, - { - "id": 1772775110, - "node_id": "MDU6TGFiZWwxNzcyNzc1MTEw", - "url": "https://api.github.com/repos/microsoft/vscode/labels/remote-explorer", - "name": "remote-explorer", - "color": "c5def5", - "default": false, - "description": "Remote explorer view" - } - ], - "state": "open", - "locked": false, - "assignee": null, - "assignees": [ - { - "login": "alexr00", - "id": 38270282, - "node_id": "MDQ6VXNlcjM4MjcwMjgy", - "avatar_url": "https://avatars.githubusercontent.com/u/38270282?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/alexr00", - "html_url": "https://github.com/alexr00", - "followers_url": "https://api.github.com/users/alexr00/followers", - "following_url": "https://api.github.com/users/alexr00/following{/other_user}", - "gists_url": "https://api.github.com/users/alexr00/gists{/gist_id}", - "starred_url": "https://api.github.com/users/alexr00/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/alexr00/subscriptions", - "organizations_url": "https://api.github.com/users/alexr00/orgs", - "repos_url": "https://api.github.com/users/alexr00/repos", - "events_url": "https://api.github.com/users/alexr00/events{/privacy}", - "received_events_url": "https://api.github.com/users/alexr00/received_events", - "type": "User", - "site_admin": false - } - ], - "milestone": { - "url": "https://api.github.com/repos/microsoft/vscode/milestones/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": 17, - "created_at": "2021-02-02T15:37:45Z", - "updated_at": "2021-03-16T00:33:17Z", - "closed_at": null, - "author_association": "MEMBER", - "active_lock_reason": null, - "body": "From @weinand:\r\nToday the tunneling service blindly forwards all communication ports.\r\nThis includes ports that are used for debugging (even if our remote debugging feature does not need these ports to be forwarded).\r\nThis is confusing for users because they see ports that they are not really interested in.\r\n\r\nI propose to add extension API so that individual ports or port ranges can be excluded from forwarding.\r\nDebug extensions could use this API.\r\n\r\n", - "performed_via_github_app": null, - "score": 1 - }, - { - "url": "https://api.github.com/repos/microsoft/vscode/issues/114898", - "repository_url": "https://api.github.com/repos/microsoft/vscode", - "labels_url": "https://api.github.com/repos/microsoft/vscode/issues/114898/labels{/name}", - "comments_url": "https://api.github.com/repos/microsoft/vscode/issues/114898/comments", - "events_url": "https://api.github.com/repos/microsoft/vscode/issues/114898/events", - "html_url": "https://github.com/microsoft/vscode/issues/114898", - "id": 793246594, - "node_id": "MDU6SXNzdWU3OTMyNDY1OTQ=", - "number": 114898, - "title": "[ext-api] provide Pseudoterminal.onDidChangeName event", - "user": { - "login": "akosyakov", - "id": 3082655, - "node_id": "MDQ6VXNlcjMwODI2NTU=", - "avatar_url": "https://avatars.githubusercontent.com/u/3082655?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/akosyakov", - "html_url": "https://github.com/akosyakov", - "followers_url": "https://api.github.com/users/akosyakov/followers", - "following_url": "https://api.github.com/users/akosyakov/following{/other_user}", - "gists_url": "https://api.github.com/users/akosyakov/gists{/gist_id}", - "starred_url": "https://api.github.com/users/akosyakov/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/akosyakov/subscriptions", - "organizations_url": "https://api.github.com/users/akosyakov/orgs", - "repos_url": "https://api.github.com/users/akosyakov/repos", - "events_url": "https://api.github.com/users/akosyakov/events{/privacy}", - "received_events_url": "https://api.github.com/users/akosyakov/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": 256129996, - "node_id": "MDU6TGFiZWwyNTYxMjk5OTY=", - "url": "https://api.github.com/repos/microsoft/vscode/labels/help%20wanted", - "name": "help wanted", - "color": "fef2c0", - "default": true, - "description": "Issues identified as good community contribution opportunities" - } - ], - "state": "open", - "locked": false, - "assignee": { - "login": "Tyriar", - "id": 2193314, - "node_id": "MDQ6VXNlcjIxOTMzMTQ=", - "avatar_url": "https://avatars.githubusercontent.com/u/2193314?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/Tyriar", - "html_url": "https://github.com/Tyriar", - "followers_url": "https://api.github.com/users/Tyriar/followers", - "following_url": "https://api.github.com/users/Tyriar/following{/other_user}", - "gists_url": "https://api.github.com/users/Tyriar/gists{/gist_id}", - "starred_url": "https://api.github.com/users/Tyriar/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/Tyriar/subscriptions", - "organizations_url": "https://api.github.com/users/Tyriar/orgs", - "repos_url": "https://api.github.com/users/Tyriar/repos", - "events_url": "https://api.github.com/users/Tyriar/events{/privacy}", - "received_events_url": "https://api.github.com/users/Tyriar/received_events", - "type": "User", - "site_admin": false - }, - "assignees": [ - { - "login": "Tyriar", - "id": 2193314, - "node_id": "MDQ6VXNlcjIxOTMzMTQ=", - "avatar_url": "https://avatars.githubusercontent.com/u/2193314?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/Tyriar", - "html_url": "https://github.com/Tyriar", - "followers_url": "https://api.github.com/users/Tyriar/followers", - "following_url": "https://api.github.com/users/Tyriar/following{/other_user}", - "gists_url": "https://api.github.com/users/Tyriar/gists{/gist_id}", - "starred_url": "https://api.github.com/users/Tyriar/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/Tyriar/subscriptions", - "organizations_url": "https://api.github.com/users/Tyriar/orgs", - "repos_url": "https://api.github.com/users/Tyriar/repos", - "events_url": "https://api.github.com/users/Tyriar/events{/privacy}", - "received_events_url": "https://api.github.com/users/Tyriar/received_events", - "type": "User", - "site_admin": false - } - ], - "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": 5, - "created_at": "2021-01-25T10:18:54Z", - "updated_at": "2021-02-16T16:25:13Z", - "closed_at": null, - "author_association": "NONE", - "active_lock_reason": null, - "body": "Current it is not possible to change the name of the pseudo terminal implemented by the extension.", - "performed_via_github_app": null, - "score": 1 - }, - { - "url": "https://api.github.com/repos/microsoft/vscode/issues/111521", - "repository_url": "https://api.github.com/repos/microsoft/vscode", - "labels_url": "https://api.github.com/repos/microsoft/vscode/issues/111521/labels{/name}", - "comments_url": "https://api.github.com/repos/microsoft/vscode/issues/111521/comments", - "events_url": "https://api.github.com/repos/microsoft/vscode/issues/111521/events", - "html_url": "https://github.com/microsoft/vscode/issues/111521", - "id": 753621208, - "node_id": "MDU6SXNzdWU3NTM2MjEyMDg=", - "number": 111521, - "title": "Support a way to have nightly/insiders versions of extensions not activate if \"main\" extension is installed/enabled", - "user": { - "login": "eamodio", - "id": 641685, - "node_id": "MDQ6VXNlcjY0MTY4NQ==", - "avatar_url": "https://avatars.githubusercontent.com/u/641685?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/eamodio", - "html_url": "https://github.com/eamodio", - "followers_url": "https://api.github.com/users/eamodio/followers", - "following_url": "https://api.github.com/users/eamodio/following{/other_user}", - "gists_url": "https://api.github.com/users/eamodio/gists{/gist_id}", - "starred_url": "https://api.github.com/users/eamodio/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/eamodio/subscriptions", - "organizations_url": "https://api.github.com/users/eamodio/orgs", - "repos_url": "https://api.github.com/users/eamodio/repos", - "events_url": "https://api.github.com/users/eamodio/events{/privacy}", - "received_events_url": "https://api.github.com/users/eamodio/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": 343063018, - "node_id": "MDU6TGFiZWwzNDMwNjMwMTg=", - "url": "https://api.github.com/repos/microsoft/vscode/labels/extensions", - "name": "extensions", - "color": "1d76db", - "default": false, - "description": "Issues concerning extensions" - }, - { - "id": 1634275230, - "node_id": "MDU6TGFiZWwxNjM0Mjc1MjMw", - "url": "https://api.github.com/repos/microsoft/vscode/labels/extensions-development", - "name": "extensions-development", - "color": "1d76db", - "default": false, - "description": "Issues for developing extensions" - }, - { - "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": 798222351, - "node_id": "MDU6TGFiZWw3OTgyMjIzNTE=", - "url": "https://api.github.com/repos/microsoft/vscode/labels/marketplace", - "name": "marketplace", - "color": "c5def5", - "default": false, - "description": "Microsoft VS Code Marketplace issues" - } - ], - "state": "open", - "locked": false, - "assignee": { - "login": "sandy081", - "id": 10746682, - "node_id": "MDQ6VXNlcjEwNzQ2Njgy", - "avatar_url": "https://avatars.githubusercontent.com/u/10746682?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/sandy081", - "html_url": "https://github.com/sandy081", - "followers_url": "https://api.github.com/users/sandy081/followers", - "following_url": "https://api.github.com/users/sandy081/following{/other_user}", - "gists_url": "https://api.github.com/users/sandy081/gists{/gist_id}", - "starred_url": "https://api.github.com/users/sandy081/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/sandy081/subscriptions", - "organizations_url": "https://api.github.com/users/sandy081/orgs", - "repos_url": "https://api.github.com/users/sandy081/repos", - "events_url": "https://api.github.com/users/sandy081/events{/privacy}", - "received_events_url": "https://api.github.com/users/sandy081/received_events", - "type": "User", - "site_admin": false - }, - "assignees": [ - { - "login": "sandy081", - "id": 10746682, - "node_id": "MDQ6VXNlcjEwNzQ2Njgy", - "avatar_url": "https://avatars.githubusercontent.com/u/10746682?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/sandy081", - "html_url": "https://github.com/sandy081", - "followers_url": "https://api.github.com/users/sandy081/followers", - "following_url": "https://api.github.com/users/sandy081/following{/other_user}", - "gists_url": "https://api.github.com/users/sandy081/gists{/gist_id}", - "starred_url": "https://api.github.com/users/sandy081/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/sandy081/subscriptions", - "organizations_url": "https://api.github.com/users/sandy081/orgs", - "repos_url": "https://api.github.com/users/sandy081/repos", - "events_url": "https://api.github.com/users/sandy081/events{/privacy}", - "received_events_url": "https://api.github.com/users/sandy081/received_events", - "type": "User", - "site_admin": false - } - ], - "milestone": { - "url": "https://api.github.com/repos/microsoft/vscode/milestones/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": 3, - "created_at": "2020-11-30T16:28:19Z", - "updated_at": "2021-03-05T14:35:33Z", - "closed_at": null, - "author_association": "MEMBER", - "active_lock_reason": null, - "body": "More an more, extension authors are providing a nightly/insiders version of their extensions. Which can cause issues for end-users if they have both versions installed. And today, extension authors can't properly deal with this because of the static contributions -- which can cause a bad end-user experience.\r\n\r\nSince we have a `preview` flag in `package.json`, I would propose that we add a new `previewOf` option that could be set to an extension id, that this preview is a preview of. So for example, GitLens Insiders (eamodio.gitlens-insiders) would set `\"previewOf\": \"eamodio.gitlens\"`.\r\n\r\nWhen VS Code sees this flag, it would determine if the `previewOf` extension is installed and enabled, and if so, then would auto-disable this extension so it wouldn't be activated at all.\r\n\r\nThis would avoid a bad end-user experience, especially if we show a reason in the extension view to why this extension is now disabled. It would also avoid issues with Codespaces issues with built-in extensions and nightlies.\r\n\r\n/cc @jrieken @sandy081 @connor4312 @RMacfarlane @JacksonKearl ", - "performed_via_github_app": null, - "score": 1 - }, - { - "url": "https://api.github.com/repos/microsoft/vscode/issues/109277", - "repository_url": "https://api.github.com/repos/microsoft/vscode", - "labels_url": "https://api.github.com/repos/microsoft/vscode/issues/109277/labels{/name}", - "comments_url": "https://api.github.com/repos/microsoft/vscode/issues/109277/comments", - "events_url": "https://api.github.com/repos/microsoft/vscode/issues/109277/events", - "html_url": "https://github.com/microsoft/vscode/issues/109277", - "id": 728636389, - "node_id": "MDU6SXNzdWU3Mjg2MzYzODk=", - "number": 109277, - "title": "Let extensions hook into url opening", - "user": { - "login": "mjbvz", - "id": 12821956, - "node_id": "MDQ6VXNlcjEyODIxOTU2", - "avatar_url": "https://avatars.githubusercontent.com/u/12821956?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/mjbvz", - "html_url": "https://github.com/mjbvz", - "followers_url": "https://api.github.com/users/mjbvz/followers", - "following_url": "https://api.github.com/users/mjbvz/following{/other_user}", - "gists_url": "https://api.github.com/users/mjbvz/gists{/gist_id}", - "starred_url": "https://api.github.com/users/mjbvz/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/mjbvz/subscriptions", - "organizations_url": "https://api.github.com/users/mjbvz/orgs", - "repos_url": "https://api.github.com/users/mjbvz/repos", - "events_url": "https://api.github.com/users/mjbvz/events{/privacy}", - "received_events_url": "https://api.github.com/users/mjbvz/received_events", - "type": "User", - "site_admin": false - }, - "labels": [ - { - "id": 290465400, - "node_id": "MDU6TGFiZWwyOTA0NjU0MDA=", - "url": "https://api.github.com/repos/microsoft/vscode/labels/api", - "name": "api", - "color": "1d76db", - "default": false, - "description": "" - }, - { - "id": 869332220, - "node_id": "MDU6TGFiZWw4NjkzMzIyMjA=", - "url": "https://api.github.com/repos/microsoft/vscode/labels/api-proposal", - "name": "api-proposal", - "color": "c5def5", - "default": false, - "description": "" - }, - { - "id": 578047123, - "node_id": "MDU6TGFiZWw1NzgwNDcxMjM=", - "url": "https://api.github.com/repos/microsoft/vscode/labels/under-discussion", - "name": "under-discussion", - "color": "dcdcdc", - "default": false, - "description": "Issue is under discussion for relevance, priority, approach" - } - ], - "state": "open", - "locked": false, - "assignee": { - "login": "mjbvz", - "id": 12821956, - "node_id": "MDQ6VXNlcjEyODIxOTU2", - "avatar_url": "https://avatars.githubusercontent.com/u/12821956?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/mjbvz", - "html_url": "https://github.com/mjbvz", - "followers_url": "https://api.github.com/users/mjbvz/followers", - "following_url": "https://api.github.com/users/mjbvz/following{/other_user}", - "gists_url": "https://api.github.com/users/mjbvz/gists{/gist_id}", - "starred_url": "https://api.github.com/users/mjbvz/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/mjbvz/subscriptions", - "organizations_url": "https://api.github.com/users/mjbvz/orgs", - "repos_url": "https://api.github.com/users/mjbvz/repos", - "events_url": "https://api.github.com/users/mjbvz/events{/privacy}", - "received_events_url": "https://api.github.com/users/mjbvz/received_events", - "type": "User", - "site_admin": false - }, - "assignees": [ - { - "login": "mjbvz", - "id": 12821956, - "node_id": "MDQ6VXNlcjEyODIxOTU2", - "avatar_url": "https://avatars.githubusercontent.com/u/12821956?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/mjbvz", - "html_url": "https://github.com/mjbvz", - "followers_url": "https://api.github.com/users/mjbvz/followers", - "following_url": "https://api.github.com/users/mjbvz/following{/other_user}", - "gists_url": "https://api.github.com/users/mjbvz/gists{/gist_id}", - "starred_url": "https://api.github.com/users/mjbvz/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/mjbvz/subscriptions", - "organizations_url": "https://api.github.com/users/mjbvz/orgs", - "repos_url": "https://api.github.com/users/mjbvz/repos", - "events_url": "https://api.github.com/users/mjbvz/events{/privacy}", - "received_events_url": "https://api.github.com/users/mjbvz/received_events", - "type": "User", - "site_admin": false - } - ], - "milestone": { - "url": "https://api.github.com/repos/microsoft/vscode/milestones/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": 10, - "created_at": "2020-10-24T02:27:26Z", - "updated_at": "2021-02-23T00:04:01Z", - "closed_at": null, - "author_association": "MEMBER", - "active_lock_reason": null, - "body": "## Overview\r\nLet extensions hook into url opening. Motivating use case: I click on a link in the integrated terminal and it opens in my [browser preview extension](https://marketplace.visualstudio.com/items?itemName=auchenberg.vscode-browser-preview)\r\n\r\nPotential places to handle links:\r\n\r\n- Links in the terminal\r\n- Links in documents\r\n- Links from the remote port forwarding views\r\n- Debugger launch?\r\n- Open external?\r\n\r\n## Additional requirements\r\n\r\n- A url opener should be able to decline opening a link\r\n\r\n Some openers may only support specific types of links, such as `localhost`\r\n\r\n- Clicking a link should activate relevant extensions\r\n\r\n We'd need a new activation event so that extensions can make sure they handle link opening\r\n\r\n- Let users fallback to VS Code's default behavior\r\n\r\n This typically is to open using the default browser\r\n\r\n- Handle multiple url openers being registered at the same time\r\n\r\n Users should be able to select which opener to use in this case. They should potentially be able to specify a default opener.\r\n", - "performed_via_github_app": null, - "score": 1 - }, - { - "url": "https://api.github.com/repos/microsoft/vscode/issues/107467", - "repository_url": "https://api.github.com/repos/microsoft/vscode", - "labels_url": "https://api.github.com/repos/microsoft/vscode/issues/107467/labels{/name}", - "comments_url": "https://api.github.com/repos/microsoft/vscode/issues/107467/comments", - "events_url": "https://api.github.com/repos/microsoft/vscode/issues/107467/events", - "html_url": "https://github.com/microsoft/vscode/issues/107467", - "id": 709128519, - "node_id": "MDU6SXNzdWU3MDkxMjg1MTk=", - "number": 107467, - "title": "Testing in VS Code", - "user": { - "login": "connor4312", - "id": 2230985, - "node_id": "MDQ6VXNlcjIyMzA5ODU=", - "avatar_url": "https://avatars.githubusercontent.com/u/2230985?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/connor4312", - "html_url": "https://github.com/connor4312", - "followers_url": "https://api.github.com/users/connor4312/followers", - "following_url": "https://api.github.com/users/connor4312/following{/other_user}", - "gists_url": "https://api.github.com/users/connor4312/gists{/gist_id}", - "starred_url": "https://api.github.com/users/connor4312/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/connor4312/subscriptions", - "organizations_url": "https://api.github.com/users/connor4312/orgs", - "repos_url": "https://api.github.com/users/connor4312/repos", - "events_url": "https://api.github.com/users/connor4312/events{/privacy}", - "received_events_url": "https://api.github.com/users/connor4312/received_events", - "type": "User", - "site_admin": false - }, - "labels": [ - { - "id": 869332220, - "node_id": "MDU6TGFiZWw4NjkzMzIyMjA=", - "url": "https://api.github.com/repos/microsoft/vscode/labels/api-proposal", - "name": "api-proposal", - "color": "c5def5", - "default": false, - "description": "" - }, - { - "id": 293426086, - "node_id": "MDU6TGFiZWwyOTM0MjYwODY=", - "url": "https://api.github.com/repos/microsoft/vscode/labels/plan-item", - "name": "plan-item", - "color": "dcdcdc", - "default": false, - "description": "VS Code - planned item for upcoming" - }, - { - "id": 2676991487, - "node_id": "MDU6TGFiZWwyNjc2OTkxNDg3", - "url": "https://api.github.com/repos/microsoft/vscode/labels/testing", - "name": "testing", - "color": "c5def5", - "default": false, - "description": "Built-in testing support" - }, - { - "id": 578047123, - "node_id": "MDU6TGFiZWw1NzgwNDcxMjM=", - "url": "https://api.github.com/repos/microsoft/vscode/labels/under-discussion", - "name": "under-discussion", - "color": "dcdcdc", - "default": false, - "description": "Issue is under discussion for relevance, priority, approach" - } - ], - "state": "open", - "locked": false, - "assignee": { - "login": "connor4312", - "id": 2230985, - "node_id": "MDQ6VXNlcjIyMzA5ODU=", - "avatar_url": "https://avatars.githubusercontent.com/u/2230985?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/connor4312", - "html_url": "https://github.com/connor4312", - "followers_url": "https://api.github.com/users/connor4312/followers", - "following_url": "https://api.github.com/users/connor4312/following{/other_user}", - "gists_url": "https://api.github.com/users/connor4312/gists{/gist_id}", - "starred_url": "https://api.github.com/users/connor4312/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/connor4312/subscriptions", - "organizations_url": "https://api.github.com/users/connor4312/orgs", - "repos_url": "https://api.github.com/users/connor4312/repos", - "events_url": "https://api.github.com/users/connor4312/events{/privacy}", - "received_events_url": "https://api.github.com/users/connor4312/received_events", - "type": "User", - "site_admin": false - }, - "assignees": [ - { - "login": "connor4312", - "id": 2230985, - "node_id": "MDQ6VXNlcjIyMzA5ODU=", - "avatar_url": "https://avatars.githubusercontent.com/u/2230985?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/connor4312", - "html_url": "https://github.com/connor4312", - "followers_url": "https://api.github.com/users/connor4312/followers", - "following_url": "https://api.github.com/users/connor4312/following{/other_user}", - "gists_url": "https://api.github.com/users/connor4312/gists{/gist_id}", - "starred_url": "https://api.github.com/users/connor4312/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/connor4312/subscriptions", - "organizations_url": "https://api.github.com/users/connor4312/orgs", - "repos_url": "https://api.github.com/users/connor4312/repos", - "events_url": "https://api.github.com/users/connor4312/events{/privacy}", - "received_events_url": "https://api.github.com/users/connor4312/received_events", - "type": "User", - "site_admin": false - }, - { - "login": "sandy081", - "id": 10746682, - "node_id": "MDQ6VXNlcjEwNzQ2Njgy", - "avatar_url": "https://avatars.githubusercontent.com/u/10746682?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/sandy081", - "html_url": "https://github.com/sandy081", - "followers_url": "https://api.github.com/users/sandy081/followers", - "following_url": "https://api.github.com/users/sandy081/following{/other_user}", - "gists_url": "https://api.github.com/users/sandy081/gists{/gist_id}", - "starred_url": "https://api.github.com/users/sandy081/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/sandy081/subscriptions", - "organizations_url": "https://api.github.com/users/sandy081/orgs", - "repos_url": "https://api.github.com/users/sandy081/repos", - "events_url": "https://api.github.com/users/sandy081/events{/privacy}", - "received_events_url": "https://api.github.com/users/sandy081/received_events", - "type": "User", - "site_admin": false - } - ], - "milestone": { - "url": "https://api.github.com/repos/microsoft/vscode/milestones/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": 62, - "created_at": "2020-09-25T17:19:53Z", - "updated_at": "2021-03-16T08:33:01Z", - "closed_at": null, - "author_association": "MEMBER", - "active_lock_reason": null, - "body": "## State of the World\r\n\r\nTesting support in VS Code has been a feature request for [a long time](https://github.com/microsoft/vscode/issues/9505). The VS Code community has build excellent extensions around testing, for example:\r\n\r\n- The [Test Explorer UI](https://marketplace.visualstudio.com/items?itemName=hbenl.vscode-test-explorer) from @hbenl\r\n- [Wallaby.js](https://wallabyjs.com/) from the Wallaby team\r\n- [Jest](https://marketplace.visualstudio.com/items?itemName=Orta.vscode-jest) from @orta\r\n- ...and many more\r\n\r\nEach implementation of testing presents a different set of features, UI, and idiomaticity. Because there is no sanctioned approach to tests in VS Code, extension developers tend to make bespoke implementations, as we've seen in the Python and Java language extensions. Ideally, like in debugging, a VS Code user would have just about the same experience as they work between projects and languages.\r\n\r\n## VS Code's Approach\r\n\r\n> Investigate how VS Code can improve the testing support. Several extensions are already providing testing support, explore what APIs/UIs could be added to improve these testing extensions and the test running experience. -- [2020 Roadmap](https://github.com/microsoft/vscode/wiki/Roadmap#testing)\r\n\r\nThe Test Explorer UI presents the best point of inspiration for us, as there are many existing extensions built on its API: it's capable and proven. Regardless of the direction we take in VS Code, we should have a way for its Test Adapters to be upgraded to the new world.\r\n\r\nWallaby is an excellent extension, but it's tailored and purpose-built to JavaScript, and includes functionality which is not readily portable to other languages. While it is a good source for inspiration, we're not aiming to encompass Wallaby's feature set in the extension points we provide, at least not yet.\r\n\r\nWe're prototyping an API in the extension host, but there are a number of approaches we can take:\r\n\r\n\r\n\t\r\n\t\t\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\r\n\t\t\r\n\t\r\n\t\r\n\t\t\r\n\t\t\r\n\t\t\r\n\t\r\n\t\r\n\t\t\r\n\t\t\r\n\t\t\r\n\t\r\n
Extension Host ('traditional' VS Code API)'Test Protocol' (like DAP/LSP)Extension (like existing test explorer)
\r\n\t\t\t+ Simple to adopt for extension authors
\r\n\t\t\t+ Easier to manage state
\r\n\t\t\t+ Clear way to build 'official' test extensions
\r\n\t\t
\r\n\t\t\t+ Encourages keeping expensive work in child processes
\r\n\t\t\t+ Could be theoretically shared with VS and other editors
\r\n\t\t
\r\n\t\t\t+ Keep VS Code core slim
\r\n\t\t\t+ Unclear whether there's significant functionality we'd want that's not already possible in exthost api
\r\n\t\t
\r\n\t\t\t- The 'obvious path' is doing heavy lifting in the extension host process, which is undesirable
\r\n\t\t
\r\n\t\t\t- Additional implementation and maintainence complexity for VS Code
\r\n\t\t\t- Less friendly, additional complexity than TS APIs for extension authors
\r\n\t\t
\r\n\t\t\t- Additional extension and set of libraries to maintain+version for types and implementation
\r\n\t\t\t- Less clear there's an official pathway for test extensions
\r\n\t\t
\r\n\r\n## API Design\r\n\r\nThe following is a working draft of an API design. It should not be considered final, or anything close to final. This post will be edited as it evolves.\r\n\r\n#### Changes versus the [Test Adapter API](https://github.com/hbenl/vscode-test-adapter-api)\r\n\r\nAs mentioned, the test adapter API and this one provide a similar end user experience. Here are the notable changes we made:\r\n\r\n- The test adapter API does not distinguish between watching a workspace and watching a file. In some cases, there is an existing process that reads workspace tests (such as a language server in Java) or it's not much more expensive to get workspace tests than file tests (such as mocha, perhaps). However, some cases, like Go, providing tests for a single file can be done very cheaply and efficiently without needing to involve the workspace.\r\n\r\n\tIn this API we expect the `TestProvider` to, after activation, always provide tests for the visible text editors, and we only request tests for the entire workspace when required (i.e. when the UI needs to enumerate them).\r\n\r\n- We have modeled the test state more closely after the existing `DiagnosticCollection`, where the Test Adapter API uses only events to enumerate tests and does not have a central collection.\r\n\r\n- The Test Adapter API makes the distinction between suites and tests, we do not. They have almost identical capabilities, and in [at least one scenario](https://blog.golang.org/subtests) the 'suites' are more like tests and the leaf 'tests' cannot be run individually.\r\n\r\n- We use object identity rather than ID for referencing tests. This is in line with other items in the VS Code API, including Diagnostics.\r\n\r\n#### Ideas and Open Questions\r\n\r\nSee the [`testing`](https://github.com/microsoft/vscode/labels/testing) label for current work, questions, and problems.\r\n\r\n### API\r\n\r\nSee the current working proposal in https://github.com/microsoft/vscode/blob/master/src/vs/vscode.proposed.d.ts (ctrl+f for 107467)", - "performed_via_github_app": null, - "score": 1 - }, - { - "url": "https://api.github.com/repos/microsoft/vscode/issues/77423", - "repository_url": "https://api.github.com/repos/microsoft/vscode", - "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": "letmaik", - "id": 530988, - "node_id": "MDQ6VXNlcjUzMDk4OA==", - "avatar_url": "https://avatars.githubusercontent.com/u/530988?v=4", - "gravatar_id": "", - "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": 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": 527005453, - "node_id": "MDU6TGFiZWw1MjcwMDU0NTM=", - "url": "https://api.github.com/repos/microsoft/vscode/labels/quick-pick", - "name": "quick-pick", - "color": "c5def5", - "default": false, - "description": "Quick-pick widget issues" - } - ], - "state": "open", - "locked": false, - "assignee": { - "login": "TylerLeonhardt", - "id": 2644648, - "node_id": "MDQ6VXNlcjI2NDQ2NDg=", - "avatar_url": "https://avatars.githubusercontent.com/u/2644648?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/TylerLeonhardt", - "html_url": "https://github.com/TylerLeonhardt", - "followers_url": "https://api.github.com/users/TylerLeonhardt/followers", - "following_url": "https://api.github.com/users/TylerLeonhardt/following{/other_user}", - "gists_url": "https://api.github.com/users/TylerLeonhardt/gists{/gist_id}", - "starred_url": "https://api.github.com/users/TylerLeonhardt/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/TylerLeonhardt/subscriptions", - "organizations_url": "https://api.github.com/users/TylerLeonhardt/orgs", - "repos_url": "https://api.github.com/users/TylerLeonhardt/repos", - "events_url": "https://api.github.com/users/TylerLeonhardt/events{/privacy}", - "received_events_url": "https://api.github.com/users/TylerLeonhardt/received_events", - "type": "User", - "site_admin": false - }, - "assignees": [ - { - "login": "TylerLeonhardt", - "id": 2644648, - "node_id": "MDQ6VXNlcjI2NDQ2NDg=", - "avatar_url": "https://avatars.githubusercontent.com/u/2644648?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/TylerLeonhardt", - "html_url": "https://github.com/TylerLeonhardt", - "followers_url": "https://api.github.com/users/TylerLeonhardt/followers", - "following_url": "https://api.github.com/users/TylerLeonhardt/following{/other_user}", - "gists_url": "https://api.github.com/users/TylerLeonhardt/gists{/gist_id}", - "starred_url": "https://api.github.com/users/TylerLeonhardt/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/TylerLeonhardt/subscriptions", - "organizations_url": "https://api.github.com/users/TylerLeonhardt/orgs", - "repos_url": "https://api.github.com/users/TylerLeonhardt/repos", - "events_url": "https://api.github.com/users/TylerLeonhardt/events{/privacy}", - "received_events_url": "https://api.github.com/users/TylerLeonhardt/received_events", - "type": "User", - "site_admin": false - }, - { - "login": "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 - } - ], - "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": 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": "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 - } - ] - }, - { - "mime": "text/markdown", - "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" - } - ] + "editable": true } ] \ No newline at end of file diff --git a/.vscode/notebooks/endgame.github-issues b/.vscode/notebooks/endgame.github-issues index c0a624c6..881af2c1 100644 --- a/.vscode/notebooks/endgame.github-issues +++ b/.vscode/notebooks/endgame.github-issues @@ -2,127 +2,106 @@ { "kind": 1, "language": "markdown", - "value": "#### Macros", - "editable": true + "value": "#### Macros" }, { "kind": 2, "language": "github-issues", - "value": "$REPOS=repo:microsoft/vscode repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-js-debug repo:microsoft/vscode-remote-release repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-emmet-helper\n\n$MILESTONE=milestone:\"March 2021\"", - "editable": true + "value": "$REPOS=repo:microsoft/vscode repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-js-debug repo:microsoft/vscode-remote-release repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-remotehub\n\n$MILESTONE=milestone:\"April 2021\"" }, { "kind": 1, "language": "markdown", - "value": "# Preparation", - "editable": true + "value": "# Preparation" }, { "kind": 1, "language": "markdown", - "value": "## Open Pull Requests on the Milestone", - "editable": true + "value": "## Open Pull Requests on the Milestone" }, { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE is:pr is:open", - "editable": true + "value": "$REPOS $MILESTONE is:pr is:open" }, { "kind": 1, "language": "markdown", - "value": "## Open Issues on the Milestone", - "editable": true + "value": "## Open Issues on the Milestone" }, { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE is:issue is:open -label:iteration-plan -label:endgame-plan -label:testplan-item", - "editable": true + "value": "$REPOS $MILESTONE is:issue is:open -label:iteration-plan -label:endgame-plan -label:testplan-item" }, { "kind": 1, "language": "markdown", - "value": "## Feature Requests Missing Labels", - "editable": true + "value": "## Feature Requests Missing Labels" }, { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE is:issue is:closed label:feature-request -label:verification-needed -label:on-testplan -label:verified -label:*duplicate", - "editable": true + "value": "$REPOS $MILESTONE is:issue is:closed label:feature-request -label:verification-needed -label:on-testplan -label:verified -label:*duplicate" }, { "kind": 1, "language": "markdown", - "value": "# Testing", - "editable": true + "value": "# Testing" }, { "kind": 1, "language": "markdown", - "value": "## Test Plan Items", - "editable": true + "value": "## Test Plan Items" }, { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE is:issue is:open label:testplan-item", - "editable": true + "value": "$REPOS $MILESTONE is:issue is:open label:testplan-item" }, { "kind": 1, "language": "markdown", - "value": "## Verification Needed", - "editable": true + "value": "## Verification Needed" }, { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE is:issue is:closed label:feature-request label:verification-needed -label:verified", - "editable": true + "value": "$REPOS $MILESTONE is:issue is:closed label:feature-request label:verification-needed -label:verified" }, { "kind": 1, "language": "markdown", - "value": "# Verification", - "editable": true + "value": "# Verification" }, { "kind": 1, "language": "markdown", - "value": "## Verifiable Fixes", - "editable": true + "value": "## Verifiable Fixes" }, { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE is:issue is:closed sort:updated-asc label:bug -label:verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:verification-found -label:z-author-verified -label:unreleased", - "editable": true + "value": "$REPOS $MILESTONE is:issue is:closed sort:updated-asc label:bug -label:verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:verification-found -label:z-author-verified -label:unreleased" }, { "kind": 1, "language": "markdown", - "value": "## Unreleased Fixes", - "editable": true + "value": "## Unreleased Fixes" }, { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE is:issue is:closed sort:updated-asc label:bug -label:verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:verification-found -label:z-author-verified label:unreleased", - "editable": true + "value": "$REPOS $MILESTONE is:issue is:closed sort:updated-asc label:bug -label:verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:verification-found -label:z-author-verified label:unreleased" }, { "kind": 1, "language": "markdown", - "value": "# Candidates", - "editable": true + "value": "# Candidates" }, { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE is:open label:candidate", - "editable": true + "value": "$REPOS $MILESTONE is:open label:candidate" } ] \ No newline at end of file diff --git a/.vscode/notebooks/inbox.github-issues b/.vscode/notebooks/inbox.github-issues index 69c1b7b0..d19b240a 100644 --- a/.vscode/notebooks/inbox.github-issues +++ b/.vscode/notebooks/inbox.github-issues @@ -3,56 +3,48 @@ "kind": 1, "language": "markdown", "value": "## tl;dr: Triage Inbox\n\nAll inbox issues but not those that need more information. These issues need to be triaged, e.g assigned to a user or ask for more information", - "editable": true, - "outputs": [] + "editable": true }, { "kind": 2, "language": "github-issues", "value": "$inbox -label:\"needs more info\"", - "editable": true, - "outputs": [] + "editable": true }, { "kind": 1, "language": "markdown", "value": "##### `Config`: defines the inbox query", - "editable": true, - "outputs": [] + "editable": true }, { "kind": 2, "language": "github-issues", "value": "$inbox=repo:microsoft/vscode is:open no:assignee -label:feature-request -label:testplan-item -label:plan-item ", - "editable": true, - "outputs": [] + "editable": true }, { "kind": 1, "language": "markdown", "value": "## Inbox tracking and Issue triage", - "editable": true, - "outputs": [] + "editable": true }, { "kind": 1, "language": "markdown", "value": "New issues or pull requests submitted by the community are initially triaged by an [automatic classification bot](https://github.com/microsoft/vscode-github-triage-actions/tree/master/classifier-deep). Issues that the bot does not correctly triage are then triaged by a team member. The team rotates the inbox tracker on a weekly basis.\n\nA [mirror](https://github.com/JacksonKearl/testissues/issues) of the VS Code issue stream is available with details about how the bot classifies issues, including feature-area classifications and confidence ratings. Per-category confidence thresholds and feature-area ownership data is maintained in [.github/classifier.json](https://github.com/microsoft/vscode/blob/main/.github/classifier.json). \n\n💡 The bot is being run through a GitHub action that runs every 30 minutes. Give the bot the opportunity to classify an issue before doing it manually.\n\n### Inbox Tracking\n\nThe inbox tracker is responsible for the [global inbox](https://github.com/microsoft/vscode/issues?utf8=%E2%9C%93&q=is%3Aopen+no%3Aassignee+-label%3Afeature-request+-label%3Atestplan-item+-label%3Aplan-item) containing all **open issues and pull requests** that\n- are neither **feature requests** nor **test plan items** nor **plan items** and\n- have **no owner assignment**.\n\nThe **inbox tracker** may perform any step described in our [issue triaging documentation](https://github.com/microsoft/vscode/wiki/Issues-Triaging) but its main responsibility is to route issues to the actual feature area owner.\n\nFeature area owners track the **feature area inbox** containing all **open issues and pull requests** that\n- are personally assigned to them and are not assigned to any milestone\n- are labeled with their feature area label and are not assigned to any milestone.\nThis secondary triage may involve any of the steps described in our [issue triaging documentation](https://github.com/microsoft/vscode/wiki/Issues-Triaging) and results in a fully triaged or closed issue.\n\nThe [github triage extension](https://github.com/microsoft/vscode-github-triage-extension) can be used to assist with triaging — it provides a \"Command Palette\"-style list of triaging actions like assignment, labeling, and triggers for various bot actions.", - "editable": true, - "outputs": [] + "editable": true }, { "kind": 1, "language": "markdown", "value": "## All Inbox Items\n\nAll issues that have no assignee and that have neither **feature requests** nor **test plan items** nor **plan items**.", - "editable": true, - "outputs": [] + "editable": true }, { "kind": 2, "language": "github-issues", "value": "$inbox", - "editable": true, - "outputs": [] + "editable": true } ] \ No newline at end of file diff --git a/.vscode/notebooks/my-endgame.github-issues b/.vscode/notebooks/my-endgame.github-issues index 16a30c9f..c435ee77 100644 --- a/.vscode/notebooks/my-endgame.github-issues +++ b/.vscode/notebooks/my-endgame.github-issues @@ -2,253 +2,181 @@ { "kind": 1, "language": "markdown", - "value": "#### Macros", - "editable": true, - "outputs": [] + "value": "#### Macros" }, { "kind": 2, "language": "github-issues", - "value": "$REPOS=repo:microsoft/vscode repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-js-debug repo:microsoft/vscode-remote-release repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-settings-sync-server\n\n$MILESTONE=milestone:\"March 2021\"\n\n$MINE=assignee:@me", - "editable": true, - "outputs": [] + "value": "$REPOS=repo:microsoft/vscode repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-js-debug repo:microsoft/vscode-remote-release repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-remotehub\n\n$MILESTONE=milestone:\"April 2021\"\n\n$MINE=assignee:@me" }, { "kind": 1, "language": "markdown", - "value": "# Preparation", - "editable": true, - "outputs": [] + "value": "# Preparation" }, { "kind": 1, "language": "markdown", - "value": "## Open Pull Requests on the Milestone", - "editable": true, - "outputs": [] + "value": "## Open Pull Requests on the Milestone" }, { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE $MINE is:pr is:open", - "editable": true, - "outputs": [] + "value": "$REPOS $MILESTONE $MINE is:pr is:open" }, { "kind": 1, "language": "markdown", - "value": "## Open Issues on the Milestone", - "editable": true, - "outputs": [] + "value": "## Open Issues on the Milestone" }, { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE $MINE is:issue is:open -label:iteration-plan -label:endgame-plan -label:testplan-item", - "editable": true, - "outputs": [] + "value": "$REPOS $MILESTONE $MINE is:issue is:open -label:iteration-plan -label:endgame-plan -label:testplan-item" }, { "kind": 1, "language": "markdown", - "value": "## Feature Requests Missing Labels", - "editable": true, - "outputs": [] + "value": "## Feature Requests Missing Labels" }, { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE $MINE is:issue is:closed label:feature-request -label:verification-needed -label:on-testplan -label:verified -label:*duplicate", - "editable": true, - "outputs": [] + "value": "$REPOS $MILESTONE $MINE is:issue is:closed label:feature-request -label:verification-needed -label:on-testplan -label:verified -label:*duplicate" }, { "kind": 1, "language": "markdown", - "value": "## Test Plan Items", - "editable": true, - "outputs": [] + "value": "## Test Plan Items" }, { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE is:issue is:open author:@me label:testplan-item", - "editable": true, - "outputs": [] + "value": "$REPOS $MILESTONE is:issue is:open author:@me label:testplan-item" }, { "kind": 1, "language": "markdown", - "value": "## Verification Needed", - "editable": true, - "outputs": [] + "value": "## Verification Needed" }, { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE $MINE is:issue is:closed label:feature-request label:verification-needed", - "editable": true, - "outputs": [] + "value": "$REPOS $MILESTONE $MINE is:issue is:closed label:feature-request label:verification-needed" }, { "kind": 1, "language": "markdown", - "value": "# Testing", - "editable": true, - "outputs": [] + "value": "# Testing" }, { "kind": 1, "language": "markdown", - "value": "## Test Plan Items", - "editable": true, - "outputs": [] + "value": "## Test Plan Items" }, { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE $MINE is:issue is:open label:testplan-item", - "editable": true, - "outputs": [] + "value": "$REPOS $MILESTONE $MINE is:issue is:open label:testplan-item" }, { "kind": 1, "language": "markdown", - "value": "## Verification Needed", - "editable": true, - "outputs": [] + "value": "## Verification Needed" }, { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE -$MINE is:issue is:closed -assignee:@me -label:verified -label:z-author-verified label:feature-request label:verification-needed", - "editable": true, - "outputs": [] + "value": "$REPOS $MILESTONE -$MINE is:issue is:closed -assignee:@me -label:verified -label:z-author-verified label:feature-request label:verification-needed" }, { "kind": 1, "language": "markdown", - "value": "# Fixing", - "editable": true, - "outputs": [] + "value": "# Fixing" }, { "kind": 1, "language": "markdown", - "value": "## Open Issues", - "editable": true, - "outputs": [] + "value": "## Open Issues" }, { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE $MINE is:issue is:open -label:endgame-plan -label:testplan-item -label:iteration-plan", - "editable": true, - "outputs": [] + "value": "$REPOS $MILESTONE $MINE is:issue is:open -label:endgame-plan -label:testplan-item -label:iteration-plan" }, { "kind": 1, "language": "markdown", - "value": "## Open Bugs", - "editable": true, - "outputs": [] + "value": "## Open Bugs" }, { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE $MINE is:issue is:open label:bug", - "editable": true, - "outputs": [] + "value": "$REPOS $MILESTONE $MINE is:issue is:open label:bug" }, { "kind": 1, "language": "markdown", - "value": "# Verification", - "editable": true, - "outputs": [] + "value": "# Verification" }, { "kind": 1, "language": "markdown", - "value": "## My Issues (verification-steps-needed)", - "editable": true, - "outputs": [] + "value": "## My Issues (verification-steps-needed)" }, { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE $MINE is:issue label:bug label:verification-steps-needed", - "editable": true, - "outputs": [] + "value": "$REPOS $MILESTONE $MINE is:issue label:bug label:verification-steps-needed" }, { "kind": 1, "language": "markdown", - "value": "## My Issues (verification-found)", - "editable": true, - "outputs": [] + "value": "## My Issues (verification-found)" }, { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE $MINE is:issue label:bug label:verification-found", - "editable": true, - "outputs": [] + "value": "$REPOS $MILESTONE $MINE is:issue label:bug label:verification-found" }, { "kind": 1, "language": "markdown", - "value": "## Issues filed by me", - "editable": true, - "outputs": [] + "value": "## Issues filed by me" }, { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE -$MINE is:issue is:closed author:@me sort:updated-asc label:bug -label:verified -label:z-author-verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:verification-found", - "editable": true, - "outputs": [] + "value": "$REPOS $MILESTONE -$MINE is:issue is:closed author:@me sort:updated-asc label:bug -label:verified -label:z-author-verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:verification-found" }, { "kind": 1, "language": "markdown", - "value": "## Issues filed from outside team", - "editable": true, - "outputs": [] + "value": "## Issues filed from outside team" }, { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE -$MINE is:issue is:closed sort:updated-asc label:bug -label:verified -label:z-author-verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:verification-found -author:aeschli -author:alexdima -author:alexr00 -author:AmandaSilver -author:bamurtaugh -author:bpasero -author:btholt -author:chrisdias -author:chrmarti -author:Chuxel -author:connor4312 -author:dbaeumer -author:deepak1556 -author:devinvalenciano -author:digitarald -author:eamodio -author:egamma -author:fiveisprime -author:gregvanl -author:isidorn -author:ItalyPaleAle -author:JacksonKearl -author:joaomoreno -author:jrieken -author:kieferrm -author:lszomoru -author:meganrogge -author:misolori -author:mjbvz -author:ornellaalt -author:orta -author:rebornix -author:RMacfarlane -author:roblourens -author:rzhao271 -author:sana-ajani -author:sandy081 -author:sbatten -author:stevencl -author:Tyriar -author:weinand -author:TylerLeonhardt -author:lramos15", - "editable": true, - "outputs": [] + "value": "$REPOS $MILESTONE -$MINE is:issue is:closed sort:updated-asc label:bug -label:verified -label:z-author-verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:verification-found -author:aeschli -author:alexdima -author:alexr00 -author:AmandaSilver -author:bamurtaugh -author:bpasero -author:btholt -author:chrisdias -author:chrmarti -author:Chuxel -author:connor4312 -author:dbaeumer -author:deepak1556 -author:devinvalenciano -author:digitarald -author:eamodio -author:egamma -author:fiveisprime -author:gregvanl -author:isidorn -author:ItalyPaleAle -author:JacksonKearl -author:joaomoreno -author:jrieken -author:kieferrm -author:lszomoru -author:meganrogge -author:misolori -author:mjbvz -author:ornellaalt -author:orta -author:rebornix -author:RMacfarlane -author:roblourens -author:rzhao271 -author:sana-ajani -author:sandy081 -author:sbatten -author:stevencl -author:Tyriar -author:weinand -author:TylerLeonhardt -author:lramos15" }, { "kind": 1, "language": "markdown", - "value": "## Issues filed by others", - "editable": true, - "outputs": [] + "value": "## Issues filed by others" }, { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE -$MINE is:issue is:closed -author:@me sort:updated-asc label:bug -label:verified -label:z-author-verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:verification-found", - "editable": true, - "outputs": [] + "value": "$REPOS $MILESTONE -$MINE is:issue is:closed -author:@me sort:updated-asc label:bug -label:verified -label:z-author-verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:verification-found" }, { "kind": 1, "language": "markdown", - "value": "# Release Notes", - "editable": true, - "outputs": [] + "value": "# Release Notes" }, { "kind": 2, "language": "github-issues", - "value": "repo:microsoft/vscode $MILESTONE $MINE is:issue is:closed label:feature-request -label:on-release-notes", - "editable": true, - "outputs": [] + "value": "repo:microsoft/vscode $MILESTONE $MINE is:issue is:closed label:feature-request -label:on-release-notes" } ] \ No newline at end of file diff --git a/.vscode/notebooks/my-work.github-issues b/.vscode/notebooks/my-work.github-issues index 278ab639..4e288133 100644 --- a/.vscode/notebooks/my-work.github-issues +++ b/.vscode/notebooks/my-work.github-issues @@ -8,7 +8,7 @@ { "kind": 2, "language": "github-issues", - "value": "// list of repos we work in\n$repos=repo:microsoft/vscode repo:microsoft/vscode-remote-release repo:microsoft/vscode-js-debug repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-internalbacklog\n\n// current milestone name\n$milestone=milestone:\"March 2021\"", + "value": "// list of repos we work in\n$repos=repo:microsoft/vscode repo:microsoft/vscode-remote-release repo:microsoft/vscode-js-debug repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-internalbacklog\n\n// current milestone name\n$milestone=milestone:\"April 2021\"", "editable": true }, { diff --git a/.vscode/notebooks/papercuts.github-issues b/.vscode/notebooks/papercuts.github-issues index 01bb2151..7db9be89 100644 --- a/.vscode/notebooks/papercuts.github-issues +++ b/.vscode/notebooks/papercuts.github-issues @@ -3,49 +3,42 @@ "kind": 1, "language": "markdown", "value": "## Papercuts\n\nThis notebook serves as an ongoing collection of papercut issues that we encounter while dogfooding. With that in mind only promote issues that really turn you off, e.g. issues that make you want to stop using VS Code or its extensions. To mark an issue (bug, feature-request, etc.) as papercut add the labels: `papercut :drop_of_blood:`", - "editable": true, - "outputs": [] + "editable": true }, { "kind": 1, "language": "markdown", "value": "## All Papercuts\n\nThese are all papercut issues that we encounter while dogfooding vscode or extensions that we author.", - "editable": true, - "outputs": [] + "editable": true }, { "kind": 2, "language": "github-issues", "value": "repo:microsoft/vscode is:open -label:notebook label:\"papercut :drop_of_blood:\"", - "editable": true, - "outputs": [] + "editable": true }, { "kind": 1, "language": "markdown", "value": "## Native Notebook", - "editable": true, - "outputs": [] + "editable": true }, { "kind": 2, "language": "github-issues", "value": "repo:microsoft/vscode is:open label:notebook label:\"papercut :drop_of_blood:\"", - "editable": true, - "outputs": [] + "editable": true }, { "kind": 1, "language": "markdown", "value": "### My Papercuts", - "editable": true, - "outputs": [] + "editable": true }, { "kind": 2, "language": "github-issues", "value": "repo:microsoft/vscode is:open assignee:@me label:\"papercut :drop_of_blood:\"", - "editable": true, - "outputs": [] + "editable": true } ] \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index caea9547..a9784168 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -85,6 +85,6 @@ "editor.formatOnSave": true, }, "typescript.tsc.autoDetect": "off", - // "notebook.experimental.useMarkdownRenderer": true, - "testing.autoRun.mode": "onlyPreviouslyRun", + "notebook.experimental.useMarkdownRenderer": true, + "testing.autoRun.mode": "rerun", } diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 007d2c3f..8fb5cb44 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -4,10 +4,11 @@ { "type": "npm", "script": "watch-clientd", - "label": "Build VS Code Core", + "label": "Core - Build", "isBackground": true, "presentation": { - "reveal": "never" + "reveal": "never", + "group": "buildWatchers" }, "problemMatcher": { "owner": "typescript", @@ -30,10 +31,11 @@ { "type": "npm", "script": "watch-extensionsd", - "label": "Build VS Code Extensions", + "label": "Ext - Build", "isBackground": true, "presentation": { - "reveal": "never" + "reveal": "never", + "group": "buildWatchers" }, "problemMatcher": { "owner": "typescript", @@ -54,10 +56,38 @@ } }, { - "label": "Build VS Code", + "type": "npm", + "script": "watch-extension-mediad", + "label": "Ext Media - Build", + "isBackground": true, + "presentation": { + "reveal": "never", + "group": "buildWatchers" + }, + "problemMatcher": { + "owner": "typescript", + "applyTo": "closedDocuments", + "fileLocation": [ + "absolute" + ], + "pattern": { + "regexp": "Error: ([^(]+)\\((\\d+|\\d+,\\d+|\\d+,\\d+,\\d+,\\d+)\\): (.*)$", + "file": 1, + "location": 2, + "message": 3 + }, + "background": { + "beginsPattern": "Starting compilation", + "endsPattern": "Finished compilation" + } + } + }, + { + "label": "VS Code - Build", "dependsOn": [ - "Build VS Code Core", - "Build VS Code Extensions" + "Core - Build", + "Ext - Build", + "Ext Media - Build", ], "group": { "kind": "build", @@ -68,28 +98,42 @@ { "type": "npm", "script": "kill-watch-clientd", - "label": "Kill Build VS Code Core", + "label": "Kill Core - Build", "group": "build", "presentation": { - "reveal": "never" + "reveal": "never", + "group": "buildKillers" }, "problemMatcher": "$tsc" }, { "type": "npm", "script": "kill-watch-extensionsd", - "label": "Kill Build VS Code Extensions", + "label": "Kill Ext - Build", "group": "build", "presentation": { - "reveal": "never" + "reveal": "never", + "group": "buildKillers" }, "problemMatcher": "$tsc" }, { - "label": "Kill Build VS Code", + "type": "npm", + "script": "kill-watch-extension-mediad", + "label": "Kill Ext Media - Build", + "group": "build", + "presentation": { + "reveal": "never", + "group": "buildKillers" + }, + "problemMatcher": "$tsc" + }, + { + "label": "Kill VS Code - Build", "dependsOn": [ - "Kill Build VS Code Core", - "Kill Build VS Code Extensions" + "Kill Core - Build", + "Kill Ext - Build", + "Kill Ext Media - Build", ], "group": "build", "problemMatcher": [] @@ -97,7 +141,7 @@ { "type": "npm", "script": "watch-webd", - "label": "Build Web Extensions", + "label": "Web Ext - Build", "group": "build", "isBackground": true, "presentation": { @@ -124,7 +168,7 @@ { "type": "npm", "script": "kill-watch-webd", - "label": "Kill Build Web Extensions", + "label": "Kill Web Ext - Build", "group": "build", "presentation": { "reveal": "never" diff --git a/.yarnrc b/.yarnrc index 0c5cf55a..1965e671 100644 --- a/.yarnrc +++ b/.yarnrc @@ -1,3 +1,3 @@ disturl "https://electronjs.org/headers" -target "11.3.0" +target "12.0.4" runtime "electron" diff --git a/ThirdPartyNotices.txt b/ThirdPartyNotices.txt index a4434f21..e30e71ee 100644 --- a/ThirdPartyNotices.txt +++ b/ThirdPartyNotices.txt @@ -10,7 +10,7 @@ This project incorporates components from the projects listed below. The origina 3. atom/language-coffee-script version 0.49.3 (https://github.com/atom/language-coffee-script) 4. atom/language-css version 0.44.4 (https://github.com/atom/language-css) 5. atom/language-java version 0.32.1 (https://github.com/atom/language-java) -6. atom/language-sass version 0.62.1 (https://github.com/atom/language-sass) +6. atom/language-sass version 0.61.4 (https://github.com/atom/language-sass) 7. atom/language-shellscript version 0.26.0 (https://github.com/atom/language-shellscript) 8. atom/language-xml version 0.35.2 (https://github.com/atom/language-xml) 9. better-go-syntax version 1.0.0 (https://github.com/jeff-hykin/better-go-syntax/ ) @@ -44,30 +44,31 @@ This project incorporates components from the projects listed below. The origina 37. microsoft/vscode-markdown-tm-grammar version 1.0.0 (https://github.com/microsoft/vscode-markdown-tm-grammar) 38. microsoft/vscode-mssql version 1.9.0 (https://github.com/microsoft/vscode-mssql) 39. mmims/language-batchfile version 0.7.5 (https://github.com/mmims/language-batchfile) -40. PowerShell/EditorSyntax version 1.0.0 (https://github.com/PowerShell/EditorSyntax) -41. rust-syntax version 0.4.3 (https://github.com/dustypomerleau/rust-syntax) -42. seti-ui version 0.1.0 (https://github.com/jesseweed/seti-ui) -43. shaders-tmLanguage version 0.1.0 (https://github.com/tgjones/shaders-tmLanguage) -44. textmate/asp.vb.net.tmbundle (https://github.com/textmate/asp.vb.net.tmbundle) -45. textmate/c.tmbundle (https://github.com/textmate/c.tmbundle) -46. textmate/diff.tmbundle (https://github.com/textmate/diff.tmbundle) -47. textmate/git.tmbundle (https://github.com/textmate/git.tmbundle) -48. textmate/groovy.tmbundle (https://github.com/textmate/groovy.tmbundle) -49. textmate/html.tmbundle (https://github.com/textmate/html.tmbundle) -50. textmate/ini.tmbundle (https://github.com/textmate/ini.tmbundle) -51. textmate/javascript.tmbundle (https://github.com/textmate/javascript.tmbundle) -52. textmate/lua.tmbundle (https://github.com/textmate/lua.tmbundle) -53. textmate/markdown.tmbundle (https://github.com/textmate/markdown.tmbundle) -54. textmate/perl.tmbundle (https://github.com/textmate/perl.tmbundle) -55. textmate/ruby.tmbundle (https://github.com/textmate/ruby.tmbundle) -56. textmate/yaml.tmbundle (https://github.com/textmate/yaml.tmbundle) -57. TypeScript-TmLanguage version 0.1.8 (https://github.com/microsoft/TypeScript-TmLanguage) -58. TypeScript-TmLanguage version 1.0.0 (https://github.com/microsoft/TypeScript-TmLanguage) -59. Unicode version 12.0.0 (https://home.unicode.org/) -60. vscode-codicons version 0.0.14 (https://github.com/microsoft/vscode-codicons) -61. vscode-logfile-highlighter version 2.11.0 (https://github.com/emilast/vscode-logfile-highlighter) -62. vscode-swift version 0.0.1 (https://github.com/owensd/vscode-swift) -63. Web Background Synchronization (https://github.com/WICG/background-sync) +40. NVIDIA/cuda-cpp-grammar (https://github.com/NVIDIA/cuda-cpp-grammar) +41. PowerShell/EditorSyntax version 1.0.0 (https://github.com/PowerShell/EditorSyntax) +42. rust-syntax version 0.4.3 (https://github.com/dustypomerleau/rust-syntax) +43. seti-ui version 0.1.0 (https://github.com/jesseweed/seti-ui) +44. shaders-tmLanguage version 0.1.0 (https://github.com/tgjones/shaders-tmLanguage) +45. textmate/asp.vb.net.tmbundle (https://github.com/textmate/asp.vb.net.tmbundle) +46. textmate/c.tmbundle (https://github.com/textmate/c.tmbundle) +47. textmate/diff.tmbundle (https://github.com/textmate/diff.tmbundle) +48. textmate/git.tmbundle (https://github.com/textmate/git.tmbundle) +49. textmate/groovy.tmbundle (https://github.com/textmate/groovy.tmbundle) +50. textmate/html.tmbundle (https://github.com/textmate/html.tmbundle) +51. textmate/ini.tmbundle (https://github.com/textmate/ini.tmbundle) +52. textmate/javascript.tmbundle (https://github.com/textmate/javascript.tmbundle) +53. textmate/lua.tmbundle (https://github.com/textmate/lua.tmbundle) +54. textmate/markdown.tmbundle (https://github.com/textmate/markdown.tmbundle) +55. textmate/perl.tmbundle (https://github.com/textmate/perl.tmbundle) +56. textmate/ruby.tmbundle (https://github.com/textmate/ruby.tmbundle) +57. textmate/yaml.tmbundle (https://github.com/textmate/yaml.tmbundle) +58. TypeScript-TmLanguage version 0.1.8 (https://github.com/microsoft/TypeScript-TmLanguage) +59. TypeScript-TmLanguage version 1.0.0 (https://github.com/microsoft/TypeScript-TmLanguage) +60. Unicode version 12.0.0 (https://home.unicode.org/) +61. vscode-codicons version 0.0.14 (https://github.com/microsoft/vscode-codicons) +62. vscode-logfile-highlighter version 2.11.0 (https://github.com/emilast/vscode-logfile-highlighter) +63. vscode-swift version 0.0.1 (https://github.com/owensd/vscode-swift) +64. Web Background Synchronization (https://github.com/WICG/background-sync) %% JuliaEditorSupport/atom-language-julia NOTICES AND INFORMATION BEGIN HERE @@ -1718,7 +1719,7 @@ END OF microsoft/vscode-mssql NOTICES AND INFORMATION ========================================= The MIT License (MIT) -Copyright (c) 2017 Michael Mims +Copyright (c) 2021 Michael Mims Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -1740,6 +1741,20 @@ THE SOFTWARE. ========================================= END OF mmims/language-batchfile NOTICES AND INFORMATION +%% NVIDIA/cuda-cpp-grammar NOTICES AND INFORMATION BEGIN HERE +========================================= +The MIT License (MIT) + +Copyright 2021 NVIDIA Corporation + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +========================================= +END OF NVIDIA/cuda-cpp-grammar NOTICES AND INFORMATION + %% PowerShell/EditorSyntax NOTICES AND INFORMATION BEGIN HERE ========================================= Copyright (c) Microsoft Corporation diff --git a/build/.cachesalt b/build/.cachesalt index 057dd5fd..01324414 100644 --- a/build/.cachesalt +++ b/build/.cachesalt @@ -1 +1 @@ -2021-01-28T11:52:11.376Z +2021-04-07T03:52:18.011Z diff --git a/build/azure-pipelines/darwin/app-entitlements.plist b/build/azure-pipelines/darwin/app-entitlements.plist index 90031d93..b43b4b28 100644 --- a/build/azure-pipelines/darwin/app-entitlements.plist +++ b/build/azure-pipelines/darwin/app-entitlements.plist @@ -8,5 +8,11 @@ com.apple.security.cs.allow-dyld-environment-variables + com.apple.security.device.audio-input + + com.apple.security.device.camera + + com.apple.security.automation.apple-events + diff --git a/build/azure-pipelines/darwin/product-build-darwin-sign.yml b/build/azure-pipelines/darwin/product-build-darwin-sign.yml index b978cf57..4ad8349c 100644 --- a/build/azure-pipelines/darwin/product-build-darwin-sign.yml +++ b/build/azure-pipelines/darwin/product-build-darwin-sign.yml @@ -1,7 +1,7 @@ steps: - task: NodeTool@0 inputs: - versionSpec: "12.18.3" + versionSpec: "14.x" - task: AzureKeyVault@1 displayName: "Azure Key Vault: Get Secrets" diff --git a/build/azure-pipelines/darwin/product-build-darwin.yml b/build/azure-pipelines/darwin/product-build-darwin.yml index d3346552..186920fe 100644 --- a/build/azure-pipelines/darwin/product-build-darwin.yml +++ b/build/azure-pipelines/darwin/product-build-darwin.yml @@ -1,7 +1,7 @@ steps: - task: NodeTool@0 inputs: - versionSpec: "12.18.3" + versionSpec: "14.x" - task: AzureKeyVault@1 displayName: "Azure Key Vault: Get Secrets" @@ -83,6 +83,7 @@ steps: set -e export npm_config_arch=$(VSCODE_ARCH) export npm_config_node_gyp=$(which node-gyp) + export npm_config_build_from_source=true export SDKROOT=/Applications/Xcode_12.2.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.0.sdk for i in {1..3}; do # try 3 times, for Terrapin @@ -107,20 +108,6 @@ steps: condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) displayName: Create node_modules archive - - script: | - set -e - export npm_config_arch=$(VSCODE_ARCH) - export npm_config_node_gyp=$(which node-gyp) - export npm_config_build_from_source=true - export SDKROOT=/Applications/Xcode_12.2.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.0.sdk - ls /Applications/Xcode_12.2.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/ - yarn electron-rebuild - # remove once https://github.com/prebuild/prebuild-install/pull/140 is merged and found in keytar - cd ./node_modules/keytar - node-gyp rebuild - displayName: Rebuild native modules for ARM64 - condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'arm64')) - # This script brings in the right resources (images, icons, etc) based on the quality (insiders, stable, exploration) - script: | set -e @@ -223,7 +210,7 @@ steps: VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-web-darwin" \ ./resources/server/test/test-web-integration.sh --browser webkit displayName: Run integration tests (Browser) - timeoutInMinutes: 7 + timeoutInMinutes: 10 condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) - script: | diff --git a/build/azure-pipelines/distro-build.yml b/build/azure-pipelines/distro-build.yml index 22d6983e..dc5d2803 100644 --- a/build/azure-pipelines/distro-build.yml +++ b/build/azure-pipelines/distro-build.yml @@ -8,7 +8,7 @@ pr: steps: - task: NodeTool@0 inputs: - versionSpec: "12.18.3" + versionSpec: "14.x" - task: AzureKeyVault@1 displayName: "Azure Key Vault: Get Secrets" diff --git a/build/azure-pipelines/exploration-build.yml b/build/azure-pipelines/exploration-build.yml index 719e6e46..ff3ce383 100644 --- a/build/azure-pipelines/exploration-build.yml +++ b/build/azure-pipelines/exploration-build.yml @@ -7,7 +7,7 @@ pr: none steps: - task: NodeTool@0 inputs: - versionSpec: "12.18.3" + versionSpec: "14.x" - task: AzureKeyVault@1 displayName: "Azure Key Vault: Get Secrets" @@ -27,10 +27,10 @@ steps: git config user.email "vscode@microsoft.com" git config user.name "VSCode" - git checkout origin/electron-11.x.y + git checkout origin/electron-12.x.y git merge origin/main # Push main branch into exploration branch - git push origin HEAD:electron-11.x.y + git push origin HEAD:electron-12.x.y displayName: Sync & Merge Exploration diff --git a/build/azure-pipelines/linux/product-build-alpine.yml b/build/azure-pipelines/linux/product-build-alpine.yml index 4a1b8a2c..8376c079 100644 --- a/build/azure-pipelines/linux/product-build-alpine.yml +++ b/build/azure-pipelines/linux/product-build-alpine.yml @@ -1,7 +1,7 @@ steps: - task: NodeTool@0 inputs: - versionSpec: "12.18.3" + versionSpec: "14.x" - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 inputs: diff --git a/build/azure-pipelines/linux/product-build-linux.yml b/build/azure-pipelines/linux/product-build-linux.yml index 60b5ea9f..cb06bf6a 100644 --- a/build/azure-pipelines/linux/product-build-linux.yml +++ b/build/azure-pipelines/linux/product-build-linux.yml @@ -1,7 +1,7 @@ steps: - task: NodeTool@0 inputs: - versionSpec: "12.18.3" + versionSpec: "14.x" - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 inputs: @@ -59,13 +59,6 @@ steps: condition: and(succeeded(), eq(variables.NODE_MODULES_RESTORED, 'true')) displayName: Extract node_modules cache - - script: | - set -e - npm install -g node-gyp@latest - node-gyp --version - displayName: Update node-gyp - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), eq(variables['VSCODE_ARCH'], 'x64')) - - script: | set -e npx https://aka.ms/enablesecurefeed standAlone @@ -86,7 +79,6 @@ steps: if [ "$VSCODE_ARCH" == "x64" ]; then export VSCODE_REMOTE_CC=$(which gcc-4.8) export VSCODE_REMOTE_CXX=$(which g++-4.8) - export VSCODE_REMOTE_NODE_GYP=$(which node-gyp) fi for i in {1..3}; do # try 3 times, for Terrapin @@ -177,7 +169,7 @@ steps: VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-web-linux-$(VSCODE_ARCH)" \ DISPLAY=:10 ./resources/server/test/test-web-integration.sh --browser chromium displayName: Run integration tests (Browser) - timeoutInMinutes: 7 + timeoutInMinutes: 10 condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) - script: | diff --git a/build/azure-pipelines/linux/snap-build-linux.yml b/build/azure-pipelines/linux/snap-build-linux.yml index c2f82083..f5e0288f 100644 --- a/build/azure-pipelines/linux/snap-build-linux.yml +++ b/build/azure-pipelines/linux/snap-build-linux.yml @@ -1,7 +1,7 @@ steps: - task: NodeTool@0 inputs: - versionSpec: "12.18.3" + versionSpec: "14.x" - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 inputs: diff --git a/build/azure-pipelines/product-build.yml b/build/azure-pipelines/product-build.yml index df60d23e..fd698a0e 100644 --- a/build/azure-pipelines/product-build.yml +++ b/build/azure-pipelines/product-build.yml @@ -271,7 +271,7 @@ stages: VSCODE_ARCH: arm64 steps: - template: darwin/product-build-darwin.yml - - ${{ if ne(variables['VSCODE_PUBLISH'], 'false') }}: + - ${{ if ne(variables['VSCODE_PUBLISH'], 'false') }}: - job: macOSARM64Sign dependsOn: - macOSARM64 diff --git a/build/azure-pipelines/product-compile.yml b/build/azure-pipelines/product-compile.yml index 5c386c94..52c7758c 100644 --- a/build/azure-pipelines/product-compile.yml +++ b/build/azure-pipelines/product-compile.yml @@ -1,7 +1,7 @@ steps: - task: NodeTool@0 inputs: - versionSpec: "12.x" + versionSpec: "14.x" - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 inputs: diff --git a/build/azure-pipelines/publish-types/publish-types.yml b/build/azure-pipelines/publish-types/publish-types.yml index 09964dc6..df8b665f 100644 --- a/build/azure-pipelines/publish-types/publish-types.yml +++ b/build/azure-pipelines/publish-types/publish-types.yml @@ -9,7 +9,7 @@ pr: none steps: - task: NodeTool@0 inputs: - versionSpec: "12.18.3" + versionSpec: "14.x" - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 inputs: diff --git a/build/azure-pipelines/release.yml b/build/azure-pipelines/release.yml index edd293c0..1c5ec73c 100644 --- a/build/azure-pipelines/release.yml +++ b/build/azure-pipelines/release.yml @@ -1,7 +1,7 @@ steps: - task: NodeTool@0 inputs: - versionSpec: "10.x" + versionSpec: "14.x" - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 inputs: diff --git a/build/azure-pipelines/sync-mooncake.yml b/build/azure-pipelines/sync-mooncake.yml index 280c9e63..6e379754 100644 --- a/build/azure-pipelines/sync-mooncake.yml +++ b/build/azure-pipelines/sync-mooncake.yml @@ -1,7 +1,7 @@ steps: - task: NodeTool@0 inputs: - versionSpec: "12.18.3" + versionSpec: "14.x" - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 inputs: diff --git a/build/azure-pipelines/web/product-build-web.yml b/build/azure-pipelines/web/product-build-web.yml index 0a8e1c36..772fe1c0 100644 --- a/build/azure-pipelines/web/product-build-web.yml +++ b/build/azure-pipelines/web/product-build-web.yml @@ -1,7 +1,7 @@ steps: - task: NodeTool@0 inputs: - versionSpec: "12.18.3" + versionSpec: "14.x" - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 inputs: diff --git a/build/azure-pipelines/win32/product-build-win32.yml b/build/azure-pipelines/win32/product-build-win32.yml index 5ca1f825..2dcaf8b2 100644 --- a/build/azure-pipelines/win32/product-build-win32.yml +++ b/build/azure-pipelines/win32/product-build-win32.yml @@ -1,7 +1,7 @@ steps: - task: NodeTool@0 inputs: - versionSpec: "12.18.3" + versionSpec: "14.x" - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 inputs: @@ -78,6 +78,7 @@ steps: . build/azure-pipelines/win32/retry.ps1 $ErrorActionPreference = "Stop" $env:npm_config_arch="$(VSCODE_ARCH)" + $env:npm_config_build_from_source="true" $env:CHILD_CONCURRENCY="1" retry { exec { yarn --frozen-lockfile } } env: @@ -179,7 +180,7 @@ steps: $ErrorActionPreference = "Stop" exec { $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\vscode-reh-web-win32-$(VSCODE_ARCH)"; .\resources\server\test\test-web-integration.bat --browser firefox } displayName: Run integration tests (Browser) - timeoutInMinutes: 7 + timeoutInMinutes: 10 condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false'), ne(variables['VSCODE_ARCH'], 'arm64')) - powershell: | diff --git a/build/builtin/main.js b/build/builtin/main.js index 42865157..3a11d363 100644 --- a/build/builtin/main.js +++ b/build/builtin/main.js @@ -23,7 +23,17 @@ ipcMain.handle('pickdir', async () => { }); app.once('ready', () => { - window = new BrowserWindow({ width: 800, height: 600, webPreferences: { nodeIntegration: true, webviewTag: true, enableWebSQL: false, nativeWindowOpen: true } }); + window = new BrowserWindow({ + width: 800, + height: 600, + webPreferences: { + nodeIntegration: true, + contextIsolation: false, + webviewTag: true, + enableWebSQL: false, + nativeWindowOpen: true + } + }); window.setMenuBarVisibility(false); window.loadURL(url.format({ pathname: path.join(__dirname, 'index.html'), protocol: 'file:', slashes: true })); // window.webContents.openDevTools(); diff --git a/build/darwin/create-universal-app.js b/build/darwin/create-universal-app.js index 6e4acb28..d455a5ce 100644 --- a/build/darwin/create-universal-app.js +++ b/build/darwin/create-universal-app.js @@ -33,6 +33,7 @@ async function main() { 'Credits.rtf', 'CodeResources', 'fsevents.node', + 'Info.plist', // TODO@deepak1556: regressed with 11.4.2 internal builds '.npmrc' ], outAppPath, diff --git a/build/darwin/create-universal-app.ts b/build/darwin/create-universal-app.ts index 0ec9e2a5..b13e8010 100644 --- a/build/darwin/create-universal-app.ts +++ b/build/darwin/create-universal-app.ts @@ -38,6 +38,7 @@ async function main() { 'Credits.rtf', 'CodeResources', 'fsevents.node', + 'Info.plist', // TODO@deepak1556: regressed with 11.4.2 internal builds '.npmrc' ], outAppPath, diff --git a/build/darwin/sign.js b/build/darwin/sign.js index e086b681..08993aeb 100644 --- a/build/darwin/sign.js +++ b/build/darwin/sign.js @@ -5,7 +5,9 @@ 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); const codesign = require("electron-osx-sign"); +const fs = require("fs-extra"); const path = require("path"); +const plist = require("plist"); const util = require("../lib/util"); const product = require("../../product.json"); async function main() { @@ -25,6 +27,7 @@ async function main() { const helperAppBaseName = product.nameShort; const gpuHelperAppName = helperAppBaseName + ' Helper (GPU).app'; const rendererHelperAppName = helperAppBaseName + ' Helper (Renderer).app'; + const infoPlistPath = path.resolve(appRoot, appName, 'Contents', 'Info.plist'); const defaultOpts = { app: path.join(appRoot, appName), platform: 'darwin', @@ -46,6 +49,14 @@ async function main() { } }); const gpuHelperOpts = Object.assign(Object.assign({}, defaultOpts), { app: path.join(appFrameworkPath, gpuHelperAppName), entitlements: path.join(baseDir, 'azure-pipelines', 'darwin', 'helper-gpu-entitlements.plist'), 'entitlements-inherit': path.join(baseDir, 'azure-pipelines', 'darwin', 'helper-gpu-entitlements.plist') }); const rendererHelperOpts = Object.assign(Object.assign({}, defaultOpts), { app: path.join(appFrameworkPath, rendererHelperAppName), entitlements: path.join(baseDir, 'azure-pipelines', 'darwin', 'helper-renderer-entitlements.plist'), 'entitlements-inherit': path.join(baseDir, 'azure-pipelines', 'darwin', 'helper-renderer-entitlements.plist') }); + let infoPlistString = await fs.readFile(infoPlistPath, 'utf8'); + let infoPlistJson = plist.parse(infoPlistString); + Object.assign(infoPlistJson, { + NSAppleEventsUsageDescription: 'An application in Visual Studio Code wants to use AppleScript.', + NSMicrophoneUsageDescription: 'An application in Visual Studio Code wants to use the Microphone.', + NSCameraUsageDescription: 'An application in Visual Studio Code wants to use the Camera.' + }); + await fs.writeFile(infoPlistPath, plist.build(infoPlistJson), 'utf8'); await codesign.signAsync(gpuHelperOpts); await codesign.signAsync(rendererHelperOpts); await codesign.signAsync(appOpts); diff --git a/build/darwin/sign.ts b/build/darwin/sign.ts index f1908b14..8f4e2cb2 100644 --- a/build/darwin/sign.ts +++ b/build/darwin/sign.ts @@ -6,7 +6,9 @@ 'use strict'; import * as codesign from 'electron-osx-sign'; +import * as fs from 'fs-extra'; import * as path from 'path'; +import * as plist from 'plist'; import * as util from '../lib/util'; import * as product from '../../product.json'; @@ -30,6 +32,7 @@ async function main(): Promise { const helperAppBaseName = product.nameShort; const gpuHelperAppName = helperAppBaseName + ' Helper (GPU).app'; const rendererHelperAppName = helperAppBaseName + ' Helper (Renderer).app'; + const infoPlistPath = path.resolve(appRoot, appName, 'Contents', 'Info.plist'); const defaultOpts: codesign.SignOptions = { app: path.join(appRoot, appName), @@ -68,6 +71,15 @@ async function main(): Promise { 'entitlements-inherit': path.join(baseDir, 'azure-pipelines', 'darwin', 'helper-renderer-entitlements.plist'), }; + let infoPlistString = await fs.readFile(infoPlistPath, 'utf8'); + let infoPlistJson = plist.parse(infoPlistString); + Object.assign(infoPlistJson, { + NSAppleEventsUsageDescription: 'An application in Visual Studio Code wants to use AppleScript.', + NSMicrophoneUsageDescription: 'An application in Visual Studio Code wants to use the Microphone.', + NSCameraUsageDescription: 'An application in Visual Studio Code wants to use the Camera.' + }); + await fs.writeFile(infoPlistPath, plist.build(infoPlistJson), 'utf8'); + await codesign.signAsync(gpuHelperOpts); await codesign.signAsync(rendererHelperOpts); await codesign.signAsync(appOpts as any); diff --git a/build/gulpfile.extensions.js b/build/gulpfile.extensions.js index e7df9718..9312f6c9 100644 --- a/build/gulpfile.extensions.js +++ b/build/gulpfile.extensions.js @@ -8,6 +8,7 @@ require('events').EventEmitter.defaultMaxListeners = 100; const gulp = require('gulp'); const path = require('path'); +const child_process = require('child_process'); const nodeUtil = require('util'); const es = require('event-stream'); const filter = require('gulp-filter'); @@ -39,7 +40,6 @@ const compilations = [ 'debug-server-ready/tsconfig.json', 'emmet/tsconfig.json', 'extension-editing/tsconfig.json', - 'git-ui/tsconfig.json', 'git/tsconfig.json', 'github-authentication/tsconfig.json', 'github/tsconfig.json', @@ -202,13 +202,17 @@ gulp.task(compileExtensionsBuildLegacyTask); //#region Extension media // Additional projects to webpack. These typically build code for webviews -const mediaCompilations = [ +const webpackMediaConfigFiles = [ 'markdown-language-features/webpack.config.js', - 'markdown-language-features/webpack.notebook.js', - 'notebook-markdown-extensions/webpack.notebook.js', 'simple-browser/webpack.config.js', ]; +// Additional projects to run esbuild on. These typically build code for webviews +const esbuildMediaScripts = [ + 'markdown-language-features/esbuild.js', + 'notebook-markdown-extensions/esbuild.js', +]; + const compileExtensionMediaTask = task.define('compile-extension-media', () => buildExtensionMedia(false)); gulp.task(compileExtensionMediaTask); exports.compileExtensionMediaTask = compileExtensionMediaTask; @@ -217,17 +221,24 @@ const watchExtensionMedia = task.define('watch-extension-media', () => buildExte gulp.task(watchExtensionMedia); exports.watchExtensionMedia = watchExtensionMedia; -function buildExtensionMedia(isWatch, outputRoot) { - const webpackConfigLocations = mediaCompilations.map(p => { +const compileExtensionMediaBuildTask = task.define('compile-extension-media-build', () => buildExtensionMedia(false, '.build/extensions')); +gulp.task(compileExtensionMediaBuildTask); + +async function buildExtensionMedia(isWatch, outputRoot) { + const webpackConfigLocations = webpackMediaConfigFiles.map(p => { return { configPath: path.join(extensionsPath, p), outputRoot: outputRoot ? path.join(root, outputRoot, path.dirname(p)) : undefined }; }); - return webpackExtensions('packaging extension media', isWatch, webpackConfigLocations); + return Promise.all([ + webpackExtensions('webpacking extension media', isWatch, webpackConfigLocations), + esbuildExtensions('esbuilding extension media', isWatch, esbuildMediaScripts.map(p => ({ + script: path.join(extensionsPath, p), + outputRoot: outputRoot ? path.join(root, outputRoot, path.dirname(p)) : undefined + }))), + ]); } -const compileExtensionMediaBuildTask = task.define('compile-extension-media-build', () => buildExtensionMedia(false, '.build/extensions')); -gulp.task(compileExtensionMediaBuildTask); //#endregion @@ -337,4 +348,44 @@ async function webpackExtensions(taskName, isWatch, webpackConfigLocations) { }); } +/** + * @param {string} taskName + * @param {boolean} isWatch + * @param {{ script: string, outputRoot?: string }}} scripts + */ +async function esbuildExtensions(taskName, isWatch, scripts) { + function reporter(/** @type {string} */ stdError, /** @type {string} */script) { + const matches = (stdError || '').match(/\> (.+): error: (.+)?/g); + fancyLog(`Finished ${ansiColors.green(taskName)} ${script} with ${matches ? matches.length : 0} errors.`); + for (const match of matches || []) { + fancyLog.error(match); + } + } + const tasks = scripts.map(({ script, outputRoot }) => { + return new Promise((resolve, reject) => { + const args = [script]; + if (isWatch) { + args.push('--watch'); + } + if (outputRoot) { + args.push('--outputRoot', outputRoot); + } + const proc = child_process.execFile(process.argv[0], args, {}, (error, _stdout, stderr) => { + if (error) { + return reject(error); + } + reporter(stderr, script); + if (stderr) { + return reject(); + } + return resolve(); + }); + + proc.stdout.on('data', (data) => { + fancyLog(`${ansiColors.green(taskName)}: ${data.toString('utf8')}`); + }); + }); + }); + return Promise.all(tasks); +} diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index a06f3f63..3eeba69d 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -223,7 +223,7 @@ function packageTask(platform, arch, sourceFolderName, destinationFolderName, op const dependenciesSrc = _.flatten(productionDependencies.map(d => path.relative(root, d.path)).map(d => [`${d}/**`, `!${d}/**/{test,tests}/**`])); const deps = gulp.src(dependenciesSrc, { base: '.', dot: true }) - .pipe(filter(['**', `!**/${config.version}/**`, '!**/bin/darwin-arm64-85/**', '!**/package-lock.json', '!**/yarn.lock', '!**/*.js.map'])) + .pipe(filter(['**', `!**/${config.version}/**`, '!**/bin/darwin-arm64-87/**', '!**/package-lock.json', '!**/yarn.lock', '!**/*.js.map'])) .pipe(util.cleanNodeModules(path.join(__dirname, '.moduleignore'))) .pipe(jsFilter) .pipe(util.rewriteSourceMappingURL(sourceMappingURLBase)) diff --git a/build/lib/i18n.resources.json b/build/lib/i18n.resources.json index aae62a1a..43ded2f2 100644 --- a/build/lib/i18n.resources.json +++ b/build/lib/i18n.resources.json @@ -30,10 +30,6 @@ "name": "vs/workbench/api/common", "project": "vscode-workbench" }, - { - "name": "vs/workbench/contrib/backup", - "project": "vscode-workbench" - }, { "name": "vs/workbench/contrib/bulkEdit", "project": "vscode-workbench" @@ -258,6 +254,10 @@ "name": "vs/workbench/services/authToken", "project": "vscode-workbench" }, + { + "name": "vs/workbench/services/backup", + "project": "vscode-workbench" + }, { "name": "vs/workbench/services/bulkEdit", "project": "vscode-workbench" @@ -397,6 +397,10 @@ { "name": "vs/workbench/services/gettingStarted", "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/host", + "project": "vscode-workbench" } ] } diff --git a/build/lib/test/i18n.test.js b/build/lib/test/i18n.test.js index 3dd10425..4d339c12 100644 --- a/build/lib/test/i18n.test.js +++ b/build/lib/test/i18n.test.js @@ -21,20 +21,20 @@ suite('XLF Parser Tests', () => { }); test('XLF to keys & messages conversion', () => { i18n.XLF.parse(sampleTranslatedXlf).then(function (resolvedFiles) { - assert.deepEqual(resolvedFiles[0].messages, translatedMessages); + assert.deepStrictEqual(resolvedFiles[0].messages, translatedMessages); assert.strictEqual(resolvedFiles[0].originalFilePath, originalFilePath); }); }); test('JSON file source path to Transifex resource match', () => { const editorProject = 'vscode-editor', workbenchProject = 'vscode-workbench'; const platform = { name: 'vs/platform', project: editorProject }, editorContrib = { name: 'vs/editor/contrib', project: editorProject }, editor = { name: 'vs/editor', project: editorProject }, base = { name: 'vs/base', project: editorProject }, code = { name: 'vs/code', project: workbenchProject }, workbenchParts = { name: 'vs/workbench/contrib/html', project: workbenchProject }, workbenchServices = { name: 'vs/workbench/services/textfile', project: workbenchProject }, workbench = { name: 'vs/workbench', project: workbenchProject }; - assert.deepEqual(i18n.getResource('vs/platform/actions/browser/menusExtensionPoint'), platform); - assert.deepEqual(i18n.getResource('vs/editor/contrib/clipboard/browser/clipboard'), editorContrib); - assert.deepEqual(i18n.getResource('vs/editor/common/modes/modesRegistry'), editor); - assert.deepEqual(i18n.getResource('vs/base/common/errorMessage'), base); - assert.deepEqual(i18n.getResource('vs/code/electron-main/window'), code); - assert.deepEqual(i18n.getResource('vs/workbench/contrib/html/browser/webview'), workbenchParts); - assert.deepEqual(i18n.getResource('vs/workbench/services/textfile/node/testFileService'), workbenchServices); - assert.deepEqual(i18n.getResource('vs/workbench/browser/parts/panel/panelActions'), workbench); + assert.deepStrictEqual(i18n.getResource('vs/platform/actions/browser/menusExtensionPoint'), platform); + assert.deepStrictEqual(i18n.getResource('vs/editor/contrib/clipboard/browser/clipboard'), editorContrib); + assert.deepStrictEqual(i18n.getResource('vs/editor/common/modes/modesRegistry'), editor); + assert.deepStrictEqual(i18n.getResource('vs/base/common/errorMessage'), base); + assert.deepStrictEqual(i18n.getResource('vs/code/electron-main/window'), code); + assert.deepStrictEqual(i18n.getResource('vs/workbench/contrib/html/browser/webview'), workbenchParts); + assert.deepStrictEqual(i18n.getResource('vs/workbench/services/textfile/node/testFileService'), workbenchServices); + assert.deepStrictEqual(i18n.getResource('vs/workbench/browser/parts/panel/panelActions'), workbench); }); }); diff --git a/build/lib/test/i18n.test.ts b/build/lib/test/i18n.test.ts index 29a0f665..ab0924c4 100644 --- a/build/lib/test/i18n.test.ts +++ b/build/lib/test/i18n.test.ts @@ -23,8 +23,8 @@ suite('XLF Parser Tests', () => { }); test('XLF to keys & messages conversion', () => { - i18n.XLF.parse(sampleTranslatedXlf).then(function(resolvedFiles) { - assert.deepEqual(resolvedFiles[0].messages, translatedMessages); + i18n.XLF.parse(sampleTranslatedXlf).then(function (resolvedFiles) { + assert.deepStrictEqual(resolvedFiles[0].messages, translatedMessages); assert.strictEqual(resolvedFiles[0].originalFilePath, originalFilePath); }); }); @@ -40,15 +40,15 @@ suite('XLF Parser Tests', () => { code = { name: 'vs/code', project: workbenchProject }, workbenchParts = { name: 'vs/workbench/contrib/html', project: workbenchProject }, workbenchServices = { name: 'vs/workbench/services/textfile', project: workbenchProject }, - workbench = { name: 'vs/workbench', project: workbenchProject}; + workbench = { name: 'vs/workbench', project: workbenchProject }; - assert.deepEqual(i18n.getResource('vs/platform/actions/browser/menusExtensionPoint'), platform); - assert.deepEqual(i18n.getResource('vs/editor/contrib/clipboard/browser/clipboard'), editorContrib); - assert.deepEqual(i18n.getResource('vs/editor/common/modes/modesRegistry'), editor); - assert.deepEqual(i18n.getResource('vs/base/common/errorMessage'), base); - assert.deepEqual(i18n.getResource('vs/code/electron-main/window'), code); - assert.deepEqual(i18n.getResource('vs/workbench/contrib/html/browser/webview'), workbenchParts); - assert.deepEqual(i18n.getResource('vs/workbench/services/textfile/node/testFileService'), workbenchServices); - assert.deepEqual(i18n.getResource('vs/workbench/browser/parts/panel/panelActions'), workbench); + assert.deepStrictEqual(i18n.getResource('vs/platform/actions/browser/menusExtensionPoint'), platform); + assert.deepStrictEqual(i18n.getResource('vs/editor/contrib/clipboard/browser/clipboard'), editorContrib); + assert.deepStrictEqual(i18n.getResource('vs/editor/common/modes/modesRegistry'), editor); + assert.deepStrictEqual(i18n.getResource('vs/base/common/errorMessage'), base); + assert.deepStrictEqual(i18n.getResource('vs/code/electron-main/window'), code); + assert.deepStrictEqual(i18n.getResource('vs/workbench/contrib/html/browser/webview'), workbenchParts); + assert.deepStrictEqual(i18n.getResource('vs/workbench/services/textfile/node/testFileService'), workbenchServices); + assert.deepStrictEqual(i18n.getResource('vs/workbench/browser/parts/panel/panelActions'), workbench); }); -}); \ No newline at end of file +}); diff --git a/build/lib/treeshaking.js b/build/lib/treeshaking.js index 19c45a10..3abe0208 100644 --- a/build/lib/treeshaking.js +++ b/build/lib/treeshaking.js @@ -311,7 +311,7 @@ function markNodes(ts, languageService, options) { setColor(node, 0 /* White */); // add to black queue enqueue_black(node); - // // move from one queue to the other + // move from one queue to the other // black_queue.push(node); // setColor(node, NodeColor.Black); return; diff --git a/build/lib/treeshaking.ts b/build/lib/treeshaking.ts index cb042679..f941e979 100644 --- a/build/lib/treeshaking.ts +++ b/build/lib/treeshaking.ts @@ -410,7 +410,7 @@ function markNodes(ts: typeof import('typescript'), languageService: ts.Language // add to black queue enqueue_black(node); - // // move from one queue to the other + // move from one queue to the other // black_queue.push(node); // setColor(node, NodeColor.Black); return; diff --git a/build/monaco/monaco.d.ts.recipe b/build/monaco/monaco.d.ts.recipe index 0449ec6b..bb053136 100644 --- a/build/monaco/monaco.d.ts.recipe +++ b/build/monaco/monaco.d.ts.recipe @@ -91,6 +91,7 @@ declare namespace monaco.languages { declare namespace monaco.worker { +#include(vs/editor/common/model/mirrorTextModel): IMirrorTextModel #includeAll(vs/editor/common/services/editorSimpleWorker;): } diff --git a/build/npm/dirs.js b/build/npm/dirs.js index 202219eb..ce069fbe 100644 --- a/build/npm/dirs.js +++ b/build/npm/dirs.js @@ -17,7 +17,6 @@ exports.dirs = [ 'extensions/emmet', 'extensions/extension-editing', 'extensions/git', - 'extensions/git-ui', 'extensions/github', 'extensions/github-authentication', 'extensions/grunt', diff --git a/build/npm/preinstall.js b/build/npm/preinstall.js index 8d51e7a7..09dac06d 100644 --- a/build/npm/preinstall.js +++ b/build/npm/preinstall.js @@ -7,8 +7,8 @@ let err = false; const majorNodeVersion = parseInt(/^(\d+)\./.exec(process.versions.node)[1]); -if (majorNodeVersion < 10 || majorNodeVersion >= 16) { - console.error('\033[1;31m*** Please use node >=10 and <=16.\033[0;0m'); +if (majorNodeVersion < 10 || majorNodeVersion >= 17) { + console.error('\033[1;31m*** Please use node.js versions >=10 and <=17.\033[0;0m'); err = true; } diff --git a/build/package.json b/build/package.json index b0e1e837..cd119a5f 100644 --- a/build/package.json +++ b/build/package.json @@ -24,7 +24,7 @@ "@types/minimist": "^1.2.1", "@types/mkdirp": "^1.0.1", "@types/mocha": "^8.2.0", - "@types/node": "^12.19.9", + "@types/node": "^14.14.37", "@types/p-limit": "^2.2.0", "@types/plist": "^3.0.2", "@types/pump": "^1.0.1", @@ -52,7 +52,7 @@ "p-limit": "^3.1.0", "plist": "^3.0.1", "source-map": "0.6.1", - "typescript": "^4.3.0-dev.20210305", + "typescript": "^4.3.0-dev.20210426", "vsce": "1.48.0", "vscode-universal": "deepak1556/universal#61454d96223b774c53cda10f72c2098c0ce02d58" }, diff --git a/build/yarn.lock b/build/yarn.lock index 870750b9..38b1f00f 100644 --- a/build/yarn.lock +++ b/build/yarn.lock @@ -371,16 +371,16 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.51.tgz#b31d716fb8d58eeb95c068a039b9b6292817d5fb" integrity sha512-El3+WJk2D/ppWNd2X05aiP5l2k4EwF7KwheknQZls+I26eSICoWRhRIJ56jGgw2dqNGQ5LtNajmBU2ajS28EvQ== -"@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/node@^14.14.21": version "14.14.22" resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.22.tgz#0d29f382472c4ccf3bd96ff0ce47daf5b7b84b18" integrity sha512-g+f/qj/cNcqKkc3tFqlXOYjrmZA+jNBiDzbP3kH+B+otKFqAdPgVTGP1IeKRdMml/aE69as5S4FqtxAbl+LaMw== +"@types/node@^14.14.37": + version "14.14.37" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.37.tgz#a3dd8da4eb84a996c36e331df98d82abd76b516e" + integrity sha512-XYmBiy+ohOR4Lh5jE379fV2IU+6Jn4g5qASinhitfyO71b/sCo6MKsMLF5tc7Zf2CE8hViVQyYSobJNke8OvUw== + "@types/p-limit@^2.2.0": version "2.2.0" resolved "https://registry.yarnpkg.com/@types/p-limit/-/p-limit-2.2.0.tgz#94a608e9b258a6c6156a13d1a14fd720dba70b97" @@ -1884,10 +1884,10 @@ typescript@^4.1.3: resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.3.tgz#519d582bd94cba0cf8934c7d8e8467e473f53bb7" integrity sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg== -typescript@^4.3.0-dev.20210305: - version "4.3.0-dev.20210305" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.0-dev.20210305.tgz#5e354b303c435f84a25fa82f45e9c412bfd5bd8f" - integrity sha512-OTALeeen7kl6FU1tcXRk3h+WY1NnE5lwyTGAZUCt9hw6tdaifgLXqEkfw9NHJc0xKV6PnU8GgnYFFVVyHLPSHg== +typescript@^4.3.0-dev.20210426: + version "4.3.0-dev.20210426" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.0-dev.20210426.tgz#00198cb8828f6a04b4e0ae32554a486bf7137a53" + integrity sha512-8YTqlzf3w8O8XwnnRlwRV2rswu7V7WEPUnAnH1BPPMrr06thNByMjIadA5SDW3tUJc1MG8Uj3NgZYocU5fWTVg== uc.micro@^1.0.1, uc.micro@^1.0.5: version "1.0.5" diff --git a/cglicenses.json b/cglicenses.json index a0131e4f..621c771c 100644 --- a/cglicenses.json +++ b/cglicenses.json @@ -46,42 +46,6 @@ "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE." ] }, - { - // Reason: The license at https://git.coolaj86.com/coolaj86/atob.js/src/branch/master/LICENSE - // cannot be found by the OSS tool automatically. - "name": "atob", - "fullLicenseText": [ - "The MIT License (MIT)", - "", - "Copyright (c) 2015 AJ ONeal", - "", - "Permission is hereby granted, free of charge, to any person obtaining a copy", - "of this software and associated documentation files (the \"Software\"), to deal", - "in the Software without restriction, including without limitation the rights", - "to use, copy, modify, merge, publish, distribute, sublicense, and/or sell", - "copies of the Software, and to permit persons to whom the Software is", - "furnished to do so, subject to the following conditions:", - "", - "The above copyright notice and this permission notice shall be included in all", - "copies or substantial portions of the Software.", - "", - "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR", - "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,", - "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE", - "AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER", - "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,", - "OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE", - "SOFTWARE." - ] - }, - { - // Reason: The license at https://github.com/microsoft/tslib/blob/master/LICENSE.txt - // does not include a clear Copyright statement. - "name": "tslib", - "prependLicenseText": [ - "Copyright (c) Microsoft Corporation. All rights reserved." - ] - }, { // Reason: The license at https://github.com/rbuckton/reflect-metadata/blob/master/LICENSE // does not include a clear Copyright statement (it's in https://github.com/rbuckton/reflect-metadata/blob/master/CopyrightNotice.txt). @@ -91,87 +55,8 @@ ] }, { - // Reason: The license at https://github.com/reem/rust-unreachable/blob/master/LICENSE-MIT - // cannot be found by the OSS tool automatically. - "name": "reem/rust-unreachable", - "fullLicenseText": [ - "Copyright (c) 2015 The rust-unreachable Developers", - "", - "Permission is hereby granted, free of charge, to any person obtaining a copy", - "of this software and associated documentation files (the \"Software\"), to deal", - "in the Software without restriction, including without limitation the rights", - "to use, copy, modify, merge, publish, distribute, sublicense, and/or sell", - "copies of the Software, and to permit persons to whom the Software is", - "furnished to do so, subject to the following conditions:", - "", - "The above copyright notice and this permission notice shall be included in all", - "copies or substantial portions of the Software.", - "", - "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR", - "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,", - "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE", - "AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER", - "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,", - "OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE", - "SOFTWARE." - ] - }, - { - // Reason: The license at https://github.com/reem/rust-void/blob/master/LICENSE-MIT - // cannot be found by the OSS tool automatically. - "name": "reem/rust-void", - "fullLicenseText": [ - "Copyright (c) 2015 The rust-void Developers", - "", - "Permission is hereby granted, free of charge, to any person obtaining a copy", - "of this software and associated documentation files (the \"Software\"), to deal", - "in the Software without restriction, including without limitation the rights", - "to use, copy, modify, merge, publish, distribute, sublicense, and/or sell", - "copies of the Software, and to permit persons to whom the Software is", - "furnished to do so, subject to the following conditions:", - "", - "The above copyright notice and this permission notice shall be included in all", - "copies or substantial portions of the Software.", - "", - "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR", - "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,", - "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE", - "AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER", - "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,", - "OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE", - "SOFTWARE." - ] - }, - { - // Reason: The license at https://github.com/mrhooray/crc-rs/blob/master/LICENSE-MIT - // cannot be found by the OSS tool automatically. - "name": "mrhooray/crc-rs", - "fullLicenseText": [ - "MIT License", - "", - "Copyright (c) 2017 crc-rs Developers", - "", - "Permission is hereby granted, free of charge, to any person obtaining a copy", - "of this software and associated documentation files (the \"Software\"), to deal", - "in the Software without restriction, including without limitation the rights", - "to use, copy, modify, merge, publish, distribute, sublicense, and/or sell", - "copies of the Software, and to permit persons to whom the Software is", - "furnished to do so, subject to the following conditions:", - "", - "The above copyright notice and this permission notice shall be included in all", - "copies or substantial portions of the Software.", - "", - "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR", - "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,", - "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE", - "AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER", - "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,", - "OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE", - "SOFTWARE." - ] - }, - { - // Reason: The license at https://github.com/floatdrop/pinkie/blob/master/license + // Reason: The npm package lacks a repoURL field + // So the license at https://github.com/floatdrop/pinkie/blob/master/license // cannot be found by the OSS tool automatically. "name": "pinkie", "fullLicenseText": [ @@ -205,139 +90,25 @@ ] }, { - // Reason: The license at https://github.com/justmoon/node-extend/blob/main/LICENSE - // cannot be found by the OSS tool automatically. - "name": "extend", + // Reason: The license cannot be found by the tool due to access controls on the repository + "name": "vscode-tas-client", "fullLicenseText": [ - "The MIT License (MIT)", + "MIT License", + "Copyright (c) 2020 - present Microsoft Corporation", "", - "Copyright (c) 2014 Stefan Thomas", - "", - "Permission is hereby granted, free of charge, to any person obtaining", - "a copy of this software and associated documentation files (the", - "\"Software\"), to deal in the Software without restriction, including", - "without limitation the rights to use, copy, modify, merge, publish,", - "distribute, sublicense, and/or sell copies of the Software, and to", - "permit persons to whom the Software is furnished to do so, subject to", - "the following conditions:", - "", - "The above copyright notice and this permission notice shall be", - "included in all copies or substantial portions of the Software.", - "", - "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,", - "EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF", - "MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND", - "NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE", - "LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION", - "OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION", - "WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE." - ] - }, - { - // Reason: The license at https://github.com/retep998/winapi-rs/blob/0.3/LICENSE-MIT - // cannot be found by the OSS tool automatically. - "name": "retep998/winapi-rs", - "fullLicenseText": [ - "Copyright (c) 2015-2018 The winapi-rs Developers", - "", - "Permission is hereby granted, free of charge, to any person obtaining a copy", - "of this software and associated documentation files (the \"Software\"), to deal", - "in the Software without restriction, including without limitation the rights", - "to use, copy, modify, merge, publish, distribute, sublicense, and/or sell", - "copies of the Software, and to permit persons to whom the Software is", - "furnished to do so, subject to the following conditions:", - "", - "The above copyright notice and this permission notice shall be included in all", - "copies or substantial portions of the Software.", - "", - "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR", - "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,", - "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE", - "AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER", - "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,", - "OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE", - "SOFTWARE." - ] - }, - { - // Reason: The license at https://github.com/digitaldesignlabs/es6-promisify/blob/main/LICENSE - // cannot be found by the OSS tool automatically. - "name": "es6-promisify", - "fullLicenseText": [ - "Copyright (c) 2014 Mike Hall / Digital Design Labs", - "", - "Permission is hereby granted, free of charge, to any person obtaining a copy", - "of this software and associated documentation files (the \"Software\"), to deal", - "in the Software without restriction, including without limitation the rights", - "to use, copy, modify, merge, publish, distribute, sublicense, and/or sell", - "copies of the Software, and to permit persons to whom the Software is", - "furnished to do so, subject to the following conditions:", - "", - "The above copyright notice and this permission notice shall be included in all", - "copies or substantial portions of the Software.", - "", - "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR", - "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,", - "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE", - "AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER", - "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,", - "OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE", - "SOFTWARE." - ] - }, - { - // Reason: The license at https://github.com/zkat/json-parse-better-errors/blob/latest/LICENSE.md - // cannot be found by the OSS tool automatically. - "name": "json-parse-better-errors", - "fullLicenseText": [ - "Copyright 2017 Kat Marchán", - "", - "Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the", - "\"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute,", - "sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following", - "conditions:", + "Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:", "", "The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.", "", - "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE", - "WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS", - "OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR", - "OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE." + "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE." ] }, { - // Reason: The license at https://github.com/time-rs/time/blob/main/LICENSE-MIT - // cannot be found by the OSS tool automatically. - "name": "time-rs/time", + // Reason: The license cannot be found by the tool due to access controls on the repository + "name": "tas-client", "fullLicenseText": [ - "Copyright (c) 2019 Jacob Pratt", - "", - "Permission is hereby granted, free of charge, to any person obtaining a copy", - "of this software and associated documentation files (the \"Software\"), to deal", - "in the Software without restriction, including without limitation the rights", - "to use, copy, modify, merge, publish, distribute, sublicense, and/or sell", - "copies of the Software, and to permit persons to whom the Software is", - "furnished to do so, subject to the following conditions:", - "", - "The above copyright notice and this permission notice shall be included in all", - "copies or substantial portions of the Software.", - "", - "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR", - "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,", - "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE", - "AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER", - "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,", - "OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE", - "SOFTWARE." - ] - }, - { - // Reason: The license at https://github.com/colorjs/color-name/blob/master/LICENSE - // cannot be found by the OSS tool automatically. - "name": "color-name", - "fullLicenseText": [ - "The MIT License (MIT)", - "Copyright (c) 2015 Dmitry Ivanov", + "MIT License", + "Copyright (c) 2020 - present Microsoft Corporation", "", "Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:", "", @@ -361,53 +132,8 @@ ] }, { - // Reason: The license at https://github.com/acornjs/acorn/blob/master/acorn/LICENSE - // cannot be found by the OSS tool automatically. - "name": "acorn", - "fullLicenseText": [ - "MIT License", - "Copyright (C) 2012-2018 by various contributors (see AUTHORS)", - "", - "Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:", - "", - "The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.", - "", - "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE." - ] - }, - { - // Reason: The license at https://github.com/acornjs/acorn/blob/master/acorn-loose/LICENSE - // cannot be found by the OSS tool automatically. - "name": "acorn-loose", - "fullLicenseText": [ - "MIT License", - "Copyright (C) 2012-2018 by various contributors (see AUTHORS)", - "", - "Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:", - "", - "The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.", - "", - "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE." - ] - }, - { - // Reason: The license at https://github.com/jquery/esprima/blob/master/LICENSE.BSD - // cannot be found by the OSS tool automatically. - "name": "esprima", - "fullLicenseText": [ - "BSD 2-Clause \"Simplified\" License", - "Copyright JS Foundation and other contributors, https://js.foundation/", - "", - "Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:", - " * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.", - " * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.", - "", - "THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." - ] - }, - { - // Reason: The license at https://github.com/LinusU/load-yaml-file/blob/master/readme.md - // cannot be found by the OSS tool automatically. + // Reason: Repository lacks license text. + // https://github.com/LinusU/load-yaml-file/blob/master/package.json declares MIT. "name": "load-yaml-file", "fullLicenseText": [ "MIT License", @@ -419,5 +145,48 @@ "", "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE." ] + }, + { + // Reason: Repository lacks license text. + // https://github.com/othiym23/emitter-listener/blob/master/package.json declares BSD-2-Clause. + "name": "emitter-listener", + "fullLicenseText": [ + "BSD 2-Clause \"Simplified\" License", + "Copyright (c) 2018, Forrest L Norvell ", + "", + "Redistribution and use in source and binary forms, with or without", + "modification, are permitted provided that the following conditions are met:", + "", + "1. Redistributions of source code must retain the above copyright notice, this", + " list of conditions and the following disclaimer.", + "2. Redistributions in binary form must reproduce the above copyright notice,", + " this list of conditions and the following disclaimer in the documentation", + " and/or other materials provided with the distribution.", + "", + "THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND", + "ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED", + "WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE", + "DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR", + "ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES", + "(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;", + "LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND", + "ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT", + "(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS", + "SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." + ] + }, + { + // Reason: Repository has been deleted (package.json declares MIT). + "name": "vscode-js-debug-cdp-proxy-api", + "fullLicenseText": [ + "MIT License", + "Copyright (c) Manuel Alabor", + "", + "Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:", + "", + "The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.", + "", + "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE." + ] } ] diff --git a/cgmanifest.json b/cgmanifest.json index 3349aa8b..aa54e324 100644 --- a/cgmanifest.json +++ b/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "chromium", "repositoryUrl": "https://chromium.googlesource.com/chromium/src", - "commitHash": "c0dfcf99c0bbc9c4763c70e5034eb1a970a9ff3b" + "commitHash": "5342041f85833c038dcbc5632d62fc10f7592323" } }, "licenseDetail": [ @@ -40,7 +40,7 @@ "SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." ], "isOnlyProductionDependency": true, - "version": "87.0.4280.141" + "version": "89.0.4389.114" }, { "component": { @@ -48,11 +48,11 @@ "git": { "name": "nodejs", "repositoryUrl": "https://github.com/nodejs/node", - "commitHash": "e3e0927bb93ed92bcdfe81e7ad9af3d78ccc74fb" + "commitHash": "bd60e93357a118204ea238d94e7a9e4209d93062" } }, "isOnlyProductionDependency": true, - "version": "12.18.3" + "version": "14.16.0" }, { "component": { @@ -60,12 +60,12 @@ "git": { "name": "electron", "repositoryUrl": "https://github.com/electron/electron", - "commitHash": "1631fc9b0c0d84283105a669c98bbeb8bab39109" + "commitHash": "9ce7c512475aa6aa91417a3b08e19f85a8587a30" } }, "isOnlyProductionDependency": true, "license": "MIT", - "version": "11.3.0" + "version": "12.0.4" }, { "component": { diff --git a/extensions/configuration-editing/build/tsconfig.json b/extensions/configuration-editing/build/tsconfig.json index 0ac2f0de..0f4c9da0 100644 --- a/extensions/configuration-editing/build/tsconfig.json +++ b/extensions/configuration-editing/build/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../shared.tsconfig.json", + "extends": "../../tsconfig.base.json", "compilerOptions": { "resolveJsonModule": true, "outDir": "./out" diff --git a/extensions/configuration-editing/images/icon.png b/extensions/configuration-editing/images/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..2af18a8dc1bdad21d74f2d6046bbcd99799f0a02 GIT binary patch literal 2825 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 Ygf#TsPM}V9ZvSZ=>|AV1Z7|XQ1B{=AyZ`_I literal 0 HcmV?d00001 diff --git a/extensions/configuration-editing/package.json b/extensions/configuration-editing/package.json index f1670e83..324e0c80 100644 --- a/extensions/configuration-editing/package.json +++ b/extensions/configuration-editing/package.json @@ -8,6 +8,7 @@ "engines": { "vscode": "^1.0.0" }, + "icon": "images/icon.png", "activationEvents": [ "onLanguage:json", "onLanguage:jsonc" @@ -22,6 +23,12 @@ "jsonc-parser": "^2.2.1", "vscode-nls": "^4.1.1" }, + "capabilities": { + "virtualWorkspaces": true, + "untrustedWorkspaces": { + "supported": true + } + }, "contributes": { "languages": [ { diff --git a/extensions/configuration-editing/src/extensionsProposals.ts b/extensions/configuration-editing/src/extensionsProposals.ts index 7fef9836..458f3be4 100644 --- a/extensions/configuration-editing/src/extensionsProposals.ts +++ b/extensions/configuration-editing/src/extensionsProposals.ts @@ -33,3 +33,28 @@ export function provideInstalledExtensionProposals(existing: string[], additiona return undefined; } +export function provideWorkspaceTrustExtensionProposals(existing: string[], range: vscode.Range): vscode.ProviderResult { + if (Array.isArray(existing)) { + const extensions = vscode.extensions.all.filter(e => e.packageJSON.main); + const extensionProposals = extensions.filter(e => existing.indexOf(e.id) === -1); + if (extensionProposals.length) { + return extensionProposals.map(e => { + const item = new vscode.CompletionItem(e.id); + const insertText = `"${e.id}": {\n\t"supported": false,\n\t"version": "${e.packageJSON.version}"\n}`; + item.kind = vscode.CompletionItemKind.Value; + item.insertText = insertText; + item.range = range; + item.filterText = insertText; + return item; + }); + } else { + const example = new vscode.CompletionItem(localize('exampleExtension', "Example")); + example.insertText = '"vscode.csharp: {\n\t"supported": false,\n\t"version": "0.0.0"\n}`;"'; + example.kind = vscode.CompletionItemKind.Value; + example.range = range; + return [example]; + } + } + + return undefined; +} diff --git a/extensions/configuration-editing/src/settingsDocumentHelper.ts b/extensions/configuration-editing/src/settingsDocumentHelper.ts index f3461f0c..a21ec18e 100644 --- a/extensions/configuration-editing/src/settingsDocumentHelper.ts +++ b/extensions/configuration-editing/src/settingsDocumentHelper.ts @@ -6,7 +6,7 @@ import * as vscode from 'vscode'; import { getLocation, Location, parse } from 'jsonc-parser'; import * as nls from 'vscode-nls'; -import { provideInstalledExtensionProposals } from './extensionsProposals'; +import { provideInstalledExtensionProposals, provideWorkspaceTrustExtensionProposals } from './extensionsProposals'; const localize = nls.loadMessageBundle(); @@ -60,6 +60,15 @@ export class SettingsDocument { return provideInstalledExtensionProposals(alreadyConfigured, `: [\n\t"ui"\n]`, range, true); } + // extensions.supportUntrustedWorkspaces + if (location.path[0] === 'extensions.supportUntrustedWorkspaces' && location.path.length === 2 && location.isAtPropertyKey) { + let alreadyConfigured: string[] = []; + try { + alreadyConfigured = Object.keys(parse(this.document.getText())['extensions.supportUntrustedWorkspaces']); + } catch (e) {/* ignore error */ } + return provideWorkspaceTrustExtensionProposals(alreadyConfigured, range); + } + return this.provideLanguageOverridesCompletionItems(location, position); } diff --git a/extensions/configuration-editing/tsconfig.json b/extensions/configuration-editing/tsconfig.json index 296ddb38..070854d6 100644 --- a/extensions/configuration-editing/tsconfig.json +++ b/extensions/configuration-editing/tsconfig.json @@ -1,9 +1,9 @@ { - "extends": "../shared.tsconfig.json", + "extends": "../tsconfig.base.json", "compilerOptions": { "outDir": "./out" }, "include": [ "src/**/*" ] -} \ No newline at end of file +} diff --git a/extensions/cpp/build/update-grammars.js b/extensions/cpp/build/update-grammars.js index a09e9470..dd265436 100644 --- a/extensions/cpp/build/update-grammars.js +++ b/extensions/cpp/build/update-grammars.js @@ -10,6 +10,8 @@ updateGrammar.update('jeff-hykin/cpp-textmate-grammar', 'syntaxes/c.tmLanguage.j updateGrammar.update('jeff-hykin/cpp-textmate-grammar', 'syntaxes/cpp.tmLanguage.json', './syntaxes/cpp.tmLanguage.json', undefined, 'master', 'source/languages/cpp/'); updateGrammar.update('jeff-hykin/cpp-textmate-grammar', 'syntaxes/cpp.embedded.macro.tmLanguage.json', './syntaxes/cpp.embedded.macro.tmLanguage.json', undefined, 'master', 'source/languages/cpp/'); +updateGrammar.update('NVIDIA/cuda-cpp-grammar', 'syntaxes/cuda-cpp.tmLanguage.json', './syntaxes/cuda-cpp.tmLanguage.json', undefined, 'master'); + // `source.c.platform` which is still included by other grammars updateGrammar.update('textmate/c.tmbundle', 'Syntaxes/Platform.tmLanguage', './syntaxes/platform.tmLanguage.json'); diff --git a/extensions/cpp/cgmanifest.json b/extensions/cpp/cgmanifest.json index a483664d..54ee5c88 100644 --- a/extensions/cpp/cgmanifest.json +++ b/extensions/cpp/cgmanifest.json @@ -39,6 +39,19 @@ ], "license": "TextMate Bundle License", "version": "0.0.0" + }, + { + "component": { + "type": "git", + "git": { + "name": "NVIDIA/cuda-cpp-grammar", + "repositoryUrl": "https://github.com/NVIDIA/cuda-cpp-grammar", + "commitHash": "81e88eaec5170aa8585736c63627c73e3589998c" + } + }, + "license": "MIT", + "version": "0.0.0", + "description": "The file syntaxes/cuda-cpp.tmLanguage.json was derived from https://github.com/jeff-hykin/cpp-textmate-grammar, which was derived from https://github.com/atom/language-c, which was originally converted from the C TextMate bundle https://github.com/textmate/c.tmbundle." } ], "version": 1 diff --git a/extensions/cpp/language-configuration.json b/extensions/cpp/language-configuration.json index d430c4d1..9a88814f 100644 --- a/extensions/cpp/language-configuration.json +++ b/extensions/cpp/language-configuration.json @@ -23,6 +23,7 @@ ["'", "'"], ["<", ">"] ], + "wordPattern": "(-?\\d*\\.\\d\\w*)|([^\\`\\~\\!\\@\\#\\%\\^\\&\\*\\(\\)\\-\\=\\+\\[\\{\\]\\}\\\\\\|\\;\\:\\'\\\"\\,\\.\\<\\>\\/\\?\\s]+)", "folding": { "markers": { "start": "^\\s*#pragma\\s+region\\b", diff --git a/extensions/cpp/package.json b/extensions/cpp/package.json index 4d30a114..78eb36d7 100644 --- a/extensions/cpp/package.json +++ b/extensions/cpp/package.json @@ -50,6 +50,17 @@ "cpp" ], "configuration": "./language-configuration.json" + }, + { + "id": "cuda-cpp", + "extensions": [ + ".cu", + ".cuh" + ], + "aliases": [ + "CUDA C++" + ], + "configuration": "./language-configuration.json" } ], "grammars": [ @@ -71,8 +82,35 @@ { "scopeName": "source.c.platform", "path": "./syntaxes/platform.tmLanguage.json" + }, + { + "language": "cuda-cpp", + "scopeName": "source.cuda-cpp", + "path": "./syntaxes/cuda-cpp.tmLanguage.json" } ], + "problemPatterns": [ + { + "name": "nvcc-location", + "regexp": "^(.*)\\((\\d+)\\):\\s+(warning|error):\\s+(.*)", + "kind": "location", + "file": 1, + "location": 2, + "severity": 3, + "message": 4 + } + ], + "problemMatchers": [ + { + "name": "nvcc", + "owner": "cuda-cpp", + "fileLocation": [ + "relative", + "${workspaceFolder}" + ], + "pattern": "$nvcc-location" + } + ], "snippets": [ { "language": "c", diff --git a/extensions/cpp/syntaxes/cuda-cpp.tmLanguage.json b/extensions/cpp/syntaxes/cuda-cpp.tmLanguage.json new file mode 100644 index 00000000..b910a5cd --- /dev/null +++ b/extensions/cpp/syntaxes/cuda-cpp.tmLanguage.json @@ -0,0 +1,19818 @@ +{ + "information_for_contributors": [ + "This file has been converted from https://github.com/NVIDIA/cuda-cpp-grammar/blob/master/syntaxes/cuda-cpp.tmLanguage.json", + "If you want to provide a fix or improvement, please create a pull request against the original repository.", + "Once accepted there, we are happy to receive an update request." + ], + "version": "https://github.com/NVIDIA/cuda-cpp-grammar/commit/81e88eaec5170aa8585736c63627c73e3589998c", + "name": "CUDA C++", + "scopeName": "source.cuda-cpp", + "patterns": [ + { + "include": "#ever_present_context" + }, + { + "include": "#constructor_root" + }, + { + "include": "#destructor_root" + }, + { + "include": "#function_definition" + }, + { + "include": "#operator_overload" + }, + { + "include": "#using_namespace" + }, + { + "include": "#type_alias" + }, + { + "include": "#using_name" + }, + { + "include": "#namespace_alias" + }, + { + "include": "#namespace_block" + }, + { + "include": "#extern_block" + }, + { + "include": "#typedef_class" + }, + { + "include": "#typedef_struct" + }, + { + "include": "#typedef_union" + }, + { + "include": "#misc_keywords" + }, + { + "include": "#standard_declares" + }, + { + "include": "#class_block" + }, + { + "include": "#struct_block" + }, + { + "include": "#union_block" + }, + { + "include": "#enum_block" + }, + { + "include": "#template_isolated_definition" + }, + { + "include": "#template_definition" + }, + { + "include": "#access_control_keywords" + }, + { + "include": "#block" + }, + { + "include": "#static_assert" + }, + { + "include": "#assembly" + }, + { + "include": "#function_pointer" + }, + { + "include": "#evaluation_context" + } + ], + "repository": { + "access_control_keywords": { + "match": "((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(((?:(?:protected)|(?:private)|(?:public)))(?:(?:\\s)+)?(:))", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "3": { + "name": "storage.type.modifier.access.control.$4.cuda-cpp" + }, + "4": {}, + "5": { + "name": "punctuation.separator.colon.access.control.cuda-cpp" + } + } + }, + "alignas_attribute": { + "begin": "alignas\\(", + "end": "\\)", + "beginCaptures": { + "0": { + "name": "punctuation.section.attribute.begin.cuda-cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.attribute.end.cuda-cpp" + } + }, + "name": "support.other.attribute.cuda-cpp", + "patterns": [ + { + "include": "#attributes_context" + }, + { + "begin": "\\(", + "end": "\\)", + "beginCaptures": {}, + "endCaptures": {}, + "patterns": [ + { + "include": "#attributes_context" + }, + { + "include": "#string_context" + } + ] + }, + { + "match": "(using)(?:\\s)+((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", + "end": "\\)", + "beginCaptures": { + "1": { + "name": "keyword.operator.functionlike.cuda-cpp keyword.operator.alignas.cuda-cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "4": { + "name": "comment.block.cuda-cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "6": { + "name": "punctuation.section.arguments.begin.bracket.round.operator.alignas.cuda-cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.arguments.end.bracket.round.operator.alignas.cuda-cpp" + } + }, + "contentName": "meta.arguments.operator.alignas", + "patterns": [ + { + "include": "#evaluation_context" + } + ] + }, + "alignof_operator": { + "begin": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", + "end": "\\)", + "beginCaptures": { + "1": { + "name": "keyword.operator.functionlike.cuda-cpp keyword.operator.alignof.cuda-cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "4": { + "name": "comment.block.cuda-cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "6": { + "name": "punctuation.section.arguments.begin.bracket.round.operator.alignof.cuda-cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.arguments.end.bracket.round.operator.alignof.cuda-cpp" + } + }, + "contentName": "meta.arguments.operator.alignof", + "patterns": [ + { + "include": "#evaluation_context" + } + ] + }, + "assembly": { + "begin": "(\\b(?:__asm__|asm)\\b)(?:(?:\\s)+)?((?:volatile)?)", + "end": "(?!\\G)", + "beginCaptures": { + "1": { + "name": "storage.type.asm.cuda-cpp" + }, + "2": { + "name": "storage.modifier.cuda-cpp" + } + }, + "endCaptures": {}, + "name": "meta.asm.cuda-cpp", + "patterns": [ + { + "match": "^((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:\\n)|$)", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "3": { + "name": "comment.block.cuda-cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + }, + { + "include": "#comments" + }, + { + "begin": "((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\(", + "end": "\\)", + "beginCaptures": { + "0": { + "name": "punctuation.section.parens.begin.bracket.round.assembly.cuda-cpp" + }, + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "3": { + "name": "comment.block.cuda-cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.parens.end.bracket.round.assembly.cuda-cpp" + } + }, + "patterns": [ + { + "begin": "(R?)(\")", + "end": "\"", + "beginCaptures": { + "1": { + "name": "meta.encoding.cuda-cpp" + }, + "2": { + "name": "punctuation.definition.string.begin.assembly.cuda-cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.assembly.cuda-cpp" + } + }, + "name": "string.quoted.double.cuda-cpp", + "contentName": "meta.embedded.assembly", + "patterns": [ + { + "include": "source.asm" + }, + { + "include": "source.x86" + }, + { + "include": "source.x86_64" + }, + { + "include": "source.arm" + }, + { + "include": "#backslash_escapes" + }, + { + "include": "#string_escaped_char" + } + ] + }, + { + "begin": "\\(", + "end": "\\)", + "beginCaptures": { + "0": { + "name": "punctuation.section.parens.begin.bracket.round.assembly.inner.cuda-cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.parens.end.bracket.round.assembly.inner.cuda-cpp" + } + }, + "patterns": [ + { + "include": "#evaluation_context" + } + ] + }, + { + "match": "\\[((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\]", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "3": { + "name": "comment.block.cuda-cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "5": { + "name": "variable.other.asm.label.cuda-cpp" + }, + "6": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "7": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "8": { + "name": "comment.block.cuda-cpp" + }, + "9": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + }, + { + "match": ":", + "name": "punctuation.separator.delimiter.colon.assembly.cuda-cpp" + }, + { + "include": "#comments" + } + ] + } + ] + }, + "assignment_operator": { + "match": "\\=", + "name": "keyword.operator.assignment.cuda-cpp" + }, + "attributes_context": { + "patterns": [ + { + "include": "#cpp_attributes" + }, + { + "include": "#gcc_attributes" + }, + { + "include": "#ms_attributes" + }, + { + "include": "#alignas_attribute" + } + ] + }, + "backslash_escapes": { + "match": "(?x)\\\\ (\n\\\\\t\t\t |\n[abefnprtv'\"?] |\n[0-3][0-7]{,2}\t |\n[4-7]\\d?\t\t|\nx[a-fA-F0-9]{,2} |\nu[a-fA-F0-9]{,4} |\nU[a-fA-F0-9]{,8} )", + "name": "constant.character.escape" + }, + "block": { + "begin": "{", + "end": "}|(?=\\s*#\\s*(?:elif|else|endif)\\b)", + "beginCaptures": { + "0": { + "name": "punctuation.section.block.begin.bracket.curly.cuda-cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.block.end.bracket.curly.cuda-cpp" + } + }, + "name": "meta.block.cuda-cpp", + "patterns": [ + { + "include": "#function_body_context" + } + ] + }, + "block_comment": { + "begin": "\\s*+(\\/\\*)", + "end": "\\*\\/", + "beginCaptures": { + "1": { + "name": "punctuation.definition.comment.begin.cuda-cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.definition.comment.end.cuda-cpp" + } + }, + "name": "comment.block.cuda-cpp" + }, + "builtin_storage_type_initilizer": { + "begin": "(?:\\s)*+(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?={)|(?:((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*+)?(?:((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(:(?!:)))?)", + "end": "(?:(?:(?<=\\}|%>|\\?\\?>)(?:(?:\\s)+)?(;)|(;))|(?=[;>\\[\\]=]))", + "beginCaptures": { + "0": { + "name": "meta.head.class.cuda-cpp" + }, + "1": { + "name": "storage.type.$1.cuda-cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "4": { + "name": "comment.block.cuda-cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "6": { + "patterns": [ + { + "include": "#attributes_context" + }, + { + "include": "#number_literal" + } + ] + }, + "7": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "8": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "9": { + "name": "comment.block.cuda-cpp" + }, + "10": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "11": { + "patterns": [ + { + "match": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))", + "captures": { + "1": { + "name": "storage.type.modifier.final.cuda-cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "4": { + "name": "comment.block.cuda-cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + }, + { + "match": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?(?=:|{|$)", + "captures": { + "1": { + "name": "entity.name.type.class.cuda-cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "4": { + "name": "comment.block.cuda-cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "6": { + "name": "storage.type.modifier.final.cuda-cpp" + }, + "7": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "8": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "9": { + "name": "comment.block.cuda-cpp" + }, + "10": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + }, + { + "match": "DLLEXPORT", + "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cuda-cpp" + }, + { + "match": "(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*", + "name": "entity.name.other.preprocessor.macro.predefined.probably.$0.cuda-cpp" + } + ] + }, + "12": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "13": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "14": { + "name": "comment.block.cuda-cpp" + }, + "15": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "16": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "17": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "18": { + "name": "comment.block.cuda-cpp" + }, + "19": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "20": { + "name": "punctuation.separator.colon.inheritance.cuda-cpp" + } + }, + "endCaptures": { + "1": { + "name": "punctuation.terminator.statement.cuda-cpp" + }, + "2": { + "name": "punctuation.terminator.statement.cuda-cpp" + } + }, + "name": "meta.block.class.cuda-cpp", + "patterns": [ + { + "begin": "\\G ?", + "end": "(?:\\{|<%|\\?\\?<|(?=;))", + "beginCaptures": {}, + "endCaptures": { + "0": { + "name": "punctuation.section.block.begin.bracket.curly.class.cuda-cpp" + } + }, + "name": "meta.head.class.cuda-cpp", + "patterns": [ + { + "include": "#ever_present_context" + }, + { + "include": "#inheritance_context" + }, + { + "include": "#template_call_range" + } + ] + }, + { + "begin": "(?<=\\{|<%|\\?\\?<)", + "end": "\\}|%>|\\?\\?>", + "beginCaptures": {}, + "endCaptures": { + "0": { + "name": "punctuation.section.block.end.bracket.curly.class.cuda-cpp" + } + }, + "name": "meta.body.class.cuda-cpp", + "patterns": [ + { + "include": "#function_pointer" + }, + { + "include": "#static_assert" + }, + { + "include": "#constructor_inline" + }, + { + "include": "#destructor_inline" + }, + { + "include": "$self" + } + ] + }, + { + "begin": "(?<=\\}|%>|\\?\\?>)[\\s]*", + "end": "[\\s]*(?=;)", + "beginCaptures": {}, + "endCaptures": {}, + "name": "meta.tail.class.cuda-cpp", + "patterns": [ + { + "include": "$self" + } + ] + } + ] + }, + "class_declare": { + "match": "((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\b(?!override\\W|override\\$|final\\W|final\\$)((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\S)(?![:{a-zA-Z])", + "captures": { + "1": { + "name": "storage.type.class.declare.cuda-cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "4": { + "name": "entity.name.type.class.cuda-cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*", + "name": "storage.modifier.pointer.cuda-cpp" + }, + { + "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "3": { + "name": "comment.block.cuda-cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + }, + "name": "invalid.illegal.reference-type.cuda-cpp" + }, + { + "match": "\\&", + "name": "storage.modifier.reference.cuda-cpp" + } + ] + }, + "6": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "7": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "8": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "9": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "10": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "11": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "12": { + "name": "variable.other.object.declare.cuda-cpp" + }, + "13": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "14": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + } + } + }, + "comma": { + "match": ",", + "name": "punctuation.separator.delimiter.comma.cuda-cpp" + }, + "comma_in_template_argument": { + "match": ",", + "name": "punctuation.separator.delimiter.comma.template.argument.cuda-cpp" + }, + "comments": { + "patterns": [ + { + "begin": "^(?:(?:\\s)+)?+(\\/\\/[!\\/]+)", + "end": "(?<=\\n)(?|%|\"|\\.|=|::|\\||\\-\\-|\\-\\-\\-)\\b(?:\\{[^}]*\\})?", + "name": "storage.type.class.doxygen.cuda-cpp" + }, + { + "match": "((?<=[\\s*!\\/])[\\\\@](?:a|em|e))(?:\\s)+(\\S+)", + "captures": { + "1": { + "name": "storage.type.class.doxygen.cuda-cpp" + }, + "2": { + "name": "markup.italic.doxygen.cuda-cpp" + } + } + }, + { + "match": "((?<=[\\s*!\\/])[\\\\@]b)(?:\\s)+(\\S+)", + "captures": { + "1": { + "name": "storage.type.class.doxygen.cuda-cpp" + }, + "2": { + "name": "markup.bold.doxygen.cuda-cpp" + } + } + }, + { + "match": "((?<=[\\s*!\\/])[\\\\@](?:c|p))(?:\\s)+(\\S+)", + "captures": { + "1": { + "name": "storage.type.class.doxygen.cuda-cpp" + }, + "2": { + "name": "markup.inline.raw.string.cuda-cpp" + } + } + }, + { + "match": "(?<=[\\s*!\\/])[\\\\@](?:a|anchor|b|c|cite|copybrief|copydetail|copydoc|def|dir|dontinclude|e|em|emoji|enum|example|extends|file|idlexcept|implements|include|includedoc|includelineno|latexinclude|link|memberof|namespace|p|package|ref|refitem|related|relates|relatedalso|relatesalso|verbinclude)\\b(?:\\{[^}]*\\})?", + "name": "storage.type.class.doxygen.cuda-cpp" + }, + { + "match": "(?<=[\\s*!\\/])[\\\\@](?:addindex|addtogroup|category|class|defgroup|diafile|dotfile|elseif|fn|headerfile|if|ifnot|image|ingroup|interface|line|mainpage|mscfile|name|overload|page|property|protocol|section|skip|skipline|snippet|snippetdoc|snippetlineno|struct|subpage|subsection|subsubsection|typedef|union|until|vhdlflow|weakgroup)\\b(?:\\{[^}]*\\})?", + "name": "storage.type.class.doxygen.cuda-cpp" + }, + { + "match": "((?<=[\\s*!\\/])[\\\\@]param)(?:\\s*\\[((?:,?(?:(?:\\s)+)?(?:in|out)(?:(?:\\s)+)?)+)\\])?(?:\\s)+(\\b\\w+\\b)", + "captures": { + "1": { + "name": "storage.type.class.doxygen.cuda-cpp" + }, + "2": { + "patterns": [ + { + "match": "in|out", + "name": "keyword.other.parameter.direction.$0.cuda-cpp" + } + ] + }, + "3": { + "name": "variable.parameter.cuda-cpp" + } + } + }, + { + "match": "(?<=[\\s*!\\/])[\\\\@](?:arg|attention|author|authors|brief|bug|copyright|date|deprecated|details|exception|invariant|li|note|par|paragraph|param|post|pre|remark|remarks|result|return|returns|retval|sa|see|short|since|test|throw|throws|todo|tparam|version|warning|xrefitem)\\b(?:\\{[^}]*\\})?", + "name": "storage.type.class.doxygen.cuda-cpp" + }, + { + "match": "(?<=[\\s*!\\/])[\\\\@](?:code|cond|docbookonly|dot|htmlonly|internal|latexonly|link|manonly|msc|parblock|rtfonly|secreflist|startuml|verbatim|xmlonly|endcode|endcond|enddocbookonly|enddot|endhtmlonly|endinternal|endlatexonly|endlink|endmanonly|endmsc|endparblock|endrtfonly|endsecreflist|enduml|endverbatim|endxmlonly)\\b(?:\\{[^}]*\\})?", + "name": "storage.type.class.doxygen.cuda-cpp" + }, + { + "match": "(?:\\b[A-Z]+:|@[a-z_]+:)", + "name": "storage.type.class.gtkdoc.cuda-cpp" + } + ] + }, + { + "match": "(\\/\\*[!*]+(?=\\s))(.+)([!*]*\\*\\/)", + "captures": { + "1": { + "name": "punctuation.definition.comment.begin.documentation.cuda-cpp" + }, + "2": { + "patterns": [ + { + "match": "(?<=[\\s*!\\/])[\\\\@](?:callergraph|callgraph|else|endif|f\\$|f\\[|f\\]|hidecallergraph|hidecallgraph|hiderefby|hiderefs|hideinitializer|htmlinclude|n|nosubgrouping|private|privatesection|protected|protectedsection|public|publicsection|pure|showinitializer|showrefby|showrefs|tableofcontents|\\$|\\#|<|>|%|\"|\\.|=|::|\\||\\-\\-|\\-\\-\\-)\\b(?:\\{[^}]*\\})?", + "name": "storage.type.class.doxygen.cuda-cpp" + }, + { + "match": "((?<=[\\s*!\\/])[\\\\@](?:a|em|e))(?:\\s)+(\\S+)", + "captures": { + "1": { + "name": "storage.type.class.doxygen.cuda-cpp" + }, + "2": { + "name": "markup.italic.doxygen.cuda-cpp" + } + } + }, + { + "match": "((?<=[\\s*!\\/])[\\\\@]b)(?:\\s)+(\\S+)", + "captures": { + "1": { + "name": "storage.type.class.doxygen.cuda-cpp" + }, + "2": { + "name": "markup.bold.doxygen.cuda-cpp" + } + } + }, + { + "match": "((?<=[\\s*!\\/])[\\\\@](?:c|p))(?:\\s)+(\\S+)", + "captures": { + "1": { + "name": "storage.type.class.doxygen.cuda-cpp" + }, + "2": { + "name": "markup.inline.raw.string.cuda-cpp" + } + } + }, + { + "match": "(?<=[\\s*!\\/])[\\\\@](?:a|anchor|b|c|cite|copybrief|copydetail|copydoc|def|dir|dontinclude|e|em|emoji|enum|example|extends|file|idlexcept|implements|include|includedoc|includelineno|latexinclude|link|memberof|namespace|p|package|ref|refitem|related|relates|relatedalso|relatesalso|verbinclude)\\b(?:\\{[^}]*\\})?", + "name": "storage.type.class.doxygen.cuda-cpp" + }, + { + "match": "(?<=[\\s*!\\/])[\\\\@](?:addindex|addtogroup|category|class|defgroup|diafile|dotfile|elseif|fn|headerfile|if|ifnot|image|ingroup|interface|line|mainpage|mscfile|name|overload|page|property|protocol|section|skip|skipline|snippet|snippetdoc|snippetlineno|struct|subpage|subsection|subsubsection|typedef|union|until|vhdlflow|weakgroup)\\b(?:\\{[^}]*\\})?", + "name": "storage.type.class.doxygen.cuda-cpp" + }, + { + "match": "((?<=[\\s*!\\/])[\\\\@]param)(?:\\s*\\[((?:,?(?:(?:\\s)+)?(?:in|out)(?:(?:\\s)+)?)+)\\])?(?:\\s)+(\\b\\w+\\b)", + "captures": { + "1": { + "name": "storage.type.class.doxygen.cuda-cpp" + }, + "2": { + "patterns": [ + { + "match": "in|out", + "name": "keyword.other.parameter.direction.$0.cuda-cpp" + } + ] + }, + "3": { + "name": "variable.parameter.cuda-cpp" + } + } + }, + { + "match": "(?<=[\\s*!\\/])[\\\\@](?:arg|attention|author|authors|brief|bug|copyright|date|deprecated|details|exception|invariant|li|note|par|paragraph|param|post|pre|remark|remarks|result|return|returns|retval|sa|see|short|since|test|throw|throws|todo|tparam|version|warning|xrefitem)\\b(?:\\{[^}]*\\})?", + "name": "storage.type.class.doxygen.cuda-cpp" + }, + { + "match": "(?<=[\\s*!\\/])[\\\\@](?:code|cond|docbookonly|dot|htmlonly|internal|latexonly|link|manonly|msc|parblock|rtfonly|secreflist|startuml|verbatim|xmlonly|endcode|endcond|enddocbookonly|enddot|endhtmlonly|endinternal|endlatexonly|endlink|endmanonly|endmsc|endparblock|endrtfonly|endsecreflist|enduml|endverbatim|endxmlonly)\\b(?:\\{[^}]*\\})?", + "name": "storage.type.class.doxygen.cuda-cpp" + }, + { + "match": "(?:\\b[A-Z]+:|@[a-z_]+:)", + "name": "storage.type.class.gtkdoc.cuda-cpp" + } + ] + }, + "3": { + "name": "punctuation.definition.comment.end.documentation.cuda-cpp" + } + }, + "name": "comment.block.documentation.cuda-cpp" + }, + { + "begin": "(?:(?:\\s)+)?+\\/\\*[!*]+(?:(?:(?:\\n)|$)|(?=\\s))", + "end": "[!*]*\\*\\/", + "beginCaptures": { + "0": { + "name": "punctuation.definition.comment.begin.documentation.cuda-cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.definition.comment.end.documentation.cuda-cpp" + } + }, + "name": "comment.block.documentation.cuda-cpp", + "patterns": [ + { + "match": "(?<=[\\s*!\\/])[\\\\@](?:callergraph|callgraph|else|endif|f\\$|f\\[|f\\]|hidecallergraph|hidecallgraph|hiderefby|hiderefs|hideinitializer|htmlinclude|n|nosubgrouping|private|privatesection|protected|protectedsection|public|publicsection|pure|showinitializer|showrefby|showrefs|tableofcontents|\\$|\\#|<|>|%|\"|\\.|=|::|\\||\\-\\-|\\-\\-\\-)\\b(?:\\{[^}]*\\})?", + "name": "storage.type.class.doxygen.cuda-cpp" + }, + { + "match": "((?<=[\\s*!\\/])[\\\\@](?:a|em|e))(?:\\s)+(\\S+)", + "captures": { + "1": { + "name": "storage.type.class.doxygen.cuda-cpp" + }, + "2": { + "name": "markup.italic.doxygen.cuda-cpp" + } + } + }, + { + "match": "((?<=[\\s*!\\/])[\\\\@]b)(?:\\s)+(\\S+)", + "captures": { + "1": { + "name": "storage.type.class.doxygen.cuda-cpp" + }, + "2": { + "name": "markup.bold.doxygen.cuda-cpp" + } + } + }, + { + "match": "((?<=[\\s*!\\/])[\\\\@](?:c|p))(?:\\s)+(\\S+)", + "captures": { + "1": { + "name": "storage.type.class.doxygen.cuda-cpp" + }, + "2": { + "name": "markup.inline.raw.string.cuda-cpp" + } + } + }, + { + "match": "(?<=[\\s*!\\/])[\\\\@](?:a|anchor|b|c|cite|copybrief|copydetail|copydoc|def|dir|dontinclude|e|em|emoji|enum|example|extends|file|idlexcept|implements|include|includedoc|includelineno|latexinclude|link|memberof|namespace|p|package|ref|refitem|related|relates|relatedalso|relatesalso|verbinclude)\\b(?:\\{[^}]*\\})?", + "name": "storage.type.class.doxygen.cuda-cpp" + }, + { + "match": "(?<=[\\s*!\\/])[\\\\@](?:addindex|addtogroup|category|class|defgroup|diafile|dotfile|elseif|fn|headerfile|if|ifnot|image|ingroup|interface|line|mainpage|mscfile|name|overload|page|property|protocol|section|skip|skipline|snippet|snippetdoc|snippetlineno|struct|subpage|subsection|subsubsection|typedef|union|until|vhdlflow|weakgroup)\\b(?:\\{[^}]*\\})?", + "name": "storage.type.class.doxygen.cuda-cpp" + }, + { + "match": "((?<=[\\s*!\\/])[\\\\@]param)(?:\\s*\\[((?:,?(?:(?:\\s)+)?(?:in|out)(?:(?:\\s)+)?)+)\\])?(?:\\s)+(\\b\\w+\\b)", + "captures": { + "1": { + "name": "storage.type.class.doxygen.cuda-cpp" + }, + "2": { + "patterns": [ + { + "match": "in|out", + "name": "keyword.other.parameter.direction.$0.cuda-cpp" + } + ] + }, + "3": { + "name": "variable.parameter.cuda-cpp" + } + } + }, + { + "match": "(?<=[\\s*!\\/])[\\\\@](?:arg|attention|author|authors|brief|bug|copyright|date|deprecated|details|exception|invariant|li|note|par|paragraph|param|post|pre|remark|remarks|result|return|returns|retval|sa|see|short|since|test|throw|throws|todo|tparam|version|warning|xrefitem)\\b(?:\\{[^}]*\\})?", + "name": "storage.type.class.doxygen.cuda-cpp" + }, + { + "match": "(?<=[\\s*!\\/])[\\\\@](?:code|cond|docbookonly|dot|htmlonly|internal|latexonly|link|manonly|msc|parblock|rtfonly|secreflist|startuml|verbatim|xmlonly|endcode|endcond|enddocbookonly|enddot|endhtmlonly|endinternal|endlatexonly|endlink|endmanonly|endmsc|endparblock|endrtfonly|endsecreflist|enduml|endverbatim|endxmlonly)\\b(?:\\{[^}]*\\})?", + "name": "storage.type.class.doxygen.cuda-cpp" + }, + { + "match": "(?:\\b[A-Z]+:|@[a-z_]+:)", + "name": "storage.type.class.gtkdoc.cuda-cpp" + } + ] + }, + { + "include": "#emacs_file_banner" + }, + { + "include": "#block_comment" + }, + { + "include": "#line_comment" + }, + { + "include": "#invalid_comment_end" + } + ] + }, + "constructor_inline": { + "begin": "^((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:(?:(?:__forceinline__)|(?:__noinline__)|(?:__global__)|(?:__device__)|(?:constexpr)|(?:explicit)|(?:__host__)|(?:mutable)|(?:virtual)|(?:inline)|(?:friend))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?|\\?\\?>)|(?=[;>\\[\\]=]))", + "beginCaptures": { + "0": { + "name": "meta.head.function.definition.special.constructor.cuda-cpp" + }, + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "3": { + "name": "comment.block.cuda-cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "5": { + "patterns": [ + { + "include": "#functional_specifiers_pre_parameters" + } + ] + }, + "6": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "7": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "8": { + "name": "comment.block.cuda-cpp" + }, + "9": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "10": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "11": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "12": { + "name": "comment.block.cuda-cpp" + }, + "13": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "14": { + "name": "storage.type.modifier.calling-convention.cuda-cpp" + }, + "15": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "16": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "17": { + "name": "comment.block.cuda-cpp" + }, + "18": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "19": { + "name": "entity.name.function.constructor.cuda-cpp entity.name.function.definition.special.constructor.cuda-cpp" + } + }, + "endCaptures": {}, + "name": "meta.function.definition.special.constructor.cuda-cpp", + "patterns": [ + { + "begin": "\\G ?", + "end": "(?:\\{|<%|\\?\\?<|(?=;))", + "beginCaptures": {}, + "endCaptures": { + "0": { + "name": "punctuation.section.block.begin.bracket.curly.function.definition.special.constructor.cuda-cpp" + } + }, + "name": "meta.head.function.definition.special.constructor.cuda-cpp", + "patterns": [ + { + "include": "#ever_present_context" + }, + { + "match": "(\\=)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(default)|(delete))", + "captures": { + "1": { + "name": "keyword.operator.assignment.cuda-cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "4": { + "name": "comment.block.cuda-cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "6": { + "name": "keyword.other.default.constructor.cuda-cpp" + }, + "7": { + "name": "keyword.other.delete.constructor.cuda-cpp" + } + } + }, + { + "include": "#functional_specifiers_pre_parameters" + }, + { + "begin": ":", + "end": "(?=\\{)", + "beginCaptures": { + "0": { + "name": "punctuation.separator.initializers.cuda-cpp" + } + }, + "endCaptures": {}, + "patterns": [ + { + "begin": "((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:\\s)*+)?(\\()", + "end": "\\)", + "beginCaptures": { + "1": { + "name": "entity.name.function.call.initializer.cuda-cpp" + }, + "2": { + "name": "meta.template.call.cuda-cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "3": {}, + "4": { + "name": "punctuation.section.arguments.begin.bracket.round.function.call.initializer.cuda-cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.arguments.end.bracket.round.function.call.initializer.cuda-cpp" + } + }, + "contentName": "meta.parameter.initialization", + "patterns": [ + { + "include": "#evaluation_context" + } + ] + }, + { + "begin": "((?|\\?\\?>", + "beginCaptures": {}, + "endCaptures": { + "0": { + "name": "punctuation.section.block.end.bracket.curly.function.definition.special.constructor.cuda-cpp" + } + }, + "name": "meta.body.function.definition.special.constructor.cuda-cpp", + "patterns": [ + { + "include": "#function_body_context" + } + ] + }, + { + "begin": "(?<=\\}|%>|\\?\\?>)[\\s]*", + "end": "[\\s]*(?=;)", + "beginCaptures": {}, + "endCaptures": {}, + "name": "meta.tail.function.definition.special.constructor.cuda-cpp", + "patterns": [ + { + "include": "$self" + } + ] + } + ] + }, + "constructor_root": { + "begin": "\\s*+((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|__forceinline__|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|__constant__|__restrict__|__noinline__|thread_local|synchronized|static_cast|__managed__|const_cast|__shared__|__global__|__device__|co_return|constexpr|constexpr|constexpr|consteval|protected|namespace|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|__host__|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)(?:\\s)*+)?::)*+)(((?>(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))::((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\14((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\())", + "end": "(?:(?<=\\}|%>|\\?\\?>)|(?=[;>\\[\\]=]))", + "beginCaptures": { + "0": { + "name": "meta.head.function.definition.special.constructor.cuda-cpp" + }, + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "3": { + "name": "comment.block.cuda-cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "5": { + "name": "storage.type.modifier.calling-convention.cuda-cpp" + }, + "6": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "7": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "8": { + "name": "comment.block.cuda-cpp" + }, + "9": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "10": { + "patterns": [ + { + "match": "::", + "name": "punctuation.separator.namespace.access.cuda-cpp punctuation.separator.scope-resolution.constructor.cuda-cpp" + }, + { + "match": "(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(default)|(delete))", + "captures": { + "1": { + "name": "keyword.operator.assignment.cuda-cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "4": { + "name": "comment.block.cuda-cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "6": { + "name": "keyword.other.default.constructor.cuda-cpp" + }, + "7": { + "name": "keyword.other.delete.constructor.cuda-cpp" + } + } + }, + { + "include": "#functional_specifiers_pre_parameters" + }, + { + "begin": ":", + "end": "(?=\\{)", + "beginCaptures": { + "0": { + "name": "punctuation.separator.initializers.cuda-cpp" + } + }, + "endCaptures": {}, + "patterns": [ + { + "begin": "((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:\\s)*+)?(\\()", + "end": "\\)", + "beginCaptures": { + "1": { + "name": "entity.name.function.call.initializer.cuda-cpp" + }, + "2": { + "name": "meta.template.call.cuda-cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "3": {}, + "4": { + "name": "punctuation.section.arguments.begin.bracket.round.function.call.initializer.cuda-cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.arguments.end.bracket.round.function.call.initializer.cuda-cpp" + } + }, + "contentName": "meta.parameter.initialization", + "patterns": [ + { + "include": "#evaluation_context" + } + ] + }, + { + "begin": "((?|\\?\\?>", + "beginCaptures": {}, + "endCaptures": { + "0": { + "name": "punctuation.section.block.end.bracket.curly.function.definition.special.constructor.cuda-cpp" + } + }, + "name": "meta.body.function.definition.special.constructor.cuda-cpp", + "patterns": [ + { + "include": "#function_body_context" + } + ] + }, + { + "begin": "(?<=\\}|%>|\\?\\?>)[\\s]*", + "end": "[\\s]*(?=;)", + "beginCaptures": {}, + "endCaptures": {}, + "name": "meta.tail.function.definition.special.constructor.cuda-cpp", + "patterns": [ + { + "include": "$self" + } + ] + } + ] + }, + "control_flow_keywords": { + "match": "((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "3": { + "name": "keyword.control.$3.cuda-cpp" + } + } + }, + "cpp_attributes": { + "begin": "\\[\\[", + "end": "\\]\\]", + "beginCaptures": { + "0": { + "name": "punctuation.section.attribute.begin.cuda-cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.attribute.end.cuda-cpp" + } + }, + "name": "support.other.attribute.cuda-cpp", + "patterns": [ + { + "include": "#attributes_context" + }, + { + "begin": "\\(", + "end": "\\)", + "beginCaptures": {}, + "endCaptures": {}, + "patterns": [ + { + "include": "#attributes_context" + }, + { + "include": "#string_context" + } + ] + }, + { + "match": "(using)(?:\\s)+((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|__forceinline__|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|__constant__|__restrict__|__noinline__|thread_local|synchronized|static_cast|__managed__|const_cast|__shared__|__global__|__device__|co_return|constexpr|constexpr|constexpr|consteval|protected|namespace|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|__host__|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)(?:\\s)*+)?::)*+)?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:__forceinline__)|(?:atomic_noexcept)|(?:__has_include)|(?:atomic_cancel)|(?:atomic_commit)|(?:dynamic_cast)|(?:__constant__)|(?:__restrict__)|(?:__noinline__)|(?:thread_local)|(?:synchronized)|(?:static_cast)|(?:__managed__)|(?:const_cast)|(?:__shared__)|(?:__global__)|(?:__device__)|(?:co_return)|(?:constexpr)|(?:constexpr)|(?:constexpr)|(?:consteval)|(?:protected)|(?:threadIdx)|(?:namespace)|(?:co_return)|(?:noexcept)|(?:noexcept)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:__host__)|(?:override)|(?:volatile)|(?:noexcept)|(?:blockIdx)|(?:blockDim)|(?:warpSize)|(?:template)|(?:operator)|(?:decltype)|(?:typename)|(?:requires)|(?:co_await)|(?:co_yield)|(?:reflexpr)|(?:alignof)|(?:alignas)|(?:default)|(?:nullptr)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:gridDim)|(?:typedef)|(?:__asm__)|(?:concept)|(?:sizeof)|(?:delete)|(?:not_eq)|(?:bitand)|(?:and_eq)|(?:xor_eq)|(?:typeid)|(?:switch)|(?:return)|(?:static)|(?:extern)|(?:inline)|(?:friend)|(?:public)|(?:ifndef)|(?:define)|(?:pragma)|(?:export)|(?:import)|(?:module)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:false)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:else)|(?:goto)|(?:case)|(?:NULL)|(?:true)|(?:elif)|(?:else)|(?:line)|(?:this)|(?:not)|(?:new)|(?:xor)|(?:and)|(?:for)|(?:try)|(?:asm)|(?:or)|(?:do)|(?:if)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)?(?![\\w<:.]))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\{)", + "end": "\\}", + "beginCaptures": { + "1": { + "name": "meta.qualified_type.cuda-cpp", + "patterns": [ + { + "match": "::", + "name": "punctuation.separator.namespace.access.cuda-cpp punctuation.separator.scope-resolution.cuda-cpp" + }, + { + "match": "(?", + "beginCaptures": { + "0": { + "name": "punctuation.section.angle-brackets.begin.template.call.cuda-cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.angle-brackets.end.template.call.cuda-cpp" + } + }, + "name": "meta.template.call.cuda-cpp", + "patterns": [ + { + "include": "#template_call_context" + } + ] + }, + { + "match": "(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*", + "name": "entity.name.type.cuda-cpp" + } + ] + }, + "2": { + "patterns": [ + { + "include": "#attributes_context" + }, + { + "include": "#number_literal" + } + ] + }, + "3": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "4": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "5": { + "name": "comment.block.cuda-cpp" + }, + "6": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "7": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "8": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "9": { + "name": "comment.block.cuda-cpp" + }, + "10": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "11": { + "patterns": [ + { + "match": "::", + "name": "punctuation.separator.namespace.access.cuda-cpp punctuation.separator.scope-resolution.type.cuda-cpp" + }, + { + "match": "(?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((import))(?:(?:\\s)+)?(?:(?:(?:((<)[^>]*(>?)((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:\\n)|$)|(?=\\/\\/)))|((\\\")[^\\\"]*((?:\\\")?)((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:\\n)|$)|(?=\\/\\/))))|(((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*(?:\\.(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)*((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:\\n)|$)|(?=(?:\\/\\/|;)))))|((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:\\n)|$)|(?=(?:\\/\\/|;))))(?:(?:\\s)+)?(;?)", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "3": { + "name": "keyword.control.directive.import.cuda-cpp" + }, + "5": { + "name": "string.quoted.other.lt-gt.include.cuda-cpp" + }, + "6": { + "name": "punctuation.definition.string.begin.cuda-cpp" + }, + "7": { + "name": "punctuation.definition.string.end.cuda-cpp" + }, + "8": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "9": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "10": { + "name": "string.quoted.double.include.cuda-cpp" + }, + "11": { + "name": "punctuation.definition.string.begin.cuda-cpp" + }, + "12": { + "name": "punctuation.definition.string.end.cuda-cpp" + }, + "13": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "14": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "15": { + "name": "entity.name.other.preprocessor.macro.include.cuda-cpp" + }, + "16": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "17": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "18": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "19": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "20": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "21": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "22": { + "name": "punctuation.terminator.statement.cuda-cpp" + } + }, + "name": "meta.preprocessor.import.cuda-cpp" + }, + "d9bc4796b0b_preprocessor_number_literal": { + "match": "(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", + "end": "\\)", + "beginCaptures": { + "1": { + "name": "keyword.operator.functionlike.cuda-cpp keyword.other.decltype.cuda-cpp storage.type.decltype.cuda-cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "4": { + "name": "comment.block.cuda-cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "6": { + "name": "punctuation.section.arguments.begin.bracket.round.decltype.cuda-cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.arguments.end.bracket.round.decltype.cuda-cpp" + } + }, + "contentName": "meta.arguments.decltype", + "patterns": [ + { + "include": "#evaluation_context" + } + ] + }, + "decltype_specifier": { + "begin": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", + "end": "\\)", + "beginCaptures": { + "1": { + "name": "keyword.operator.functionlike.cuda-cpp keyword.other.decltype.cuda-cpp storage.type.decltype.cuda-cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "4": { + "name": "comment.block.cuda-cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "6": { + "name": "punctuation.section.arguments.begin.bracket.round.decltype.cuda-cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.arguments.end.bracket.round.decltype.cuda-cpp" + } + }, + "contentName": "meta.arguments.decltype", + "patterns": [ + { + "include": "#evaluation_context" + } + ] + }, + "default_statement": { + "begin": "((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:(?:(?:__forceinline__)|(?:__noinline__)|(?:__global__)|(?:__device__)|(?:constexpr)|(?:explicit)|(?:__host__)|(?:mutable)|(?:virtual)|(?:inline)|(?:friend))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*)(~(?|\\?\\?>)|(?=[;>\\[\\]=]))", + "beginCaptures": { + "0": { + "name": "meta.head.function.definition.special.member.destructor.cuda-cpp" + }, + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "3": { + "name": "comment.block.cuda-cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "5": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "6": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "7": { + "name": "comment.block.cuda-cpp" + }, + "8": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "9": { + "name": "storage.type.modifier.calling-convention.cuda-cpp" + }, + "10": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "11": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "12": { + "name": "comment.block.cuda-cpp" + }, + "13": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "14": { + "patterns": [ + { + "include": "#functional_specifiers_pre_parameters" + } + ] + }, + "15": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "16": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "17": { + "name": "comment.block.cuda-cpp" + }, + "18": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "19": { + "name": "entity.name.function.destructor.cuda-cpp entity.name.function.definition.special.member.destructor.cuda-cpp" + } + }, + "endCaptures": {}, + "name": "meta.function.definition.special.member.destructor.cuda-cpp", + "patterns": [ + { + "begin": "\\G ?", + "end": "(?:\\{|<%|\\?\\?<|(?=;))", + "beginCaptures": {}, + "endCaptures": { + "0": { + "name": "punctuation.section.block.begin.bracket.curly.function.definition.special.member.destructor.cuda-cpp" + } + }, + "name": "meta.head.function.definition.special.member.destructor.cuda-cpp", + "patterns": [ + { + "include": "#ever_present_context" + }, + { + "match": "(\\=)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(default)|(delete))", + "captures": { + "1": { + "name": "keyword.operator.assignment.cuda-cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "4": { + "name": "comment.block.cuda-cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "6": { + "name": "keyword.other.default.constructor.cuda-cpp" + }, + "7": { + "name": "keyword.other.delete.constructor.cuda-cpp" + } + } + }, + { + "begin": "\\(", + "end": "\\)", + "beginCaptures": { + "0": { + "name": "punctuation.section.parameters.begin.bracket.round.special.member.destructor.cuda-cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.parameters.end.bracket.round.special.member.destructor.cuda-cpp" + } + }, + "contentName": "meta.function.definition.parameters.special.member.destructor", + "patterns": [] + }, + { + "match": "((?:(?:final)|(?:override)))+", + "captures": { + "1": { + "name": "keyword.operator.wordlike.cuda-cpp keyword.operator.$1.cuda-cpp" + } + } + }, + { + "include": "$self" + } + ] + }, + { + "begin": "(?<=\\{|<%|\\?\\?<)", + "end": "\\}|%>|\\?\\?>", + "beginCaptures": {}, + "endCaptures": { + "0": { + "name": "punctuation.section.block.end.bracket.curly.function.definition.special.member.destructor.cuda-cpp" + } + }, + "name": "meta.body.function.definition.special.member.destructor.cuda-cpp", + "patterns": [ + { + "include": "#function_body_context" + } + ] + }, + { + "begin": "(?<=\\}|%>|\\?\\?>)[\\s]*", + "end": "[\\s]*(?=;)", + "beginCaptures": {}, + "endCaptures": {}, + "name": "meta.tail.function.definition.special.member.destructor.cuda-cpp", + "patterns": [ + { + "include": "$self" + } + ] + } + ] + }, + "destructor_root": { + "begin": "((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|__forceinline__|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|__constant__|__restrict__|__noinline__|thread_local|synchronized|static_cast|__managed__|const_cast|__shared__|__global__|__device__|co_return|constexpr|constexpr|constexpr|consteval|protected|namespace|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|__host__|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)(?:\\s)*+)?::)*+)(((?>(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))::((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))~\\14((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\())", + "end": "(?:(?<=\\}|%>|\\?\\?>)|(?=[;>\\[\\]=]))", + "beginCaptures": { + "0": { + "name": "meta.head.function.definition.special.member.destructor.cuda-cpp" + }, + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "3": { + "name": "comment.block.cuda-cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "5": { + "name": "storage.type.modifier.calling-convention.cuda-cpp" + }, + "6": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "7": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "8": { + "name": "comment.block.cuda-cpp" + }, + "9": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "10": { + "patterns": [ + { + "match": "::", + "name": "punctuation.separator.namespace.access.cuda-cpp punctuation.separator.scope-resolution.destructor.cuda-cpp" + }, + { + "match": "(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(default)|(delete))", + "captures": { + "1": { + "name": "keyword.operator.assignment.cuda-cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "4": { + "name": "comment.block.cuda-cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "6": { + "name": "keyword.other.default.constructor.cuda-cpp" + }, + "7": { + "name": "keyword.other.delete.constructor.cuda-cpp" + } + } + }, + { + "begin": "\\(", + "end": "\\)", + "beginCaptures": { + "0": { + "name": "punctuation.section.parameters.begin.bracket.round.special.member.destructor.cuda-cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.parameters.end.bracket.round.special.member.destructor.cuda-cpp" + } + }, + "contentName": "meta.function.definition.parameters.special.member.destructor", + "patterns": [] + }, + { + "match": "((?:(?:final)|(?:override)))+", + "captures": { + "1": { + "name": "keyword.operator.wordlike.cuda-cpp keyword.operator.$1.cuda-cpp" + } + } + }, + { + "include": "$self" + } + ] + }, + { + "begin": "(?<=\\{|<%|\\?\\?<)", + "end": "\\}|%>|\\?\\?>", + "beginCaptures": {}, + "endCaptures": { + "0": { + "name": "punctuation.section.block.end.bracket.curly.function.definition.special.member.destructor.cuda-cpp" + } + }, + "name": "meta.body.function.definition.special.member.destructor.cuda-cpp", + "patterns": [ + { + "include": "#function_body_context" + } + ] + }, + { + "begin": "(?<=\\}|%>|\\?\\?>)[\\s]*", + "end": "[\\s]*(?=;)", + "beginCaptures": {}, + "endCaptures": {}, + "name": "meta.tail.function.definition.special.member.destructor.cuda-cpp", + "patterns": [ + { + "include": "$self" + } + ] + } + ] + }, + "diagnostic": { + "begin": "(^((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(#)(?:(?:\\s)+)?((?:error|warning)))\\b(?:(?:\\s)+)?", + "end": "(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)(?:\\s)*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|__forceinline__|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|__constant__|__restrict__|__noinline__|thread_local|synchronized|static_cast|__managed__|const_cast|__shared__|__global__|__device__|co_return|constexpr|constexpr|constexpr|consteval|protected|namespace|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|__host__|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)(?:\\s)*+)?(::))?(?:(?:\\s)+)?((?|\\?\\?>)(?:(?:\\s)+)?(;)|(;))|(?=[;>\\[\\]=]))", + "beginCaptures": { + "0": { + "name": "meta.head.enum.cuda-cpp" + }, + "1": { + "name": "storage.type.enum.cuda-cpp" + }, + "2": { + "name": "storage.type.enum.enum-key.$2.cuda-cpp" + }, + "3": { + "patterns": [ + { + "include": "#attributes_context" + }, + { + "include": "#number_literal" + } + ] + }, + "4": { + "name": "entity.name.type.enum.cuda-cpp" + }, + "5": { + "name": "punctuation.separator.colon.type-specifier.cuda-cpp" + }, + "6": { + "patterns": [ + { + "include": "#scope_resolution_inner_generated" + } + ] + }, + "7": { + "name": "punctuation.separator.namespace.access.cuda-cpp punctuation.separator.scope-resolution.cuda-cpp" + }, + "8": { + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "9": {}, + "10": { + "name": "entity.name.scope-resolution.cuda-cpp" + }, + "11": { + "name": "meta.template.call.cuda-cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "12": {}, + "13": { + "name": "punctuation.separator.namespace.access.cuda-cpp punctuation.separator.scope-resolution.cuda-cpp" + }, + "14": { + "name": "storage.type.integral.$14.cuda-cpp" + } + }, + "endCaptures": { + "1": { + "name": "punctuation.terminator.statement.cuda-cpp" + }, + "2": { + "name": "punctuation.terminator.statement.cuda-cpp" + } + }, + "name": "meta.block.enum.cuda-cpp", + "patterns": [ + { + "begin": "\\G ?", + "end": "(?:\\{|<%|\\?\\?<|(?=;))", + "beginCaptures": {}, + "endCaptures": { + "0": { + "name": "punctuation.section.block.begin.bracket.curly.enum.cuda-cpp" + } + }, + "name": "meta.head.enum.cuda-cpp", + "patterns": [ + { + "include": "$self" + } + ] + }, + { + "begin": "(?<=\\{|<%|\\?\\?<)", + "end": "\\}|%>|\\?\\?>", + "beginCaptures": {}, + "endCaptures": { + "0": { + "name": "punctuation.section.block.end.bracket.curly.enum.cuda-cpp" + } + }, + "name": "meta.body.enum.cuda-cpp", + "patterns": [ + { + "include": "#ever_present_context" + }, + { + "include": "#enumerator_list" + }, + { + "include": "#comments" + }, + { + "include": "#comma" + }, + { + "include": "#semicolon" + } + ] + }, + { + "begin": "(?<=\\}|%>|\\?\\?>)[\\s]*", + "end": "[\\s]*(?=;)", + "beginCaptures": {}, + "endCaptures": {}, + "name": "meta.tail.enum.cuda-cpp", + "patterns": [ + { + "include": "$self" + } + ] + } + ] + }, + "enum_declare": { + "match": "((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\b(?!override\\W|override\\$|final\\W|final\\$)((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\S)(?![:{a-zA-Z])", + "captures": { + "1": { + "name": "storage.type.enum.declare.cuda-cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "4": { + "name": "entity.name.type.enum.cuda-cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*", + "name": "storage.modifier.pointer.cuda-cpp" + }, + { + "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "3": { + "name": "comment.block.cuda-cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + }, + "name": "invalid.illegal.reference-type.cuda-cpp" + }, + { + "match": "\\&", + "name": "storage.modifier.reference.cuda-cpp" + } + ] + }, + "6": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "7": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "8": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "9": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "10": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "11": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "12": { + "name": "variable.other.object.declare.cuda-cpp" + }, + "13": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "14": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + } + } + }, + "enumerator_list": { + "match": "((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "3": { + "name": "keyword.control.exception.$3.cuda-cpp" + } + } + }, + "extern_block": { + "begin": "((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(extern)(?=\\s*\\\")", + "end": "(?:(?:(?<=\\}|%>|\\?\\?>)(?:(?:\\s)+)?(;)|(;))|(?=[;>\\[\\]=]))", + "beginCaptures": { + "0": { + "name": "meta.head.extern.cuda-cpp" + }, + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "3": { + "name": "comment.block.cuda-cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "5": { + "name": "storage.type.extern.cuda-cpp" + } + }, + "endCaptures": { + "1": { + "name": "punctuation.terminator.statement.cuda-cpp" + }, + "2": { + "name": "punctuation.terminator.statement.cuda-cpp" + } + }, + "name": "meta.block.extern.cuda-cpp", + "patterns": [ + { + "begin": "\\G ?", + "end": "(?:\\{|<%|\\?\\?<|(?=;))", + "beginCaptures": {}, + "endCaptures": { + "0": { + "name": "punctuation.section.block.begin.bracket.curly.extern.cuda-cpp" + } + }, + "name": "meta.head.extern.cuda-cpp", + "patterns": [ + { + "include": "$self" + } + ] + }, + { + "begin": "(?<=\\{|<%|\\?\\?<)", + "end": "\\}|%>|\\?\\?>", + "beginCaptures": {}, + "endCaptures": { + "0": { + "name": "punctuation.section.block.end.bracket.curly.extern.cuda-cpp" + } + }, + "name": "meta.body.extern.cuda-cpp", + "patterns": [ + { + "include": "$self" + } + ] + }, + { + "begin": "(?<=\\}|%>|\\?\\?>)[\\s]*", + "end": "[\\s]*(?=;)", + "beginCaptures": {}, + "endCaptures": {}, + "name": "meta.tail.extern.cuda-cpp", + "patterns": [ + { + "include": "$self" + } + ] + }, + { + "include": "$self" + } + ] + }, + "function_body_context": { + "patterns": [ + { + "include": "#ever_present_context" + }, + { + "include": "#using_namespace" + }, + { + "include": "#type_alias" + }, + { + "include": "#using_name" + }, + { + "include": "#namespace_alias" + }, + { + "include": "#typedef_class" + }, + { + "include": "#typedef_struct" + }, + { + "include": "#typedef_union" + }, + { + "include": "#misc_keywords" + }, + { + "include": "#standard_declares" + }, + { + "include": "#class_block" + }, + { + "include": "#struct_block" + }, + { + "include": "#union_block" + }, + { + "include": "#enum_block" + }, + { + "include": "#access_control_keywords" + }, + { + "include": "#block" + }, + { + "include": "#static_assert" + }, + { + "include": "#assembly" + }, + { + "include": "#function_pointer" + }, + { + "include": "#switch_statement" + }, + { + "include": "#goto_statement" + }, + { + "include": "#evaluation_context" + }, + { + "include": "#label" + } + ] + }, + "function_call": { + "begin": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|__forceinline__|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|__constant__|__restrict__|__noinline__|thread_local|synchronized|static_cast|__managed__|const_cast|__shared__|__global__|__device__|co_return|constexpr|constexpr|constexpr|consteval|protected|namespace|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|__host__|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<11>?)+>)(?:\\s)*+)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<11>?)+>)(?:\\s)*+)?(\\()", + "end": "\\)", + "beginCaptures": { + "1": { + "patterns": [ + { + "include": "#scope_resolution_function_call_inner_generated" + } + ] + }, + "2": { + "name": "punctuation.separator.namespace.access.cuda-cpp punctuation.separator.scope-resolution.function.call.cuda-cpp" + }, + "3": { + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "4": {}, + "5": { + "name": "entity.name.function.call.cuda-cpp" + }, + "6": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "7": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "8": { + "name": "comment.block.cuda-cpp" + }, + "9": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "10": { + "name": "meta.template.call.cuda-cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "11": {}, + "12": { + "name": "punctuation.section.arguments.begin.bracket.round.function.call.cuda-cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.arguments.end.bracket.round.function.call.cuda-cpp" + } + }, + "patterns": [ + { + "include": "#evaluation_context" + } + ] + }, + "function_definition": { + "begin": "(?:(?:^|\\G|(?<=;|\\}))|(?<=>))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*)(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|__forceinline__|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|__constant__|__restrict__|__noinline__|thread_local|synchronized|static_cast|__managed__|const_cast|__shared__|__global__|__device__|co_return|constexpr|constexpr|constexpr|consteval|protected|namespace|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|__host__|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<60>?)+>)(?:\\s)*+)?::)*+)?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:__forceinline__)|(?:atomic_noexcept)|(?:__has_include)|(?:atomic_cancel)|(?:atomic_commit)|(?:dynamic_cast)|(?:__constant__)|(?:__restrict__)|(?:__noinline__)|(?:thread_local)|(?:synchronized)|(?:static_cast)|(?:__managed__)|(?:const_cast)|(?:__shared__)|(?:__global__)|(?:__device__)|(?:co_return)|(?:constexpr)|(?:constexpr)|(?:constexpr)|(?:consteval)|(?:protected)|(?:threadIdx)|(?:namespace)|(?:co_return)|(?:noexcept)|(?:noexcept)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:__host__)|(?:override)|(?:volatile)|(?:noexcept)|(?:blockIdx)|(?:blockDim)|(?:warpSize)|(?:template)|(?:operator)|(?:decltype)|(?:typename)|(?:requires)|(?:co_await)|(?:co_yield)|(?:reflexpr)|(?:alignof)|(?:alignas)|(?:default)|(?:nullptr)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:gridDim)|(?:typedef)|(?:__asm__)|(?:concept)|(?:sizeof)|(?:delete)|(?:not_eq)|(?:bitand)|(?:and_eq)|(?:xor_eq)|(?:typeid)|(?:switch)|(?:return)|(?:static)|(?:extern)|(?:inline)|(?:friend)|(?:public)|(?:ifndef)|(?:define)|(?:pragma)|(?:export)|(?:import)|(?:module)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:false)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:else)|(?:goto)|(?:case)|(?:NULL)|(?:true)|(?:elif)|(?:else)|(?:line)|(?:this)|(?:not)|(?:new)|(?:xor)|(?:and)|(?:for)|(?:try)|(?:asm)|(?:or)|(?:do)|(?:if)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<60>?)+>)?(?![\\w<:.]))(((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|__forceinline__|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|__constant__|__restrict__|__noinline__|thread_local|synchronized|static_cast|__managed__|const_cast|__shared__|__global__|__device__|co_return|constexpr|constexpr|constexpr|consteval|protected|namespace|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|__host__|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<60>?)+>)(?:\\s)*+)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\()", + "end": "(?:(?<=\\}|%>|\\?\\?>)|(?=[;>\\[\\]=]))", + "beginCaptures": { + "0": { + "name": "meta.head.function.definition.cuda-cpp" + }, + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "3": { + "name": "comment.block.cuda-cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "5": { + "name": "storage.type.template.cuda-cpp" + }, + "6": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "7": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "8": { + "name": "comment.block.cuda-cpp" + }, + "9": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "10": { + "patterns": [ + { + "include": "#attributes_context" + }, + { + "include": "#number_literal" + } + ] + }, + "11": { + "patterns": [ + { + "match": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))", + "captures": { + "1": { + "name": "storage.modifier.$1.cuda-cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "4": { + "name": "comment.block.cuda-cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "12": { + "name": "storage.modifier.$12.cuda-cpp" + }, + "13": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "14": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "15": { + "name": "comment.block.cuda-cpp" + }, + "16": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "17": { + "name": "meta.qualified_type.cuda-cpp", + "patterns": [ + { + "match": "::", + "name": "punctuation.separator.namespace.access.cuda-cpp punctuation.separator.scope-resolution.cuda-cpp" + }, + { + "match": "(?", + "beginCaptures": { + "0": { + "name": "punctuation.section.angle-brackets.begin.template.call.cuda-cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.angle-brackets.end.template.call.cuda-cpp" + } + }, + "name": "meta.template.call.cuda-cpp", + "patterns": [ + { + "include": "#template_call_context" + } + ] + }, + { + "match": "(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*", + "name": "entity.name.type.cuda-cpp" + } + ] + }, + "18": { + "patterns": [ + { + "include": "#attributes_context" + }, + { + "include": "#number_literal" + } + ] + }, + "19": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "20": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "21": { + "name": "comment.block.cuda-cpp" + }, + "22": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "23": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "24": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "25": { + "name": "comment.block.cuda-cpp" + }, + "26": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "27": { + "patterns": [ + { + "match": "::", + "name": "punctuation.separator.namespace.access.cuda-cpp punctuation.separator.scope-resolution.type.cuda-cpp" + }, + { + "match": "(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "3": { + "name": "comment.block.cuda-cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + }, + "name": "invalid.illegal.reference-type.cuda-cpp" + }, + { + "match": "\\&", + "name": "storage.modifier.reference.cuda-cpp" + } + ] + }, + "36": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "37": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "38": { + "name": "comment.block.cuda-cpp" + }, + "39": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "40": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "41": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "42": { + "name": "comment.block.cuda-cpp" + }, + "43": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "44": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "45": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "46": { + "name": "comment.block.cuda-cpp" + }, + "47": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "48": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "49": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "50": { + "name": "comment.block.cuda-cpp" + }, + "51": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "52": { + "name": "storage.type.modifier.calling-convention.cuda-cpp" + }, + "53": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "54": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "55": { + "name": "comment.block.cuda-cpp" + }, + "56": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "57": { + "patterns": [ + { + "include": "#scope_resolution_function_definition_inner_generated" + } + ] + }, + "58": { + "name": "punctuation.separator.namespace.access.cuda-cpp punctuation.separator.scope-resolution.function.definition.cuda-cpp" + }, + "59": { + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "60": {}, + "61": { + "name": "entity.name.function.definition.cuda-cpp" + }, + "62": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "63": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "64": { + "name": "comment.block.cuda-cpp" + }, + "65": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + }, + "endCaptures": {}, + "name": "meta.function.definition.cuda-cpp", + "patterns": [ + { + "begin": "\\G ?", + "end": "(?:\\{|<%|\\?\\?<|(?=;))", + "beginCaptures": {}, + "endCaptures": { + "0": { + "name": "punctuation.section.block.begin.bracket.curly.function.definition.cuda-cpp" + } + }, + "name": "meta.head.function.definition.cuda-cpp", + "patterns": [ + { + "include": "#ever_present_context" + }, + { + "begin": "\\(", + "end": "\\)", + "beginCaptures": { + "0": { + "name": "punctuation.section.parameters.begin.bracket.round.cuda-cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.parameters.end.bracket.round.cuda-cpp" + } + }, + "contentName": "meta.function.definition.parameters", + "patterns": [ + { + "include": "#ever_present_context" + }, + { + "include": "#parameter_or_maybe_value" + }, + { + "include": "#comma" + }, + { + "include": "#evaluation_context" + } + ] + }, + { + "match": "(?<=^|\\))(?:(?:\\s)+)?(->)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|__forceinline__|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|__constant__|__restrict__|__noinline__|thread_local|synchronized|static_cast|__managed__|const_cast|__shared__|__global__|__device__|co_return|constexpr|constexpr|constexpr|consteval|protected|namespace|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|__host__|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<23>?)+>)(?:\\s)*+)?::)*+)?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:__forceinline__)|(?:atomic_noexcept)|(?:__has_include)|(?:atomic_cancel)|(?:atomic_commit)|(?:dynamic_cast)|(?:__constant__)|(?:__restrict__)|(?:__noinline__)|(?:thread_local)|(?:synchronized)|(?:static_cast)|(?:__managed__)|(?:const_cast)|(?:__shared__)|(?:__global__)|(?:__device__)|(?:co_return)|(?:constexpr)|(?:constexpr)|(?:constexpr)|(?:consteval)|(?:protected)|(?:threadIdx)|(?:namespace)|(?:co_return)|(?:noexcept)|(?:noexcept)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:__host__)|(?:override)|(?:volatile)|(?:noexcept)|(?:blockIdx)|(?:blockDim)|(?:warpSize)|(?:template)|(?:operator)|(?:decltype)|(?:typename)|(?:requires)|(?:co_await)|(?:co_yield)|(?:reflexpr)|(?:alignof)|(?:alignas)|(?:default)|(?:nullptr)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:gridDim)|(?:typedef)|(?:__asm__)|(?:concept)|(?:sizeof)|(?:delete)|(?:not_eq)|(?:bitand)|(?:and_eq)|(?:xor_eq)|(?:typeid)|(?:switch)|(?:return)|(?:static)|(?:extern)|(?:inline)|(?:friend)|(?:public)|(?:ifndef)|(?:define)|(?:pragma)|(?:export)|(?:import)|(?:module)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:false)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:else)|(?:goto)|(?:case)|(?:NULL)|(?:true)|(?:elif)|(?:else)|(?:line)|(?:this)|(?:not)|(?:new)|(?:xor)|(?:and)|(?:for)|(?:try)|(?:asm)|(?:or)|(?:do)|(?:if)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<23>?)+>)?(?![\\w<:.]))", + "captures": { + "1": { + "name": "punctuation.definition.function.return-type.cuda-cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "4": { + "name": "comment.block.cuda-cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "6": { + "name": "meta.qualified_type.cuda-cpp", + "patterns": [ + { + "match": "::", + "name": "punctuation.separator.namespace.access.cuda-cpp punctuation.separator.scope-resolution.cuda-cpp" + }, + { + "match": "(?", + "beginCaptures": { + "0": { + "name": "punctuation.section.angle-brackets.begin.template.call.cuda-cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.angle-brackets.end.template.call.cuda-cpp" + } + }, + "name": "meta.template.call.cuda-cpp", + "patterns": [ + { + "include": "#template_call_context" + } + ] + }, + { + "match": "(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*", + "name": "entity.name.type.cuda-cpp" + } + ] + }, + "7": { + "patterns": [ + { + "include": "#attributes_context" + }, + { + "include": "#number_literal" + } + ] + }, + "8": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "9": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "10": { + "name": "comment.block.cuda-cpp" + }, + "11": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "12": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "13": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "14": { + "name": "comment.block.cuda-cpp" + }, + "15": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "16": { + "patterns": [ + { + "match": "::", + "name": "punctuation.separator.namespace.access.cuda-cpp punctuation.separator.scope-resolution.type.cuda-cpp" + }, + { + "match": "(?|\\?\\?>", + "beginCaptures": {}, + "endCaptures": { + "0": { + "name": "punctuation.section.block.end.bracket.curly.function.definition.cuda-cpp" + } + }, + "name": "meta.body.function.definition.cuda-cpp", + "patterns": [ + { + "include": "#function_body_context" + } + ] + }, + { + "begin": "(?<=\\}|%>|\\?\\?>)[\\s]*", + "end": "[\\s]*(?=;)", + "beginCaptures": {}, + "endCaptures": {}, + "name": "meta.tail.function.definition.cuda-cpp", + "patterns": [ + { + "include": "$self" + } + ] + } + ] + }, + "function_parameter_context": { + "patterns": [ + { + "include": "#ever_present_context" + }, + { + "include": "#parameter" + }, + { + "include": "#comma" + } + ] + }, + "function_pointer": { + "begin": "(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|__forceinline__|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|__constant__|__restrict__|__noinline__|thread_local|synchronized|static_cast|__managed__|const_cast|__shared__|__global__|__device__|co_return|constexpr|constexpr|constexpr|consteval|protected|namespace|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|__host__|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)(?:\\s)*+)?::)*+)?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:__forceinline__)|(?:atomic_noexcept)|(?:__has_include)|(?:atomic_cancel)|(?:atomic_commit)|(?:dynamic_cast)|(?:__constant__)|(?:__restrict__)|(?:__noinline__)|(?:thread_local)|(?:synchronized)|(?:static_cast)|(?:__managed__)|(?:const_cast)|(?:__shared__)|(?:__global__)|(?:__device__)|(?:co_return)|(?:constexpr)|(?:constexpr)|(?:constexpr)|(?:consteval)|(?:protected)|(?:threadIdx)|(?:namespace)|(?:co_return)|(?:noexcept)|(?:noexcept)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:__host__)|(?:override)|(?:volatile)|(?:noexcept)|(?:blockIdx)|(?:blockDim)|(?:warpSize)|(?:template)|(?:operator)|(?:decltype)|(?:typename)|(?:requires)|(?:co_await)|(?:co_yield)|(?:reflexpr)|(?:alignof)|(?:alignas)|(?:default)|(?:nullptr)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:gridDim)|(?:typedef)|(?:__asm__)|(?:concept)|(?:sizeof)|(?:delete)|(?:not_eq)|(?:bitand)|(?:and_eq)|(?:xor_eq)|(?:typeid)|(?:switch)|(?:return)|(?:static)|(?:extern)|(?:inline)|(?:friend)|(?:public)|(?:ifndef)|(?:define)|(?:pragma)|(?:export)|(?:import)|(?:module)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:false)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:else)|(?:goto)|(?:case)|(?:NULL)|(?:true)|(?:elif)|(?:else)|(?:line)|(?:this)|(?:not)|(?:new)|(?:xor)|(?:and)|(?:for)|(?:try)|(?:asm)|(?:or)|(?:do)|(?:if)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)?(?![\\w<:.]))(((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()(\\*)(?:(?:\\s)+)?((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)?)(?:(?:\\s)+)?(?:(\\[)(\\w*)(\\])(?:(?:\\s)+)?)*(\\))(?:(?:\\s)+)?(\\()", + "end": "(\\))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=[{=,);>]|\\n)(?!\\()", + "beginCaptures": { + "1": { + "name": "meta.qualified_type.cuda-cpp", + "patterns": [ + { + "match": "::", + "name": "punctuation.separator.namespace.access.cuda-cpp punctuation.separator.scope-resolution.cuda-cpp" + }, + { + "match": "(?", + "beginCaptures": { + "0": { + "name": "punctuation.section.angle-brackets.begin.template.call.cuda-cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.angle-brackets.end.template.call.cuda-cpp" + } + }, + "name": "meta.template.call.cuda-cpp", + "patterns": [ + { + "include": "#template_call_context" + } + ] + }, + { + "match": "(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*", + "name": "entity.name.type.cuda-cpp" + } + ] + }, + "2": { + "patterns": [ + { + "include": "#attributes_context" + }, + { + "include": "#number_literal" + } + ] + }, + "3": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "4": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "5": { + "name": "comment.block.cuda-cpp" + }, + "6": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "7": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "8": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "9": { + "name": "comment.block.cuda-cpp" + }, + "10": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "11": { + "patterns": [ + { + "match": "::", + "name": "punctuation.separator.namespace.access.cuda-cpp punctuation.separator.scope-resolution.type.cuda-cpp" + }, + { + "match": "(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "3": { + "name": "comment.block.cuda-cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + }, + "name": "invalid.illegal.reference-type.cuda-cpp" + }, + { + "match": "\\&", + "name": "storage.modifier.reference.cuda-cpp" + } + ] + }, + "20": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "21": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "22": { + "name": "comment.block.cuda-cpp" + }, + "23": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "24": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "25": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "26": { + "name": "comment.block.cuda-cpp" + }, + "27": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "28": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "29": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "30": { + "name": "comment.block.cuda-cpp" + }, + "31": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "32": { + "name": "punctuation.section.parens.begin.bracket.round.function.pointer.cuda-cpp" + }, + "33": { + "name": "punctuation.definition.function.pointer.dereference.cuda-cpp" + }, + "34": { + "name": "variable.other.definition.pointer.function.cuda-cpp" + }, + "35": { + "name": "punctuation.definition.begin.bracket.square.cuda-cpp" + }, + "36": { + "patterns": [ + { + "include": "#evaluation_context" + } + ] + }, + "37": { + "name": "punctuation.definition.end.bracket.square.cuda-cpp" + }, + "38": { + "name": "punctuation.section.parens.end.bracket.round.function.pointer.cuda-cpp" + }, + "39": { + "name": "punctuation.section.parameters.begin.bracket.round.function.pointer.cuda-cpp" + } + }, + "endCaptures": { + "1": { + "name": "punctuation.section.parameters.end.bracket.round.function.pointer.cuda-cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "4": { + "name": "comment.block.cuda-cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + }, + "patterns": [ + { + "include": "#function_parameter_context" + } + ] + }, + "function_pointer_parameter": { + "begin": "(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|__forceinline__|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|__constant__|__restrict__|__noinline__|thread_local|synchronized|static_cast|__managed__|const_cast|__shared__|__global__|__device__|co_return|constexpr|constexpr|constexpr|consteval|protected|namespace|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|__host__|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)(?:\\s)*+)?::)*+)?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:__forceinline__)|(?:atomic_noexcept)|(?:__has_include)|(?:atomic_cancel)|(?:atomic_commit)|(?:dynamic_cast)|(?:__constant__)|(?:__restrict__)|(?:__noinline__)|(?:thread_local)|(?:synchronized)|(?:static_cast)|(?:__managed__)|(?:const_cast)|(?:__shared__)|(?:__global__)|(?:__device__)|(?:co_return)|(?:constexpr)|(?:constexpr)|(?:constexpr)|(?:consteval)|(?:protected)|(?:threadIdx)|(?:namespace)|(?:co_return)|(?:noexcept)|(?:noexcept)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:__host__)|(?:override)|(?:volatile)|(?:noexcept)|(?:blockIdx)|(?:blockDim)|(?:warpSize)|(?:template)|(?:operator)|(?:decltype)|(?:typename)|(?:requires)|(?:co_await)|(?:co_yield)|(?:reflexpr)|(?:alignof)|(?:alignas)|(?:default)|(?:nullptr)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:gridDim)|(?:typedef)|(?:__asm__)|(?:concept)|(?:sizeof)|(?:delete)|(?:not_eq)|(?:bitand)|(?:and_eq)|(?:xor_eq)|(?:typeid)|(?:switch)|(?:return)|(?:static)|(?:extern)|(?:inline)|(?:friend)|(?:public)|(?:ifndef)|(?:define)|(?:pragma)|(?:export)|(?:import)|(?:module)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:false)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:else)|(?:goto)|(?:case)|(?:NULL)|(?:true)|(?:elif)|(?:else)|(?:line)|(?:this)|(?:not)|(?:new)|(?:xor)|(?:and)|(?:for)|(?:try)|(?:asm)|(?:or)|(?:do)|(?:if)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)?(?![\\w<:.]))(((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()(\\*)(?:(?:\\s)+)?((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)?)(?:(?:\\s)+)?(?:(\\[)(\\w*)(\\])(?:(?:\\s)+)?)*(\\))(?:(?:\\s)+)?(\\()", + "end": "(\\))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=[{=,);>]|\\n)(?!\\()", + "beginCaptures": { + "1": { + "name": "meta.qualified_type.cuda-cpp", + "patterns": [ + { + "match": "::", + "name": "punctuation.separator.namespace.access.cuda-cpp punctuation.separator.scope-resolution.cuda-cpp" + }, + { + "match": "(?", + "beginCaptures": { + "0": { + "name": "punctuation.section.angle-brackets.begin.template.call.cuda-cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.angle-brackets.end.template.call.cuda-cpp" + } + }, + "name": "meta.template.call.cuda-cpp", + "patterns": [ + { + "include": "#template_call_context" + } + ] + }, + { + "match": "(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*", + "name": "entity.name.type.cuda-cpp" + } + ] + }, + "2": { + "patterns": [ + { + "include": "#attributes_context" + }, + { + "include": "#number_literal" + } + ] + }, + "3": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "4": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "5": { + "name": "comment.block.cuda-cpp" + }, + "6": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "7": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "8": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "9": { + "name": "comment.block.cuda-cpp" + }, + "10": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "11": { + "patterns": [ + { + "match": "::", + "name": "punctuation.separator.namespace.access.cuda-cpp punctuation.separator.scope-resolution.type.cuda-cpp" + }, + { + "match": "(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "3": { + "name": "comment.block.cuda-cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + }, + "name": "invalid.illegal.reference-type.cuda-cpp" + }, + { + "match": "\\&", + "name": "storage.modifier.reference.cuda-cpp" + } + ] + }, + "20": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "21": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "22": { + "name": "comment.block.cuda-cpp" + }, + "23": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "24": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "25": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "26": { + "name": "comment.block.cuda-cpp" + }, + "27": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "28": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "29": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "30": { + "name": "comment.block.cuda-cpp" + }, + "31": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "32": { + "name": "punctuation.section.parens.begin.bracket.round.function.pointer.cuda-cpp" + }, + "33": { + "name": "punctuation.definition.function.pointer.dereference.cuda-cpp" + }, + "34": { + "name": "variable.parameter.pointer.function.cuda-cpp" + }, + "35": { + "name": "punctuation.definition.begin.bracket.square.cuda-cpp" + }, + "36": { + "patterns": [ + { + "include": "#evaluation_context" + } + ] + }, + "37": { + "name": "punctuation.definition.end.bracket.square.cuda-cpp" + }, + "38": { + "name": "punctuation.section.parens.end.bracket.round.function.pointer.cuda-cpp" + }, + "39": { + "name": "punctuation.section.parameters.begin.bracket.round.function.pointer.cuda-cpp" + } + }, + "endCaptures": { + "1": { + "name": "punctuation.section.parameters.end.bracket.round.function.pointer.cuda-cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "4": { + "name": "comment.block.cuda-cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + }, + "patterns": [ + { + "include": "#function_parameter_context" + } + ] + }, + "functional_specifiers_pre_parameters": { + "match": "(?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)", + "captures": { + "1": { + "name": "keyword.control.goto.cuda-cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "4": { + "name": "entity.name.label.call.cuda-cpp" + } + } + }, + "identifier": { + "match": "(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*" + }, + "include": { + "match": "^((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((#)(?:(?:\\s)+)?((?:include|include_next))\\b)(?:(?:\\s)+)?(?:(?:(?:((<)[^>]*(>?)((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:\\n)|$)|(?=\\/\\/)))|((\\\")[^\\\"]*((?:\\\")?)((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:\\n)|$)|(?=\\/\\/))))|(((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*(?:\\.(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)*((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:\\n)|$)|(?=(?:\\/\\/|;)))))|((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:\\n)|$)|(?=(?:\\/\\/|;))))", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "3": { + "name": "keyword.control.directive.$5.cuda-cpp" + }, + "4": { + "name": "punctuation.definition.directive.cuda-cpp" + }, + "6": { + "name": "string.quoted.other.lt-gt.include.cuda-cpp" + }, + "7": { + "name": "punctuation.definition.string.begin.cuda-cpp" + }, + "8": { + "name": "punctuation.definition.string.end.cuda-cpp" + }, + "9": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "10": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "11": { + "name": "string.quoted.double.include.cuda-cpp" + }, + "12": { + "name": "punctuation.definition.string.begin.cuda-cpp" + }, + "13": { + "name": "punctuation.definition.string.end.cuda-cpp" + }, + "14": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "15": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "16": { + "name": "entity.name.other.preprocessor.macro.include.cuda-cpp" + }, + "17": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "18": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "19": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "20": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "21": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "22": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + } + }, + "name": "meta.preprocessor.include.cuda-cpp" + }, + "inheritance_context": { + "patterns": [ + { + "include": "#ever_present_context" + }, + { + "match": ",", + "name": "punctuation.separator.delimiter.comma.inheritance.cuda-cpp" + }, + { + "match": "(?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|__forceinline__|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|__constant__|__restrict__|__noinline__|thread_local|synchronized|static_cast|__managed__|const_cast|__shared__|__global__|__device__|co_return|constexpr|constexpr|constexpr|consteval|protected|namespace|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|__host__|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)(?:\\s)*+)?::)*+)?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:__forceinline__)|(?:atomic_noexcept)|(?:__has_include)|(?:atomic_cancel)|(?:atomic_commit)|(?:dynamic_cast)|(?:__constant__)|(?:__restrict__)|(?:__noinline__)|(?:thread_local)|(?:synchronized)|(?:static_cast)|(?:__managed__)|(?:const_cast)|(?:__shared__)|(?:__global__)|(?:__device__)|(?:co_return)|(?:constexpr)|(?:constexpr)|(?:constexpr)|(?:consteval)|(?:protected)|(?:threadIdx)|(?:namespace)|(?:co_return)|(?:noexcept)|(?:noexcept)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:__host__)|(?:override)|(?:volatile)|(?:noexcept)|(?:blockIdx)|(?:blockDim)|(?:warpSize)|(?:template)|(?:operator)|(?:decltype)|(?:typename)|(?:requires)|(?:co_await)|(?:co_yield)|(?:reflexpr)|(?:alignof)|(?:alignas)|(?:default)|(?:nullptr)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:gridDim)|(?:typedef)|(?:__asm__)|(?:concept)|(?:sizeof)|(?:delete)|(?:not_eq)|(?:bitand)|(?:and_eq)|(?:xor_eq)|(?:typeid)|(?:switch)|(?:return)|(?:static)|(?:extern)|(?:inline)|(?:friend)|(?:public)|(?:ifndef)|(?:define)|(?:pragma)|(?:export)|(?:import)|(?:module)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:false)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:else)|(?:goto)|(?:case)|(?:NULL)|(?:true)|(?:elif)|(?:else)|(?:line)|(?:this)|(?:not)|(?:new)|(?:xor)|(?:and)|(?:for)|(?:try)|(?:asm)|(?:or)|(?:do)|(?:if)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)?(?![\\w<:.]))", + "captures": { + "1": { + "name": "meta.qualified_type.cuda-cpp", + "patterns": [ + { + "match": "::", + "name": "punctuation.separator.namespace.access.cuda-cpp punctuation.separator.scope-resolution.cuda-cpp" + }, + { + "match": "(?", + "beginCaptures": { + "0": { + "name": "punctuation.section.angle-brackets.begin.template.call.cuda-cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.angle-brackets.end.template.call.cuda-cpp" + } + }, + "name": "meta.template.call.cuda-cpp", + "patterns": [ + { + "include": "#template_call_context" + } + ] + }, + { + "match": "(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*", + "name": "entity.name.type.cuda-cpp" + } + ] + }, + "2": { + "patterns": [ + { + "include": "#attributes_context" + }, + { + "include": "#number_literal" + } + ] + }, + "3": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "4": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "5": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "6": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "7": { + "patterns": [ + { + "match": "::", + "name": "punctuation.separator.namespace.access.cuda-cpp punctuation.separator.scope-resolution.type.cuda-cpp" + }, + { + "match": "(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "12": {} + } + } + ] + }, + "inline_builtin_storage_type": { + "match": "(?:\\s)*+(?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(:)", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "3": { + "name": "entity.name.label.cuda-cpp" + }, + "4": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "5": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "6": { + "name": "punctuation.separator.label.cuda-cpp" + } + } + }, + "lambdas": { + "begin": "(?:(?<=[^\\s]|^)(?])|(?<=\\Wreturn|^return))(?:(?:\\s)+)?(\\[(?!\\[| *+\"| *+\\d))((?:[^\\[\\]]|((??)++\\]))*+)(\\](?!((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))[\\[\\];]))", + "end": "(?<=[;}])", + "beginCaptures": { + "1": { + "name": "punctuation.definition.capture.begin.lambda.cuda-cpp" + }, + "2": { + "name": "meta.lambda.capture.cuda-cpp", + "patterns": [ + { + "include": "#the_this_keyword" + }, + { + "match": "((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?=\\]|\\z|$)|(,))|(\\=))", + "captures": { + "1": { + "name": "variable.parameter.capture.cuda-cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "4": { + "name": "comment.block.cuda-cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "6": { + "name": "punctuation.separator.delimiter.comma.cuda-cpp" + }, + "7": { + "name": "keyword.operator.assignment.cuda-cpp" + } + } + }, + { + "include": "#evaluation_context" + } + ] + }, + "3": {}, + "4": { + "name": "punctuation.definition.capture.end.lambda.cuda-cpp" + }, + "5": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "6": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "7": { + "name": "comment.block.cuda-cpp" + }, + "8": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + }, + "endCaptures": {}, + "patterns": [ + { + "begin": "\\(", + "end": "\\)", + "beginCaptures": { + "0": { + "name": "punctuation.definition.parameters.begin.lambda.cuda-cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.definition.parameters.end.lambda.cuda-cpp" + } + }, + "name": "meta.function.definition.parameters.lambda.cuda-cpp", + "patterns": [ + { + "include": "#function_parameter_context" + } + ] + }, + { + "match": "(?)((?:.+?(?=\\{|$))?)", + "captures": { + "1": { + "name": "punctuation.definition.lambda.return-type.cuda-cpp" + }, + "2": { + "name": "storage.type.return-type.lambda.cuda-cpp" + } + } + }, + { + "begin": "\\{", + "end": "\\}", + "beginCaptures": { + "0": { + "name": "punctuation.section.block.begin.bracket.curly.lambda.cuda-cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.block.end.bracket.curly.lambda.cuda-cpp" + } + }, + "name": "meta.function.definition.body.lambda.cuda-cpp", + "patterns": [ + { + "include": "$self" + } + ] + } + ] + }, + "language_constants": { + "match": "(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(#)(?:(?:\\s)+)?line\\b", + "end": "(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(#)(?:(?:\\s)+)?define\\b)(?:(?:\\s)+)?((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?\\*|->)))((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*(?:(?:\\s)+)?(?:(?:\\.\\*|\\.)|(?:->\\*|->))(?:(?:\\s)+)?)*)(?:(?:\\s)+)?(\\b(?!uint_least16_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|uint_least32_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|uint_least64_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|int_least16_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|int_least32_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|int_least64_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|uint_least8_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|uint_fast16_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|uint_fast32_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|uint_fast64_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|int_least8_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|int_fast16_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|int_fast32_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|int_fast64_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|uint_fast8_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|suseconds_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|int_fast8_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|useconds_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|ulonglong1[^Pattern.new(\n match: \\/\\w\\/,\n)]|ulonglong2[^Pattern.new(\n match: \\/\\w\\/,\n)]|ulonglong3[^Pattern.new(\n match: \\/\\w\\/,\n)]|ulonglong4[^Pattern.new(\n match: \\/\\w\\/,\n)]|blksize_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|in_addr_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|in_port_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|uintptr_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|uintmax_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|uintmax_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|uintmax_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|longlong1[^Pattern.new(\n match: \\/\\w\\/,\n)]|longlong2[^Pattern.new(\n match: \\/\\w\\/,\n)]|longlong3[^Pattern.new(\n match: \\/\\w\\/,\n)]|longlong4[^Pattern.new(\n match: \\/\\w\\/,\n)]|unsigned[^Pattern.new(\n match: \\/\\w\\/,\n)]|u_quad_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|blkcnt_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|uint16_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|uint32_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|uint64_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|intptr_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|intmax_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|intmax_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|wchar_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|u_short[^Pattern.new(\n match: \\/\\w\\/,\n)]|qaddr_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|caddr_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|daddr_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|fixpt_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|nlink_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|segsz_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|swblk_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|clock_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|ssize_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|int16_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|int32_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|int64_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|uint8_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|ushort1[^Pattern.new(\n match: \\/\\w\\/,\n)]|ushort2[^Pattern.new(\n match: \\/\\w\\/,\n)]|ushort3[^Pattern.new(\n match: \\/\\w\\/,\n)]|ushort4[^Pattern.new(\n match: \\/\\w\\/,\n)]|double1[^Pattern.new(\n match: \\/\\w\\/,\n)]|double2[^Pattern.new(\n match: \\/\\w\\/,\n)]|double3[^Pattern.new(\n match: \\/\\w\\/,\n)]|double4[^Pattern.new(\n match: \\/\\w\\/,\n)]|signed[^Pattern.new(\n match: \\/\\w\\/,\n)]|double[^Pattern.new(\n match: \\/\\w\\/,\n)]|u_char[^Pattern.new(\n match: \\/\\w\\/,\n)]|u_long[^Pattern.new(\n match: \\/\\w\\/,\n)]|ushort[^Pattern.new(\n match: \\/\\w\\/,\n)]|quad_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|mode_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|size_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|time_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|int8_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|uchar1[^Pattern.new(\n match: \\/\\w\\/,\n)]|uchar2[^Pattern.new(\n match: \\/\\w\\/,\n)]|uchar3[^Pattern.new(\n match: \\/\\w\\/,\n)]|uchar4[^Pattern.new(\n match: \\/\\w\\/,\n)]|short1[^Pattern.new(\n match: \\/\\w\\/,\n)]|short2[^Pattern.new(\n match: \\/\\w\\/,\n)]|short3[^Pattern.new(\n match: \\/\\w\\/,\n)]|short4[^Pattern.new(\n match: \\/\\w\\/,\n)]|ulong4[^Pattern.new(\n match: \\/\\w\\/,\n)]|ulong1[^Pattern.new(\n match: \\/\\w\\/,\n)]|ulong2[^Pattern.new(\n match: \\/\\w\\/,\n)]|ulong3[^Pattern.new(\n match: \\/\\w\\/,\n)]|ulong4[^Pattern.new(\n match: \\/\\w\\/,\n)]|float1[^Pattern.new(\n match: \\/\\w\\/,\n)]|float2[^Pattern.new(\n match: \\/\\w\\/,\n)]|float3[^Pattern.new(\n match: \\/\\w\\/,\n)]|float4[^Pattern.new(\n match: \\/\\w\\/,\n)]|short[^Pattern.new(\n match: \\/\\w\\/,\n)]|float[^Pattern.new(\n match: \\/\\w\\/,\n)]|u_int[^Pattern.new(\n match: \\/\\w\\/,\n)]|div_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|dev_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|gid_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|ino_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|key_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|pid_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|off_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|uid_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|char1[^Pattern.new(\n match: \\/\\w\\/,\n)]|char2[^Pattern.new(\n match: \\/\\w\\/,\n)]|char3[^Pattern.new(\n match: \\/\\w\\/,\n)]|char4[^Pattern.new(\n match: \\/\\w\\/,\n)]|uint1[^Pattern.new(\n match: \\/\\w\\/,\n)]|uint2[^Pattern.new(\n match: \\/\\w\\/,\n)]|uint3[^Pattern.new(\n match: \\/\\w\\/,\n)]|uint4[^Pattern.new(\n match: \\/\\w\\/,\n)]|long1[^Pattern.new(\n match: \\/\\w\\/,\n)]|long2[^Pattern.new(\n match: \\/\\w\\/,\n)]|long3[^Pattern.new(\n match: \\/\\w\\/,\n)]|auto[^Pattern.new(\n match: \\/\\w\\/,\n)]|void[^Pattern.new(\n match: \\/\\w\\/,\n)]|char[^Pattern.new(\n match: \\/\\w\\/,\n)]|long[^Pattern.new(\n match: \\/\\w\\/,\n)]|bool[^Pattern.new(\n match: \\/\\w\\/,\n)]|uint[^Pattern.new(\n match: \\/\\w\\/,\n)]|id_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|id_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|int1[^Pattern.new(\n match: \\/\\w\\/,\n)]|int2[^Pattern.new(\n match: \\/\\w\\/,\n)]|int3[^Pattern.new(\n match: \\/\\w\\/,\n)]|int4[^Pattern.new(\n match: \\/\\w\\/,\n)]|dim3[^Pattern.new(\n match: \\/\\w\\/,\n)]|int[^Pattern.new(\n match: \\/\\w\\/,\n)])(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b(?!\\())", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "3": { + "name": "variable.language.this.cuda-cpp" + }, + "4": { + "name": "variable.other.object.access.cuda-cpp" + }, + "5": { + "name": "punctuation.separator.dot-access.cuda-cpp" + }, + "6": { + "name": "punctuation.separator.pointer-access.cuda-cpp" + }, + "7": { + "patterns": [ + { + "match": "(?<=(?:\\.\\*|\\.|->|->\\*))(?:(?:\\s)+)?(?:((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?\\*|->)))", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "3": { + "name": "comment.block.cuda-cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "5": { + "name": "variable.language.this.cuda-cpp" + }, + "6": { + "name": "variable.other.object.property.cuda-cpp" + }, + "7": { + "name": "punctuation.separator.dot-access.cuda-cpp" + }, + "8": { + "name": "punctuation.separator.pointer-access.cuda-cpp" + } + } + }, + { + "match": "(?:((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?\\*|->)))", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "3": { + "name": "comment.block.cuda-cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "5": { + "name": "variable.language.this.cuda-cpp" + }, + "6": { + "name": "variable.other.object.access.cuda-cpp" + }, + "7": { + "name": "punctuation.separator.dot-access.cuda-cpp" + }, + "8": { + "name": "punctuation.separator.pointer-access.cuda-cpp" + } + } + }, + { + "include": "#member_access" + }, + { + "include": "#method_access" + } + ] + }, + "8": { + "name": "variable.other.property.cuda-cpp" + } + } + }, + "memory_operators": { + "match": "((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:(?:(delete)(?:(?:\\s)+)?(\\[\\])|(delete))|(new))(?!\\w))", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "3": { + "name": "keyword.operator.wordlike.cuda-cpp" + }, + "4": { + "name": "keyword.operator.delete.array.cuda-cpp" + }, + "5": { + "name": "keyword.operator.delete.array.bracket.cuda-cpp" + }, + "6": { + "name": "keyword.operator.delete.cuda-cpp" + }, + "7": { + "name": "keyword.operator.new.cuda-cpp" + } + } + }, + "method_access": { + "begin": "(?:((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?\\*|->)))((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*(?:(?:\\s)+)?(?:(?:\\.\\*|\\.)|(?:->\\*|->))(?:(?:\\s)+)?)*)(?:(?:\\s)+)?(~?(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)(?:(?:\\s)+)?(\\()", + "end": "\\)", + "beginCaptures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "3": { + "name": "comment.block.cuda-cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "5": { + "name": "variable.language.this.cuda-cpp" + }, + "6": { + "name": "variable.other.object.access.cuda-cpp" + }, + "7": { + "name": "punctuation.separator.dot-access.cuda-cpp" + }, + "8": { + "name": "punctuation.separator.pointer-access.cuda-cpp" + }, + "9": { + "patterns": [ + { + "match": "(?<=(?:\\.\\*|\\.|->|->\\*))(?:(?:\\s)+)?(?:((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?\\*|->)))", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "3": { + "name": "comment.block.cuda-cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "5": { + "name": "variable.language.this.cuda-cpp" + }, + "6": { + "name": "variable.other.object.property.cuda-cpp" + }, + "7": { + "name": "punctuation.separator.dot-access.cuda-cpp" + }, + "8": { + "name": "punctuation.separator.pointer-access.cuda-cpp" + } + } + }, + { + "match": "(?:((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?\\*|->)))", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "3": { + "name": "comment.block.cuda-cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "5": { + "name": "variable.language.this.cuda-cpp" + }, + "6": { + "name": "variable.other.object.access.cuda-cpp" + }, + "7": { + "name": "punctuation.separator.dot-access.cuda-cpp" + }, + "8": { + "name": "punctuation.separator.pointer-access.cuda-cpp" + } + } + }, + { + "include": "#member_access" + }, + { + "include": "#method_access" + } + ] + }, + "10": { + "name": "entity.name.function.member.cuda-cpp" + }, + "11": { + "name": "punctuation.section.arguments.begin.bracket.round.function.member.cuda-cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.arguments.end.bracket.round.function.member.cuda-cpp" + } + }, + "patterns": [ + { + "include": "#evaluation_context" + } + ] + }, + "misc_keywords": { + "match": "((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "3": { + "name": "keyword.other.$3.cuda-cpp" + } + } + }, + "ms_attributes": { + "begin": "__declspec\\(", + "end": "\\)", + "beginCaptures": { + "0": { + "name": "punctuation.section.attribute.begin.cuda-cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.attribute.end.cuda-cpp" + } + }, + "name": "support.other.attribute.cuda-cpp", + "patterns": [ + { + "include": "#attributes_context" + }, + { + "begin": "\\(", + "end": "\\)", + "beginCaptures": {}, + "endCaptures": {}, + "patterns": [ + { + "include": "#attributes_context" + }, + { + "include": "#string_context" + } + ] + }, + { + "match": "(using)(?:\\s)+((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<8>?)+>)(?:\\s)*+)?::)*\\s*+)(?:(?:\\s)+)?((?|\\?\\?>)|(?=[;>\\[\\]=]))", + "beginCaptures": { + "0": { + "name": "meta.head.namespace.cuda-cpp" + }, + "1": { + "name": "keyword.other.namespace.definition.cuda-cpp storage.type.namespace.definition.cuda-cpp" + } + }, + "endCaptures": {}, + "name": "meta.block.namespace.cuda-cpp", + "patterns": [ + { + "begin": "\\G ?", + "end": "(?:\\{|<%|\\?\\?<|(?=;))", + "beginCaptures": {}, + "endCaptures": { + "0": { + "name": "punctuation.section.block.begin.bracket.curly.namespace.cuda-cpp" + } + }, + "name": "meta.head.namespace.cuda-cpp", + "patterns": [ + { + "include": "#ever_present_context" + }, + { + "include": "#attributes_context" + }, + { + "match": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|__forceinline__|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|__constant__|__restrict__|__noinline__|thread_local|synchronized|static_cast|__managed__|const_cast|__shared__|__global__|__device__|co_return|constexpr|constexpr|constexpr|consteval|protected|namespace|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|__host__|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<4>?)+>)(?:\\s)*+)?::)*\\s*+)(?:(?:\\s)+)?((?|\\?\\?>", + "beginCaptures": {}, + "endCaptures": { + "0": { + "name": "punctuation.section.block.end.bracket.curly.namespace.cuda-cpp" + } + }, + "name": "meta.body.namespace.cuda-cpp", + "patterns": [ + { + "include": "$self" + } + ] + }, + { + "begin": "(?<=\\}|%>|\\?\\?>)[\\s]*", + "end": "[\\s]*(?=;)", + "beginCaptures": {}, + "endCaptures": {}, + "name": "meta.tail.namespace.cuda-cpp", + "patterns": [ + { + "include": "$self" + } + ] + } + ] + }, + "noexcept_operator": { + "begin": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", + "end": "\\)", + "beginCaptures": { + "1": { + "name": "keyword.operator.functionlike.cuda-cpp keyword.operator.noexcept.cuda-cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "4": { + "name": "comment.block.cuda-cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "6": { + "name": "punctuation.section.arguments.begin.bracket.round.operator.noexcept.cuda-cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.arguments.end.bracket.round.operator.noexcept.cuda-cpp" + } + }, + "contentName": "meta.arguments.operator.noexcept", + "patterns": [ + { + "include": "#evaluation_context" + } + ] + }, + "number_literal": { + "match": "(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|__forceinline__|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|__constant__|__restrict__|__noinline__|thread_local|synchronized|static_cast|__managed__|const_cast|__shared__|__global__|__device__|co_return|constexpr|constexpr|constexpr|consteval|protected|namespace|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|__host__|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<55>?)+>)(?:\\s)*+)?::)*+)?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:__forceinline__)|(?:atomic_noexcept)|(?:__has_include)|(?:atomic_cancel)|(?:atomic_commit)|(?:dynamic_cast)|(?:__constant__)|(?:__restrict__)|(?:__noinline__)|(?:thread_local)|(?:synchronized)|(?:static_cast)|(?:__managed__)|(?:const_cast)|(?:__shared__)|(?:__global__)|(?:__device__)|(?:co_return)|(?:constexpr)|(?:constexpr)|(?:constexpr)|(?:consteval)|(?:protected)|(?:threadIdx)|(?:namespace)|(?:co_return)|(?:noexcept)|(?:noexcept)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:__host__)|(?:override)|(?:volatile)|(?:noexcept)|(?:blockIdx)|(?:blockDim)|(?:warpSize)|(?:template)|(?:operator)|(?:decltype)|(?:typename)|(?:requires)|(?:co_await)|(?:co_yield)|(?:reflexpr)|(?:alignof)|(?:alignas)|(?:default)|(?:nullptr)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:gridDim)|(?:typedef)|(?:__asm__)|(?:concept)|(?:sizeof)|(?:delete)|(?:not_eq)|(?:bitand)|(?:and_eq)|(?:xor_eq)|(?:typeid)|(?:switch)|(?:return)|(?:static)|(?:extern)|(?:inline)|(?:friend)|(?:public)|(?:ifndef)|(?:define)|(?:pragma)|(?:export)|(?:import)|(?:module)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:false)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:else)|(?:goto)|(?:case)|(?:NULL)|(?:true)|(?:elif)|(?:else)|(?:line)|(?:this)|(?:not)|(?:new)|(?:xor)|(?:and)|(?:for)|(?:try)|(?:asm)|(?:or)|(?:do)|(?:if)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<55>?)+>)?(?![\\w<:.]))(((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|__forceinline__|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|__constant__|__restrict__|__noinline__|thread_local|synchronized|static_cast|__managed__|const_cast|__shared__|__global__|__device__|co_return|constexpr|constexpr|constexpr|consteval|protected|namespace|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|__host__|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<55>?)+>)(?:\\s)*+)?::)*+)(operator)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|__forceinline__|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|__constant__|__restrict__|__noinline__|thread_local|synchronized|static_cast|__managed__|const_cast|__shared__|__global__|__device__|co_return|constexpr|constexpr|constexpr|consteval|protected|namespace|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|__host__|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<55>?)+>)(?:\\s)*+)?::)*+)(?:(?:((?:(?:delete\\[\\])|(?:delete)|(?:new\\[\\])|(?:new)|(?:\\->\\*)|(?:<<=)|(?:>>=)|(?:<=>)|(?:\\+\\+)|(?:\\-\\-)|(?:\\(\\))|(?:\\[\\])|(?:\\->)|(?:\\+\\+)|(?:\\-\\-)|(?:<<)|(?:>>)|(?:<=)|(?:>=)|(?:==)|(?:!=)|(?:&&)|(?:\\|\\|)|(?:\\+=)|(?:\\-=)|(?:\\*=)|(?:\\/=)|(?:%=)|(?:&=)|(?:\\^=)|(?:\\|=)|(?:\\+)|(?:\\-)|!|~|(?:\\*)|&|(?:\\*)|(?:\\/)|%|(?:\\+)|(?:\\-)|<|>|&|(?:\\^)|(?:\\|)|=|,))|((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:\\[\\])?)))|(\"\")((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\<|\\()", + "end": "(?:(?<=\\}|%>|\\?\\?>)|(?=[;>\\[\\]=]))", + "beginCaptures": { + "0": { + "name": "meta.head.function.definition.special.operator-overload.cuda-cpp" + }, + "1": { + "name": "meta.qualified_type.cuda-cpp", + "patterns": [ + { + "match": "::", + "name": "punctuation.separator.namespace.access.cuda-cpp punctuation.separator.scope-resolution.cuda-cpp" + }, + { + "match": "(?", + "beginCaptures": { + "0": { + "name": "punctuation.section.angle-brackets.begin.template.call.cuda-cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.angle-brackets.end.template.call.cuda-cpp" + } + }, + "name": "meta.template.call.cuda-cpp", + "patterns": [ + { + "include": "#template_call_context" + } + ] + }, + { + "match": "(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*", + "name": "entity.name.type.cuda-cpp" + } + ] + }, + "2": { + "patterns": [ + { + "include": "#attributes_context" + }, + { + "include": "#number_literal" + } + ] + }, + "3": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "4": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "5": { + "name": "comment.block.cuda-cpp" + }, + "6": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "7": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "8": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "9": { + "name": "comment.block.cuda-cpp" + }, + "10": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "11": { + "patterns": [ + { + "match": "::", + "name": "punctuation.separator.namespace.access.cuda-cpp punctuation.separator.scope-resolution.type.cuda-cpp" + }, + { + "match": "(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "3": { + "name": "comment.block.cuda-cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + }, + "name": "invalid.illegal.reference-type.cuda-cpp" + }, + { + "match": "\\&", + "name": "storage.modifier.reference.cuda-cpp" + } + ] + }, + "20": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "21": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "22": { + "name": "comment.block.cuda-cpp" + }, + "23": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "24": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "25": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "26": { + "name": "comment.block.cuda-cpp" + }, + "27": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "28": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "29": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "30": { + "name": "comment.block.cuda-cpp" + }, + "31": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "32": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "33": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "34": { + "name": "comment.block.cuda-cpp" + }, + "35": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "36": { + "name": "storage.type.modifier.calling-convention.cuda-cpp" + }, + "37": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "38": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "39": { + "name": "comment.block.cuda-cpp" + }, + "40": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "41": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "42": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "43": { + "name": "comment.block.cuda-cpp" + }, + "44": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "45": { + "patterns": [ + { + "match": "::", + "name": "punctuation.separator.namespace.access.cuda-cpp punctuation.separator.scope-resolution.operator.cuda-cpp" + }, + { + "match": "(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "3": { + "name": "comment.block.cuda-cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + }, + "name": "invalid.illegal.reference-type.cuda-cpp" + }, + { + "match": "\\&", + "name": "entity.name.operator.type.reference.cuda-cpp" + } + ] + }, + "59": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "60": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "61": { + "name": "comment.block.cuda-cpp" + }, + "62": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "63": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "64": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "65": { + "name": "comment.block.cuda-cpp" + }, + "66": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "67": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "68": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "69": { + "name": "comment.block.cuda-cpp" + }, + "70": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "71": { + "name": "entity.name.operator.type.array.cuda-cpp" + }, + "72": { + "name": "entity.name.operator.custom-literal.cuda-cpp" + }, + "73": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "74": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "75": { + "name": "comment.block.cuda-cpp" + }, + "76": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "77": { + "name": "entity.name.operator.custom-literal.cuda-cpp" + }, + "78": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "79": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "80": { + "name": "comment.block.cuda-cpp" + }, + "81": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + }, + "endCaptures": {}, + "name": "meta.function.definition.special.operator-overload.cuda-cpp", + "patterns": [ + { + "begin": "\\G ?", + "end": "(?:\\{|<%|\\?\\?<|(?=;))", + "beginCaptures": {}, + "endCaptures": { + "0": { + "name": "punctuation.section.block.begin.bracket.curly.function.definition.special.operator-overload.cuda-cpp" + } + }, + "name": "meta.head.function.definition.special.operator-overload.cuda-cpp", + "patterns": [ + { + "include": "#ever_present_context" + }, + { + "include": "#template_call_range" + }, + { + "begin": "\\(", + "end": "\\)", + "beginCaptures": { + "0": { + "name": "punctuation.section.parameters.begin.bracket.round.special.operator-overload.cuda-cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.parameters.end.bracket.round.special.operator-overload.cuda-cpp" + } + }, + "contentName": "meta.function.definition.parameters.special.operator-overload", + "patterns": [ + { + "include": "#function_parameter_context" + }, + { + "include": "#evaluation_context" + } + ] + }, + { + "include": "#qualifiers_and_specifiers_post_parameters" + }, + { + "include": "$self" + } + ] + }, + { + "begin": "(?<=\\{|<%|\\?\\?<)", + "end": "\\}|%>|\\?\\?>", + "beginCaptures": {}, + "endCaptures": { + "0": { + "name": "punctuation.section.block.end.bracket.curly.function.definition.special.operator-overload.cuda-cpp" + } + }, + "name": "meta.body.function.definition.special.operator-overload.cuda-cpp", + "patterns": [ + { + "include": "#function_body_context" + } + ] + }, + { + "begin": "(?<=\\}|%>|\\?\\?>)[\\s]*", + "end": "[\\s]*(?=;)", + "beginCaptures": {}, + "endCaptures": {}, + "name": "meta.tail.function.definition.special.operator-overload.cuda-cpp", + "patterns": [ + { + "include": "$self" + } + ] + } + ] + }, + "operators": { + "patterns": [ + { + "begin": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", + "end": "\\)", + "beginCaptures": { + "1": { + "name": "keyword.operator.functionlike.cuda-cpp keyword.operator.sizeof.cuda-cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "4": { + "name": "comment.block.cuda-cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "6": { + "name": "punctuation.section.arguments.begin.bracket.round.operator.sizeof.cuda-cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.arguments.end.bracket.round.operator.sizeof.cuda-cpp" + } + }, + "contentName": "meta.arguments.operator.sizeof", + "patterns": [ + { + "include": "#evaluation_context" + } + ] + }, + { + "begin": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", + "end": "\\)", + "beginCaptures": { + "1": { + "name": "keyword.operator.functionlike.cuda-cpp keyword.operator.alignof.cuda-cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "4": { + "name": "comment.block.cuda-cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "6": { + "name": "punctuation.section.arguments.begin.bracket.round.operator.alignof.cuda-cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.arguments.end.bracket.round.operator.alignof.cuda-cpp" + } + }, + "contentName": "meta.arguments.operator.alignof", + "patterns": [ + { + "include": "#evaluation_context" + } + ] + }, + { + "begin": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", + "end": "\\)", + "beginCaptures": { + "1": { + "name": "keyword.operator.functionlike.cuda-cpp keyword.operator.alignas.cuda-cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "4": { + "name": "comment.block.cuda-cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "6": { + "name": "punctuation.section.arguments.begin.bracket.round.operator.alignas.cuda-cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.arguments.end.bracket.round.operator.alignas.cuda-cpp" + } + }, + "contentName": "meta.arguments.operator.alignas", + "patterns": [ + { + "include": "#evaluation_context" + } + ] + }, + { + "begin": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", + "end": "\\)", + "beginCaptures": { + "1": { + "name": "keyword.operator.functionlike.cuda-cpp keyword.operator.typeid.cuda-cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "4": { + "name": "comment.block.cuda-cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "6": { + "name": "punctuation.section.arguments.begin.bracket.round.operator.typeid.cuda-cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.arguments.end.bracket.round.operator.typeid.cuda-cpp" + } + }, + "contentName": "meta.arguments.operator.typeid", + "patterns": [ + { + "include": "#evaluation_context" + } + ] + }, + { + "begin": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", + "end": "\\)", + "beginCaptures": { + "1": { + "name": "keyword.operator.functionlike.cuda-cpp keyword.operator.noexcept.cuda-cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "4": { + "name": "comment.block.cuda-cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "6": { + "name": "punctuation.section.arguments.begin.bracket.round.operator.noexcept.cuda-cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.arguments.end.bracket.round.operator.noexcept.cuda-cpp" + } + }, + "contentName": "meta.arguments.operator.noexcept", + "patterns": [ + { + "include": "#evaluation_context" + } + ] + }, + { + "begin": "(\\bsizeof\\.\\.\\.)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", + "end": "\\)", + "beginCaptures": { + "1": { + "name": "keyword.operator.functionlike.cuda-cpp keyword.operator.sizeof.variadic.cuda-cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "4": { + "name": "comment.block.cuda-cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "6": { + "name": "punctuation.section.arguments.begin.bracket.round.operator.sizeof.variadic.cuda-cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.arguments.end.bracket.round.operator.sizeof.variadic.cuda-cpp" + } + }, + "contentName": "meta.arguments.operator.sizeof.variadic", + "patterns": [ + { + "include": "#evaluation_context" + } + ] + }, + { + "match": "--", + "name": "keyword.operator.decrement.cuda-cpp" + }, + { + "match": "\\+\\+", + "name": "keyword.operator.increment.cuda-cpp" + }, + { + "match": "%=|\\+=|-=|\\*=|(?>=|\\|=", + "name": "keyword.operator.assignment.compound.bitwise.cuda-cpp" + }, + { + "match": "<<|>>", + "name": "keyword.operator.bitwise.shift.cuda-cpp" + }, + { + "match": "!=|<=|>=|==|<|>", + "name": "keyword.operator.comparison.cuda-cpp" + }, + { + "match": "&&|!|\\|\\|", + "name": "keyword.operator.logical.cuda-cpp" + }, + { + "match": "&|\\||\\^|~", + "name": "keyword.operator.cuda-cpp" + }, + { + "include": "#assignment_operator" + }, + { + "match": "%|\\*|\\/|-|\\+", + "name": "keyword.operator.cuda-cpp" + }, + { + "include": "#ternary_operator" + } + ] + }, + "over_qualified_types": { + "patterns": [ + { + "match": "(struct)((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:\\[((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\]((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?(?=,|\\)|\\n)", + "captures": { + "1": { + "name": "storage.type.struct.parameter.cuda-cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "4": { + "name": "entity.name.type.struct.parameter.cuda-cpp" + }, + "5": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "6": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "7": { + "patterns": [ + { + "match": "\\*", + "name": "storage.modifier.pointer.cuda-cpp" + }, + { + "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "3": { + "name": "comment.block.cuda-cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + }, + "name": "invalid.illegal.reference-type.cuda-cpp" + }, + { + "match": "\\&", + "name": "storage.modifier.reference.cuda-cpp" + } + ] + }, + "8": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "9": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "10": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "11": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "12": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "13": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "14": { + "name": "variable.other.object.declare.cuda-cpp" + }, + "15": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "16": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "17": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "18": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "19": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "20": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + } + } + }, + { + "match": "(enum)((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:\\[((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\]((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?(?=,|\\)|\\n)", + "captures": { + "1": { + "name": "storage.type.enum.parameter.cuda-cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "4": { + "name": "entity.name.type.enum.parameter.cuda-cpp" + }, + "5": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "6": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "7": { + "patterns": [ + { + "match": "\\*", + "name": "storage.modifier.pointer.cuda-cpp" + }, + { + "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "3": { + "name": "comment.block.cuda-cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + }, + "name": "invalid.illegal.reference-type.cuda-cpp" + }, + { + "match": "\\&", + "name": "storage.modifier.reference.cuda-cpp" + } + ] + }, + "8": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "9": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "10": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "11": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "12": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "13": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "14": { + "name": "variable.other.object.declare.cuda-cpp" + }, + "15": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "16": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "17": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "18": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "19": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "20": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + } + } + }, + { + "match": "(union)((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:\\[((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\]((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?(?=,|\\)|\\n)", + "captures": { + "1": { + "name": "storage.type.union.parameter.cuda-cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "4": { + "name": "entity.name.type.union.parameter.cuda-cpp" + }, + "5": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "6": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "7": { + "patterns": [ + { + "match": "\\*", + "name": "storage.modifier.pointer.cuda-cpp" + }, + { + "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "3": { + "name": "comment.block.cuda-cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + }, + "name": "invalid.illegal.reference-type.cuda-cpp" + }, + { + "match": "\\&", + "name": "storage.modifier.reference.cuda-cpp" + } + ] + }, + "8": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "9": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "10": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "11": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "12": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "13": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "14": { + "name": "variable.other.object.declare.cuda-cpp" + }, + "15": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "16": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "17": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "18": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "19": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "20": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + } + } + }, + { + "match": "(class)((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:\\[((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\]((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?(?=,|\\)|\\n)", + "captures": { + "1": { + "name": "storage.type.class.parameter.cuda-cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "4": { + "name": "entity.name.type.class.parameter.cuda-cpp" + }, + "5": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "6": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "7": { + "patterns": [ + { + "match": "\\*", + "name": "storage.modifier.pointer.cuda-cpp" + }, + { + "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "3": { + "name": "comment.block.cuda-cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + }, + "name": "invalid.illegal.reference-type.cuda-cpp" + }, + { + "match": "\\&", + "name": "storage.modifier.reference.cuda-cpp" + } + ] + }, + "8": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "9": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "10": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "11": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "12": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "13": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "14": { + "name": "variable.other.object.declare.cuda-cpp" + }, + "15": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "16": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "17": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "18": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "19": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "20": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + } + } + } + ] + }, + "parameter": { + "begin": "((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\w)", + "end": "(?:(?=\\))|(,))", + "beginCaptures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "3": { + "name": "comment.block.cuda-cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + }, + "endCaptures": { + "1": { + "name": "punctuation.separator.delimiter.comma.cuda-cpp" + } + }, + "name": "meta.parameter.cuda-cpp", + "patterns": [ + { + "include": "#ever_present_context" + }, + { + "include": "#function_pointer_parameter" + }, + { + "include": "#decltype" + }, + { + "include": "#vararg_ellipses" + }, + { + "match": "((?:((?:(?:__constant__)|(?:__restrict__)|(?:__managed__)|(?:__shared__)|(?:volatile)|(?:register)|(?:restrict)|(?:static)|(?:extern)|(?:const)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))+)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:\\s)*+(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=,|\\)|=)", + "captures": { + "1": { + "patterns": [ + { + "include": "#storage_types" + } + ] + }, + "2": { + "name": "storage.modifier.specifier.parameter.cuda-cpp" + }, + "3": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "4": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "5": { + "name": "comment.block.cuda-cpp" + }, + "6": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "7": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "8": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "9": { + "name": "comment.block.cuda-cpp" + }, + "10": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "11": { + "name": "storage.type.primitive.cuda-cpp storage.type.built-in.primitive.cuda-cpp" + }, + "12": { + "name": "storage.type.cuda-cpp storage.type.built-in.cuda-cpp" + }, + "13": { + "name": "support.type.posix-reserved.pthread.cuda-cpp support.type.built-in.posix-reserved.pthread.cuda-cpp" + }, + "14": { + "name": "support.type.posix-reserved.cuda-cpp support.type.built-in.posix-reserved.cuda-cpp" + }, + "15": { + "name": "entity.name.type.parameter.cuda-cpp" + }, + "16": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "17": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "18": { + "name": "comment.block.cuda-cpp" + }, + "19": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + }, + { + "include": "#storage_types" + }, + { + "include": "#scope_resolution_parameter_inner_generated" + }, + { + "match": "(?:(?:struct)|(?:class)|(?:union)|(?:enum))", + "name": "storage.type.$0.cuda-cpp" + }, + { + "begin": "(?<==)", + "end": "(?:(?=\\))|(,))", + "beginCaptures": {}, + "endCaptures": { + "1": { + "name": "punctuation.separator.delimiter.comma.cuda-cpp" + } + }, + "patterns": [ + { + "include": "#evaluation_context" + } + ] + }, + { + "match": "\\=", + "name": "keyword.operator.assignment.cuda-cpp" + }, + { + "match": "(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\)|,|\\[|=|\\n)", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "3": { + "name": "comment.block.cuda-cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "5": { + "name": "variable.parameter.cuda-cpp" + }, + "6": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "7": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "8": { + "name": "comment.block.cuda-cpp" + }, + "9": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + }, + { + "include": "#attributes_context" + }, + { + "begin": "\\[", + "end": "\\]", + "beginCaptures": { + "0": { + "name": "punctuation.definition.begin.bracket.square.array.type.cuda-cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.definition.end.bracket.square.array.type.cuda-cpp" + } + }, + "name": "meta.bracket.square.array.cuda-cpp", + "patterns": [ + { + "include": "#evaluation_context" + } + ] + }, + { + "match": "(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*))", + "captures": { + "0": { + "patterns": [ + { + "match": "\\*", + "name": "storage.modifier.pointer.cuda-cpp" + }, + { + "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "3": { + "name": "comment.block.cuda-cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + }, + "name": "invalid.illegal.reference-type.cuda-cpp" + }, + { + "match": "\\&", + "name": "storage.modifier.reference.cuda-cpp" + } + ] + }, + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "3": { + "name": "comment.block.cuda-cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "5": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "6": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "7": { + "name": "comment.block.cuda-cpp" + }, + "8": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "parameter_class": { + "match": "(class)((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:\\[((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\]((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?(?=,|\\)|\\n)", + "captures": { + "1": { + "name": "storage.type.class.parameter.cuda-cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "4": { + "name": "entity.name.type.class.parameter.cuda-cpp" + }, + "5": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "6": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "7": { + "patterns": [ + { + "match": "\\*", + "name": "storage.modifier.pointer.cuda-cpp" + }, + { + "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "3": { + "name": "comment.block.cuda-cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + }, + "name": "invalid.illegal.reference-type.cuda-cpp" + }, + { + "match": "\\&", + "name": "storage.modifier.reference.cuda-cpp" + } + ] + }, + "8": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "9": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "10": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "11": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "12": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "13": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "14": { + "name": "variable.other.object.declare.cuda-cpp" + }, + "15": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "16": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "17": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "18": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "19": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "20": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + } + } + }, + "parameter_enum": { + "match": "(enum)((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:\\[((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\]((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?(?=,|\\)|\\n)", + "captures": { + "1": { + "name": "storage.type.enum.parameter.cuda-cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "4": { + "name": "entity.name.type.enum.parameter.cuda-cpp" + }, + "5": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "6": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "7": { + "patterns": [ + { + "match": "\\*", + "name": "storage.modifier.pointer.cuda-cpp" + }, + { + "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "3": { + "name": "comment.block.cuda-cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + }, + "name": "invalid.illegal.reference-type.cuda-cpp" + }, + { + "match": "\\&", + "name": "storage.modifier.reference.cuda-cpp" + } + ] + }, + "8": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "9": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "10": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "11": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "12": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "13": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "14": { + "name": "variable.other.object.declare.cuda-cpp" + }, + "15": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "16": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "17": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "18": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "19": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "20": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + } + } + }, + "parameter_or_maybe_value": { + "begin": "((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\w)", + "end": "(?:(?=\\))|(,))", + "beginCaptures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "3": { + "name": "comment.block.cuda-cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + }, + "endCaptures": { + "1": { + "name": "punctuation.separator.delimiter.comma.cuda-cpp" + } + }, + "name": "meta.parameter.cuda-cpp", + "patterns": [ + { + "include": "#ever_present_context" + }, + { + "include": "#function_pointer_parameter" + }, + { + "include": "#memory_operators" + }, + { + "include": "#builtin_storage_type_initilizer" + }, + { + "include": "#curly_initializer" + }, + { + "include": "#decltype" + }, + { + "include": "#vararg_ellipses" + }, + { + "match": "((?:((?:(?:__constant__)|(?:__restrict__)|(?:__managed__)|(?:__shared__)|(?:volatile)|(?:register)|(?:restrict)|(?:static)|(?:extern)|(?:const)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))+)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:\\s)*+(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=,|\\)|=)", + "captures": { + "1": { + "patterns": [ + { + "include": "#storage_types" + } + ] + }, + "2": { + "name": "storage.modifier.specifier.parameter.cuda-cpp" + }, + "3": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "4": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "5": { + "name": "comment.block.cuda-cpp" + }, + "6": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "7": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "8": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "9": { + "name": "comment.block.cuda-cpp" + }, + "10": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "11": { + "name": "storage.type.primitive.cuda-cpp storage.type.built-in.primitive.cuda-cpp" + }, + "12": { + "name": "storage.type.cuda-cpp storage.type.built-in.cuda-cpp" + }, + "13": { + "name": "support.type.posix-reserved.pthread.cuda-cpp support.type.built-in.posix-reserved.pthread.cuda-cpp" + }, + "14": { + "name": "support.type.posix-reserved.cuda-cpp support.type.built-in.posix-reserved.cuda-cpp" + }, + "15": { + "name": "entity.name.type.parameter.cuda-cpp" + }, + "16": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "17": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "18": { + "name": "comment.block.cuda-cpp" + }, + "19": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + }, + { + "include": "#storage_types" + }, + { + "include": "#function_call" + }, + { + "include": "#scope_resolution_parameter_inner_generated" + }, + { + "match": "(?:(?:struct)|(?:class)|(?:union)|(?:enum))", + "name": "storage.type.$0.cuda-cpp" + }, + { + "begin": "(?<==)", + "end": "(?:(?=\\))|(,))", + "beginCaptures": {}, + "endCaptures": { + "1": { + "name": "punctuation.separator.delimiter.comma.cuda-cpp" + } + }, + "patterns": [ + { + "include": "#evaluation_context" + } + ] + }, + { + "match": "(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=(?:\\)|,|\\[|=|\\/\\/|(?:(?:\\n)|$)))", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "3": { + "name": "comment.block.cuda-cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "5": { + "name": "variable.parameter.cuda-cpp" + }, + "6": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "7": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "8": { + "name": "comment.block.cuda-cpp" + }, + "9": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + }, + { + "include": "#attributes_context" + }, + { + "begin": "\\[", + "end": "\\]", + "beginCaptures": { + "0": { + "name": "punctuation.definition.begin.bracket.square.array.type.cuda-cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.definition.end.bracket.square.array.type.cuda-cpp" + } + }, + "name": "meta.bracket.square.array.cuda-cpp", + "patterns": [ + { + "include": "#evaluation_context" + } + ] + }, + { + "match": "(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*))", + "captures": { + "0": { + "patterns": [ + { + "match": "\\*", + "name": "storage.modifier.pointer.cuda-cpp" + }, + { + "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "3": { + "name": "comment.block.cuda-cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + }, + "name": "invalid.illegal.reference-type.cuda-cpp" + }, + { + "match": "\\&", + "name": "storage.modifier.reference.cuda-cpp" + } + ] + }, + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "3": { + "name": "comment.block.cuda-cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "5": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "6": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "7": { + "name": "comment.block.cuda-cpp" + }, + "8": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + }, + { + "include": "#evaluation_context" + } + ] + }, + "parameter_struct": { + "match": "(struct)((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:\\[((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\]((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?(?=,|\\)|\\n)", + "captures": { + "1": { + "name": "storage.type.struct.parameter.cuda-cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "4": { + "name": "entity.name.type.struct.parameter.cuda-cpp" + }, + "5": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "6": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "7": { + "patterns": [ + { + "match": "\\*", + "name": "storage.modifier.pointer.cuda-cpp" + }, + { + "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "3": { + "name": "comment.block.cuda-cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + }, + "name": "invalid.illegal.reference-type.cuda-cpp" + }, + { + "match": "\\&", + "name": "storage.modifier.reference.cuda-cpp" + } + ] + }, + "8": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "9": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "10": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "11": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "12": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "13": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "14": { + "name": "variable.other.object.declare.cuda-cpp" + }, + "15": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "16": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "17": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "18": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "19": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "20": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + } + } + }, + "parameter_union": { + "match": "(union)((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:\\[((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\]((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?(?=,|\\)|\\n)", + "captures": { + "1": { + "name": "storage.type.union.parameter.cuda-cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "4": { + "name": "entity.name.type.union.parameter.cuda-cpp" + }, + "5": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "6": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "7": { + "patterns": [ + { + "match": "\\*", + "name": "storage.modifier.pointer.cuda-cpp" + }, + { + "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "3": { + "name": "comment.block.cuda-cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + }, + "name": "invalid.illegal.reference-type.cuda-cpp" + }, + { + "match": "\\&", + "name": "storage.modifier.reference.cuda-cpp" + } + ] + }, + "8": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "9": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "10": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "11": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "12": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "13": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "14": { + "name": "variable.other.object.declare.cuda-cpp" + }, + "15": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "16": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "17": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "18": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "19": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "20": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + } + } + }, + "parentheses": { + "begin": "\\(", + "end": "\\)", + "beginCaptures": { + "0": { + "name": "punctuation.section.parens.begin.bracket.round.cuda-cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.parens.end.bracket.round.cuda-cpp" + } + }, + "name": "meta.parens.cuda-cpp", + "patterns": [ + { + "include": "#over_qualified_types" + }, + { + "match": "(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(#)(?:(?:\\s)+)?pragma\\b", + "end": "(?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(#)(?:(?:\\s)+)?pragma(?:\\s)+mark)(?:\\s)+(.*)", + "captures": { + "1": { + "name": "keyword.control.directive.pragma.pragma-mark.cuda-cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "4": { + "name": "punctuation.definition.directive.cuda-cpp" + }, + "5": { + "name": "entity.name.tag.pragma-mark.cuda-cpp" + } + }, + "name": "meta.preprocessor.pragma.cuda-cpp" + }, + "predefined_macros": { + "patterns": [ + { + "match": "\\b(__cplusplus|__DATE__|__FILE__|__LINE__|__STDC__|__STDC_HOSTED__|__STDC_NO_COMPLEX__|__STDC_VERSION__|__STDCPP_THREADS__|__TIME__|NDEBUG|__OBJC__|__ASSEMBLER__|__ATOM__|__AVX__|__AVX2__|_CHAR_UNSIGNED|__CLR_VER|_CONTROL_FLOW_GUARD|__COUNTER__|__cplusplus_cli|__cplusplus_winrt|_CPPRTTI|_CPPUNWIND|_DEBUG|_DLL|__FUNCDNAME__|__FUNCSIG__|__FUNCTION__|_INTEGRAL_MAX_BITS|__INTELLISENSE__|_ISO_VOLATILE|_KERNEL_MODE|_M_AMD64|_M_ARM|_M_ARM_ARMV7VE|_M_ARM_FP|_M_ARM64|_M_CEE|_M_CEE_PURE|_M_CEE_SAFE|_M_FP_EXCEPT|_M_FP_FAST|_M_FP_PRECISE|_M_FP_STRICT|_M_IX86|_M_IX86_FP|_M_X64|_MANAGED|_MSC_BUILD|_MSC_EXTENSIONS|_MSC_FULL_VER|_MSC_VER|_MSVC_LANG|__MSVC_RUNTIME_CHECKS|_MT|_NATIVE_WCHAR_T_DEFINED|_OPENMP|_PREFAST|__TIMESTAMP__|_VC_NO_DEFAULTLIB|_WCHAR_T_DEFINED|_WIN32|_WIN64|_WINRT_DLL|_ATL_VER|_MFC_VER|__GFORTRAN__|__GNUC__|__GNUC_MINOR__|__GNUC_PATCHLEVEL__|__GNUG__|__STRICT_ANSI__|__BASE_FILE__|__INCLUDE_LEVEL__|__ELF__|__VERSION__|__OPTIMIZE__|__OPTIMIZE_SIZE__|__NO_INLINE__|__GNUC_STDC_INLINE__|__CHAR_UNSIGNED__|__WCHAR_UNSIGNED__|__REGISTER_PREFIX__|__REGISTER_PREFIX__|__SIZE_TYPE__|__PTRDIFF_TYPE__|__WCHAR_TYPE__|__WINT_TYPE__|__INTMAX_TYPE__|__UINTMAX_TYPE__|__SIG_ATOMIC_TYPE__|__INT8_TYPE__|__INT16_TYPE__|__INT32_TYPE__|__INT64_TYPE__|__UINT8_TYPE__|__UINT16_TYPE__|__UINT32_TYPE__|__UINT64_TYPE__|__INT_LEAST8_TYPE__|__INT_LEAST16_TYPE__|__INT_LEAST32_TYPE__|__INT_LEAST64_TYPE__|__UINT_LEAST8_TYPE__|__UINT_LEAST16_TYPE__|__UINT_LEAST32_TYPE__|__UINT_LEAST64_TYPE__|__INT_FAST8_TYPE__|__INT_FAST16_TYPE__|__INT_FAST32_TYPE__|__INT_FAST64_TYPE__|__UINT_FAST8_TYPE__|__UINT_FAST16_TYPE__|__UINT_FAST32_TYPE__|__UINT_FAST64_TYPE__|__INTPTR_TYPE__|__UINTPTR_TYPE__|__CHAR_BIT__|__SCHAR_MAX__|__WCHAR_MAX__|__SHRT_MAX__|__INT_MAX__|__LONG_MAX__|__LONG_LONG_MAX__|__WINT_MAX__|__SIZE_MAX__|__PTRDIFF_MAX__|__INTMAX_MAX__|__UINTMAX_MAX__|__SIG_ATOMIC_MAX__|__INT8_MAX__|__INT16_MAX__|__INT32_MAX__|__INT64_MAX__|__UINT8_MAX__|__UINT16_MAX__|__UINT32_MAX__|__UINT64_MAX__|__INT_LEAST8_MAX__|__INT_LEAST16_MAX__|__INT_LEAST32_MAX__|__INT_LEAST64_MAX__|__UINT_LEAST8_MAX__|__UINT_LEAST16_MAX__|__UINT_LEAST32_MAX__|__UINT_LEAST64_MAX__|__INT_FAST8_MAX__|__INT_FAST16_MAX__|__INT_FAST32_MAX__|__INT_FAST64_MAX__|__UINT_FAST8_MAX__|__UINT_FAST16_MAX__|__UINT_FAST32_MAX__|__UINT_FAST64_MAX__|__INTPTR_MAX__|__UINTPTR_MAX__|__WCHAR_MIN__|__WINT_MIN__|__SIG_ATOMIC_MIN__|__SCHAR_WIDTH__|__SHRT_WIDTH__|__INT_WIDTH__|__LONG_WIDTH__|__LONG_LONG_WIDTH__|__PTRDIFF_WIDTH__|__SIG_ATOMIC_WIDTH__|__SIZE_WIDTH__|__WCHAR_WIDTH__|__WINT_WIDTH__|__INT_LEAST8_WIDTH__|__INT_LEAST16_WIDTH__|__INT_LEAST32_WIDTH__|__INT_LEAST64_WIDTH__|__INT_FAST8_WIDTH__|__INT_FAST16_WIDTH__|__INT_FAST32_WIDTH__|__INT_FAST64_WIDTH__|__INTPTR_WIDTH__|__INTMAX_WIDTH__|__SIZEOF_INT__|__SIZEOF_LONG__|__SIZEOF_LONG_LONG__|__SIZEOF_SHORT__|__SIZEOF_POINTER__|__SIZEOF_FLOAT__|__SIZEOF_DOUBLE__|__SIZEOF_LONG_DOUBLE__|__SIZEOF_SIZE_T__|__SIZEOF_WCHAR_T__|__SIZEOF_WINT_T__|__SIZEOF_PTRDIFF_T__|__BYTE_ORDER__|__ORDER_LITTLE_ENDIAN__|__ORDER_BIG_ENDIAN__|__ORDER_PDP_ENDIAN__|__FLOAT_WORD_ORDER__|__DEPRECATED|__EXCEPTIONS|__GXX_RTTI|__USING_SJLJ_EXCEPTIONS__|__GXX_EXPERIMENTAL_CXX0X__|__GXX_WEAK__|__NEXT_RUNTIME__|__LP64__|_LP64|__SSP__|__SSP_ALL__|__SSP_STRONG__|__SSP_EXPLICIT__|__SANITIZE_ADDRESS__|__SANITIZE_THREAD__|__GCC_HAVE_SYNC_COMPARE_AND_SWAP_1|__GCC_HAVE_SYNC_COMPARE_AND_SWAP_2|__GCC_HAVE_SYNC_COMPARE_AND_SWAP_4|__GCC_HAVE_SYNC_COMPARE_AND_SWAP_8|__GCC_HAVE_SYNC_COMPARE_AND_SWAP_16|__HAVE_SPECULATION_SAFE_VALUE|__GCC_HAVE_DWARF2_CFI_ASM|__FP_FAST_FMA|__FP_FAST_FMAF|__FP_FAST_FMAL|__FP_FAST_FMAF16|__FP_FAST_FMAF32|__FP_FAST_FMAF64|__FP_FAST_FMAF128|__FP_FAST_FMAF32X|__FP_FAST_FMAF64X|__FP_FAST_FMAF128X|__GCC_IEC_559|__GCC_IEC_559_COMPLEX|__NO_MATH_ERRNO__|__has_builtin|__has_feature|__has_extension|__has_cpp_attribute|__has_c_attribute|__has_attribute|__has_declspec_attribute|__is_identifier|__has_include|__has_include_next|__has_warning|__BASE_FILE__|__FILE_NAME__|__clang__|__clang_major__|__clang_minor__|__clang_patchlevel__|__clang_version__|__fp16|_Float16)\\b", + "captures": { + "1": { + "name": "entity.name.other.preprocessor.macro.predefined.$1.cuda-cpp" + } + } + }, + { + "match": "\\b__([A-Z_]+)__\\b", + "name": "entity.name.other.preprocessor.macro.predefined.probably.$1.cuda-cpp" + } + ] + }, + "preprocessor_conditional_context": { + "patterns": [ + { + "include": "#preprocessor_conditional_defined" + }, + { + "include": "#comments" + }, + { + "include": "#language_constants" + }, + { + "include": "#string_context" + }, + { + "include": "#d9bc4796b0b_preprocessor_number_literal" + }, + { + "include": "#operators" + }, + { + "include": "#predefined_macros" + }, + { + "include": "#macro_name" + }, + { + "include": "#line_continuation_character" + } + ] + }, + "preprocessor_conditional_defined": { + "begin": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(#)(?:(?:\\s)+)?((?:(?:ifndef|ifdef)|if))", + "end": "^(?!\\s*+#\\s*(?:else|endif))", + "beginCaptures": { + "0": { + "name": "keyword.control.directive.conditional.$6.cuda-cpp" + }, + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "3": { + "name": "comment.block.cuda-cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "5": { + "name": "punctuation.definition.directive.cuda-cpp" + }, + "6": {} + }, + "endCaptures": {}, + "patterns": [ + { + "begin": "\\G(?<=ifndef|ifdef|if)", + "end": "(?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(#)(?:(?:\\s)+)?((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "3": { + "name": "punctuation.definition.directive.cuda-cpp" + } + }, + "name": "keyword.control.directive.$4.cuda-cpp" + }, + "preprocessor_context": { + "patterns": [ + { + "include": "#pragma_mark" + }, + { + "include": "#pragma" + }, + { + "include": "#include" + }, + { + "include": "#line" + }, + { + "include": "#diagnostic" + }, + { + "include": "#undef" + }, + { + "include": "#preprocessor_conditional_range" + }, + { + "include": "#single_line_macro" + }, + { + "include": "#macro" + }, + { + "include": "#preprocessor_conditional_standalone" + }, + { + "include": "#macro_argument" + } + ] + }, + "qualified_type": { + "match": "\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|__forceinline__|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|__constant__|__restrict__|__noinline__|thread_local|synchronized|static_cast|__managed__|const_cast|__shared__|__global__|__device__|co_return|constexpr|constexpr|constexpr|consteval|protected|namespace|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|__host__|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<11>?)+>)(?:\\s)*+)?::)*+)?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:__forceinline__)|(?:atomic_noexcept)|(?:__has_include)|(?:atomic_cancel)|(?:atomic_commit)|(?:dynamic_cast)|(?:__constant__)|(?:__restrict__)|(?:__noinline__)|(?:thread_local)|(?:synchronized)|(?:static_cast)|(?:__managed__)|(?:const_cast)|(?:__shared__)|(?:__global__)|(?:__device__)|(?:co_return)|(?:constexpr)|(?:constexpr)|(?:constexpr)|(?:consteval)|(?:protected)|(?:threadIdx)|(?:namespace)|(?:co_return)|(?:noexcept)|(?:noexcept)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:__host__)|(?:override)|(?:volatile)|(?:noexcept)|(?:blockIdx)|(?:blockDim)|(?:warpSize)|(?:template)|(?:operator)|(?:decltype)|(?:typename)|(?:requires)|(?:co_await)|(?:co_yield)|(?:reflexpr)|(?:alignof)|(?:alignas)|(?:default)|(?:nullptr)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:gridDim)|(?:typedef)|(?:__asm__)|(?:concept)|(?:sizeof)|(?:delete)|(?:not_eq)|(?:bitand)|(?:and_eq)|(?:xor_eq)|(?:typeid)|(?:switch)|(?:return)|(?:static)|(?:extern)|(?:inline)|(?:friend)|(?:public)|(?:ifndef)|(?:define)|(?:pragma)|(?:export)|(?:import)|(?:module)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:false)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:else)|(?:goto)|(?:case)|(?:NULL)|(?:true)|(?:elif)|(?:else)|(?:line)|(?:this)|(?:not)|(?:new)|(?:xor)|(?:and)|(?:for)|(?:try)|(?:asm)|(?:or)|(?:do)|(?:if)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<11>?)+>)?(?![\\w<:.])", + "captures": { + "0": { + "patterns": [ + { + "match": "::", + "name": "punctuation.separator.namespace.access.cuda-cpp punctuation.separator.scope-resolution.cuda-cpp" + }, + { + "match": "(?", + "beginCaptures": { + "0": { + "name": "punctuation.section.angle-brackets.begin.template.call.cuda-cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.angle-brackets.end.template.call.cuda-cpp" + } + }, + "name": "meta.template.call.cuda-cpp", + "patterns": [ + { + "include": "#template_call_context" + } + ] + }, + { + "match": "(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*", + "name": "entity.name.type.cuda-cpp" + } + ] + }, + "1": { + "patterns": [ + { + "include": "#attributes_context" + }, + { + "include": "#number_literal" + } + ] + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "4": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "5": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "6": { + "patterns": [ + { + "match": "::", + "name": "punctuation.separator.namespace.access.cuda-cpp punctuation.separator.scope-resolution.type.cuda-cpp" + }, + { + "match": "(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + } + }, + "name": "meta.qualified_type.cuda-cpp" + }, + "qualifiers_and_specifiers_post_parameters": { + "match": "((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "3": { + "name": "storage.modifier.specifier.functional.post-parameters.$3.cuda-cpp" + }, + "4": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "5": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + } + } + }, + "scope_resolution": { + "match": "(::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|__forceinline__|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|__constant__|__restrict__|__noinline__|thread_local|synchronized|static_cast|__managed__|const_cast|__shared__|__global__|__device__|co_return|constexpr|constexpr|constexpr|consteval|protected|namespace|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|__host__|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:\\s)*+)?::)*\\s*+", + "captures": { + "0": { + "patterns": [ + { + "include": "#scope_resolution_inner_generated" + } + ] + }, + "1": { + "name": "punctuation.separator.namespace.access.cuda-cpp punctuation.separator.scope-resolution.cuda-cpp" + }, + "2": { + "patterns": [ + { + "include": "#template_call_range" + } + ] + } + } + }, + "scope_resolution_function_call": { + "match": "(::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|__forceinline__|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|__constant__|__restrict__|__noinline__|thread_local|synchronized|static_cast|__managed__|const_cast|__shared__|__global__|__device__|co_return|constexpr|constexpr|constexpr|consteval|protected|namespace|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|__host__|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:\\s)*+)?::)*\\s*+", + "captures": { + "0": { + "patterns": [ + { + "include": "#scope_resolution_function_call_inner_generated" + } + ] + }, + "1": { + "name": "punctuation.separator.namespace.access.cuda-cpp punctuation.separator.scope-resolution.function.call.cuda-cpp" + }, + "2": { + "patterns": [ + { + "include": "#template_call_range" + } + ] + } + } + }, + "scope_resolution_function_call_inner_generated": { + "match": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|__forceinline__|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|__constant__|__restrict__|__noinline__|thread_local|synchronized|static_cast|__managed__|const_cast|__shared__|__global__|__device__|co_return|constexpr|constexpr|constexpr|consteval|protected|namespace|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|__host__|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|__forceinline__|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|__constant__|__restrict__|__noinline__|thread_local|synchronized|static_cast|__managed__|const_cast|__shared__|__global__|__device__|co_return|constexpr|constexpr|constexpr|consteval|protected|namespace|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|__host__|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?(::)", + "captures": { + "1": { + "patterns": [ + { + "include": "#scope_resolution_function_call_inner_generated" + } + ] + }, + "2": { + "name": "punctuation.separator.namespace.access.cuda-cpp punctuation.separator.scope-resolution.function.call.cuda-cpp" + }, + "3": { + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "4": {}, + "5": { + "name": "entity.name.scope-resolution.function.call.cuda-cpp" + }, + "6": { + "name": "meta.template.call.cuda-cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "7": {}, + "8": { + "name": "punctuation.separator.namespace.access.cuda-cpp punctuation.separator.scope-resolution.function.call.cuda-cpp" + } + } + }, + "scope_resolution_function_definition": { + "match": "(::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|__forceinline__|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|__constant__|__restrict__|__noinline__|thread_local|synchronized|static_cast|__managed__|const_cast|__shared__|__global__|__device__|co_return|constexpr|constexpr|constexpr|consteval|protected|namespace|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|__host__|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:\\s)*+)?::)*\\s*+", + "captures": { + "0": { + "patterns": [ + { + "include": "#scope_resolution_function_definition_inner_generated" + } + ] + }, + "1": { + "name": "punctuation.separator.namespace.access.cuda-cpp punctuation.separator.scope-resolution.function.definition.cuda-cpp" + }, + "2": { + "patterns": [ + { + "include": "#template_call_range" + } + ] + } + } + }, + "scope_resolution_function_definition_inner_generated": { + "match": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|__forceinline__|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|__constant__|__restrict__|__noinline__|thread_local|synchronized|static_cast|__managed__|const_cast|__shared__|__global__|__device__|co_return|constexpr|constexpr|constexpr|consteval|protected|namespace|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|__host__|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|__forceinline__|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|__constant__|__restrict__|__noinline__|thread_local|synchronized|static_cast|__managed__|const_cast|__shared__|__global__|__device__|co_return|constexpr|constexpr|constexpr|consteval|protected|namespace|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|__host__|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?(::)", + "captures": { + "1": { + "patterns": [ + { + "include": "#scope_resolution_function_definition_inner_generated" + } + ] + }, + "2": { + "name": "punctuation.separator.namespace.access.cuda-cpp punctuation.separator.scope-resolution.function.definition.cuda-cpp" + }, + "3": { + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "4": {}, + "5": { + "name": "entity.name.scope-resolution.function.definition.cuda-cpp" + }, + "6": { + "name": "meta.template.call.cuda-cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "7": {}, + "8": { + "name": "punctuation.separator.namespace.access.cuda-cpp punctuation.separator.scope-resolution.function.definition.cuda-cpp" + } + } + }, + "scope_resolution_function_definition_operator_overload": { + "match": "(::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|__forceinline__|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|__constant__|__restrict__|__noinline__|thread_local|synchronized|static_cast|__managed__|const_cast|__shared__|__global__|__device__|co_return|constexpr|constexpr|constexpr|consteval|protected|namespace|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|__host__|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:\\s)*+)?::)*\\s*+", + "captures": { + "0": { + "patterns": [ + { + "include": "#scope_resolution_function_definition_operator_overload_inner_generated" + } + ] + }, + "1": { + "name": "punctuation.separator.namespace.access.cuda-cpp punctuation.separator.scope-resolution.function.definition.operator-overload.cuda-cpp" + }, + "2": { + "patterns": [ + { + "include": "#template_call_range" + } + ] + } + } + }, + "scope_resolution_function_definition_operator_overload_inner_generated": { + "match": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|__forceinline__|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|__constant__|__restrict__|__noinline__|thread_local|synchronized|static_cast|__managed__|const_cast|__shared__|__global__|__device__|co_return|constexpr|constexpr|constexpr|consteval|protected|namespace|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|__host__|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|__forceinline__|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|__constant__|__restrict__|__noinline__|thread_local|synchronized|static_cast|__managed__|const_cast|__shared__|__global__|__device__|co_return|constexpr|constexpr|constexpr|consteval|protected|namespace|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|__host__|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?(::)", + "captures": { + "1": { + "patterns": [ + { + "include": "#scope_resolution_function_definition_operator_overload_inner_generated" + } + ] + }, + "2": { + "name": "punctuation.separator.namespace.access.cuda-cpp punctuation.separator.scope-resolution.function.definition.operator-overload.cuda-cpp" + }, + "3": { + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "4": {}, + "5": { + "name": "entity.name.scope-resolution.function.definition.operator-overload.cuda-cpp" + }, + "6": { + "name": "meta.template.call.cuda-cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "7": {}, + "8": { + "name": "punctuation.separator.namespace.access.cuda-cpp punctuation.separator.scope-resolution.function.definition.operator-overload.cuda-cpp" + } + } + }, + "scope_resolution_inner_generated": { + "match": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|__forceinline__|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|__constant__|__restrict__|__noinline__|thread_local|synchronized|static_cast|__managed__|const_cast|__shared__|__global__|__device__|co_return|constexpr|constexpr|constexpr|consteval|protected|namespace|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|__host__|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|__forceinline__|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|__constant__|__restrict__|__noinline__|thread_local|synchronized|static_cast|__managed__|const_cast|__shared__|__global__|__device__|co_return|constexpr|constexpr|constexpr|consteval|protected|namespace|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|__host__|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?(::)", + "captures": { + "1": { + "patterns": [ + { + "include": "#scope_resolution_inner_generated" + } + ] + }, + "2": { + "name": "punctuation.separator.namespace.access.cuda-cpp punctuation.separator.scope-resolution.cuda-cpp" + }, + "3": { + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "4": {}, + "5": { + "name": "entity.name.scope-resolution.cuda-cpp" + }, + "6": { + "name": "meta.template.call.cuda-cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "7": {}, + "8": { + "name": "punctuation.separator.namespace.access.cuda-cpp punctuation.separator.scope-resolution.cuda-cpp" + } + } + }, + "scope_resolution_namespace_alias": { + "match": "(::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|__forceinline__|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|__constant__|__restrict__|__noinline__|thread_local|synchronized|static_cast|__managed__|const_cast|__shared__|__global__|__device__|co_return|constexpr|constexpr|constexpr|consteval|protected|namespace|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|__host__|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:\\s)*+)?::)*\\s*+", + "captures": { + "0": { + "patterns": [ + { + "include": "#scope_resolution_namespace_alias_inner_generated" + } + ] + }, + "1": { + "name": "punctuation.separator.namespace.access.cuda-cpp punctuation.separator.scope-resolution.namespace.alias.cuda-cpp" + }, + "2": { + "patterns": [ + { + "include": "#template_call_range" + } + ] + } + } + }, + "scope_resolution_namespace_alias_inner_generated": { + "match": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|__forceinline__|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|__constant__|__restrict__|__noinline__|thread_local|synchronized|static_cast|__managed__|const_cast|__shared__|__global__|__device__|co_return|constexpr|constexpr|constexpr|consteval|protected|namespace|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|__host__|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|__forceinline__|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|__constant__|__restrict__|__noinline__|thread_local|synchronized|static_cast|__managed__|const_cast|__shared__|__global__|__device__|co_return|constexpr|constexpr|constexpr|consteval|protected|namespace|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|__host__|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?(::)", + "captures": { + "1": { + "patterns": [ + { + "include": "#scope_resolution_namespace_alias_inner_generated" + } + ] + }, + "2": { + "name": "punctuation.separator.namespace.access.cuda-cpp punctuation.separator.scope-resolution.namespace.alias.cuda-cpp" + }, + "3": { + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "4": {}, + "5": { + "name": "entity.name.scope-resolution.namespace.alias.cuda-cpp" + }, + "6": { + "name": "meta.template.call.cuda-cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "7": {}, + "8": { + "name": "punctuation.separator.namespace.access.cuda-cpp punctuation.separator.scope-resolution.namespace.alias.cuda-cpp" + } + } + }, + "scope_resolution_namespace_block": { + "match": "(::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|__forceinline__|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|__constant__|__restrict__|__noinline__|thread_local|synchronized|static_cast|__managed__|const_cast|__shared__|__global__|__device__|co_return|constexpr|constexpr|constexpr|consteval|protected|namespace|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|__host__|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:\\s)*+)?::)*\\s*+", + "captures": { + "0": { + "patterns": [ + { + "include": "#scope_resolution_namespace_block_inner_generated" + } + ] + }, + "1": { + "name": "punctuation.separator.namespace.access.cuda-cpp punctuation.separator.scope-resolution.namespace.block.cuda-cpp" + }, + "2": { + "patterns": [ + { + "include": "#template_call_range" + } + ] + } + } + }, + "scope_resolution_namespace_block_inner_generated": { + "match": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|__forceinline__|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|__constant__|__restrict__|__noinline__|thread_local|synchronized|static_cast|__managed__|const_cast|__shared__|__global__|__device__|co_return|constexpr|constexpr|constexpr|consteval|protected|namespace|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|__host__|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|__forceinline__|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|__constant__|__restrict__|__noinline__|thread_local|synchronized|static_cast|__managed__|const_cast|__shared__|__global__|__device__|co_return|constexpr|constexpr|constexpr|consteval|protected|namespace|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|__host__|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?(::)", + "captures": { + "1": { + "patterns": [ + { + "include": "#scope_resolution_namespace_block_inner_generated" + } + ] + }, + "2": { + "name": "punctuation.separator.namespace.access.cuda-cpp punctuation.separator.scope-resolution.namespace.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "4": {}, + "5": { + "name": "entity.name.scope-resolution.namespace.block.cuda-cpp" + }, + "6": { + "name": "meta.template.call.cuda-cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "7": {}, + "8": { + "name": "punctuation.separator.namespace.access.cuda-cpp punctuation.separator.scope-resolution.namespace.block.cuda-cpp" + } + } + }, + "scope_resolution_namespace_using": { + "match": "(::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|__forceinline__|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|__constant__|__restrict__|__noinline__|thread_local|synchronized|static_cast|__managed__|const_cast|__shared__|__global__|__device__|co_return|constexpr|constexpr|constexpr|consteval|protected|namespace|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|__host__|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:\\s)*+)?::)*\\s*+", + "captures": { + "0": { + "patterns": [ + { + "include": "#scope_resolution_namespace_using_inner_generated" + } + ] + }, + "1": { + "name": "punctuation.separator.namespace.access.cuda-cpp punctuation.separator.scope-resolution.namespace.using.cuda-cpp" + }, + "2": { + "patterns": [ + { + "include": "#template_call_range" + } + ] + } + } + }, + "scope_resolution_namespace_using_inner_generated": { + "match": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|__forceinline__|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|__constant__|__restrict__|__noinline__|thread_local|synchronized|static_cast|__managed__|const_cast|__shared__|__global__|__device__|co_return|constexpr|constexpr|constexpr|consteval|protected|namespace|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|__host__|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|__forceinline__|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|__constant__|__restrict__|__noinline__|thread_local|synchronized|static_cast|__managed__|const_cast|__shared__|__global__|__device__|co_return|constexpr|constexpr|constexpr|consteval|protected|namespace|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|__host__|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?(::)", + "captures": { + "1": { + "patterns": [ + { + "include": "#scope_resolution_namespace_using_inner_generated" + } + ] + }, + "2": { + "name": "punctuation.separator.namespace.access.cuda-cpp punctuation.separator.scope-resolution.namespace.using.cuda-cpp" + }, + "3": { + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "4": {}, + "5": { + "name": "entity.name.scope-resolution.namespace.using.cuda-cpp" + }, + "6": { + "name": "meta.template.call.cuda-cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "7": {}, + "8": { + "name": "punctuation.separator.namespace.access.cuda-cpp punctuation.separator.scope-resolution.namespace.using.cuda-cpp" + } + } + }, + "scope_resolution_parameter": { + "match": "(::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|__forceinline__|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|__constant__|__restrict__|__noinline__|thread_local|synchronized|static_cast|__managed__|const_cast|__shared__|__global__|__device__|co_return|constexpr|constexpr|constexpr|consteval|protected|namespace|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|__host__|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:\\s)*+)?::)*\\s*+", + "captures": { + "0": { + "patterns": [ + { + "include": "#scope_resolution_parameter_inner_generated" + } + ] + }, + "1": { + "name": "punctuation.separator.namespace.access.cuda-cpp punctuation.separator.scope-resolution.parameter.cuda-cpp" + }, + "2": { + "patterns": [ + { + "include": "#template_call_range" + } + ] + } + } + }, + "scope_resolution_parameter_inner_generated": { + "match": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|__forceinline__|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|__constant__|__restrict__|__noinline__|thread_local|synchronized|static_cast|__managed__|const_cast|__shared__|__global__|__device__|co_return|constexpr|constexpr|constexpr|consteval|protected|namespace|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|__host__|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|__forceinline__|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|__constant__|__restrict__|__noinline__|thread_local|synchronized|static_cast|__managed__|const_cast|__shared__|__global__|__device__|co_return|constexpr|constexpr|constexpr|consteval|protected|namespace|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|__host__|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?(::)", + "captures": { + "1": { + "patterns": [ + { + "include": "#scope_resolution_parameter_inner_generated" + } + ] + }, + "2": { + "name": "punctuation.separator.namespace.access.cuda-cpp punctuation.separator.scope-resolution.parameter.cuda-cpp" + }, + "3": { + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "4": {}, + "5": { + "name": "entity.name.scope-resolution.parameter.cuda-cpp" + }, + "6": { + "name": "meta.template.call.cuda-cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "7": {}, + "8": { + "name": "punctuation.separator.namespace.access.cuda-cpp punctuation.separator.scope-resolution.parameter.cuda-cpp" + } + } + }, + "scope_resolution_template_call": { + "match": "(::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|__forceinline__|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|__constant__|__restrict__|__noinline__|thread_local|synchronized|static_cast|__managed__|const_cast|__shared__|__global__|__device__|co_return|constexpr|constexpr|constexpr|consteval|protected|namespace|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|__host__|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:\\s)*+)?::)*\\s*+", + "captures": { + "0": { + "patterns": [ + { + "include": "#scope_resolution_template_call_inner_generated" + } + ] + }, + "1": { + "name": "punctuation.separator.namespace.access.cuda-cpp punctuation.separator.scope-resolution.template.call.cuda-cpp" + }, + "2": { + "patterns": [ + { + "include": "#template_call_range" + } + ] + } + } + }, + "scope_resolution_template_call_inner_generated": { + "match": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|__forceinline__|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|__constant__|__restrict__|__noinline__|thread_local|synchronized|static_cast|__managed__|const_cast|__shared__|__global__|__device__|co_return|constexpr|constexpr|constexpr|consteval|protected|namespace|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|__host__|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|__forceinline__|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|__constant__|__restrict__|__noinline__|thread_local|synchronized|static_cast|__managed__|const_cast|__shared__|__global__|__device__|co_return|constexpr|constexpr|constexpr|consteval|protected|namespace|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|__host__|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?(::)", + "captures": { + "1": { + "patterns": [ + { + "include": "#scope_resolution_template_call_inner_generated" + } + ] + }, + "2": { + "name": "punctuation.separator.namespace.access.cuda-cpp punctuation.separator.scope-resolution.template.call.cuda-cpp" + }, + "3": { + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "4": {}, + "5": { + "name": "entity.name.scope-resolution.template.call.cuda-cpp" + }, + "6": { + "name": "meta.template.call.cuda-cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "7": {}, + "8": { + "name": "punctuation.separator.namespace.access.cuda-cpp punctuation.separator.scope-resolution.template.call.cuda-cpp" + } + } + }, + "scope_resolution_template_definition": { + "match": "(::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|__forceinline__|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|__constant__|__restrict__|__noinline__|thread_local|synchronized|static_cast|__managed__|const_cast|__shared__|__global__|__device__|co_return|constexpr|constexpr|constexpr|consteval|protected|namespace|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|__host__|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:\\s)*+)?::)*\\s*+", + "captures": { + "0": { + "patterns": [ + { + "include": "#scope_resolution_template_definition_inner_generated" + } + ] + }, + "1": { + "name": "punctuation.separator.namespace.access.cuda-cpp punctuation.separator.scope-resolution.template.definition.cuda-cpp" + }, + "2": { + "patterns": [ + { + "include": "#template_call_range" + } + ] + } + } + }, + "scope_resolution_template_definition_inner_generated": { + "match": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|__forceinline__|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|__constant__|__restrict__|__noinline__|thread_local|synchronized|static_cast|__managed__|const_cast|__shared__|__global__|__device__|co_return|constexpr|constexpr|constexpr|consteval|protected|namespace|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|__host__|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|__forceinline__|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|__constant__|__restrict__|__noinline__|thread_local|synchronized|static_cast|__managed__|const_cast|__shared__|__global__|__device__|co_return|constexpr|constexpr|constexpr|consteval|protected|namespace|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|__host__|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?(::)", + "captures": { + "1": { + "patterns": [ + { + "include": "#scope_resolution_template_definition_inner_generated" + } + ] + }, + "2": { + "name": "punctuation.separator.namespace.access.cuda-cpp punctuation.separator.scope-resolution.template.definition.cuda-cpp" + }, + "3": { + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "4": {}, + "5": { + "name": "entity.name.scope-resolution.template.definition.cuda-cpp" + }, + "6": { + "name": "meta.template.call.cuda-cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "7": {}, + "8": { + "name": "punctuation.separator.namespace.access.cuda-cpp punctuation.separator.scope-resolution.template.definition.cuda-cpp" + } + } + }, + "semicolon": { + "match": ";", + "name": "punctuation.terminator.statement.cuda-cpp" + }, + "simple_type": { + "match": "(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|__forceinline__|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|__constant__|__restrict__|__noinline__|thread_local|synchronized|static_cast|__managed__|const_cast|__shared__|__global__|__device__|co_return|constexpr|constexpr|constexpr|consteval|protected|namespace|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|__host__|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)(?:\\s)*+)?::)*+)?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:__forceinline__)|(?:atomic_noexcept)|(?:__has_include)|(?:atomic_cancel)|(?:atomic_commit)|(?:dynamic_cast)|(?:__constant__)|(?:__restrict__)|(?:__noinline__)|(?:thread_local)|(?:synchronized)|(?:static_cast)|(?:__managed__)|(?:const_cast)|(?:__shared__)|(?:__global__)|(?:__device__)|(?:co_return)|(?:constexpr)|(?:constexpr)|(?:constexpr)|(?:consteval)|(?:protected)|(?:threadIdx)|(?:namespace)|(?:co_return)|(?:noexcept)|(?:noexcept)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:__host__)|(?:override)|(?:volatile)|(?:noexcept)|(?:blockIdx)|(?:blockDim)|(?:warpSize)|(?:template)|(?:operator)|(?:decltype)|(?:typename)|(?:requires)|(?:co_await)|(?:co_yield)|(?:reflexpr)|(?:alignof)|(?:alignas)|(?:default)|(?:nullptr)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:gridDim)|(?:typedef)|(?:__asm__)|(?:concept)|(?:sizeof)|(?:delete)|(?:not_eq)|(?:bitand)|(?:and_eq)|(?:xor_eq)|(?:typeid)|(?:switch)|(?:return)|(?:static)|(?:extern)|(?:inline)|(?:friend)|(?:public)|(?:ifndef)|(?:define)|(?:pragma)|(?:export)|(?:import)|(?:module)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:false)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:else)|(?:goto)|(?:case)|(?:NULL)|(?:true)|(?:elif)|(?:else)|(?:line)|(?:this)|(?:not)|(?:new)|(?:xor)|(?:and)|(?:for)|(?:try)|(?:asm)|(?:or)|(?:do)|(?:if)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)?(?![\\w<:.]))(((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?", + "captures": { + "1": { + "name": "meta.qualified_type.cuda-cpp", + "patterns": [ + { + "match": "::", + "name": "punctuation.separator.namespace.access.cuda-cpp punctuation.separator.scope-resolution.cuda-cpp" + }, + { + "match": "(?", + "beginCaptures": { + "0": { + "name": "punctuation.section.angle-brackets.begin.template.call.cuda-cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.angle-brackets.end.template.call.cuda-cpp" + } + }, + "name": "meta.template.call.cuda-cpp", + "patterns": [ + { + "include": "#template_call_context" + } + ] + }, + { + "match": "(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*", + "name": "entity.name.type.cuda-cpp" + } + ] + }, + "2": { + "patterns": [ + { + "include": "#attributes_context" + }, + { + "include": "#number_literal" + } + ] + }, + "3": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "4": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "5": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "6": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "7": { + "patterns": [ + { + "match": "::", + "name": "punctuation.separator.namespace.access.cuda-cpp punctuation.separator.scope-resolution.type.cuda-cpp" + }, + { + "match": "(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "12": {}, + "13": { + "patterns": [ + { + "match": "\\*", + "name": "storage.modifier.pointer.cuda-cpp" + }, + { + "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "3": { + "name": "comment.block.cuda-cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + }, + "name": "invalid.illegal.reference-type.cuda-cpp" + }, + { + "match": "\\&", + "name": "storage.modifier.reference.cuda-cpp" + } + ] + }, + "14": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "15": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "16": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "17": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + } + } + }, + "single_line_macro": { + "match": "^((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))#define.*(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + } + } + }, + "sizeof_operator": { + "begin": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", + "end": "\\)", + "beginCaptures": { + "1": { + "name": "keyword.operator.functionlike.cuda-cpp keyword.operator.sizeof.cuda-cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "4": { + "name": "comment.block.cuda-cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "6": { + "name": "punctuation.section.arguments.begin.bracket.round.operator.sizeof.cuda-cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.arguments.end.bracket.round.operator.sizeof.cuda-cpp" + } + }, + "contentName": "meta.arguments.operator.sizeof", + "patterns": [ + { + "include": "#evaluation_context" + } + ] + }, + "sizeof_variadic_operator": { + "begin": "(\\bsizeof\\.\\.\\.)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", + "end": "\\)", + "beginCaptures": { + "1": { + "name": "keyword.operator.functionlike.cuda-cpp keyword.operator.sizeof.variadic.cuda-cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "4": { + "name": "comment.block.cuda-cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "6": { + "name": "punctuation.section.arguments.begin.bracket.round.operator.sizeof.variadic.cuda-cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.arguments.end.bracket.round.operator.sizeof.variadic.cuda-cpp" + } + }, + "contentName": "meta.arguments.operator.sizeof.variadic", + "patterns": [ + { + "include": "#evaluation_context" + } + ] + }, + "square_brackets": { + "name": "meta.bracket.square.access", + "begin": "([a-zA-Z_][a-zA-Z_0-9]*|(?<=[\\]\\)]))?(\\[)(?!\\])", + "beginCaptures": { + "1": { + "name": "variable.other.object" + }, + "2": { + "name": "punctuation.definition.begin.bracket.square" + } + }, + "end": "\\]", + "endCaptures": { + "0": { + "name": "punctuation.definition.end.bracket.square" + } + }, + "patterns": [ + { + "include": "#evaluation_context" + } + ] + }, + "standard_declares": { + "patterns": [ + { + "match": "((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\b(?!override\\W|override\\$|final\\W|final\\$)((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\S)(?![:{a-zA-Z])", + "captures": { + "1": { + "name": "storage.type.struct.declare.cuda-cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "4": { + "name": "entity.name.type.struct.cuda-cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*", + "name": "storage.modifier.pointer.cuda-cpp" + }, + { + "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "3": { + "name": "comment.block.cuda-cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + }, + "name": "invalid.illegal.reference-type.cuda-cpp" + }, + { + "match": "\\&", + "name": "storage.modifier.reference.cuda-cpp" + } + ] + }, + "6": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "7": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "8": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "9": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "10": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "11": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "12": { + "name": "variable.other.object.declare.cuda-cpp" + }, + "13": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "14": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + } + } + }, + { + "match": "((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\b(?!override\\W|override\\$|final\\W|final\\$)((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\S)(?![:{a-zA-Z])", + "captures": { + "1": { + "name": "storage.type.union.declare.cuda-cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "4": { + "name": "entity.name.type.union.cuda-cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*", + "name": "storage.modifier.pointer.cuda-cpp" + }, + { + "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "3": { + "name": "comment.block.cuda-cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + }, + "name": "invalid.illegal.reference-type.cuda-cpp" + }, + { + "match": "\\&", + "name": "storage.modifier.reference.cuda-cpp" + } + ] + }, + "6": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "7": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "8": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "9": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "10": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "11": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "12": { + "name": "variable.other.object.declare.cuda-cpp" + }, + "13": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "14": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + } + } + }, + { + "match": "((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\b(?!override\\W|override\\$|final\\W|final\\$)((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\S)(?![:{a-zA-Z])", + "captures": { + "1": { + "name": "storage.type.enum.declare.cuda-cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "4": { + "name": "entity.name.type.enum.cuda-cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*", + "name": "storage.modifier.pointer.cuda-cpp" + }, + { + "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "3": { + "name": "comment.block.cuda-cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + }, + "name": "invalid.illegal.reference-type.cuda-cpp" + }, + { + "match": "\\&", + "name": "storage.modifier.reference.cuda-cpp" + } + ] + }, + "6": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "7": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "8": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "9": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "10": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "11": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "12": { + "name": "variable.other.object.declare.cuda-cpp" + }, + "13": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "14": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + } + } + }, + { + "match": "((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\b(?!override\\W|override\\$|final\\W|final\\$)((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\S)(?![:{a-zA-Z])", + "captures": { + "1": { + "name": "storage.type.class.declare.cuda-cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "4": { + "name": "entity.name.type.class.cuda-cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*", + "name": "storage.modifier.pointer.cuda-cpp" + }, + { + "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "3": { + "name": "comment.block.cuda-cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + }, + "name": "invalid.illegal.reference-type.cuda-cpp" + }, + { + "match": "\\&", + "name": "storage.modifier.reference.cuda-cpp" + } + ] + }, + "6": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "7": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "8": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "9": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "10": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "11": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "12": { + "name": "variable.other.object.declare.cuda-cpp" + }, + "13": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "14": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + } + } + } + ] + }, + "static_assert": { + "begin": "((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", + "end": "\\)", + "beginCaptures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "3": { + "name": "comment.block.cuda-cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "5": { + "name": "keyword.other.static_assert.cuda-cpp" + }, + "6": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "7": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "8": { + "name": "comment.block.cuda-cpp" + }, + "9": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "10": { + "name": "punctuation.section.arguments.begin.bracket.round.static_assert.cuda-cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.arguments.end.bracket.round.static_assert.cuda-cpp" + } + }, + "patterns": [ + { + "begin": "(,)(?:(?:\\s)+)?(?=(?:L|u8|u|U(?:(?:\\s)+)?\\\")?)", + "end": "(?=\\))", + "beginCaptures": { + "1": { + "name": "punctuation.separator.delimiter.comma.cuda-cpp" + } + }, + "endCaptures": {}, + "name": "meta.static_assert.message.cuda-cpp", + "patterns": [ + { + "include": "#string_context" + } + ] + }, + { + "include": "#evaluation_context" + } + ] + }, + "std_space": { + "match": "(?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))", + "captures": { + "0": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "1": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + } + } + }, + "storage_specifiers": { + "match": "((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "3": { + "name": "storage.modifier.specifier.$3.cuda-cpp" + } + } + }, + "storage_types": { + "patterns": [ + { + "include": "#storage_specifiers" + }, + { + "include": "#inline_builtin_storage_type" + }, + { + "include": "#decltype" + }, + { + "include": "#typename" + } + ] + }, + "string_context": { + "patterns": [ + { + "begin": "((?:u|u8|U|L)?)\"", + "end": "\"", + "beginCaptures": { + "0": { + "name": "punctuation.definition.string.begin.cuda-cpp" + }, + "1": { + "name": "meta.encoding.cuda-cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.cuda-cpp" + } + }, + "name": "string.quoted.double.cuda-cpp", + "patterns": [ + { + "match": "(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8})", + "name": "constant.character.escape.cuda-cpp" + }, + { + "match": "\\\\['\"?\\\\abfnrtv]", + "name": "constant.character.escape.cuda-cpp" + }, + { + "match": "\\\\[0-7]{1,3}", + "name": "constant.character.escape.cuda-cpp" + }, + { + "match": "(?:(\\\\x0*[0-9a-fA-F]{2}(?![0-9a-fA-F]))|((?:\\\\x[0-9a-fA-F]*|\\\\x)))", + "captures": { + "1": { + "name": "constant.character.escape.cuda-cpp" + }, + "2": { + "name": "invalid.illegal.unknown-escape.cuda-cpp" + } + } + }, + { + "include": "#string_escapes_context_c" + } + ] + }, + { + "begin": "(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?={)|(?:((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*+)?(?:((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(:(?!:)))?)", + "end": "(?:(?:(?<=\\}|%>|\\?\\?>)(?:(?:\\s)+)?(;)|(;))|(?=[;>\\[\\]=]))", + "beginCaptures": { + "0": { + "name": "meta.head.struct.cuda-cpp" + }, + "1": { + "name": "storage.type.$1.cuda-cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "4": { + "name": "comment.block.cuda-cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "6": { + "patterns": [ + { + "include": "#attributes_context" + }, + { + "include": "#number_literal" + } + ] + }, + "7": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "8": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "9": { + "name": "comment.block.cuda-cpp" + }, + "10": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "11": { + "patterns": [ + { + "match": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))", + "captures": { + "1": { + "name": "storage.type.modifier.final.cuda-cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "4": { + "name": "comment.block.cuda-cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + }, + { + "match": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?(?=:|{|$)", + "captures": { + "1": { + "name": "entity.name.type.struct.cuda-cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "4": { + "name": "comment.block.cuda-cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "6": { + "name": "storage.type.modifier.final.cuda-cpp" + }, + "7": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "8": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "9": { + "name": "comment.block.cuda-cpp" + }, + "10": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + }, + { + "match": "DLLEXPORT", + "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cuda-cpp" + }, + { + "match": "(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*", + "name": "entity.name.other.preprocessor.macro.predefined.probably.$0.cuda-cpp" + } + ] + }, + "12": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "13": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "14": { + "name": "comment.block.cuda-cpp" + }, + "15": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "16": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "17": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "18": { + "name": "comment.block.cuda-cpp" + }, + "19": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "20": { + "name": "punctuation.separator.colon.inheritance.cuda-cpp" + } + }, + "endCaptures": { + "1": { + "name": "punctuation.terminator.statement.cuda-cpp" + }, + "2": { + "name": "punctuation.terminator.statement.cuda-cpp" + } + }, + "name": "meta.block.struct.cuda-cpp", + "patterns": [ + { + "begin": "\\G ?", + "end": "(?:\\{|<%|\\?\\?<|(?=;))", + "beginCaptures": {}, + "endCaptures": { + "0": { + "name": "punctuation.section.block.begin.bracket.curly.struct.cuda-cpp" + } + }, + "name": "meta.head.struct.cuda-cpp", + "patterns": [ + { + "include": "#ever_present_context" + }, + { + "include": "#inheritance_context" + }, + { + "include": "#template_call_range" + } + ] + }, + { + "begin": "(?<=\\{|<%|\\?\\?<)", + "end": "\\}|%>|\\?\\?>", + "beginCaptures": {}, + "endCaptures": { + "0": { + "name": "punctuation.section.block.end.bracket.curly.struct.cuda-cpp" + } + }, + "name": "meta.body.struct.cuda-cpp", + "patterns": [ + { + "include": "#function_pointer" + }, + { + "include": "#static_assert" + }, + { + "include": "#constructor_inline" + }, + { + "include": "#destructor_inline" + }, + { + "include": "$self" + } + ] + }, + { + "begin": "(?<=\\}|%>|\\?\\?>)[\\s]*", + "end": "[\\s]*(?=;)", + "beginCaptures": {}, + "endCaptures": {}, + "name": "meta.tail.struct.cuda-cpp", + "patterns": [ + { + "include": "$self" + } + ] + } + ] + }, + "struct_declare": { + "match": "((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\b(?!override\\W|override\\$|final\\W|final\\$)((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\S)(?![:{a-zA-Z])", + "captures": { + "1": { + "name": "storage.type.struct.declare.cuda-cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "4": { + "name": "entity.name.type.struct.cuda-cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*", + "name": "storage.modifier.pointer.cuda-cpp" + }, + { + "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "3": { + "name": "comment.block.cuda-cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + }, + "name": "invalid.illegal.reference-type.cuda-cpp" + }, + { + "match": "\\&", + "name": "storage.modifier.reference.cuda-cpp" + } + ] + }, + "6": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "7": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "8": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "9": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "10": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "11": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "12": { + "name": "variable.other.object.declare.cuda-cpp" + }, + "13": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "14": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + } + } + }, + "switch_conditional_parentheses": { + "begin": "((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", + "end": "\\)", + "beginCaptures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "3": { + "name": "comment.block.cuda-cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "5": { + "name": "punctuation.section.parens.begin.bracket.round.conditional.switch.cuda-cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.parens.end.bracket.round.conditional.switch.cuda-cpp" + } + }, + "name": "meta.conditional.switch.cuda-cpp", + "patterns": [ + { + "include": "#evaluation_context" + }, + { + "include": "#c_conditional_context" + } + ] + }, + "switch_statement": { + "begin": "((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?|\\?\\?>)|(?=[;>\\[\\]=]))", + "beginCaptures": { + "0": { + "name": "meta.head.switch.cuda-cpp" + }, + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "3": { + "name": "comment.block.cuda-cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "5": { + "name": "keyword.control.switch.cuda-cpp" + } + }, + "endCaptures": {}, + "name": "meta.block.switch.cuda-cpp", + "patterns": [ + { + "begin": "\\G ?", + "end": "(?:\\{|<%|\\?\\?<|(?=;))", + "beginCaptures": {}, + "endCaptures": { + "0": { + "name": "punctuation.section.block.begin.bracket.curly.switch.cuda-cpp" + } + }, + "name": "meta.head.switch.cuda-cpp", + "patterns": [ + { + "include": "#switch_conditional_parentheses" + }, + { + "include": "$self" + } + ] + }, + { + "begin": "(?<=\\{|<%|\\?\\?<)", + "end": "\\}|%>|\\?\\?>", + "beginCaptures": {}, + "endCaptures": { + "0": { + "name": "punctuation.section.block.end.bracket.curly.switch.cuda-cpp" + } + }, + "name": "meta.body.switch.cuda-cpp", + "patterns": [ + { + "include": "#default_statement" + }, + { + "include": "#case_statement" + }, + { + "include": "$self" + }, + { + "include": "#block_innards" + } + ] + }, + { + "begin": "(?<=\\}|%>|\\?\\?>)[\\s]*", + "end": "[\\s]*(?=;)", + "beginCaptures": {}, + "endCaptures": {}, + "name": "meta.tail.switch.cuda-cpp", + "patterns": [ + { + "include": "$self" + } + ] + } + ] + }, + "template_argument_defaulted": { + "match": "(?<=<|,)(?:(?:\\s)+)?((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*(?:\\s)+)*)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)(?:(?:\\s)+)?([=])", + "captures": { + "1": { + "name": "storage.type.template.cuda-cpp" + }, + "2": { + "name": "entity.name.type.template.cuda-cpp" + }, + "3": { + "name": "keyword.operator.assignment.cuda-cpp" + } + } + }, + "template_call_context": { + "patterns": [ + { + "include": "#ever_present_context" + }, + { + "include": "#template_call_range" + }, + { + "include": "#storage_types" + }, + { + "include": "#language_constants" + }, + { + "include": "#scope_resolution_template_call_inner_generated" + }, + { + "include": "#operators" + }, + { + "include": "#number_literal" + }, + { + "include": "#string_context" + }, + { + "include": "#comma_in_template_argument" + }, + { + "include": "#qualified_type" + } + ] + }, + "template_call_innards": { + "match": "((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<1>?)+>)(?:\\s)*+", + "captures": { + "0": { + "patterns": [ + { + "include": "#template_call_range" + } + ] + } + }, + "name": "meta.template.call.cuda-cpp" + }, + "template_call_range": { + "begin": "<", + "end": ">", + "beginCaptures": { + "0": { + "name": "punctuation.section.angle-brackets.begin.template.call.cuda-cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.angle-brackets.end.template.call.cuda-cpp" + } + }, + "name": "meta.template.call.cuda-cpp", + "patterns": [ + { + "include": "#template_call_context" + } + ] + }, + "template_definition": { + "begin": "(?", + "beginCaptures": { + "1": { + "name": "storage.type.template.cuda-cpp" + }, + "2": { + "name": "punctuation.section.angle-brackets.start.template.definition.cuda-cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.angle-brackets.end.template.definition.cuda-cpp" + } + }, + "name": "meta.template.definition.cuda-cpp", + "patterns": [ + { + "begin": "(?<=\\w)(?:(?:\\s)+)?<", + "end": ">", + "beginCaptures": { + "0": { + "name": "punctuation.section.angle-brackets.begin.template.call.cuda-cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.angle-brackets.begin.template.call.cuda-cpp" + } + }, + "patterns": [ + { + "include": "#template_call_context" + } + ] + }, + { + "include": "#template_definition_context" + } + ] + }, + "template_definition_argument": { + "match": "((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)|((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*(?:\\s)+)+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))|((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)(?:(?:\\s)+)?(\\.\\.\\.)(?:(?:\\s)+)?((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))(?:(?:\\s)+)?(?:(,)|(?=>|$))", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "3": { + "name": "storage.type.template.argument.$3.cuda-cpp" + }, + "4": { + "patterns": [ + { + "match": "(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*", + "name": "storage.type.template.argument.$0.cuda-cpp" + } + ] + }, + "5": { + "name": "entity.name.type.template.cuda-cpp" + }, + "6": { + "name": "storage.type.template.cuda-cpp" + }, + "7": { + "name": "punctuation.vararg-ellipses.template.definition.cuda-cpp" + }, + "8": { + "name": "entity.name.type.template.cuda-cpp" + }, + "9": { + "name": "punctuation.separator.delimiter.comma.template.argument.cuda-cpp" + } + } + }, + "template_definition_context": { + "patterns": [ + { + "include": "#scope_resolution_template_definition_inner_generated" + }, + { + "include": "#template_definition_argument" + }, + { + "include": "#template_argument_defaulted" + }, + { + "include": "#template_call_innards" + }, + { + "include": "#evaluation_context" + } + ] + }, + "template_isolated_definition": { + "match": "(?(?:(?:\\s)+)?$)", + "captures": { + "1": { + "name": "storage.type.template.cuda-cpp" + }, + "2": { + "name": "punctuation.section.angle-brackets.start.template.definition.cuda-cpp" + }, + "3": { + "name": "meta.template.definition.cuda-cpp", + "patterns": [ + { + "include": "#template_definition_context" + } + ] + }, + "4": { + "name": "punctuation.section.angle-brackets.end.template.definition.cuda-cpp" + } + } + }, + "ternary_operator": { + "begin": "\\?", + "end": ":", + "beginCaptures": { + "0": { + "name": "keyword.operator.ternary.cuda-cpp" + } + }, + "endCaptures": { + "0": { + "name": "keyword.operator.ternary.cuda-cpp" + } + }, + "patterns": [ + { + "include": "#ever_present_context" + }, + { + "include": "#string_context" + }, + { + "include": "#number_literal" + }, + { + "include": "#method_access" + }, + { + "include": "#member_access" + }, + { + "include": "#predefined_macros" + }, + { + "include": "#operators" + }, + { + "include": "#memory_operators" + }, + { + "include": "#wordlike_operators" + }, + { + "include": "#type_casting_operators" + }, + { + "include": "#control_flow_keywords" + }, + { + "include": "#exception_keywords" + }, + { + "include": "#the_this_keyword" + }, + { + "include": "#language_constants" + }, + { + "include": "#builtin_storage_type_initilizer" + }, + { + "include": "#qualifiers_and_specifiers_post_parameters" + }, + { + "include": "#functional_specifiers_pre_parameters" + }, + { + "include": "#storage_types" + }, + { + "include": "#lambdas" + }, + { + "include": "#attributes_context" + }, + { + "include": "#parentheses" + }, + { + "include": "#function_call" + }, + { + "include": "#scope_resolution_inner_generated" + }, + { + "include": "#square_brackets" + }, + { + "include": "#semicolon" + }, + { + "include": "#comma" + } + ], + "applyEndPatternLast": 1 + }, + "the_this_keyword": { + "match": "((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "3": { + "name": "variable.language.this.cuda-cpp" + } + } + }, + "type_alias": { + "match": "(using)(?:(?:\\s)+)?(?!namespace)(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|__forceinline__|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|__constant__|__restrict__|__noinline__|thread_local|synchronized|static_cast|__managed__|const_cast|__shared__|__global__|__device__|co_return|constexpr|constexpr|constexpr|consteval|protected|namespace|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|__host__|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<29>?)+>)(?:\\s)*+)?::)*+)?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:__forceinline__)|(?:atomic_noexcept)|(?:__has_include)|(?:atomic_cancel)|(?:atomic_commit)|(?:dynamic_cast)|(?:__constant__)|(?:__restrict__)|(?:__noinline__)|(?:thread_local)|(?:synchronized)|(?:static_cast)|(?:__managed__)|(?:const_cast)|(?:__shared__)|(?:__global__)|(?:__device__)|(?:co_return)|(?:constexpr)|(?:constexpr)|(?:constexpr)|(?:consteval)|(?:protected)|(?:threadIdx)|(?:namespace)|(?:co_return)|(?:noexcept)|(?:noexcept)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:__host__)|(?:override)|(?:volatile)|(?:noexcept)|(?:blockIdx)|(?:blockDim)|(?:warpSize)|(?:template)|(?:operator)|(?:decltype)|(?:typename)|(?:requires)|(?:co_await)|(?:co_yield)|(?:reflexpr)|(?:alignof)|(?:alignas)|(?:default)|(?:nullptr)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:gridDim)|(?:typedef)|(?:__asm__)|(?:concept)|(?:sizeof)|(?:delete)|(?:not_eq)|(?:bitand)|(?:and_eq)|(?:xor_eq)|(?:typeid)|(?:switch)|(?:return)|(?:static)|(?:extern)|(?:inline)|(?:friend)|(?:public)|(?:ifndef)|(?:define)|(?:pragma)|(?:export)|(?:import)|(?:module)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:false)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:else)|(?:goto)|(?:case)|(?:NULL)|(?:true)|(?:elif)|(?:else)|(?:line)|(?:this)|(?:not)|(?:new)|(?:xor)|(?:and)|(?:for)|(?:try)|(?:asm)|(?:or)|(?:do)|(?:if)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<29>?)+>)?(?![\\w<:.]))(?:(?:\\s)+)?(\\=)(?:(?:\\s)+)?((?:typename)?)(?:(?:\\s)+)?((?:(?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))(?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|__forceinline__|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|__constant__|__restrict__|__noinline__|thread_local|synchronized|static_cast|__managed__|const_cast|__shared__|__global__|__device__|co_return|constexpr|constexpr|constexpr|consteval|protected|namespace|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|__host__|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<29>?)+>)(?:\\s)*+)?::)*+)?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:__forceinline__)|(?:atomic_noexcept)|(?:__has_include)|(?:atomic_cancel)|(?:atomic_commit)|(?:dynamic_cast)|(?:__constant__)|(?:__restrict__)|(?:__noinline__)|(?:thread_local)|(?:synchronized)|(?:static_cast)|(?:__managed__)|(?:const_cast)|(?:__shared__)|(?:__global__)|(?:__device__)|(?:co_return)|(?:constexpr)|(?:constexpr)|(?:constexpr)|(?:consteval)|(?:protected)|(?:threadIdx)|(?:namespace)|(?:co_return)|(?:noexcept)|(?:noexcept)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:__host__)|(?:override)|(?:volatile)|(?:noexcept)|(?:blockIdx)|(?:blockDim)|(?:warpSize)|(?:template)|(?:operator)|(?:decltype)|(?:typename)|(?:requires)|(?:co_await)|(?:co_yield)|(?:reflexpr)|(?:alignof)|(?:alignas)|(?:default)|(?:nullptr)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:gridDim)|(?:typedef)|(?:__asm__)|(?:concept)|(?:sizeof)|(?:delete)|(?:not_eq)|(?:bitand)|(?:and_eq)|(?:xor_eq)|(?:typeid)|(?:switch)|(?:return)|(?:static)|(?:extern)|(?:inline)|(?:friend)|(?:public)|(?:ifndef)|(?:define)|(?:pragma)|(?:export)|(?:import)|(?:module)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:false)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:else)|(?:goto)|(?:case)|(?:NULL)|(?:true)|(?:elif)|(?:else)|(?:line)|(?:this)|(?:not)|(?:new)|(?:xor)|(?:and)|(?:for)|(?:try)|(?:asm)|(?:or)|(?:do)|(?:if)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<29>?)+>)?(?![\\w<:.]))|(.*(?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?(?:(\\[)(\\w*)(\\])(?:(?:\\s)+)?)?(?:(?:\\s)+)?(?:(;)|\\n)", + "captures": { + "1": { + "name": "keyword.other.using.directive.cuda-cpp" + }, + "2": { + "name": "meta.qualified_type.cuda-cpp", + "patterns": [ + { + "match": "::", + "name": "punctuation.separator.namespace.access.cuda-cpp punctuation.separator.scope-resolution.cuda-cpp" + }, + { + "match": "(?", + "beginCaptures": { + "0": { + "name": "punctuation.section.angle-brackets.begin.template.call.cuda-cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.angle-brackets.end.template.call.cuda-cpp" + } + }, + "name": "meta.template.call.cuda-cpp", + "patterns": [ + { + "include": "#template_call_context" + } + ] + }, + { + "match": "(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*", + "name": "entity.name.type.cuda-cpp" + } + ] + }, + "3": { + "patterns": [ + { + "include": "#attributes_context" + }, + { + "include": "#number_literal" + } + ] + }, + "4": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "5": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "6": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "7": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "8": { + "patterns": [ + { + "match": "::", + "name": "punctuation.separator.namespace.access.cuda-cpp punctuation.separator.scope-resolution.type.cuda-cpp" + }, + { + "match": "(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "14": { + "name": "keyword.operator.assignment.cuda-cpp" + }, + "15": { + "name": "keyword.other.typename.cuda-cpp" + }, + "16": { + "patterns": [ + { + "include": "#storage_specifiers" + } + ] + }, + "17": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "18": { + "name": "meta.qualified_type.cuda-cpp", + "patterns": [ + { + "match": "::", + "name": "punctuation.separator.namespace.access.cuda-cpp punctuation.separator.scope-resolution.cuda-cpp" + }, + { + "match": "(?", + "beginCaptures": { + "0": { + "name": "punctuation.section.angle-brackets.begin.template.call.cuda-cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.angle-brackets.end.template.call.cuda-cpp" + } + }, + "name": "meta.template.call.cuda-cpp", + "patterns": [ + { + "include": "#template_call_context" + } + ] + }, + { + "match": "(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*", + "name": "entity.name.type.cuda-cpp" + } + ] + }, + "19": { + "patterns": [ + { + "include": "#attributes_context" + }, + { + "include": "#number_literal" + } + ] + }, + "20": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "21": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "22": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "23": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "24": { + "patterns": [ + { + "match": "::", + "name": "punctuation.separator.namespace.access.cuda-cpp punctuation.separator.scope-resolution.type.cuda-cpp" + }, + { + "match": "(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "30": { + "name": "meta.declaration.type.alias.value.unknown.cuda-cpp", + "patterns": [ + { + "include": "#evaluation_context" + } + ] + }, + "31": { + "patterns": [ + { + "match": "\\*", + "name": "storage.modifier.pointer.cuda-cpp" + }, + { + "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "3": { + "name": "comment.block.cuda-cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + }, + "name": "invalid.illegal.reference-type.cuda-cpp" + }, + { + "match": "\\&", + "name": "storage.modifier.reference.cuda-cpp" + } + ] + }, + "32": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "33": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "34": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "35": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "36": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "37": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "38": { + "name": "punctuation.definition.begin.bracket.square.cuda-cpp" + }, + "39": { + "patterns": [ + { + "include": "#evaluation_context" + } + ] + }, + "40": { + "name": "punctuation.definition.end.bracket.square.cuda-cpp" + }, + "41": { + "name": "punctuation.terminator.statement.cuda-cpp" + } + }, + "name": "meta.declaration.type.alias.cuda-cpp" + }, + "type_casting_operators": { + "match": "((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "3": { + "name": "keyword.operator.wordlike.cuda-cpp keyword.operator.cast.$3.cuda-cpp" + } + } + }, + "typedef_class": { + "begin": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?={)|(?:((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*+)?(?:((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(:(?!:)))?)", + "end": "(?:(?:(?<=\\}|%>|\\?\\?>)(?:(?:\\s)+)?(;)|(;))|(?=[;>\\[\\]=]))", + "beginCaptures": { + "0": { + "name": "meta.head.class.cuda-cpp" + }, + "1": { + "name": "storage.type.$1.cuda-cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "4": { + "name": "comment.block.cuda-cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "6": { + "patterns": [ + { + "include": "#attributes_context" + }, + { + "include": "#number_literal" + } + ] + }, + "7": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "8": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "9": { + "name": "comment.block.cuda-cpp" + }, + "10": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "11": { + "patterns": [ + { + "match": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))", + "captures": { + "1": { + "name": "storage.type.modifier.final.cuda-cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "4": { + "name": "comment.block.cuda-cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + }, + { + "match": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?(?=:|{|$)", + "captures": { + "1": { + "name": "entity.name.type.class.cuda-cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "4": { + "name": "comment.block.cuda-cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "6": { + "name": "storage.type.modifier.final.cuda-cpp" + }, + "7": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "8": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "9": { + "name": "comment.block.cuda-cpp" + }, + "10": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + }, + { + "match": "DLLEXPORT", + "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cuda-cpp" + }, + { + "match": "(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*", + "name": "entity.name.other.preprocessor.macro.predefined.probably.$0.cuda-cpp" + } + ] + }, + "12": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "13": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "14": { + "name": "comment.block.cuda-cpp" + }, + "15": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "16": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "17": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "18": { + "name": "comment.block.cuda-cpp" + }, + "19": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "20": { + "name": "punctuation.separator.colon.inheritance.cuda-cpp" + } + }, + "endCaptures": { + "1": { + "name": "punctuation.terminator.statement.cuda-cpp" + }, + "2": { + "name": "punctuation.terminator.statement.cuda-cpp" + } + }, + "name": "meta.block.class.cuda-cpp", + "patterns": [ + { + "begin": "\\G ?", + "end": "(?:\\{|<%|\\?\\?<|(?=;))", + "beginCaptures": {}, + "endCaptures": { + "0": { + "name": "punctuation.section.block.begin.bracket.curly.class.cuda-cpp" + } + }, + "name": "meta.head.class.cuda-cpp", + "patterns": [ + { + "include": "#ever_present_context" + }, + { + "include": "#inheritance_context" + }, + { + "include": "#template_call_range" + } + ] + }, + { + "begin": "(?<=\\{|<%|\\?\\?<)", + "end": "\\}|%>|\\?\\?>", + "beginCaptures": {}, + "endCaptures": { + "0": { + "name": "punctuation.section.block.end.bracket.curly.class.cuda-cpp" + } + }, + "name": "meta.body.class.cuda-cpp", + "patterns": [ + { + "include": "#function_pointer" + }, + { + "include": "#static_assert" + }, + { + "include": "#constructor_inline" + }, + { + "include": "#destructor_inline" + }, + { + "include": "$self" + } + ] + }, + { + "begin": "(?<=\\}|%>|\\?\\?>)[\\s]*", + "end": "[\\s]*(?=;)", + "beginCaptures": {}, + "endCaptures": {}, + "name": "meta.tail.class.cuda-cpp", + "patterns": [ + { + "match": "(((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "3": { + "name": "comment.block.cuda-cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + }, + "name": "invalid.illegal.reference-type.cuda-cpp" + }, + { + "match": "\\&", + "name": "storage.modifier.reference.cuda-cpp" + } + ] + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "4": { + "name": "comment.block.cuda-cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "6": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "7": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "8": { + "name": "comment.block.cuda-cpp" + }, + "9": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "10": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "11": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "12": { + "name": "comment.block.cuda-cpp" + }, + "13": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "14": { + "name": "entity.name.type.alias.cuda-cpp" + } + } + }, + { + "match": "," + } + ] + } + ] + } + ] + }, + "typedef_function_pointer": { + "begin": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|__forceinline__|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|__constant__|__restrict__|__noinline__|thread_local|synchronized|static_cast|__managed__|const_cast|__shared__|__global__|__device__|co_return|constexpr|constexpr|constexpr|consteval|protected|namespace|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|__host__|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)(?:\\s)*+)?::)*+)?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:__forceinline__)|(?:atomic_noexcept)|(?:__has_include)|(?:atomic_cancel)|(?:atomic_commit)|(?:dynamic_cast)|(?:__constant__)|(?:__restrict__)|(?:__noinline__)|(?:thread_local)|(?:synchronized)|(?:static_cast)|(?:__managed__)|(?:const_cast)|(?:__shared__)|(?:__global__)|(?:__device__)|(?:co_return)|(?:constexpr)|(?:constexpr)|(?:constexpr)|(?:consteval)|(?:protected)|(?:threadIdx)|(?:namespace)|(?:co_return)|(?:noexcept)|(?:noexcept)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:__host__)|(?:override)|(?:volatile)|(?:noexcept)|(?:blockIdx)|(?:blockDim)|(?:warpSize)|(?:template)|(?:operator)|(?:decltype)|(?:typename)|(?:requires)|(?:co_await)|(?:co_yield)|(?:reflexpr)|(?:alignof)|(?:alignas)|(?:default)|(?:nullptr)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:gridDim)|(?:typedef)|(?:__asm__)|(?:concept)|(?:sizeof)|(?:delete)|(?:not_eq)|(?:bitand)|(?:and_eq)|(?:xor_eq)|(?:typeid)|(?:switch)|(?:return)|(?:static)|(?:extern)|(?:inline)|(?:friend)|(?:public)|(?:ifndef)|(?:define)|(?:pragma)|(?:export)|(?:import)|(?:module)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:false)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:else)|(?:goto)|(?:case)|(?:NULL)|(?:true)|(?:elif)|(?:else)|(?:line)|(?:this)|(?:not)|(?:new)|(?:xor)|(?:and)|(?:for)|(?:try)|(?:asm)|(?:or)|(?:do)|(?:if)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)?(?![\\w<:.]))(((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()(\\*)(?:(?:\\s)+)?((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)?)(?:(?:\\s)+)?(?:(\\[)(\\w*)(\\])(?:(?:\\s)+)?)*(\\))(?:(?:\\s)+)?(\\()", + "end": "(\\))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=[{=,);>]|\\n)(?!\\()", + "beginCaptures": { + "1": { + "name": "meta.qualified_type.cuda-cpp", + "patterns": [ + { + "match": "::", + "name": "punctuation.separator.namespace.access.cuda-cpp punctuation.separator.scope-resolution.cuda-cpp" + }, + { + "match": "(?", + "beginCaptures": { + "0": { + "name": "punctuation.section.angle-brackets.begin.template.call.cuda-cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.angle-brackets.end.template.call.cuda-cpp" + } + }, + "name": "meta.template.call.cuda-cpp", + "patterns": [ + { + "include": "#template_call_context" + } + ] + }, + { + "match": "(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*", + "name": "entity.name.type.cuda-cpp" + } + ] + }, + "2": { + "patterns": [ + { + "include": "#attributes_context" + }, + { + "include": "#number_literal" + } + ] + }, + "3": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "4": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "5": { + "name": "comment.block.cuda-cpp" + }, + "6": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "7": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "8": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "9": { + "name": "comment.block.cuda-cpp" + }, + "10": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "11": { + "patterns": [ + { + "match": "::", + "name": "punctuation.separator.namespace.access.cuda-cpp punctuation.separator.scope-resolution.type.cuda-cpp" + }, + { + "match": "(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "3": { + "name": "comment.block.cuda-cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + }, + "name": "invalid.illegal.reference-type.cuda-cpp" + }, + { + "match": "\\&", + "name": "storage.modifier.reference.cuda-cpp" + } + ] + }, + "20": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "21": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "22": { + "name": "comment.block.cuda-cpp" + }, + "23": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "24": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "25": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "26": { + "name": "comment.block.cuda-cpp" + }, + "27": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "28": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "29": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "30": { + "name": "comment.block.cuda-cpp" + }, + "31": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "32": { + "name": "punctuation.section.parens.begin.bracket.round.function.pointer.cuda-cpp" + }, + "33": { + "name": "punctuation.definition.function.pointer.dereference.cuda-cpp" + }, + "34": { + "name": "entity.name.type.alias.cuda-cpp entity.name.type.pointer.function.cuda-cpp" + }, + "35": { + "name": "punctuation.definition.begin.bracket.square.cuda-cpp" + }, + "36": { + "patterns": [ + { + "include": "#evaluation_context" + } + ] + }, + "37": { + "name": "punctuation.definition.end.bracket.square.cuda-cpp" + }, + "38": { + "name": "punctuation.section.parens.end.bracket.round.function.pointer.cuda-cpp" + }, + "39": { + "name": "punctuation.section.parameters.begin.bracket.round.function.pointer.cuda-cpp" + } + }, + "endCaptures": { + "1": { + "name": "punctuation.section.parameters.end.bracket.round.function.pointer.cuda-cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "4": { + "name": "comment.block.cuda-cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + }, + "patterns": [ + { + "include": "#function_parameter_context" + } + ] + } + ] + }, + "typedef_struct": { + "begin": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?={)|(?:((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*+)?(?:((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(:(?!:)))?)", + "end": "(?:(?:(?<=\\}|%>|\\?\\?>)(?:(?:\\s)+)?(;)|(;))|(?=[;>\\[\\]=]))", + "beginCaptures": { + "0": { + "name": "meta.head.struct.cuda-cpp" + }, + "1": { + "name": "storage.type.$1.cuda-cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "4": { + "name": "comment.block.cuda-cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "6": { + "patterns": [ + { + "include": "#attributes_context" + }, + { + "include": "#number_literal" + } + ] + }, + "7": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "8": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "9": { + "name": "comment.block.cuda-cpp" + }, + "10": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "11": { + "patterns": [ + { + "match": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))", + "captures": { + "1": { + "name": "storage.type.modifier.final.cuda-cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "4": { + "name": "comment.block.cuda-cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + }, + { + "match": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?(?=:|{|$)", + "captures": { + "1": { + "name": "entity.name.type.struct.cuda-cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "4": { + "name": "comment.block.cuda-cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "6": { + "name": "storage.type.modifier.final.cuda-cpp" + }, + "7": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "8": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "9": { + "name": "comment.block.cuda-cpp" + }, + "10": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + }, + { + "match": "DLLEXPORT", + "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cuda-cpp" + }, + { + "match": "(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*", + "name": "entity.name.other.preprocessor.macro.predefined.probably.$0.cuda-cpp" + } + ] + }, + "12": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "13": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "14": { + "name": "comment.block.cuda-cpp" + }, + "15": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "16": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "17": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "18": { + "name": "comment.block.cuda-cpp" + }, + "19": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "20": { + "name": "punctuation.separator.colon.inheritance.cuda-cpp" + } + }, + "endCaptures": { + "1": { + "name": "punctuation.terminator.statement.cuda-cpp" + }, + "2": { + "name": "punctuation.terminator.statement.cuda-cpp" + } + }, + "name": "meta.block.struct.cuda-cpp", + "patterns": [ + { + "begin": "\\G ?", + "end": "(?:\\{|<%|\\?\\?<|(?=;))", + "beginCaptures": {}, + "endCaptures": { + "0": { + "name": "punctuation.section.block.begin.bracket.curly.struct.cuda-cpp" + } + }, + "name": "meta.head.struct.cuda-cpp", + "patterns": [ + { + "include": "#ever_present_context" + }, + { + "include": "#inheritance_context" + }, + { + "include": "#template_call_range" + } + ] + }, + { + "begin": "(?<=\\{|<%|\\?\\?<)", + "end": "\\}|%>|\\?\\?>", + "beginCaptures": {}, + "endCaptures": { + "0": { + "name": "punctuation.section.block.end.bracket.curly.struct.cuda-cpp" + } + }, + "name": "meta.body.struct.cuda-cpp", + "patterns": [ + { + "include": "#function_pointer" + }, + { + "include": "#static_assert" + }, + { + "include": "#constructor_inline" + }, + { + "include": "#destructor_inline" + }, + { + "include": "$self" + } + ] + }, + { + "begin": "(?<=\\}|%>|\\?\\?>)[\\s]*", + "end": "[\\s]*(?=;)", + "beginCaptures": {}, + "endCaptures": {}, + "name": "meta.tail.struct.cuda-cpp", + "patterns": [ + { + "match": "(((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "3": { + "name": "comment.block.cuda-cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + }, + "name": "invalid.illegal.reference-type.cuda-cpp" + }, + { + "match": "\\&", + "name": "storage.modifier.reference.cuda-cpp" + } + ] + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "4": { + "name": "comment.block.cuda-cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "6": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "7": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "8": { + "name": "comment.block.cuda-cpp" + }, + "9": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "10": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "11": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "12": { + "name": "comment.block.cuda-cpp" + }, + "13": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "14": { + "name": "entity.name.type.alias.cuda-cpp" + } + } + }, + { + "match": "," + } + ] + } + ] + } + ] + }, + "typedef_union": { + "begin": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?={)|(?:((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*+)?(?:((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(:(?!:)))?)", + "end": "(?:(?:(?<=\\}|%>|\\?\\?>)(?:(?:\\s)+)?(;)|(;))|(?=[;>\\[\\]=]))", + "beginCaptures": { + "0": { + "name": "meta.head.union.cuda-cpp" + }, + "1": { + "name": "storage.type.$1.cuda-cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "4": { + "name": "comment.block.cuda-cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "6": { + "patterns": [ + { + "include": "#attributes_context" + }, + { + "include": "#number_literal" + } + ] + }, + "7": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "8": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "9": { + "name": "comment.block.cuda-cpp" + }, + "10": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "11": { + "patterns": [ + { + "match": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))", + "captures": { + "1": { + "name": "storage.type.modifier.final.cuda-cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "4": { + "name": "comment.block.cuda-cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + }, + { + "match": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?(?=:|{|$)", + "captures": { + "1": { + "name": "entity.name.type.union.cuda-cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "4": { + "name": "comment.block.cuda-cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "6": { + "name": "storage.type.modifier.final.cuda-cpp" + }, + "7": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "8": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "9": { + "name": "comment.block.cuda-cpp" + }, + "10": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + }, + { + "match": "DLLEXPORT", + "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cuda-cpp" + }, + { + "match": "(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*", + "name": "entity.name.other.preprocessor.macro.predefined.probably.$0.cuda-cpp" + } + ] + }, + "12": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "13": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "14": { + "name": "comment.block.cuda-cpp" + }, + "15": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "16": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "17": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "18": { + "name": "comment.block.cuda-cpp" + }, + "19": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "20": { + "name": "punctuation.separator.colon.inheritance.cuda-cpp" + } + }, + "endCaptures": { + "1": { + "name": "punctuation.terminator.statement.cuda-cpp" + }, + "2": { + "name": "punctuation.terminator.statement.cuda-cpp" + } + }, + "name": "meta.block.union.cuda-cpp", + "patterns": [ + { + "begin": "\\G ?", + "end": "(?:\\{|<%|\\?\\?<|(?=;))", + "beginCaptures": {}, + "endCaptures": { + "0": { + "name": "punctuation.section.block.begin.bracket.curly.union.cuda-cpp" + } + }, + "name": "meta.head.union.cuda-cpp", + "patterns": [ + { + "include": "#ever_present_context" + }, + { + "include": "#inheritance_context" + }, + { + "include": "#template_call_range" + } + ] + }, + { + "begin": "(?<=\\{|<%|\\?\\?<)", + "end": "\\}|%>|\\?\\?>", + "beginCaptures": {}, + "endCaptures": { + "0": { + "name": "punctuation.section.block.end.bracket.curly.union.cuda-cpp" + } + }, + "name": "meta.body.union.cuda-cpp", + "patterns": [ + { + "include": "#function_pointer" + }, + { + "include": "#static_assert" + }, + { + "include": "#constructor_inline" + }, + { + "include": "#destructor_inline" + }, + { + "include": "$self" + } + ] + }, + { + "begin": "(?<=\\}|%>|\\?\\?>)[\\s]*", + "end": "[\\s]*(?=;)", + "beginCaptures": {}, + "endCaptures": {}, + "name": "meta.tail.union.cuda-cpp", + "patterns": [ + { + "match": "(((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "3": { + "name": "comment.block.cuda-cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + }, + "name": "invalid.illegal.reference-type.cuda-cpp" + }, + { + "match": "\\&", + "name": "storage.modifier.reference.cuda-cpp" + } + ] + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "4": { + "name": "comment.block.cuda-cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "6": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "7": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "8": { + "name": "comment.block.cuda-cpp" + }, + "9": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "10": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "11": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "12": { + "name": "comment.block.cuda-cpp" + }, + "13": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "14": { + "name": "entity.name.type.alias.cuda-cpp" + } + } + }, + { + "match": "," + } + ] + } + ] + } + ] + }, + "typeid_operator": { + "begin": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", + "end": "\\)", + "beginCaptures": { + "1": { + "name": "keyword.operator.functionlike.cuda-cpp keyword.operator.typeid.cuda-cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "4": { + "name": "comment.block.cuda-cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "6": { + "name": "punctuation.section.arguments.begin.bracket.round.operator.typeid.cuda-cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.arguments.end.bracket.round.operator.typeid.cuda-cpp" + } + }, + "contentName": "meta.arguments.operator.typeid", + "patterns": [ + { + "include": "#evaluation_context" + } + ] + }, + "typename": { + "match": "(((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|__forceinline__|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|__constant__|__restrict__|__noinline__|thread_local|synchronized|static_cast|__managed__|const_cast|__shared__|__global__|__device__|co_return|constexpr|constexpr|constexpr|consteval|protected|namespace|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|__host__|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<17>?)+>)(?:\\s)*+)?::)*+)?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:__forceinline__)|(?:atomic_noexcept)|(?:__has_include)|(?:atomic_cancel)|(?:atomic_commit)|(?:dynamic_cast)|(?:__constant__)|(?:__restrict__)|(?:__noinline__)|(?:thread_local)|(?:synchronized)|(?:static_cast)|(?:__managed__)|(?:const_cast)|(?:__shared__)|(?:__global__)|(?:__device__)|(?:co_return)|(?:constexpr)|(?:constexpr)|(?:constexpr)|(?:consteval)|(?:protected)|(?:threadIdx)|(?:namespace)|(?:co_return)|(?:noexcept)|(?:noexcept)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:__host__)|(?:override)|(?:volatile)|(?:noexcept)|(?:blockIdx)|(?:blockDim)|(?:warpSize)|(?:template)|(?:operator)|(?:decltype)|(?:typename)|(?:requires)|(?:co_await)|(?:co_yield)|(?:reflexpr)|(?:alignof)|(?:alignas)|(?:default)|(?:nullptr)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:gridDim)|(?:typedef)|(?:__asm__)|(?:concept)|(?:sizeof)|(?:delete)|(?:not_eq)|(?:bitand)|(?:and_eq)|(?:xor_eq)|(?:typeid)|(?:switch)|(?:return)|(?:static)|(?:extern)|(?:inline)|(?:friend)|(?:public)|(?:ifndef)|(?:define)|(?:pragma)|(?:export)|(?:import)|(?:module)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:false)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:else)|(?:goto)|(?:case)|(?:NULL)|(?:true)|(?:elif)|(?:else)|(?:line)|(?:this)|(?:not)|(?:new)|(?:xor)|(?:and)|(?:for)|(?:try)|(?:asm)|(?:or)|(?:do)|(?:if)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<17>?)+>)?(?![\\w<:.]))", + "captures": { + "1": { + "name": "storage.modifier.cuda-cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "4": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "5": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "6": { + "name": "meta.qualified_type.cuda-cpp", + "patterns": [ + { + "match": "::", + "name": "punctuation.separator.namespace.access.cuda-cpp punctuation.separator.scope-resolution.cuda-cpp" + }, + { + "match": "(?", + "beginCaptures": { + "0": { + "name": "punctuation.section.angle-brackets.begin.template.call.cuda-cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.angle-brackets.end.template.call.cuda-cpp" + } + }, + "name": "meta.template.call.cuda-cpp", + "patterns": [ + { + "include": "#template_call_context" + } + ] + }, + { + "match": "(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*", + "name": "entity.name.type.cuda-cpp" + } + ] + }, + "7": { + "patterns": [ + { + "include": "#attributes_context" + }, + { + "include": "#number_literal" + } + ] + }, + "8": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "9": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "10": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "11": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "12": { + "patterns": [ + { + "match": "::", + "name": "punctuation.separator.namespace.access.cuda-cpp punctuation.separator.scope-resolution.type.cuda-cpp" + }, + { + "match": "(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "17": {} + } + }, + "undef": { + "match": "(^((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(#)(?:(?:\\s)+)?undef\\b)((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "4": { + "name": "punctuation.definition.directive.cuda-cpp" + }, + "5": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "6": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "7": { + "name": "entity.name.function.preprocessor.cuda-cpp" + } + }, + "name": "meta.preprocessor.undef.cuda-cpp" + }, + "union_block": { + "begin": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?={)|(?:((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*+)?(?:((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(:(?!:)))?)", + "end": "(?:(?:(?<=\\}|%>|\\?\\?>)(?:(?:\\s)+)?(;)|(;))|(?=[;>\\[\\]=]))", + "beginCaptures": { + "0": { + "name": "meta.head.union.cuda-cpp" + }, + "1": { + "name": "storage.type.$1.cuda-cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "4": { + "name": "comment.block.cuda-cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "6": { + "patterns": [ + { + "include": "#attributes_context" + }, + { + "include": "#number_literal" + } + ] + }, + "7": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "8": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "9": { + "name": "comment.block.cuda-cpp" + }, + "10": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "11": { + "patterns": [ + { + "match": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))", + "captures": { + "1": { + "name": "storage.type.modifier.final.cuda-cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "4": { + "name": "comment.block.cuda-cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + }, + { + "match": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?(?=:|{|$)", + "captures": { + "1": { + "name": "entity.name.type.union.cuda-cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "4": { + "name": "comment.block.cuda-cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "6": { + "name": "storage.type.modifier.final.cuda-cpp" + }, + "7": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "8": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "9": { + "name": "comment.block.cuda-cpp" + }, + "10": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + }, + { + "match": "DLLEXPORT", + "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cuda-cpp" + }, + { + "match": "(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*", + "name": "entity.name.other.preprocessor.macro.predefined.probably.$0.cuda-cpp" + } + ] + }, + "12": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "13": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "14": { + "name": "comment.block.cuda-cpp" + }, + "15": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "16": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "17": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "18": { + "name": "comment.block.cuda-cpp" + }, + "19": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + }, + "20": { + "name": "punctuation.separator.colon.inheritance.cuda-cpp" + } + }, + "endCaptures": { + "1": { + "name": "punctuation.terminator.statement.cuda-cpp" + }, + "2": { + "name": "punctuation.terminator.statement.cuda-cpp" + } + }, + "name": "meta.block.union.cuda-cpp", + "patterns": [ + { + "begin": "\\G ?", + "end": "(?:\\{|<%|\\?\\?<|(?=;))", + "beginCaptures": {}, + "endCaptures": { + "0": { + "name": "punctuation.section.block.begin.bracket.curly.union.cuda-cpp" + } + }, + "name": "meta.head.union.cuda-cpp", + "patterns": [ + { + "include": "#ever_present_context" + }, + { + "include": "#inheritance_context" + }, + { + "include": "#template_call_range" + } + ] + }, + { + "begin": "(?<=\\{|<%|\\?\\?<)", + "end": "\\}|%>|\\?\\?>", + "beginCaptures": {}, + "endCaptures": { + "0": { + "name": "punctuation.section.block.end.bracket.curly.union.cuda-cpp" + } + }, + "name": "meta.body.union.cuda-cpp", + "patterns": [ + { + "include": "#function_pointer" + }, + { + "include": "#static_assert" + }, + { + "include": "#constructor_inline" + }, + { + "include": "#destructor_inline" + }, + { + "include": "$self" + } + ] + }, + { + "begin": "(?<=\\}|%>|\\?\\?>)[\\s]*", + "end": "[\\s]*(?=;)", + "beginCaptures": {}, + "endCaptures": {}, + "name": "meta.tail.union.cuda-cpp", + "patterns": [ + { + "include": "$self" + } + ] + } + ] + }, + "union_declare": { + "match": "((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\b(?!override\\W|override\\$|final\\W|final\\$)((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\S)(?![:{a-zA-Z])", + "captures": { + "1": { + "name": "storage.type.union.declare.cuda-cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "4": { + "name": "entity.name.type.union.cuda-cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*", + "name": "storage.modifier.pointer.cuda-cpp" + }, + { + "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "3": { + "name": "comment.block.cuda-cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + }, + "name": "invalid.illegal.reference-type.cuda-cpp" + }, + { + "match": "\\&", + "name": "storage.modifier.reference.cuda-cpp" + } + ] + }, + "6": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "7": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "8": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "9": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "10": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "11": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + }, + "12": { + "name": "variable.other.object.declare.cuda-cpp" + }, + "13": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "14": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cuda-cpp punctuation.definition.comment.begin.cuda-cpp" + }, + "2": { + "name": "comment.block.cuda-cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cuda-cpp punctuation.definition.comment.end.cuda-cpp" + }, + { + "match": "\\*", + "name": "comment.block.cuda-cpp" + } + ] + } + } + } + ] + } + } + }, + "using_name": { + "match": "(using)(?:\\s)+(?!namespace\\b)", + "captures": { + "1": { + "name": "keyword.other.using.directive.cuda-cpp" + } + } + }, + "using_namespace": { + "begin": "(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<6>?)+>)(?:\\s)*+)?::)*\\s*+)?((?\n (?:\n (?:ref\\s+(?:readonly\\s+)?)? # ref return\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s*\\[(?:\\s*,\\s*)*\\]\\s*)* # array suffix?\n )\n)\\s+\n(\\g)\\s*\n(<([^<>]+)>)?\\s*\n(?=\\()", + "begin": "(?x)\n(?:\\b(delegate)\\b)\\s+\n(?\n (?:\n (?:ref\\s+(?:readonly\\s+)?)? # ref return\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)\\s+\n(\\g)\\s*\n(<([^<>]+)>)?\\s*\n(?=\\()", "beginCaptures": { "1": { "name": "keyword.other.delegate.cs" @@ -967,7 +970,7 @@ ] }, "field-declaration": { - "begin": "(?x)\n(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s*\\[(?:\\s*,\\s*)*\\]\\s*)* # array suffix?\n )\n)\\s+\n(\\g)\\s* # first field name\n(?!=>|==)(?=,|;|=|$)", + "begin": "(?x)\n(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)\\s+\n(\\g)\\s* # first field name\n(?!=>|==)(?=,|;|=|$)", "beginCaptures": { "1": { "patterns": [ @@ -1001,7 +1004,7 @@ ] }, "property-declaration": { - "begin": "(?x)\n\n# The negative lookahead below ensures that we don't match nested types\n# or other declarations as properties.\n(?![[:word:][:space:]]*\\b(?:class|interface|struct|enum|event)\\b)\n\n(?\n (?\n (?:\n (?:ref\\s+(?:readonly\\s+)?)? # ref return\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s*\\[(?:\\s*,\\s*)*\\]\\s*)* # array suffix?\n )\n )\\s+\n)\n(?\\g\\s*\\.\\s*)?\n(?\\g)\\s*\n(?=\\{|=>|$)", + "begin": "(?x)\n\n# The negative lookahead below ensures that we don't match nested types\n# or other declarations as properties.\n(?![[:word:][:space:]]*\\b(?:class|interface|struct|enum|event)\\b)\n\n(?\n (?\n (?:\n (?:ref\\s+(?:readonly\\s+)?)? # ref return\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n )\\s+\n)\n(?\\g\\s*\\.\\s*)?\n(?\\g)\\s*\n(?=\\{|=>|$)", "beginCaptures": { "1": { "patterns": [ @@ -1044,7 +1047,7 @@ ] }, "indexer-declaration": { - "begin": "(?x)\n(?\n (?\n (?:\n (?:ref\\s+(?:readonly\\s+)?)? # ref return\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s*\\[(?:\\s*,\\s*)*\\]\\s*)* # array suffix?\n )\n )\\s+\n)\n(?\\g\\s*\\.\\s*)?\n(?this)\\s*\n(?=\\[)", + "begin": "(?x)\n(?\n (?\n (?:\n (?:ref\\s+(?:readonly\\s+)?)? # ref return\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n )\\s+\n)\n(?\\g\\s*\\.\\s*)?\n(?this)\\s*\n(?=\\[)", "beginCaptures": { "1": { "patterns": [ @@ -1087,7 +1090,7 @@ ] }, "event-declaration": { - "begin": "(?x)\n\\b(event)\\b\\s*\n(?\n (?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s*\\[(?:\\s*,\\s*)*\\]\\s*)* # array suffix?\n )\n )\\s+\n)\n(?\\g\\s*\\.\\s*)?\n(?\\g(?:\\s*,\\s*\\g)*)\\s*\n(?=\\{|;|$)", + "begin": "(?x)\n\\b(event)\\b\\s*\n(?\n (?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n )\\s+\n)\n(?\\g\\s*\\.\\s*)?\n(?\\g(?:\\s*,\\s*\\g)*)\\s*\n(?=\\{|;|$)", "beginCaptures": { "1": { "name": "keyword.other.event.cs" @@ -1217,7 +1220,7 @@ ] }, "method-declaration": { - "begin": "(?x)\n(?\n (?\n (?:\n (?:ref\\s+(?:readonly\\s+)?)? # ref return\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s*\\[(?:\\s*,\\s*)*\\]\\s*)* # array suffix?\n )\n )\\s+\n)\n(?\\g\\s*\\.\\s*)?\n(\\g)\\s*\n(<([^<>]+)>)?\\s*\n(?=\\()", + "begin": "(?x)\n(?\n (?\n (?:\n (?:ref\\s+(?:readonly\\s+)?)? # ref return\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n )\\s+\n)\n(?\\g\\s*\\.\\s*)?\n(\\g)\\s*\n(<([^<>]+)>)?\\s*\n(?=\\()", "beginCaptures": { "1": { "patterns": [ @@ -1353,7 +1356,7 @@ ] }, "operator-declaration": { - "begin": "(?x)\n(?\n (?:\n (?:ref\\s+(?:readonly\\s+)?)? # ref return\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s*\\[(?:\\s*,\\s*)*\\]\\s*)* # array suffix?\n )\n)\\s*\n(?(?:\\b(?:operator)))\\s*\n(?(?:\\+|-|\\*|/|%|&|\\||\\^|\\<\\<|\\>\\>|==|!=|\\>|\\<|\\>=|\\<=|!|~|\\+\\+|--|true|false))\\s*\n(?=\\()", + "begin": "(?x)\n(?\n (?:\n (?:ref\\s+(?:readonly\\s+)?)? # ref return\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)\\s*\n(?(?:\\b(?:operator)))\\s*\n(?(?:\\+|-|\\*|/|%|&|\\||\\^|\\<\\<|\\>\\>|==|!=|\\>|\\<|\\>=|\\<=|!|~|\\+\\+|--|true|false))\\s*\n(?=\\()", "beginCaptures": { "1": { "patterns": [ @@ -1386,7 +1389,7 @@ ] }, "conversion-operator-declaration": { - "begin": "(?x)\n(?(?:\\b(?:explicit|implicit)))\\s*\n(?(?:\\b(?:operator)))\\s*\n(?\n (?:\n (?:ref\\s+(?:readonly\\s+)?)? # ref return\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s*\\[(?:\\s*,\\s*)*\\]\\s*)* # array suffix?\n )\n)\\s*\n(?=\\()", + "begin": "(?x)\n(?(?:\\b(?:explicit|implicit)))\\s*\n(?(?:\\b(?:operator)))\\s*\n(?\n (?:\n (?:ref\\s+(?:readonly\\s+)?)? # ref return\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)\\s*\n(?=\\()", "beginCaptures": { "1": { "patterns": [ @@ -1615,7 +1618,7 @@ "end": "(?=;)", "patterns": [ { - "include": "#expression" + "include": "#statement" } ] }, @@ -1718,6 +1721,178 @@ } ] }, + "switch-expression": { + "begin": "(?x) (?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)\\s+\n(\\g)\\b\\s*", + "beginCaptures": { + "1": { + "patterns": [ + { + "include": "#type" + } + ] + }, + "2": { + "name": "entity.name.variable.local.cs" + } + }, + "end": "(?==>)", + "patterns": [ + { + "include": "#comment" + }, + { + "include": "#switch-when-clause" + } + ] + }, + "switch-property-expression": { + "begin": "(?x) # e.g. int x OR var x\n(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)?\\s*\n(\\{)", + "beginCaptures": { + "1": { + "patterns": [ + { + "include": "#type" + } + ] + }, + "6": { + "name": "punctuation.curlybrace.open.cs" + } + }, + "end": "\\}", + "endCaptures": { + "0": { + "name": "punctuation.curlybrace.close.cs" + } + }, + "patterns": [ + { + "include": "#expression" + }, + { + "include": "#punctuation-comma" + } + ] + }, + "switch-var-pattern": { + "begin": "(?x) # match foreach (var (x, y) in ...)\n(?:\\b(var)\\b\\s*)\n(?\\((?:[^\\(\\)]|\\g)+\\))\\s*", + "beginCaptures": { + "1": { + "name": "keyword.other.var.cs" + }, + "2": { + "patterns": [ + { + "include": "#tuple-declaration-deconstruction-element-list" + } + ] + } + }, + "end": "(?==>)", + "patterns": [ + { + "include": "#comment" + }, + { + "include": "#switch-when-clause" + } + ] + }, + "switch-when-clause": { + "begin": "(?)", + "patterns": [ + { + "include": "#comment" + }, + { + "include": "#expression" + }, + { + "include": "#punctuation-comma" + }, + { + "match": "\\(", + "captures": { + "0": { + "name": "punctuation.parenthesis.open.cs" + } + } + }, + { + "match": "\\)", + "captures": { + "0": { + "name": "punctuation.parenthesis.close.cs" + } + } + } + ] + }, "switch-label": { "patterns": [ { @@ -1865,7 +2040,7 @@ }, "patterns": [ { - "match": "(?x)\n(?:\n (\\bvar\\b)|\n (?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s*\\[(?:\\s*,\\s*)*\\]\\s*)* # array suffix?\n )\n )\n)\\s+\n(\\g)\\s+\n\\b(in)\\b", + "match": "(?x)\n(?:\n (\\bvar\\b)|\n (?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n )\n)\\s+\n(\\g)\\s+\n\\b(in)\\b", "captures": { "1": { "name": "keyword.other.var.cs" @@ -1984,7 +2159,7 @@ }, "patterns": [ { - "match": "(?x)\n(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s*\\[(?:\\s*,\\s*)*\\]\\s*)* # array suffix?\n )\n)\\s*\n(?:(\\g)\\b)?", + "match": "(?x)\n(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)\\s*\n(?:(\\g)\\b)?", "captures": { "1": { "patterns": [ @@ -2144,31 +2319,37 @@ { "include": "#local-variable-declaration" }, + { + "include": "#local-function-declaration" + }, { "include": "#local-tuple-var-deconstruction" } ] }, "local-variable-declaration": { - "begin": "(?x)\n(?:\n (?:(\\bref)\\s+(?:(\\breadonly)\\s+)?)?(\\bvar\\b)| # ref local\n (?\n (?:\n (?:ref\\s+(?:readonly\\s+)?)? # ref local\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s*\\[(?:\\s*,\\s*)*\\]\\s*)* # array suffix?\n )\n )\n)\\s+\n(\\g)\\s*\n(?!=>)\n(?=,|;|=|\\))", + "begin": "(?x)\n(?:\n (?:(\\busing)\\s+)?\n (?:(\\bref)\\s+(?:(\\breadonly)\\s+)?)?(\\bvar\\b)| # ref local\n (?\n (?:\n (?:ref\\s+(?:readonly\\s+)?)? # ref local\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n )\n)\\s+\n(\\g)\\s*\n(?!=>)\n(?=,|;|=|\\))", "beginCaptures": { "1": { - "name": "storage.modifier.cs" + "name": "keyword.other.using.cs" }, "2": { "name": "storage.modifier.cs" }, "3": { - "name": "keyword.other.var.cs" + "name": "storage.modifier.cs" }, "4": { + "name": "keyword.other.var.cs" + }, + "5": { "patterns": [ { "include": "#type" } ] }, - "9": { + "10": { "name": "entity.name.variable.local.cs" } }, @@ -2190,7 +2371,7 @@ ] }, "local-constant-declaration": { - "begin": "(?x)\n(?\\b(?:const)\\b)\\s*\n(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s*\\[(?:\\s*,\\s*)*\\]\\s*)* # array suffix?\n )\n)\\s+\n(\\g)\\s*\n(?=,|;|=)", + "begin": "(?x)\n(?\\b(?:const)\\b)\\s*\n(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)\\s+\n(\\g)\\s*\n(?=,|;|=)", "beginCaptures": { "1": { "name": "storage.modifier.cs" @@ -2223,6 +2404,13 @@ } ] }, + "local-function-declaration": { + "patterns": [ + { + "include": "#method-declaration" + } + ] + }, "local-tuple-var-deconstruction": { "begin": "(?x) # e.g. var (x, y) = GetPoint();\n(?:\\b(var)\\b\\s*)\n(?\\((?:[^\\(\\)]|\\g)+\\))\\s*\n(?=;|=|\\))", "beginCaptures": { @@ -2332,7 +2520,7 @@ ] }, "declaration-expression-local": { - "match": "(?x) # e.g. int x OR var x\n(?:\n \\b(var)\\b|\n (?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s*\\[(?:\\s*,\\s*)*\\]\\s*)* # array suffix?\n )\n )\n)\\s+\n(\\g)\\b\\s*\n(?=[,)\\]])", + "match": "(?x) # e.g. int x OR var x\n(?:\n \\b(var)\\b|\n (?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n )\n)\\s+\n(\\g)\\b\\s*\n(?=[,)\\]])", "captures": { "1": { "name": "keyword.other.var.cs" @@ -2350,7 +2538,7 @@ } }, "declaration-expression-tuple": { - "match": "(?x) # e.g. int x OR var x\n(?:\n \\b(var)\\b|\n (?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s*\\[(?:\\s*,\\s*)*\\]\\s*)* # array suffix?\n )\n )\n)\\s+\n(\\g)\\b\\s*\n(?=[,)])", + "match": "(?x) # e.g. int x OR var x\n(?:\n \\b(var)\\b|\n (?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n )\n)\\s+\n(\\g)\\b\\s*\n(?=[,)])", "captures": { "1": { "name": "keyword.other.var.cs" @@ -2475,7 +2663,7 @@ }, "verbatim-interpolated-string": { "name": "string.quoted.double.cs", - "begin": "\\$@\"", + "begin": "(?:\\$@|@\\$)\"", "beginCaptures": { "0": { "name": "punctuation.definition.string.begin.cs" @@ -2712,7 +2900,7 @@ "patterns": [ { "name": "keyword.operator.assignment.compound.cs", - "match": "\\*=|/=|%=|\\+=|-=" + "match": "\\*=|/=|%=|\\+=|-=|\\?\\?=" }, { "name": "keyword.operator.assignment.compound.bitwise.cs", @@ -2760,6 +2948,26 @@ } ] }, + "switch-literal": { + "name": "constant.language.null.cs", + "match": "(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s*\\[(?:\\s*,\\s*)*\\]\\s*)* # array suffix?\n )\n)\\s*\n(\\))(?=\\s*@?[_[:alnum:]\\(])", + "match": "(?x)\n(\\()\\s*\n(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)\\s*\n(\\))(?=\\s*@?[_[:alnum:]\\(])", "captures": { "1": { "name": "punctuation.parenthesis.open.cs" @@ -2847,7 +3055,7 @@ } }, "as-expression": { - "match": "(?x)\n(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s*\\[(?:\\s*,\\s*)*\\]\\s*)* # array suffix?\n )\n)?", + "match": "(?x)\n(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)?", "captures": { "1": { "name": "keyword.other.as.cs" @@ -2862,7 +3070,7 @@ } }, "is-expression": { - "match": "(?x)\n(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s*\\[(?:\\s*,\\s*)*\\]\\s*)* # array suffix?\n )\n)?", + "match": "(?x)\n(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)?", "captures": { "1": { "name": "keyword.other.is.cs" @@ -2888,7 +3096,7 @@ } }, "invocation-expression": { - "begin": "(?x)\n(?:(\\?)\\s*)? # preceding null-conditional operator?\n(?:(\\.)\\s*)? # preceding dot?\n(@?[_[:alpha:]][_[:alnum:]]*)\\s* # method name\n(?\\s*<([^<>]|\\g)+>\\s*)?\\s* # type arguments\n(?=\\() # open paren of argument list", + "begin": "(?x)\n(?:(\\?)\\s*)? # preceding null-conditional operator?\n(?:(\\.)\\s*)? # preceding dot?\n(@?[_[:alpha:]][_[:alnum:]]*)\\s* # method name\n(?\\s*<([^<>]|\\g)+>\\s*)?\\s* # type arguments\n(?=\\() # open paren of argument list", "beginCaptures": { "1": { "name": "keyword.operator.null-conditional.cs" @@ -2954,7 +3162,7 @@ } }, { - "match": "(?x)\n(\\.)?\\s*\n(@?[_[:alpha:]][_[:alnum:]]*)\n(?\\s*<([^<>]|\\g)+>\\s*)\n(?=\n (\\s*\\?)?\n \\s*\\.\\s*@?[_[:alpha:]][_[:alnum:]]*\n)", + "match": "(?x)\n(\\.)?\\s*\n(@?[_[:alpha:]][_[:alnum:]]*)\n(?\\s*<([^<>]|\\g)+>\\s*)\n(?=\n (\\s*\\?)?\n \\s*\\.\\s*@?[_[:alpha:]][_[:alnum:]]*\n)", "captures": { "1": { "name": "punctuation.accessor.cs" @@ -2992,7 +3200,7 @@ ] }, "object-creation-expression-with-parameters": { - "begin": "(?x)\n(new)\\s+\n(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s*\\[(?:\\s*,\\s*)*\\]\\s*)* # array suffix?\n )\n)\\s*\n(?=\\()", + "begin": "(?x)\n(new)\\s+\n(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)\\s*\n(?=\\()", "beginCaptures": { "1": { "name": "keyword.other.new.cs" @@ -3013,7 +3221,7 @@ ] }, "object-creation-expression-with-no-parameters": { - "match": "(?x)\n(new)\\s+\n(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s*\\[(?:\\s*,\\s*)*\\]\\s*)* # array suffix?\n )\n)\\s*\n(?=\\{|$)", + "match": "(?x)\n(new)\\s+\n(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)\\s*\n(?=\\{|$)", "captures": { "1": { "name": "keyword.other.new.cs" @@ -3028,7 +3236,7 @@ } }, "array-creation-expression": { - "begin": "(?x)\n\\b(new|stackalloc)\\b\\s*\n(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s*\\[(?:\\s*,\\s*)*\\]\\s*)* # array suffix?\n )\n)?\\s*\n(?=\\[)", + "begin": "(?x)\n\\b(new|stackalloc)\\b\\s*\n(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)?\\s*\n(?=\\[)", "beginCaptures": { "1": { "name": "keyword.other.new.cs" @@ -3131,7 +3339,7 @@ ] }, "parameter": { - "match": "(?x)\n(?:(?:\\b(ref|params|out|in|this)\\b)\\s+)?\n(?\n (?:\n (?:ref\\s+)? # ref return\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s*\\[(?:\\s*,\\s*)*\\]\\s*)* # array suffix?\n )\n)\\s+\n(\\g)", + "match": "(?x)\n(?:(?:\\b(ref|params|out|in|this)\\b)\\s+)?\n(?\n (?:\n (?:ref\\s+)? # ref return\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)\\s+\n(\\g)", "captures": { "1": { "name": "storage.modifier.cs" @@ -3230,7 +3438,7 @@ ] }, "query-expression": { - "begin": "(?x)\n\\b(from)\\b\\s*\n(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s*\\[(?:\\s*,\\s*)*\\]\\s*)* # array suffix?\n )\n)?\n\\s+(\\g)\\b\\s*\n\\b(in)\\b\\s*", + "begin": "(?x)\n\\b(from)\\b\\s*\n(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)?\n\\s+(\\g)\\b\\s*\n\\b(in)\\b\\s*", "beginCaptures": { "1": { "name": "keyword.query.from.cs" @@ -3322,7 +3530,7 @@ ] }, "join-clause": { - "begin": "(?x)\n\\b(join)\\b\\s*\n(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s*\\[(?:\\s*,\\s*)*\\]\\s*)* # array suffix?\n )\n)?\n\\s+(\\g)\\b\\s*\n\\b(in)\\b\\s*", + "begin": "(?x)\n\\b(join)\\b\\s*\n(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)?\n\\s+(\\g)\\b\\s*\n\\b(in)\\b\\s*", "beginCaptures": { "1": { "name": "keyword.query.join.cs" @@ -3592,7 +3800,7 @@ ] }, "lambda-parameter": { - "match": "(?x)\n(?:\\b(ref|out|in)\\b)?\\s*\n(?:(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s*\\[(?:\\s*,\\s*)*\\]\\s*)* # array suffix?\n )\n)\\s+)?\n(\\g)\\b\\s*\n(?=[,)])", + "match": "(?x)\n(?:\\b(ref|out|in)\\b)?\\s*\n(?:(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)\\s+)?\n(\\g)\\b\\s*\n(?=[,)])", "captures": { "1": { "name": "storage.modifier.cs" @@ -3672,7 +3880,7 @@ ] }, "tuple-element": { - "match": "(?x)\n(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s*\\[(?:\\s*,\\s*)*\\]\\s*)* # array suffix?\n )\n)\n(?:(?\\g)\\b)?", + "match": "(?x)\n(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)\n(?:(?\\g)\\b)?", "captures": { "1": { "patterns": [ diff --git a/extensions/css-language-features/client/tsconfig.json b/extensions/css-language-features/client/tsconfig.json index 3203105c..8b4aedde 100644 --- a/extensions/css-language-features/client/tsconfig.json +++ b/extensions/css-language-features/client/tsconfig.json @@ -1,9 +1,9 @@ { - "extends": "../../shared.tsconfig.json", + "extends": "../../tsconfig.base.json", "compilerOptions": { "outDir": "./out" }, "include": [ "src/**/*" ] -} \ No newline at end of file +} diff --git a/extensions/css-language-features/package.json b/extensions/css-language-features/package.json index 174c668b..7d80d751 100644 --- a/extensions/css-language-features/package.json +++ b/extensions/css-language-features/package.json @@ -18,6 +18,12 @@ "main": "./client/out/node/cssClientMain", "browser": "./client/dist/browser/cssClientMain", "enableProposedApi": true, + "capabilities": { + "virtualWorkspaces": true, + "untrustedWorkspaces": { + "supported": true + } + }, "scripts": { "compile": "gulp compile-extension:css-language-features-client compile-extension:css-language-features-server", "watch": "gulp watch-extension:css-language-features-client watch-extension:css-language-features-server", diff --git a/extensions/css-language-features/server/package.json b/extensions/css-language-features/server/package.json index f33c90ca..ce3020c2 100644 --- a/extensions/css-language-features/server/package.json +++ b/extensions/css-language-features/server/package.json @@ -10,7 +10,7 @@ "main": "./out/node/cssServerMain", "browser": "./dist/browser/cssServerMain", "dependencies": { - "vscode-css-languageservice": "^5.1.0", + "vscode-css-languageservice": "^5.1.1", "vscode-languageserver": "^7.0.0", "vscode-uri": "^3.0.2" }, diff --git a/extensions/css-language-features/server/test/index.js b/extensions/css-language-features/server/test/index.js index 4ab853bd..1699883a 100644 --- a/extensions/css-language-features/server/test/index.js +++ b/extensions/css-language-features/server/test/index.js @@ -11,7 +11,7 @@ const suite = 'Integration CSS Extension Tests'; const options = { ui: 'tdd', - useColors: (!process.env.BUILD_ARTIFACTSTAGINGDIRECTORY && process.platform !== 'win32'), + color: true, timeout: 60000 }; diff --git a/extensions/css-language-features/server/tsconfig.json b/extensions/css-language-features/server/tsconfig.json index 3203105c..8b4aedde 100644 --- a/extensions/css-language-features/server/tsconfig.json +++ b/extensions/css-language-features/server/tsconfig.json @@ -1,9 +1,9 @@ { - "extends": "../../shared.tsconfig.json", + "extends": "../../tsconfig.base.json", "compilerOptions": { "outDir": "./out" }, "include": [ "src/**/*" ] -} \ No newline at end of file +} diff --git a/extensions/css-language-features/server/yarn.lock b/extensions/css-language-features/server/yarn.lock index 5f49fd38..3ff39d28 100644 --- a/extensions/css-language-features/server/yarn.lock +++ b/extensions/css-language-features/server/yarn.lock @@ -12,10 +12,10 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-12.19.9.tgz#990ad687ad8b26ef6dcc34a4f69c33d40c95b679" integrity sha512-yj0DOaQeUrk3nJ0bd3Y5PeDRJ6W0r+kilosLA+dzF3dola/o9hxhMSg2sFvVcA2UHS5JSOsZp4S0c1OEXc4m1Q== -vscode-css-languageservice@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-5.1.0.tgz#cd172d13e9e7ae23ba567c73778aee10475ff716" - integrity sha512-iLHd/WjRKgaZBXMNeUooHG+r0qlhJBkXa+3MpQQR6Rpm928cis/3OV2Mp1R80yAQevIMeDL32RIJfHoJCT/RRg== +vscode-css-languageservice@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-5.1.1.tgz#d68a22ea0b34a8356c169cafc7d32564c2ff6e87" + integrity sha512-QW0oe/g2y5E2AbVqY7FJNr2Q8uHiAHNSFpptI6xB8Y0KgzVKppOcIVrgmBNzXhFp9IswAwptkdqr8ExSJbqPkQ== dependencies: vscode-languageserver-textdocument "^1.0.1" vscode-languageserver-types "^3.16.0" diff --git a/extensions/debug-auto-launch/media/icon.png b/extensions/debug-auto-launch/media/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..2c90d30a1f054c51157a1bb4497e79e41d10eda4 GIT binary patch literal 7191 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 Mm#*rS=-|Wt2f~m<<^TWy literal 0 HcmV?d00001 diff --git a/extensions/debug-auto-launch/package.json b/extensions/debug-auto-launch/package.json index c472d1e2..8535f3f8 100644 --- a/extensions/debug-auto-launch/package.json +++ b/extensions/debug-auto-launch/package.json @@ -8,6 +8,13 @@ "engines": { "vscode": "^1.5.0" }, + "icon": "media/icon.png", + "capabilities": { + "virtualWorkspaces": false, + "untrustedWorkspaces": { + "supported": true + } + }, "activationEvents": [ "*" ], diff --git a/extensions/debug-auto-launch/tsconfig.json b/extensions/debug-auto-launch/tsconfig.json index f9b780a0..69aa5aa3 100644 --- a/extensions/debug-auto-launch/tsconfig.json +++ b/extensions/debug-auto-launch/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../shared.tsconfig.json", + "extends": "../tsconfig.base.json", "compilerOptions": { "outDir": "./out", "downlevelIteration": true @@ -7,4 +7,4 @@ "include": [ "src/**/*" ] -} \ No newline at end of file +} diff --git a/extensions/debug-server-ready/media/icon.png b/extensions/debug-server-ready/media/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..75bf572ebafe3487fc67ed1648b4ab27440da444 GIT binary patch literal 8309 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", - "description": "%emmetPreferencesFilterCommentAfter%" + "markdownDescription": "%emmetPreferencesFilterCommentAfter%" }, "filter.commentTrigger": { "type": "array", @@ -167,26 +168,26 @@ "id", "class" ], - "description": "%emmetPreferencesFilterCommentTrigger%" + "markdownDescription": "%emmetPreferencesFilterCommentTrigger%" }, "format.noIndentTags": { "type": "array", "default": [ "html" ], - "description": "%emmetPreferencesFormatNoIndentTags%" + "markdownDescription": "%emmetPreferencesFormatNoIndentTags%" }, "format.forceIndentationForTags": { "type": "array", "default": [ "body" ], - "description": "%emmetPreferencesFormatForceIndentTags%" + "markdownDescription": "%emmetPreferencesFormatForceIndentTags%" }, "profile.allowCompactBoolean": { "type": "boolean", "default": false, - "description": "%emmetPreferencesAllowCompactBoolean%" + "markdownDescription": "%emmetPreferencesAllowCompactBoolean%" }, "css.webkitProperties": { "type": "string", @@ -211,17 +212,28 @@ "css.fuzzySearchMinScore": { "type": "number", "default": 0.3, - "description": "%emmetPreferencesCssFuzzySearchMinScore%" + "markdownDescription": "%emmetPreferencesCssFuzzySearchMinScore%" + }, + "output.inlineBreak": { + "type": "number", + "default": 0, + "markdownDescription": "%emmetPreferencesOutputInlineBreak%" }, "output.reverseAttributes": { "type": "boolean", "default": false, - "description": "%emmetPreferencesOutputReverseAttributes%" + "markdownDescription": "%emmetPreferencesOutputReverseAttributes%" + }, + "output.selfClosingStyle": { + "type": "string", + "enum": ["html", "xhtml", "xml"], + "default": "html", + "markdownDescription": "%emmetPreferencesOutputSelfClosingStyle%" }, "css.color.short": { "type": "boolean", "default": true, - "description": "%emmetPreferencesCssColorShort%" + "markdownDescription": "%emmetPreferencesCssColorShort%" } } }, @@ -442,5 +454,11 @@ "image-size": "^0.5.2", "vscode-emmet-helper": "^2.3.0", "vscode-languageserver-textdocument": "^1.0.1" + }, + "capabilities": { + "virtualWorkspaces": true, + "untrustedWorkspaces": { + "supported": true + } } } diff --git a/extensions/emmet/package.nls.json b/extensions/emmet/package.nls.json index f8bbbc36..1cac53a6 100644 --- a/extensions/emmet/package.nls.json +++ b/extensions/emmet/package.nls.json @@ -25,36 +25,39 @@ "command.showEmmetCommands": "Show Emmet Commands", "emmetSyntaxProfiles": "Define profile for specified syntax or use your own profile with specific rules.", "emmetExclude": "An array of languages where Emmet abbreviations should not be expanded.", - "emmetExtensionsPath": "Path to a folder containing Emmet profiles and snippets.", + "emmetExtensionsPath": "An array of paths, where each path can contain Emmet syntaxProfiles and/or snippet files.\nIn case of conflicts, the profiles/snippets of later paths will override those of earlier paths.\nSee https://code.visualstudio.com/docs/editor/emmet for more information and an example snippet file.", + "emmetExtensionsPathItem": "A path containing Emmet syntaxProfiles and/or snippets.", "emmetShowExpandedAbbreviation": "Shows expanded Emmet abbreviations as suggestions.\nThe option `\"inMarkupAndStylesheetFilesOnly\"` applies to html, haml, jade, slim, xml, xsl, css, scss, sass, less and stylus.\nThe option `\"always\"` applies to all parts of the file regardless of markup/css.", "emmetShowAbbreviationSuggestions": "Shows possible Emmet abbreviations as suggestions. Not applicable in stylesheets or when emmet.showExpandedAbbreviation is set to `\"never\"`.", "emmetIncludeLanguages": "Enable Emmet abbreviations in languages that are not supported by default. Add a mapping here between the language and Emmet supported language.\n For example: `{\"vue-html\": \"html\", \"javascript\": \"javascriptreact\"}`", - "emmetVariables": "Variables to be used in Emmet snippets", + "emmetVariables": "Variables to be used in Emmet snippets.", "emmetTriggerExpansionOnTab": "When enabled, Emmet abbreviations are expanded when pressing TAB.", "emmetPreferences": "Preferences used to modify behavior of some actions and resolvers of Emmet.", - "emmetPreferencesIntUnit": "Default unit for integer values", - "emmetPreferencesFloatUnit": "Default unit for float values", - "emmetPreferencesCssAfter": "Symbol to be placed at the end of CSS property when expanding CSS abbreviations", - "emmetPreferencesSassAfter": "Symbol to be placed at the end of CSS property when expanding CSS abbreviations in Sass files", - "emmetPreferencesStylusAfter": "Symbol to be placed at the end of CSS property when expanding CSS abbreviations in Stylus files", - "emmetPreferencesCssBetween": "Symbol to be placed at the between CSS property and value when expanding CSS abbreviations", - "emmetPreferencesSassBetween": "Symbol to be placed at the between CSS property and value when expanding CSS abbreviations in Sass files", - "emmetPreferencesStylusBetween": "Symbol to be placed at the between CSS property and value when expanding CSS abbreviations in Stylus files", + "emmetPreferencesIntUnit": "Default unit for integer values.", + "emmetPreferencesFloatUnit": "Default unit for float values.", + "emmetPreferencesCssAfter": "Symbol to be placed at the end of CSS property when expanding CSS abbreviations.", + "emmetPreferencesSassAfter": "Symbol to be placed at the end of CSS property when expanding CSS abbreviations in Sass files.", + "emmetPreferencesStylusAfter": "Symbol to be placed at the end of CSS property when expanding CSS abbreviations in Stylus files.", + "emmetPreferencesCssBetween": "Symbol to be placed at the between CSS property and value when expanding CSS abbreviations.", + "emmetPreferencesSassBetween": "Symbol to be placed at the between CSS property and value when expanding CSS abbreviations in Sass files.", + "emmetPreferencesStylusBetween": "Symbol to be placed at the between CSS property and value when expanding CSS abbreviations in Stylus files.", "emmetShowSuggestionsAsSnippets": "If `true`, then Emmet suggestions will show up as snippets allowing you to order them as per `#editor.snippetSuggestions#` setting.", - "emmetPreferencesBemElementSeparator": "Element separator used for classes when using the BEM filter", - "emmetPreferencesBemModifierSeparator": "Modifier separator used for classes when using the BEM filter", + "emmetPreferencesBemElementSeparator": "Element separator used for classes when using the BEM filter.", + "emmetPreferencesBemModifierSeparator": "Modifier separator used for classes when using the BEM filter.", "emmetPreferencesFilterCommentBefore": "A definition of comment that should be placed before matched element when comment filter is applied.", "emmetPreferencesFilterCommentAfter": "A definition of comment that should be placed after matched element when comment filter is applied.", - "emmetPreferencesFilterCommentTrigger": "A comma-separated list of attribute names that should exist in abbreviation for the comment filter to be applied", - "emmetPreferencesFormatNoIndentTags": "An array of tag names that should not get inner indentation", - "emmetPreferencesFormatForceIndentTags": "An array of tag names that should always get inner indentation", - "emmetPreferencesAllowCompactBoolean": "If true, compact notation of boolean attributes are produced", + "emmetPreferencesFilterCommentTrigger": "A comma-separated list of attribute names that should exist in the abbreviation for the comment filter to be applied.", + "emmetPreferencesFormatNoIndentTags": "An array of tag names that should never get inner indentation.", + "emmetPreferencesFormatForceIndentTags": "An array of tag names that should always get inner indentation.", + "emmetPreferencesAllowCompactBoolean": "If `true`, compact notation of boolean attributes are produced.", "emmetPreferencesCssWebkitProperties": "Comma separated CSS properties that get the 'webkit' vendor prefix when used in Emmet abbreviation that starts with `-`. Set to empty string to always avoid the 'webkit' prefix.", "emmetPreferencesCssMozProperties": "Comma separated CSS properties that get the 'moz' vendor prefix when used in Emmet abbreviation that starts with `-`. Set to empty string to always avoid the 'moz' prefix.", "emmetPreferencesCssOProperties": "Comma separated CSS properties that get the 'o' vendor prefix when used in Emmet abbreviation that starts with `-`. Set to empty string to always avoid the 'o' prefix.", "emmetPreferencesCssMsProperties": "Comma separated CSS properties that get the 'ms' vendor prefix when used in Emmet abbreviation that starts with `-`. Set to empty string to always avoid the 'ms' prefix.", "emmetPreferencesCssFuzzySearchMinScore": "The minimum score (from 0 to 1) that fuzzy-matched abbreviation should achieve. Lower values may produce many false-positive matches, higher values may reduce possible matches.", "emmetOptimizeStylesheetParsing": "When set to `false`, the whole file is parsed to determine if current position is valid for expanding Emmet abbreviations. When set to `true`, only the content around the current position in CSS/SCSS/Less files is parsed.", + "emmetPreferencesOutputInlineBreak": "The number of sibling inline elements needed for line breaks to be placed between those elements. If `0`, inline elements are always expanded onto a single line.", "emmetPreferencesOutputReverseAttributes": "If `true`, reverses attribute merging directions when resolving snippets.", - "emmetPreferencesCssColorShort": "If `true`, color values like #f will be expanded to #fff instead of #ffffff." + "emmetPreferencesOutputSelfClosingStyle": "Style of self-closing tags: html (`
`), xml (`
`) or xhtml (`
`).", + "emmetPreferencesCssColorShort": "If `true`, color values like `#f` will be expanded to `#fff` instead of `#ffffff`." } diff --git a/extensions/emmet/src/abbreviationActions.ts b/extensions/emmet/src/abbreviationActions.ts index 98ad70dd..bbdec7b2 100644 --- a/extensions/emmet/src/abbreviationActions.ts +++ b/extensions/emmet/src/abbreviationActions.ts @@ -205,7 +205,7 @@ export async function wrapWithAbbreviation(args: any): Promise { let inPreviewMode = false; async function makeChanges(inputAbbreviation: string | undefined, previewChanges: boolean): Promise { const isAbbreviationValid = !!inputAbbreviation && !!inputAbbreviation.trim() && helper.isAbbreviationValid(syntax, inputAbbreviation); - const extractedResults = isAbbreviationValid ? helper.extractAbbreviationFromText(inputAbbreviation!) : undefined; + const extractedResults = isAbbreviationValid ? helper.extractAbbreviationFromText(inputAbbreviation!, syntax) : undefined; if (!extractedResults) { if (inPreviewMode) { inPreviewMode = false; @@ -311,12 +311,12 @@ export function expandEmmetAbbreviation(args: any): Thenable { + const getAbbreviation = (document: vscode.TextDocument, selection: vscode.Selection, position: vscode.Position, syntax: string): [vscode.Range | null, string, string | undefined] => { position = document.validatePosition(position); let rangeToReplace: vscode.Range = selection; let abbr = document.getText(rangeToReplace); if (!rangeToReplace.isEmpty) { - const extractedResults = helper.extractAbbreviationFromText(abbr); + const extractedResults = helper.extractAbbreviationFromText(abbr, syntax); if (extractedResults) { return [rangeToReplace, extractedResults.abbreviation, extractedResults.filter]; } diff --git a/extensions/emmet/src/defaultCompletionProvider.ts b/extensions/emmet/src/defaultCompletionProvider.ts index a8c7f464..11767416 100644 --- a/extensions/emmet/src/defaultCompletionProvider.ts +++ b/extensions/emmet/src/defaultCompletionProvider.ts @@ -176,7 +176,8 @@ export class DefaultCompletionItemProvider implements vscode.CompletionItemProvi return; } - let result = helper.doComplete(toLSTextDocument(document), position, syntax, getEmmetConfiguration(syntax!)); + const config = getEmmetConfiguration(syntax!); + const result = helper.doComplete(toLSTextDocument(document), position, syntax, config); // https://github.com/microsoft/vscode/issues/86941 if (result && result.items && result.items.length === 1) { diff --git a/extensions/emmet/src/emmetCommon.ts b/extensions/emmet/src/emmetCommon.ts index b4c57b13..a28ed225 100644 --- a/extensions/emmet/src/emmetCommon.ts +++ b/extensions/emmet/src/emmetCommon.ts @@ -19,7 +19,7 @@ import { evaluateMathExpression } from './evaluateMathExpression'; import { incrementDecrement } from './incrementDecrement'; import { LANGUAGE_MODES, getMappingForIncludedLanguages, updateEmmetExtensionsPath, migrateEmmetExtensionsPath, getPathBaseName, getSyntaxes, getEmmetMode } from './util'; import { reflectCssValue } from './reflectCssValue'; -import { addFileToParseCache, removeFileFromParseCache } from './parseDocument'; +import { addFileToParseCache, clearParseCache, removeFileFromParseCache } from './parseDocument'; export function activateEmmetExtension(context: vscode.ExtensionContext) { migrateEmmetExtensionsPath(); @@ -203,4 +203,6 @@ function registerCompletionProviders(context: vscode.ExtensionContext) { } export function deactivate() { + completionProvidersMapping.clear(); + clearParseCache(); } diff --git a/extensions/emmet/src/parseDocument.ts b/extensions/emmet/src/parseDocument.ts index c062e9d8..d18c723f 100644 --- a/extensions/emmet/src/parseDocument.ts +++ b/extensions/emmet/src/parseDocument.ts @@ -44,3 +44,7 @@ export function removeFileFromParseCache(document: TextDocument) { const filename = document.uri.toString(); _parseCache.delete(filename); } + +export function clearParseCache() { + _parseCache.clear(); +} diff --git a/extensions/emmet/src/selectItemHTML.ts b/extensions/emmet/src/selectItemHTML.ts index f818f578..64009133 100644 --- a/extensions/emmet/src/selectItemHTML.ts +++ b/extensions/emmet/src/selectItemHTML.ts @@ -19,7 +19,7 @@ export function nextItemHTML(document: vscode.TextDocument, selectionStart: vsco if (currentNode.type !== 'comment') { // If cursor is in the tag name, select tag if (currentNode.open && - selectionEndOffset < currentNode.open.start + currentNode.name.length) { + selectionEndOffset <= currentNode.open.start + currentNode.name.length) { return getSelectionFromNode(document, currentNode); } diff --git a/extensions/emmet/src/test/abbreviationAction.test.ts b/extensions/emmet/src/test/abbreviationAction.test.ts index 7577cd6a..091bf90f 100644 --- a/extensions/emmet/src/test/abbreviationAction.test.ts +++ b/extensions/emmet/src/test/abbreviationAction.test.ts @@ -44,10 +44,7 @@ const htmlContents = ` suite('Tests for Expand Abbreviations (HTML)', () => { const oldValueForExcludeLanguages = workspace.getConfiguration('emmet').inspect('excludeLanguages'); const oldValueForInlcudeLanguages = workspace.getConfiguration('emmet').inspect('includeLanguages'); - teardown(() => { - // close all editors - return closeAllEditors; - }); + teardown(closeAllEditors); test('Expand snippets (HTML)', () => { return testExpandAbbreviation('html', new Selection(3, 23, 3, 23), 'img', '\"\"'); @@ -442,7 +439,7 @@ suite('Tests for jsx, xml and xsl', () => { return withRandomFileEditor('img', 'javascriptreact', async (editor, _doc) => { editor.selection = new Selection(0, 6, 0, 6); await expandEmmetAbbreviation({ language: 'javascriptreact' }); - assert.strictEqual(editor.document.getText(), ''); + assert.strictEqual(editor.document.getText(), ''); return Promise.resolve(); }); }); @@ -452,7 +449,7 @@ suite('Tests for jsx, xml and xsl', () => { return withRandomFileEditor('img', 'javascriptreact', async (editor, _doc) => { editor.selection = new Selection(0, 6, 0, 6); await expandEmmetAbbreviation({ language: 'javascriptreact' }); - assert.strictEqual(editor.document.getText(), '\'\'/'); + assert.strictEqual(editor.document.getText(), '\'\''); return workspace.getConfiguration('emmet').update('syntaxProfiles', oldValueForSyntaxProfiles ? oldValueForSyntaxProfiles.globalValue : undefined, ConfigurationTarget.Global); }); }); diff --git a/extensions/emmet/src/test/completion.test.ts b/extensions/emmet/src/test/completion.test.ts index 91844f69..04136a21 100644 --- a/extensions/emmet/src/test/completion.test.ts +++ b/extensions/emmet/src/test/completion.test.ts @@ -12,10 +12,7 @@ import { closeAllEditors, withRandomFileEditor } from './testUtils'; const completionProvider = new DefaultCompletionItemProvider(); suite('Tests for completion in CSS embedded in HTML', () => { - teardown(() => { - // close all editors - return closeAllEditors; - }); + teardown(closeAllEditors); test('style attribute & attribute value in html', async () => { await testHtmlCompletionProvider('
{ return withRandomFileEditor(cssContents, 'css', (editor, _) => { editor.selections = [new Selection(3, 1, 3, 6), new Selection(5, 1, 5, 6)]; return expandEmmetAbbreviation(null).then(() => { - assert.equal(editor.document.getText(), cssContents.replace(/pos:f/g, 'position: fixed;')); + assert.strictEqual(editor.document.getText(), cssContents.replace(/pos:f/g, 'position: fixed;')); return Promise.resolve(); }); }); @@ -78,11 +78,11 @@ suite('Tests for Expand Abbreviations (CSS)', () => { return withRandomFileEditor(testContent, 'css', (editor, _) => { editor.selection = new Selection(3, 4, 3, 4); return expandEmmetAbbreviation(null).then(() => { - assert.equal(editor.document.getText(), testContent); + assert.strictEqual(editor.document.getText(), testContent); const cancelSrc = new CancellationTokenSource(); const completionPromise = completionProvider.provideCompletionItems(editor.document, new Position(2, 10), cancelSrc.token, { triggerKind: CompletionTriggerKind.Invoke }); if (completionPromise) { - assert.equal(1, 2, `Invalid completion at property value`); + assert.strictEqual(1, 2, `Invalid completion at property value`); } return Promise.resolve(); }); @@ -101,11 +101,11 @@ nav# return withRandomFileEditor(testContent, 'css', (editor, _) => { editor.selection = new Selection(5, 4, 5, 4); return expandEmmetAbbreviation(null).then(() => { - assert.equal(editor.document.getText(), testContent); + assert.strictEqual(editor.document.getText(), testContent); const cancelSrc = new CancellationTokenSource(); const completionPromise = completionProvider.provideCompletionItems(editor.document, new Position(2, 10), cancelSrc.token, { triggerKind: CompletionTriggerKind.Invoke }); if (completionPromise) { - assert.equal(1, 2, `Invalid completion at property value`); + assert.strictEqual(1, 2, `Invalid completion at property value`); } return Promise.resolve(); }); @@ -123,11 +123,11 @@ nav# return withRandomFileEditor(testContent, 'css', (editor, _) => { editor.selection = new Selection(2, 10, 2, 10); return expandEmmetAbbreviation(null).then(() => { - assert.equal(editor.document.getText(), testContent); + assert.strictEqual(editor.document.getText(), testContent); const cancelSrc = new CancellationTokenSource(); const completionPromise = completionProvider.provideCompletionItems(editor.document, new Position(2, 10), cancelSrc.token, { triggerKind: CompletionTriggerKind.Invoke }); if (completionPromise) { - assert.equal(1, 2, `Invalid completion at property value`); + assert.strictEqual(1, 2, `Invalid completion at property value`); } return Promise.resolve(); }); @@ -140,11 +140,11 @@ nav# return withRandomFileEditor(testContent, 'css', (editor, _) => { editor.selection = new Selection(0, 30, 0, 30); return expandEmmetAbbreviation(null).then(() => { - assert.equal(editor.document.getText(), testContent); + assert.strictEqual(editor.document.getText(), testContent); const cancelSrc = new CancellationTokenSource(); const completionPromise = completionProvider.provideCompletionItems(editor.document, new Position(0, 30), cancelSrc.token, { triggerKind: CompletionTriggerKind.Invoke }); if (completionPromise) { - assert.equal(1, 2, `Invalid completion at property value`); + assert.strictEqual(1, 2, `Invalid completion at property value`); } return Promise.resolve(); }); @@ -165,18 +165,18 @@ nav# const completionPromise2 = completionProvider.provideCompletionItems(editor.document, new Position(2, 14), cancelSrc.token, { triggerKind: CompletionTriggerKind.Invoke }); if (!completionPromise1 || !completionPromise2) { - assert.equal(1, 2, `Completion promise wasnt returned`); + assert.strictEqual(1, 2, `Completion promise wasnt returned`); return Promise.resolve(); } const callBack = (completionList: CompletionList, expandedText: string) => { if (!completionList.items || !completionList.items.length) { - assert.equal(1, 2, `Empty Completions`); + assert.strictEqual(1, 2, `Empty Completions`); return; } const emmetCompletionItem = completionList.items[0]; - assert.equal(emmetCompletionItem.label, expandedText, `Label of completion item doesnt match.`); - assert.equal((emmetCompletionItem.documentation || '').replace(/\|/g, ''), expandedText, `Docs of completion item doesnt match.`); + assert.strictEqual(emmetCompletionItem.label, expandedText, `Label of completion item doesnt match.`); + assert.strictEqual((emmetCompletionItem.documentation || '').replace(/\|/g, ''), expandedText, `Docs of completion item doesnt match.`); }; return Promise.all([completionPromise1, completionPromise2]).then(([result1, result2]) => { @@ -184,7 +184,7 @@ nav# callBack(result2, '!important'); editor.selections = [new Selection(2, 12, 2, 12), new Selection(2, 14, 2, 14)]; return expandEmmetAbbreviation(null).then(() => { - assert.equal(editor.document.getText(), testContent.replace('#12', '#121212').replace('!', '!important')); + assert.strictEqual(editor.document.getText(), testContent.replace('#12', '#121212').replace('!', '!important')); }); }); }); @@ -201,11 +201,11 @@ nav# return withRandomFileEditor(testContent, 'css', (editor, _) => { editor.selection = new Selection(3, 10, 3, 10); return expandEmmetAbbreviation(null).then(() => { - assert.equal(editor.document.getText(), testContent); + assert.strictEqual(editor.document.getText(), testContent); const cancelSrc = new CancellationTokenSource(); const completionPromise = completionProvider.provideCompletionItems(editor.document, new Position(3, 10), cancelSrc.token, { triggerKind: CompletionTriggerKind.Invoke }); if (completionPromise) { - assert.equal(1, 2, `Invalid completion at property value`); + assert.strictEqual(1, 2, `Invalid completion at property value`); } return Promise.resolve(); }); @@ -226,18 +226,18 @@ nav# const completionPromise2 = completionProvider.provideCompletionItems(editor.document, new Position(3, 14), cancelSrc.token, { triggerKind: CompletionTriggerKind.Invoke }); if (!completionPromise1 || !completionPromise2) { - assert.equal(1, 2, `Completion promise wasnt returned`); + assert.strictEqual(1, 2, `Completion promise wasnt returned`); return Promise.resolve(); } const callBack = (completionList: CompletionList, expandedText: string) => { if (!completionList.items || !completionList.items.length) { - assert.equal(1, 2, `Empty Completions`); + assert.strictEqual(1, 2, `Empty Completions`); return; } const emmetCompletionItem = completionList.items[0]; - assert.equal(emmetCompletionItem.label, expandedText, `Label of completion item doesnt match.`); - assert.equal((emmetCompletionItem.documentation || '').replace(/\|/g, ''), expandedText, `Docs of completion item doesnt match.`); + assert.strictEqual(emmetCompletionItem.label, expandedText, `Label of completion item doesnt match.`); + assert.strictEqual((emmetCompletionItem.documentation || '').replace(/\|/g, ''), expandedText, `Docs of completion item doesnt match.`); }; return Promise.all([completionPromise1, completionPromise2]).then(([result1, result2]) => { @@ -245,7 +245,7 @@ nav# callBack(result2, '!important'); editor.selections = [new Selection(3, 12, 3, 12), new Selection(3, 14, 3, 14)]; return expandEmmetAbbreviation(null).then(() => { - assert.equal(editor.document.getText(), testContent.replace('#12', '#121212').replace('!', '!important')); + assert.strictEqual(editor.document.getText(), testContent.replace('#12', '#121212').replace('!', '!important')); }); }); }); @@ -261,11 +261,11 @@ nav# return withRandomFileEditor(testContent, 'css', (editor, _) => { editor.selection = new Selection(2, 10, 2, 10); return expandEmmetAbbreviation(null).then(() => { - assert.equal(editor.document.getText(), testContent); + assert.strictEqual(editor.document.getText(), testContent); const cancelSrc = new CancellationTokenSource(); const completionPromise = completionProvider.provideCompletionItems(editor.document, new Position(2, 10), cancelSrc.token, { triggerKind: CompletionTriggerKind.Invoke }); if (completionPromise) { - assert.equal(1, 2, `Invalid completion at property value`); + assert.strictEqual(1, 2, `Invalid completion at property value`); } return Promise.resolve(); }); @@ -285,18 +285,18 @@ nav# const completionPromise2 = completionProvider.provideCompletionItems(editor.document, new Position(2, 14), cancelSrc.token, { triggerKind: CompletionTriggerKind.Invoke }); if (!completionPromise1 || !completionPromise2) { - assert.equal(1, 2, `Completion promise wasnt returned`); + assert.strictEqual(1, 2, `Completion promise wasnt returned`); return Promise.resolve(); } const callBack = (completionList: CompletionList, expandedText: string) => { if (!completionList.items || !completionList.items.length) { - assert.equal(1, 2, `Empty Completions`); + assert.strictEqual(1, 2, `Empty Completions`); return; } const emmetCompletionItem = completionList.items[0]; - assert.equal(emmetCompletionItem.label, expandedText, `Label of completion item doesnt match.`); - assert.equal((emmetCompletionItem.documentation || '').replace(/\|/g, ''), expandedText, `Docs of completion item doesnt match.`); + assert.strictEqual(emmetCompletionItem.label, expandedText, `Label of completion item doesnt match.`); + assert.strictEqual((emmetCompletionItem.documentation || '').replace(/\|/g, ''), expandedText, `Docs of completion item doesnt match.`); }; return Promise.all([completionPromise1, completionPromise2]).then(([result1, result2]) => { @@ -304,7 +304,7 @@ nav# callBack(result2, '!important'); editor.selections = [new Selection(2, 12, 2, 12), new Selection(2, 14, 2, 14)]; return expandEmmetAbbreviation(null).then(() => { - assert.equal(editor.document.getText(), testContent.replace('#12', '#121212').replace('!', '!important')); + assert.strictEqual(editor.document.getText(), testContent.replace('#12', '#121212').replace('!', '!important')); }); }); }); @@ -320,11 +320,11 @@ nav# return withRandomFileEditor(testContent, 'css', (editor, _) => { editor.selection = new Selection(2, 2, 2, 2); return expandEmmetAbbreviation(null).then(() => { - assert.equal(editor.document.getText(), testContent); + assert.strictEqual(editor.document.getText(), testContent); const cancelSrc = new CancellationTokenSource(); const completionPromise = completionProvider.provideCompletionItems(editor.document, new Position(2, 2), cancelSrc.token, { triggerKind: CompletionTriggerKind.Invoke }); if (completionPromise) { - assert.equal(1, 2, `Invalid completion of hex color at property name`); + assert.strictEqual(1, 2, `Invalid completion of hex color at property name`); } return Promise.resolve(); }); @@ -342,19 +342,19 @@ nav# const completionPromise1 = completionProvider.provideCompletionItems(editor.document, new Position(3, 6), cancelSrc.token, { triggerKind: CompletionTriggerKind.Invoke }); const completionPromise2 = completionProvider.provideCompletionItems(editor.document, new Position(5, 6), cancelSrc.token, { triggerKind: CompletionTriggerKind.Invoke }); if (!completionPromise1 || !completionPromise2) { - assert.equal(1, 2, `Problem with expanding pos:f`); + assert.strictEqual(1, 2, `Problem with expanding pos:f`); return Promise.resolve(); } const callBack = (completionList: CompletionList) => { if (!completionList.items || !completionList.items.length) { - assert.equal(1, 2, `Problem with expanding pos:f`); + assert.strictEqual(1, 2, `Problem with expanding pos:f`); return; } const emmetCompletionItem = completionList.items[0]; - assert.equal(emmetCompletionItem.label, expandedText, `Label of completion item doesnt match.`); - assert.equal((emmetCompletionItem.documentation || '').replace(/\|/g, ''), expandedText, `Docs of completion item doesnt match.`); - assert.equal(emmetCompletionItem.filterText, abbreviation, `FilterText of completion item doesnt match.`); + assert.strictEqual(emmetCompletionItem.label, expandedText, `Label of completion item doesnt match.`); + assert.strictEqual((emmetCompletionItem.documentation || '').replace(/\|/g, ''), expandedText, `Docs of completion item doesnt match.`); + assert.strictEqual(emmetCompletionItem.filterText, abbreviation, `FilterText of completion item doesnt match.`); }; return Promise.all([completionPromise1, completionPromise2]).then(([result1, result2]) => { @@ -374,7 +374,7 @@ nav# new Selection(14, 5, 14, 5) ]; return expandEmmetAbbreviation(null).then(() => { - assert.equal(editor.document.getText(), scssContents.replace(/p(\d\d)/g, 'padding: $1px;')); + assert.strictEqual(editor.document.getText(), scssContents.replace(/p(\d\d)/g, 'padding: $1px;')); return Promise.resolve(); }); }); @@ -390,16 +390,16 @@ nav# const completionPromise3 = completionProvider.provideCompletionItems(editor.document, new Position(11, 4), cancelSrc.token, { triggerKind: CompletionTriggerKind.Invoke }); const completionPromise4 = completionProvider.provideCompletionItems(editor.document, new Position(14, 5), cancelSrc.token, { triggerKind: CompletionTriggerKind.Invoke }); if (!completionPromise1) { - assert.equal(1, 2, `Problem with expanding padding abbreviations at line 3 col 4`); + assert.strictEqual(1, 2, `Problem with expanding padding abbreviations at line 3 col 4`); } if (!completionPromise2) { - assert.equal(1, 2, `Problem with expanding padding abbreviations at line 5 col 5`); + assert.strictEqual(1, 2, `Problem with expanding padding abbreviations at line 5 col 5`); } if (!completionPromise3) { - assert.equal(1, 2, `Problem with expanding padding abbreviations at line 11 col 4`); + assert.strictEqual(1, 2, `Problem with expanding padding abbreviations at line 11 col 4`); } if (!completionPromise4) { - assert.equal(1, 2, `Problem with expanding padding abbreviations at line 14 col 5`); + assert.strictEqual(1, 2, `Problem with expanding padding abbreviations at line 14 col 5`); } if (!completionPromise1 || !completionPromise2 || !completionPromise3 || !completionPromise4) { @@ -408,13 +408,13 @@ nav# const callBack = (completionList: CompletionList, abbreviation: string, expandedText: string) => { if (!completionList.items || !completionList.items.length) { - assert.equal(1, 2, `Problem with expanding m10`); + assert.strictEqual(1, 2, `Problem with expanding m10`); return; } const emmetCompletionItem = completionList.items[0]; - assert.equal(emmetCompletionItem.label, expandedText, `Label of completion item doesnt match.`); - assert.equal((emmetCompletionItem.documentation || '').replace(/\|/g, ''), expandedText, `Docs of completion item doesnt match.`); - assert.equal(emmetCompletionItem.filterText, abbreviation, `FilterText of completion item doesnt match.`); + assert.strictEqual(emmetCompletionItem.label, expandedText, `Label of completion item doesnt match.`); + assert.strictEqual((emmetCompletionItem.documentation || '').replace(/\|/g, ''), expandedText, `Docs of completion item doesnt match.`); + assert.strictEqual(emmetCompletionItem.filterText, abbreviation, `FilterText of completion item doesnt match.`); }; return Promise.all([completionPromise1, completionPromise2, completionPromise3, completionPromise4]).then(([result1, result2, result3, result4]) => { @@ -445,7 +445,7 @@ m10 new Selection(5, 15, 5, 15) // in the value part of property value ]; return expandEmmetAbbreviation(null).then(() => { - assert.equal(editor.document.getText(), scssContentsNoExpand); + assert.strictEqual(editor.document.getText(), scssContentsNoExpand); return Promise.resolve(); }); }); @@ -467,7 +467,7 @@ m10 const cancelSrc = new CancellationTokenSource(); let completionPromise = completionProvider.provideCompletionItems(editor.document, editor.selection.active, cancelSrc.token, { triggerKind: CompletionTriggerKind.Invoke }); if (completionPromise) { - assert.equal(1, 2, `m10 gets expanded in invalid location (outside rule)`); + assert.strictEqual(1, 2, `m10 gets expanded in invalid location (outside rule)`); } editor.selection = new Selection(5, 15, 5, 15); // in the value part of property value @@ -475,7 +475,7 @@ m10 if (completionPromise) { return completionPromise.then((completionList: CompletionList | undefined) => { if (completionList && completionList.items && completionList.items.length > 0) { - assert.equal(1, 2, `m10 gets expanded in invalid location (n the value part of property value)`); + assert.strictEqual(1, 2, `m10 gets expanded in invalid location (n the value part of property value)`); } return Promise.resolve(); }); @@ -484,19 +484,18 @@ m10 }); }); -}); - -test('Skip when typing property values when there is a nested rule in the next line (SCSS)', () => { - return withRandomFileEditor(scssContents, 'scss', (editor, _) => { - editor.selection = new Selection(19, 10, 19, 10); - return expandEmmetAbbreviation(null).then(() => { - assert.equal(editor.document.getText(), scssContents); - const cancelSrc = new CancellationTokenSource(); - const completionPromise = completionProvider.provideCompletionItems(editor.document, new Position(19, 10), cancelSrc.token, { triggerKind: CompletionTriggerKind.Invoke }); - if (completionPromise) { - assert.equal(1, 2, `Invalid completion at property value`); - } - return Promise.resolve(); + test('Skip when typing property values when there is a nested rule in the next line (SCSS)', () => { + return withRandomFileEditor(scssContents, 'scss', (editor, _) => { + editor.selection = new Selection(19, 10, 19, 10); + return expandEmmetAbbreviation(null).then(() => { + assert.strictEqual(editor.document.getText(), scssContents); + const cancelSrc = new CancellationTokenSource(); + const completionPromise = completionProvider.provideCompletionItems(editor.document, new Position(19, 10), cancelSrc.token, { triggerKind: CompletionTriggerKind.Invoke }); + if (completionPromise) { + assert.strictEqual(1, 2, `Invalid completion at property value`); + } + return Promise.resolve(); + }); }); }); }); diff --git a/extensions/emmet/src/test/editPointSelectItemBalance.test.ts b/extensions/emmet/src/test/editPointSelectItemBalance.test.ts index e429fe7e..b631b156 100644 --- a/extensions/emmet/src/test/editPointSelectItemBalance.test.ts +++ b/extensions/emmet/src/test/editPointSelectItemBalance.test.ts @@ -352,17 +352,16 @@ suite('Tests for Next/Previous Select/Edit point and Balance actions', () => { }); function testSelection(selection: Selection, startChar: number, startline: number, endChar?: number, endLine?: number) { - - assert.equal(selection.anchor.line, startline); - assert.equal(selection.anchor.character, startChar); + assert.strictEqual(selection.anchor.line, startline); + assert.strictEqual(selection.anchor.character, startChar); if (!endLine && endLine !== 0) { - assert.equal(selection.isSingleLine, true); + assert.strictEqual(selection.isSingleLine, true); } else { - assert.equal(selection.active.line, endLine); + assert.strictEqual(selection.active.line, endLine); } if (!endChar && endChar !== 0) { - assert.equal(selection.isEmpty, true); + assert.strictEqual(selection.isEmpty, true); } else { - assert.equal(selection.active.character, endChar); + assert.strictEqual(selection.active.character, endChar); } } diff --git a/extensions/emmet/src/test/incrementDecrement.test.ts b/extensions/emmet/src/test/incrementDecrement.test.ts index 83db92e3..829ec486 100644 --- a/extensions/emmet/src/test/incrementDecrement.test.ts +++ b/extensions/emmet/src/test/incrementDecrement.test.ts @@ -28,7 +28,7 @@ suite('Tests for Increment/Decrement Emmet Commands', () => { return withRandomFileEditor(contents, 'txt', async (editor, doc) => { editor.selections = [new Selection(1, 7, 1, 10), new Selection(2, 7, 2, 10)]; await incrementDecrement(1); - assert.equal(doc.getText(), contents.replace('123', '124').replace('999', '1000')); + assert.strictEqual(doc.getText(), contents.replace('123', '124').replace('999', '1000')); return Promise.resolve(); }); }); @@ -37,7 +37,7 @@ suite('Tests for Increment/Decrement Emmet Commands', () => { return withRandomFileEditor(contents, 'txt', async (editor, doc) => { editor.selections = [new Selection(1, 7, 1, 10), new Selection(2, 7, 2, 10)]; await incrementDecrement(10); - assert.equal(doc.getText(), contents.replace('123', '133').replace('999', '1009')); + assert.strictEqual(doc.getText(), contents.replace('123', '133').replace('999', '1009')); return Promise.resolve(); }); }); @@ -46,7 +46,7 @@ suite('Tests for Increment/Decrement Emmet Commands', () => { return withRandomFileEditor(contents, 'txt', async (editor, doc) => { editor.selections = [new Selection(1, 7, 1, 13), new Selection(2, 7, 2, 12)]; await incrementDecrement(0.1); - assert.equal(doc.getText(), contents.replace('123.43', '123.53').replace('999.9', '1000')); + assert.strictEqual(doc.getText(), contents.replace('123.43', '123.53').replace('999.9', '1000')); return Promise.resolve(); }); }); @@ -55,7 +55,7 @@ suite('Tests for Increment/Decrement Emmet Commands', () => { return withRandomFileEditor(contents, 'txt', async (editor, doc) => { editor.selections = [new Selection(1, 7, 1, 10), new Selection(3, 7, 3, 10)]; await incrementDecrement(-1); - assert.equal(doc.getText(), contents.replace('123', '122').replace('100', '99')); + assert.strictEqual(doc.getText(), contents.replace('123', '122').replace('100', '99')); return Promise.resolve(); }); }); @@ -64,7 +64,7 @@ suite('Tests for Increment/Decrement Emmet Commands', () => { return withRandomFileEditor(contents, 'txt', async (editor, doc) => { editor.selections = [new Selection(1, 7, 1, 10), new Selection(3, 7, 3, 10)]; await incrementDecrement(-10); - assert.equal(doc.getText(), contents.replace('123', '113').replace('100', '90')); + assert.strictEqual(doc.getText(), contents.replace('123', '113').replace('100', '90')); return Promise.resolve(); }); }); @@ -73,8 +73,8 @@ suite('Tests for Increment/Decrement Emmet Commands', () => { return withRandomFileEditor(contents, 'txt', async (editor, doc) => { editor.selections = [new Selection(1, 7, 1, 13), new Selection(3, 7, 3, 10)]; await incrementDecrement(-0.1); - assert.equal(doc.getText(), contents.replace('123.43', '123.33').replace('100', '99.9')); + assert.strictEqual(doc.getText(), contents.replace('123.43', '123.33').replace('100', '99.9')); return Promise.resolve(); }); }); -}); \ No newline at end of file +}); diff --git a/extensions/emmet/src/test/index.ts b/extensions/emmet/src/test/index.ts index cf92c7a6..4f09c798 100644 --- a/extensions/emmet/src/test/index.ts +++ b/extensions/emmet/src/test/index.ts @@ -8,7 +8,7 @@ const testRunner = require('../../../../test/integration/electron/testrunner'); const options: any = { ui: 'tdd', - color: (!process.env.BUILD_ARTIFACTSTAGINGDIRECTORY && process.platform !== 'win32'), + color: true, timeout: 60000 }; diff --git a/extensions/emmet/src/test/partialParsingStylesheet.test.ts b/extensions/emmet/src/test/partialParsingStylesheet.test.ts index 30cdd698..d29348af 100644 --- a/extensions/emmet/src/test/partialParsingStylesheet.test.ts +++ b/extensions/emmet/src/test/partialParsingStylesheet.test.ts @@ -5,12 +5,13 @@ import 'mocha'; import * as assert from 'assert'; -import { withRandomFileEditor } from './testUtils'; +import { closeAllEditors, withRandomFileEditor } from './testUtils'; import * as vscode from 'vscode'; import { parsePartialStylesheet, getFlatNode } from '../util'; import { isValidLocationForEmmetAbbreviation } from '../abbreviationActions'; suite('Tests for partial parse of Stylesheets', () => { + teardown(closeAllEditors); function isValid(doc: vscode.TextDocument, range: vscode.Range, syntax: string): boolean { const rootNode = parsePartialStylesheet(doc, range.end); @@ -43,10 +44,10 @@ p { ]; rangesForEmmet.forEach(range => { - assert.equal(isValid(doc, range, 'css'), true); + assert.strictEqual(isValid(doc, range, 'css'), true); }); rangesNotEmmet.forEach(range => { - assert.equal(isValid(doc, range, 'css'), false); + assert.strictEqual(isValid(doc, range, 'css'), false); }); return Promise.resolve(); @@ -73,7 +74,7 @@ dn { new vscode.Range(7, 2, 7, 4) // bg after ending of badly constructed block ]; rangesNotEmmet.forEach(range => { - assert.equal(isValid(doc, range, 'scss'), false); + assert.strictEqual(isValid(doc, range, 'scss'), false); }); return Promise.resolve(); }); @@ -108,10 +109,10 @@ comment */ new vscode.Range(10, 2, 10, 3) // p after ending of block ]; rangesForEmmet.forEach(range => { - assert.equal(isValid(doc, range, 'css'), true); + assert.strictEqual(isValid(doc, range, 'css'), true); }); rangesNotEmmet.forEach(range => { - assert.equal(isValid(doc, range, 'css'), false); + assert.strictEqual(isValid(doc, range, 'css'), false); }); return Promise.resolve(); }); @@ -143,10 +144,10 @@ comment */ new vscode.Range(6, 3, 6, 4) // In selector ]; rangesForEmmet.forEach(range => { - assert.equal(isValid(doc, range, 'scss'), true); + assert.strictEqual(isValid(doc, range, 'scss'), true); }); rangesNotEmmet.forEach(range => { - assert.equal(isValid(doc, range, 'scss'), false); + assert.strictEqual(isValid(doc, range, 'scss'), false); }); return Promise.resolve(); }); @@ -175,10 +176,10 @@ comment */ new vscode.Range(1, 66, 1, 68) // Outside any ruleset ]; rangesForEmmet.forEach(range => { - assert.equal(isValid(doc, range, 'scss'), true); + assert.strictEqual(isValid(doc, range, 'scss'), true); }); rangesNotEmmet.forEach(range => { - assert.equal(isValid(doc, range, 'scss'), false); + assert.strictEqual(isValid(doc, range, 'scss'), false); }); return Promise.resolve(); }); @@ -210,10 +211,10 @@ p.#{dn} { new vscode.Range(3, 1, 3, 2), // # inside ruleset ]; rangesForEmmet.forEach(range => { - assert.equal(isValid(doc, range, 'scss'), true); + assert.strictEqual(isValid(doc, range, 'scss'), true); }); rangesNotEmmet.forEach(range => { - assert.equal(isValid(doc, range, 'scss'), false); + assert.strictEqual(isValid(doc, range, 'scss'), false); }); return Promise.resolve(); }); @@ -248,10 +249,10 @@ ment */{ new vscode.Range(6, 3, 6, 4) // In c inside block comment ]; rangesForEmmet.forEach(range => { - assert.equal(isValid(doc, range, 'scss'), true); + assert.strictEqual(isValid(doc, range, 'scss'), true); }); rangesNotEmmet.forEach(range => { - assert.equal(isValid(doc, range, 'scss'), false); + assert.strictEqual(isValid(doc, range, 'scss'), false); }); return Promise.resolve(); }); diff --git a/extensions/emmet/src/test/reflectCssValue.test.ts b/extensions/emmet/src/test/reflectCssValue.test.ts index 17cae8b7..2f083030 100644 --- a/extensions/emmet/src/test/reflectCssValue.test.ts +++ b/extensions/emmet/src/test/reflectCssValue.test.ts @@ -50,7 +50,7 @@ suite('Tests for Emmet: Reflect CSS Value command', () => { return withRandomFileEditor(cssContents, '.css', (editor, doc) => { editor.selections = [new Selection(5, 10, 5, 10)]; return reflectCssValue().then(() => { - assert.equal(doc.getText(), cssContents.replace(/\(50deg\)/g, '(20deg)')); + assert.strictEqual(doc.getText(), cssContents.replace(/\(50deg\)/g, '(20deg)')); return Promise.resolve(); }); }); @@ -60,7 +60,7 @@ suite('Tests for Emmet: Reflect CSS Value command', () => { return withRandomFileEditor(cssContents, '.css', (editor, doc) => { editor.selections = [new Selection(5, 2, 5, 32)]; return reflectCssValue().then(() => { - assert.equal(doc.getText(), cssContents.replace(/\(50deg\)/g, '(20deg)')); + assert.strictEqual(doc.getText(), cssContents.replace(/\(50deg\)/g, '(20deg)')); return Promise.resolve(); }); }); @@ -70,7 +70,7 @@ suite('Tests for Emmet: Reflect CSS Value command', () => { return withRandomFileEditor(htmlContents, '.html', (editor, doc) => { editor.selections = [new Selection(7, 20, 7, 20)]; return reflectCssValue().then(() => { - assert.equal(doc.getText(), htmlContents.replace(/\(50deg\)/g, '(20deg)')); + assert.strictEqual(doc.getText(), htmlContents.replace(/\(50deg\)/g, '(20deg)')); return Promise.resolve(); }); }); @@ -80,10 +80,10 @@ suite('Tests for Emmet: Reflect CSS Value command', () => { return withRandomFileEditor(htmlContents, '.html', (editor, doc) => { editor.selections = [new Selection(7, 4, 7, 34)]; return reflectCssValue().then(() => { - assert.equal(doc.getText(), htmlContents.replace(/\(50deg\)/g, '(20deg)')); + assert.strictEqual(doc.getText(), htmlContents.replace(/\(50deg\)/g, '(20deg)')); return Promise.resolve(); }); }); }); -}); \ No newline at end of file +}); diff --git a/extensions/emmet/src/test/tagActions.test.ts b/extensions/emmet/src/test/tagActions.test.ts index 8c217bf3..94069299 100644 --- a/extensions/emmet/src/test/tagActions.test.ts +++ b/extensions/emmet/src/test/tagActions.test.ts @@ -14,10 +14,7 @@ import { splitJoinTag } from '../splitJoinTag'; import { mergeLines } from '../mergeLines'; suite('Tests for Emmet actions on html tags', () => { - teardown(() => { - // close all editors - return closeAllEditors; - }); + teardown(closeAllEditors); const contents = `
@@ -60,7 +57,7 @@ suite('Tests for Emmet actions on html tags', () => { ]; return updateTag('section')!.then(() => { - assert.equal(doc.getText(), expectedContents); + assert.strictEqual(doc.getText(), expectedContents); return Promise.resolve(); }); }); @@ -85,7 +82,7 @@ suite('Tests for Emmet actions on html tags', () => { ]; return updateTag('section')!.then(() => { - assert.equal(doc.getText(), expectedContents); + assert.strictEqual(doc.getText(), expectedContents); return Promise.resolve(); }); }); @@ -109,7 +106,7 @@ suite('Tests for Emmet actions on html tags', () => { ]; return updateTag('section')!.then(() => { - assert.equal(doc.getText(), expectedContents); + assert.strictEqual(doc.getText(), expectedContents); return Promise.resolve(); }); }); @@ -136,7 +133,7 @@ suite('Tests for Emmet actions on html tags', () => { ]; return removeTag()!.then(() => { - assert.equal(doc.getText(), expectedContents); + assert.strictEqual(doc.getText(), expectedContents); return Promise.resolve(); }); }); @@ -161,7 +158,7 @@ suite('Tests for Emmet actions on html tags', () => { ]; return removeTag()!.then(() => { - assert.equal(doc.getText(), expectedContents); + assert.strictEqual(doc.getText(), expectedContents); return Promise.resolve(); }); }); @@ -185,7 +182,7 @@ suite('Tests for Emmet actions on html tags', () => { ]; return removeTag()!.then(() => { - assert.equal(doc.getText(), expectedContents); + assert.strictEqual(doc.getText(), expectedContents); return Promise.resolve(); }); }); @@ -211,7 +208,7 @@ suite('Tests for Emmet actions on html tags', () => { ]; return splitJoinTag()!.then(() => { - assert.equal(doc.getText(), expectedContents); + assert.strictEqual(doc.getText(), expectedContents); return Promise.resolve(); }); }); @@ -235,7 +232,7 @@ suite('Tests for Emmet actions on html tags', () => { ]; return splitJoinTag()!.then(() => { - assert.equal(doc.getText(), expectedContents); + assert.strictEqual(doc.getText(), expectedContents); return Promise.resolve(); }); }); @@ -259,7 +256,7 @@ suite('Tests for Emmet actions on html tags', () => { ]; return splitJoinTag()!.then(() => { - assert.equal(doc.getText(), expectedContents); + assert.strictEqual(doc.getText(), expectedContents); return Promise.resolve(); }); }); @@ -285,7 +282,7 @@ suite('Tests for Emmet actions on html tags', () => { ]; return splitJoinTag()!.then(() => { - assert.equal(doc.getText(), expectedContents); + assert.strictEqual(doc.getText(), expectedContents); return workspace.getConfiguration('emmet').update('syntaxProfiles', oldValueForSyntaxProfiles ? oldValueForSyntaxProfiles.globalValue : undefined, ConfigurationTarget.Global); }); }); @@ -308,10 +305,10 @@ suite('Tests for Emmet actions on html tags', () => { matchTag(); editor.selections.forEach(selection => { - assert.equal(selection.active.line, 8); - assert.equal(selection.active.character, 3); - assert.equal(selection.anchor.line, 8); - assert.equal(selection.anchor.character, 3); + assert.strictEqual(selection.active.line, 8); + assert.strictEqual(selection.active.character, 3); + assert.strictEqual(selection.anchor.line, 8); + assert.strictEqual(selection.anchor.character, 3); }); return Promise.resolve(); @@ -334,10 +331,10 @@ suite('Tests for Emmet actions on html tags', () => { matchTag(); editor.selections.forEach(selection => { - assert.equal(selection.active.line, 4); - assert.equal(selection.active.character, 4); - assert.equal(selection.anchor.line, 4); - assert.equal(selection.anchor.character, 4); + assert.strictEqual(selection.active.line, 4); + assert.strictEqual(selection.active.character, 4); + assert.strictEqual(selection.anchor.line, 4); + assert.strictEqual(selection.anchor.character, 4); }); return Promise.resolve(); @@ -360,7 +357,7 @@ suite('Tests for Emmet actions on html tags', () => { ]; return mergeLines()!.then(() => { - assert.equal(doc.getText(), expectedContents); + assert.strictEqual(doc.getText(), expectedContents); return Promise.resolve(); }); }); @@ -379,7 +376,7 @@ suite('Tests for Emmet actions on html tags', () => { ]; return mergeLines()!.then(() => { - assert.equal(doc.getText(), expectedContents); + assert.strictEqual(doc.getText(), expectedContents); return Promise.resolve(); }); }); @@ -394,7 +391,7 @@ suite('Tests for Emmet actions on html tags', () => { ]; return mergeLines()!.then(() => { - assert.equal(doc.getText(), contents); + assert.strictEqual(doc.getText(), contents); return Promise.resolve(); }); }); diff --git a/extensions/emmet/src/test/test-fixtures/marker.txt b/extensions/emmet/src/test/test-fixtures/marker.txt deleted file mode 100644 index a4c9ad40..00000000 --- a/extensions/emmet/src/test/test-fixtures/marker.txt +++ /dev/null @@ -1 +0,0 @@ -DO NOT DELETE, USED BY INTEGRATION TESTS diff --git a/extensions/emmet/src/test/testUtils.ts b/extensions/emmet/src/test/testUtils.ts index 8e6644f4..30e11ee7 100644 --- a/extensions/emmet/src/test/testUtils.ts +++ b/extensions/emmet/src/test/testUtils.ts @@ -48,7 +48,6 @@ export function deleteFile(file: vscode.Uri): Thenable { export function closeAllEditors(): Thenable { return vscode.commands.executeCommand('workbench.action.closeAllEditors'); - } export function withRandomFileEditor(initialContents: string, fileExtension: string = 'txt', run: (editor: vscode.TextEditor, doc: vscode.TextDocument) => Thenable): Thenable { @@ -67,4 +66,4 @@ export function withRandomFileEditor(initialContents: string, fileExtension: str }); }); }); -} \ No newline at end of file +} diff --git a/extensions/emmet/src/test/toggleComment.test.ts b/extensions/emmet/src/test/toggleComment.test.ts index 42a867f9..5678dad7 100644 --- a/extensions/emmet/src/test/toggleComment.test.ts +++ b/extensions/emmet/src/test/toggleComment.test.ts @@ -79,7 +79,7 @@ suite('Tests for Toggle Comment action from Emmet (HTML)', () => { ]; return toggleComment().then(() => { - assert.equal(doc.getText(), expectedContents); + assert.strictEqual(doc.getText(), expectedContents); return Promise.resolve(); }); }); @@ -120,7 +120,7 @@ suite('Tests for Toggle Comment action from Emmet (HTML)', () => { ]; return toggleComment().then(() => { - assert.equal(doc.getText(), expectedContents); + assert.strictEqual(doc.getText(), expectedContents); return Promise.resolve(); }); }); @@ -158,7 +158,7 @@ suite('Tests for Toggle Comment action from Emmet (HTML)', () => { ]; return toggleComment().then(() => { - assert.equal(doc.getText(), expectedContents); + assert.strictEqual(doc.getText(), expectedContents); return Promise.resolve(); }); }); @@ -196,7 +196,7 @@ suite('Tests for Toggle Comment action from Emmet (HTML)', () => { ]; return toggleComment().then(() => { - assert.equal(doc.getText(), expectedContents); + assert.strictEqual(doc.getText(), expectedContents); return Promise.resolve(); }); }); @@ -241,7 +241,7 @@ suite('Tests for Toggle Comment action from Emmet (HTML)', () => { ]; return toggleComment().then(() => { - assert.equal(doc.getText(), expectedContents); + assert.strictEqual(doc.getText(), expectedContents); return Promise.resolve(); }); @@ -272,7 +272,7 @@ suite('Tests for Toggle Comment action from Emmet (HTML)', () => { new Selection(4, 18, 4, 18), // cursor inside the noncommented span ]; return toggleComment().then(() => { - assert.equal(doc.getText(), expectedContents); + assert.strictEqual(doc.getText(), expectedContents); return Promise.resolve(); }); }); @@ -315,9 +315,9 @@ suite('Tests for Toggle Comment action from Emmet (CSS)', () => { ]; return toggleComment().then(() => { - assert.equal(doc.getText(), expectedContents); + assert.strictEqual(doc.getText(), expectedContents); return toggleComment().then(() => { - assert.equal(doc.getText(), contents); + assert.strictEqual(doc.getText(), contents); return Promise.resolve(); }); }); @@ -345,9 +345,9 @@ suite('Tests for Toggle Comment action from Emmet (CSS)', () => { ]; return toggleComment().then(() => { - assert.equal(doc.getText(), expectedContents); + assert.strictEqual(doc.getText(), expectedContents); //return toggleComment().then(() => { - //assert.equal(doc.getText(), contents); + //assert.strictEqual(doc.getText(), contents); return Promise.resolve(); //}); }); @@ -376,9 +376,9 @@ suite('Tests for Toggle Comment action from Emmet (CSS)', () => { ]; return toggleComment().then(() => { - assert.equal(doc.getText(), expectedContents); + assert.strictEqual(doc.getText(), expectedContents); return toggleComment().then(() => { - assert.equal(doc.getText(), contents); + assert.strictEqual(doc.getText(), contents); return Promise.resolve(); }); }); @@ -404,9 +404,9 @@ suite('Tests for Toggle Comment action from Emmet (CSS)', () => { ]; return toggleComment().then(() => { - assert.equal(doc.getText(), expectedContents); + assert.strictEqual(doc.getText(), expectedContents); return toggleComment().then(() => { - assert.equal(doc.getText(), contents); + assert.strictEqual(doc.getText(), contents); return Promise.resolve(); }); }); @@ -432,9 +432,9 @@ suite('Tests for Toggle Comment action from Emmet (CSS)', () => { ]; return toggleComment().then(() => { - assert.equal(doc.getText(), expectedContents); + assert.strictEqual(doc.getText(), expectedContents); return toggleComment().then(() => { - assert.equal(doc.getText(), contents); + assert.strictEqual(doc.getText(), contents); return Promise.resolve(); }); }); @@ -460,9 +460,9 @@ suite('Tests for Toggle Comment action from Emmet (CSS)', () => { ]; return toggleComment().then(() => { - assert.equal(doc.getText(), expectedContents); + assert.strictEqual(doc.getText(), expectedContents); return toggleComment().then(() => { - assert.equal(doc.getText(), contents); + assert.strictEqual(doc.getText(), contents); return Promise.resolve(); }); }); @@ -488,9 +488,9 @@ suite('Tests for Toggle Comment action from Emmet (CSS)', () => { ]; return toggleComment().then(() => { - assert.equal(doc.getText(), expectedContents); + assert.strictEqual(doc.getText(), expectedContents); return toggleComment().then(() => { - assert.equal(doc.getText(), contents); + assert.strictEqual(doc.getText(), contents); return Promise.resolve(); }); }); @@ -517,9 +517,9 @@ suite('Tests for Toggle Comment action from Emmet (CSS)', () => { ]; return toggleComment().then(() => { - assert.equal(doc.getText(), expectedContents); + assert.strictEqual(doc.getText(), expectedContents); return toggleComment().then(() => { - assert.equal(doc.getText(), contents); + assert.strictEqual(doc.getText(), contents); return Promise.resolve(); }); }); @@ -568,9 +568,9 @@ suite('Tests for Toggle Comment action from Emmet in nested css (SCSS)', () => { ]; return toggleComment().then(() => { - assert.equal(doc.getText(), expectedContents); + assert.strictEqual(doc.getText(), expectedContents); return toggleComment().then(() => { - assert.equal(doc.getText(), contents); + assert.strictEqual(doc.getText(), contents); return Promise.resolve(); }); }); @@ -599,9 +599,9 @@ suite('Tests for Toggle Comment action from Emmet in nested css (SCSS)', () => { ]; return toggleComment().then(() => { - assert.equal(doc.getText(), expectedContents); + assert.strictEqual(doc.getText(), expectedContents); return toggleComment().then(() => { - assert.equal(doc.getText(), contents); + assert.strictEqual(doc.getText(), contents); return Promise.resolve(); }); }); @@ -629,9 +629,9 @@ suite('Tests for Toggle Comment action from Emmet in nested css (SCSS)', () => { ]; return toggleComment().then(() => { - assert.equal(doc.getText(), expectedContents); + assert.strictEqual(doc.getText(), expectedContents); //return toggleComment().then(() => { - // assert.equal(doc.getText(), contents); + // assert.strictEqual(doc.getText(), contents); return Promise.resolve(); //}); }); @@ -659,9 +659,9 @@ suite('Tests for Toggle Comment action from Emmet in nested css (SCSS)', () => { ]; return toggleComment().then(() => { - assert.equal(doc.getText(), expectedContents); + assert.strictEqual(doc.getText(), expectedContents); return toggleComment().then(() => { - assert.equal(doc.getText(), contents); + assert.strictEqual(doc.getText(), contents); return Promise.resolve(); }); }); @@ -689,9 +689,9 @@ suite('Tests for Toggle Comment action from Emmet in nested css (SCSS)', () => { ]; return toggleComment().then(() => { - assert.equal(doc.getText(), expectedContents); + assert.strictEqual(doc.getText(), expectedContents); return toggleComment().then(() => { - assert.equal(doc.getText(), contents); + assert.strictEqual(doc.getText(), contents); return Promise.resolve(); }); }); @@ -717,9 +717,9 @@ suite('Tests for Toggle Comment action from Emmet in nested css (SCSS)', () => { ]; return toggleComment().then(() => { - assert.equal(doc.getText(), expectedContents); + assert.strictEqual(doc.getText(), expectedContents); return toggleComment().then(() => { - assert.equal(doc.getText(), contents); + assert.strictEqual(doc.getText(), contents); return Promise.resolve(); }); }); @@ -743,9 +743,9 @@ suite('Tests for Toggle Comment action from Emmet in nested css (SCSS)', () => { ]; return toggleComment().then(() => { - assert.equal(doc.getText(), expectedContents); + assert.strictEqual(doc.getText(), expectedContents); return toggleComment().then(() => { - assert.equal(doc.getText(), contents); + assert.strictEqual(doc.getText(), contents); return Promise.resolve(); }); }); diff --git a/extensions/emmet/src/test/updateImageSize.test.ts b/extensions/emmet/src/test/updateImageSize.test.ts index b43b3e6e..606f2655 100644 --- a/extensions/emmet/src/test/updateImageSize.test.ts +++ b/extensions/emmet/src/test/updateImageSize.test.ts @@ -55,7 +55,7 @@ suite('Tests for Emmet actions on html tags', () => { ]; return updateImageSize()!.then(() => { - assert.equal(doc.getText(), expectedContents); + assert.strictEqual(doc.getText(), expectedContents); return Promise.resolve(); }); }); @@ -112,7 +112,7 @@ suite('Tests for Emmet actions on html tags', () => { ]; return updateImageSize()!.then(() => { - assert.equal(doc.getText(), expectedContents); + assert.strictEqual(doc.getText(), expectedContents); return Promise.resolve(); }); }); @@ -141,7 +141,7 @@ suite('Tests for Emmet actions on html tags', () => { ]; return updateImageSize()!.then(() => { - assert.equal(doc.getText(), expectedContents); + assert.strictEqual(doc.getText(), expectedContents); return Promise.resolve(); }); }); diff --git a/extensions/emmet/src/test/wrapWithAbbreviation.test.ts b/extensions/emmet/src/test/wrapWithAbbreviation.test.ts index 320aa79e..020f0191 100644 --- a/extensions/emmet/src/test/wrapWithAbbreviation.test.ts +++ b/extensions/emmet/src/test/wrapWithAbbreviation.test.ts @@ -398,12 +398,12 @@ function testWrapWithAbbreviation(selections: Selection[], abbreviation: string, editor.selections = selections; const promise = wrapWithAbbreviation({ abbreviation }); if (!promise) { - assert.equal(1, 2, 'Wrap with Abbreviation returned undefined.'); + assert.strictEqual(1, 2, 'Wrap with Abbreviation returned undefined.'); return Promise.resolve(); } return promise.then(() => { - assert.equal(editor.document.getText(), expectedContents); + assert.strictEqual(editor.document.getText(), expectedContents); return Promise.resolve(); }); }); @@ -414,12 +414,12 @@ function testWrapIndividualLinesWithAbbreviation(selections: Selection[], abbrev editor.selections = selections; const promise = wrapWithAbbreviation({ abbreviation }); if (!promise) { - assert.equal(1, 2, 'Wrap individual lines with Abbreviation returned undefined.'); + assert.strictEqual(1, 2, 'Wrap individual lines with Abbreviation returned undefined.'); return Promise.resolve(); } return promise.then(() => { - assert.equal(editor.document.getText(), expectedContents); + assert.strictEqual(editor.document.getText(), expectedContents); return Promise.resolve(); }); }); diff --git a/extensions/emmet/src/util.ts b/extensions/emmet/src/util.ts index 6b91b607..cb63f504 100644 --- a/extensions/emmet/src/util.ts +++ b/extensions/emmet/src/util.ts @@ -42,17 +42,13 @@ export function updateEmmetExtensionsPath(forceRefresh: boolean = false) { } if (forceRefresh || _currentExtensionsPath !== extensionsPath) { _currentExtensionsPath = extensionsPath; - if (!vscode.workspace.workspaceFolders || vscode.workspace.workspaceFolders.length === 0) { - return; - } else { - const rootPath = vscode.workspace.workspaceFolders[0].uri; - const fileSystem = vscode.workspace.fs; - helper.updateExtensionsPath(extensionsPath, fileSystem, rootPath, _homeDir).catch(err => { - if (Array.isArray(extensionsPath) && extensionsPath.length) { - vscode.window.showErrorMessage(err.message); - } - }); - } + const rootPath = vscode.workspace.workspaceFolders?.length ? vscode.workspace.workspaceFolders[0].uri : undefined; + const fileSystem = vscode.workspace.fs; + helper.updateExtensionsPath(extensionsPath, fileSystem, rootPath, _homeDir).catch(err => { + if (Array.isArray(extensionsPath) && extensionsPath.length) { + vscode.window.showErrorMessage(err.message); + } + }); } } @@ -88,19 +84,19 @@ export function migrateEmmetExtensionsPath() { * Mapping between languages that support Emmet and completion trigger characters */ export const LANGUAGE_MODES: { [id: string]: string[] } = { - 'html': ['!', '.', '}', ':', '*', '$', ']', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], - 'jade': ['!', '.', '}', ':', '*', '$', ']', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], - 'slim': ['!', '.', '}', ':', '*', '$', ']', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], - 'haml': ['!', '.', '}', ':', '*', '$', ']', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], - 'xml': ['.', '}', '*', '$', ']', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], - 'xsl': ['!', '.', '}', '*', '$', ']', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], + 'html': ['!', '.', '}', ':', '*', '$', ']', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], + 'jade': ['!', '.', '}', ':', '*', '$', ']', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], + 'slim': ['!', '.', '}', ':', '*', '$', ']', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], + 'haml': ['!', '.', '}', ':', '*', '$', ']', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], + 'xml': ['.', '}', '*', '$', ']', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], + 'xsl': ['!', '.', '}', '*', '$', '/', ']', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], 'css': [':', '!', '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], 'scss': [':', '!', '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], 'sass': [':', '!', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], 'less': [':', '!', '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], 'stylus': [':', '!', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], - 'javascriptreact': ['!', '.', '}', '*', '$', ']', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], - 'typescriptreact': ['!', '.', '}', '*', '$', ']', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'] + 'javascriptreact': ['!', '.', '}', '*', '$', ']', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], + 'typescriptreact': ['!', '.', '}', '*', '$', ']', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'] }; export function isStyleSheet(syntax: string): boolean { @@ -380,32 +376,36 @@ export const allowedMimeTypesInScriptTag = ['text/html', 'text/plain', 'text/x-t * If position is inside a script tag of type template, then it will be parsed to find the inner HTML node as well */ export function getHtmlFlatNode(documentText: string, root: FlatNode | undefined, offset: number, includeNodeBoundary: boolean): HtmlFlatNode | undefined { - const currentNode: HtmlFlatNode | undefined = getFlatNode(root, offset, includeNodeBoundary); + let currentNode: HtmlFlatNode | undefined = getFlatNode(root, offset, includeNodeBoundary); if (!currentNode) { return; } - const isTemplateScript = currentNode.name === 'script' && - (currentNode.attributes && - currentNode.attributes.some(x => x.name.toString() === 'type' - && allowedMimeTypesInScriptTag.includes(x.value.toString()))); - if (isTemplateScript - && currentNode.open - && offset > currentNode.open.end - && (!currentNode.close || offset < currentNode.close.start)) { - // blank out the rest of the document and search for the node within - const beforePadding = ' '.repeat(currentNode.open.end); - const endToUse = currentNode.close ? currentNode.close.start : currentNode.end; - const scriptBodyText = beforePadding + documentText.substring(currentNode.open.end, endToUse); - const innerRoot: HtmlFlatNode = parse(scriptBodyText); - const scriptBodyNode = getHtmlFlatNode(scriptBodyText, innerRoot, offset, includeNodeBoundary); - if (scriptBodyNode) { - scriptBodyNode.parent = currentNode; - currentNode.children.push(scriptBodyNode); - return scriptBodyNode; - } + // If the currentNode is a script one, first set up its subtree and then find HTML node. + if (currentNode.name === 'script' && currentNode.children.length === 0) { + setUpScriptNodeSubtree(documentText, currentNode); + currentNode = getFlatNode(currentNode, offset, includeNodeBoundary) ?? currentNode; } return currentNode; } +export function setUpScriptNodeSubtree(documentText: string, scriptNode: HtmlFlatNode): void { + const isTemplateScript = scriptNode.name === 'script' && + (scriptNode.attributes && + scriptNode.attributes.some(x => x.name.toString() === 'type' + && allowedMimeTypesInScriptTag.includes(x.value.toString()))); + if (isTemplateScript + && scriptNode.open) { + // blank out the rest of the document and generate the subtree. + const beforePadding = ' '.repeat(scriptNode.open.end); + const endToUse = scriptNode.close ? scriptNode.close.start : scriptNode.end; + const scriptBodyText = beforePadding + documentText.substring(scriptNode.open.end, endToUse); + const innerRoot: HtmlFlatNode = parse(scriptBodyText); + innerRoot.children.forEach(child => { + scriptNode.children.push(child); + child.parent = scriptNode; + }); + } +} + export function isOffsetInsideOpenOrCloseTag(node: FlatNode, offset: number): boolean { const htmlNode = node as HtmlFlatNode; if ((htmlNode.open && offset > htmlNode.open.start && offset < htmlNode.open.end) @@ -583,7 +583,7 @@ export function getEmmetConfiguration(syntax: string) { ) { syntaxProfiles[syntax] = { ...syntaxProfiles[syntax], - selfClosingStyle: 'xml' + selfClosingStyle: syntax === 'jsx' ? 'xhtml' : 'xml' }; } } @@ -657,10 +657,8 @@ export function getEmbeddedCssNodeIfAny(document: vscode.TextDocument, currentNo const currentHtmlNode = currentNode; if (currentHtmlNode && currentHtmlNode.open && currentHtmlNode.close) { const offset = document.offsetAt(position); - if (currentHtmlNode.open.end <= offset && offset <= currentHtmlNode.close.start) { - if (currentHtmlNode.name === 'style' - && currentHtmlNode.open.end < offset - && currentHtmlNode.close.start > offset) { + if (currentHtmlNode.open.end < offset && offset <= currentHtmlNode.close.start) { + if (currentHtmlNode.name === 'style') { const buffer = ' '.repeat(currentHtmlNode.open.end) + document.getText().substring(currentHtmlNode.open.end, currentHtmlNode.close.start); return parseStylesheet(buffer); } diff --git a/extensions/emmet/src/test/test-fixtures/.vscode/settings.json b/extensions/emmet/test-workspace/.vscode/settings.json similarity index 100% rename from extensions/emmet/src/test/test-fixtures/.vscode/settings.json rename to extensions/emmet/test-workspace/.vscode/settings.json diff --git a/extensions/emmet/tsconfig.json b/extensions/emmet/tsconfig.json index 936dac48..22cffd9c 100644 --- a/extensions/emmet/tsconfig.json +++ b/extensions/emmet/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../shared.tsconfig.json", + "extends": "../tsconfig.base.json", "compilerOptions": { "outDir": "./out" }, @@ -10,4 +10,4 @@ "include": [ "src/**/*" ] -} \ No newline at end of file +} diff --git a/extensions/emmet/yarn.lock b/extensions/emmet/yarn.lock index 5e8b187e..3578363b 100644 --- a/extensions/emmet/yarn.lock +++ b/extensions/emmet/yarn.lock @@ -54,9 +54,9 @@ integrity sha1-Rs/+oRmgoAMxKiHC2bVijLX81EI= "@types/node@^12.19.9": - version "12.20.6" - resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.6.tgz#7b73cce37352936e628c5ba40326193443cfba25" - integrity sha512-sRVq8d+ApGslmkE9e3i+D3gFGk7aZHAT+G4cIpIEdLJYPsWiSPwcAnJEjddLQQDqV3Ra2jOclX/Sv6YrvGYiWA== + version "12.20.7" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.7.tgz#1cb61fd0c85cb87e728c43107b5fd82b69bc9ef8" + integrity sha512-gWL8VUkg8VRaCAUgG9WmhefMqHmMblxe2rVpMF86nZY/+ZysU+BkAp+3cz03AixWDSSz0ks5WX59yAhv/cDwFA== emmet@^2.3.0: version "2.3.4" @@ -77,9 +77,9 @@ jsonc-parser@^2.3.0: integrity sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg== vscode-emmet-helper@^2.3.0: - version "2.4.2" - resolved "https://registry.yarnpkg.com/vscode-emmet-helper/-/vscode-emmet-helper-2.4.2.tgz#98dc3275a22668f0e0ef9f2ee1fa76653d71e78f" - integrity sha512-j6N6xBn0NOigk2RYWESFlsnMQNJm5B10UUgOeHxRpm66Kck9Bq1nxwy6qT9eqKvzxz4hpC29Xv4aPGlOzsKw3w== + version "2.6.2" + resolved "https://registry.yarnpkg.com/vscode-emmet-helper/-/vscode-emmet-helper-2.6.2.tgz#777b471a7851ba0ca8e4151533be7f92511f39b0" + integrity sha512-SkL1WjZZsA+bfTo52QH4PgqXCQAJSqzOmJtAY3rOl17MKbY6iJhVv2T26PshjmUnHoXnXMNa7PcLMCS75RsQDQ== dependencies: emmet "^2.3.0" jsonc-parser "^2.3.0" diff --git a/extensions/extension-editing/images/icon.png b/extensions/extension-editing/images/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..45e0f8d47b63deb90dd8db4eeec68b29e2ebba88 GIT binary patch literal 2007 zcmYk7c{r47AIIX;fn;b4Gu?*Y&&Z@8|yBzvcexzR&ync~g`Pl>q=0 zpFLgy06+?a012m%_=ey>h1~J?4cw(rkp2{4e2~rq8BbR^eE^&qSlu#u0M-LC6s$0L z0P9}`#`m04-<@Bc0!5rs6!KP&ei>K|MU&M&imLwawf#ut3%Oj*eE7bGC$VKe#~!}t zPs|9V^Fryu)ZDU0CLe7OQuF^~%l>c2o3D+}rvwwT)cj$jYLxp%R{Z1xe`41E*H^_| z;-an(iYXMfQH0{o>rw*z4gjGB+A)P8LMoNcXWBLv7yH-vb83nb>4Hd3lWh}953!#C zfY|KgwIh%b@opqMuZyZmKy^-RzP@M2@oLW`qgc60WjQoSYaI?_RwOE|Kku_C^gyh2 z$gv_E_m0&$R$_d%8HYPVUw25<5;=|ad+*)?Bq#GAop|TG1;<CL{}!2++@pGVsXt5LaMJsqkNYy?_9q1;bSRp=HxwN z{5?LtiTmHmVPonmI=Hg-ab~VF!x+nx{7L9d(M(_z|0dI;GZ#8x(S4%1argO_`NUM| z&AKFW()j#%(2|8~2-P_*nr#2@>G9MYa5zTzaf#XV@X1N8t`ocUc4?2?HFxpfFRjc9 zs=~Yevcakj|1hD#DW`r@X?n>t5Y~}xSYHUEr~hL9RAYG`f92EiFqu(cH9F`~i?F3z zSSc$$gyh_@gng4W!J=}ngP@w3%%bG9HCHjyp(0l1!m@f~_Z$w6zU3-1`D7brARNJQ zN7zOz>ZBU=(JUCn1;!|GjnR2hlqx74f|``{-C+YIBsv*Lpqw;_4~vGvHsV2|5i86X z8(0}Zj6srkmPO_uEd^w%cU}B7NX%4ylMKyE&5Bj1X>~8*nz`|e7Zxy;*LROQnY)xW zy=0>ag!duMiGxr)CbbfHU5+G3ptjpskzN&S}+g6o_u{8sS-B zT#>(R)p#fEox?g_9&In0Y77z$86*AUZ*Z675#25O$RcIhUO!B=jhGCJ?HN2}4K=VS z0GuXShd^a*1bc!G_Dw@8DDv zu^zG>28?uPAZZzoXHM{sxc}Ne42p%JthqVbdXHc=XFDZ-7DE=Vrbh<#$n-+=S$hJ| zIrn2ngJucvXZ6bVA{U96@{{UF6fS$qGGVfJ zDQ8-mjfZ#Li2(S`84jwp1fI1Vu zWY~IUIK&TpOL1y~8`QxC9h9}xU_Dohw#g4Oq*<)z8q#d)@N|Kj8J3%qqe(^19>E7A zI5!h4m&g&};y4gJ3Z&y4`2@~3Bz_dYj<2)pfi9zT60ZN!R-%Fy`nhN$*$D(~B8F}2A2i`~hGol? zNDK$26zDV`Nn`aawf^Yl?F_IYD(Wn^$oYE@9=f{_&3Y81iGMoLXje60M$+d+ZnnLy zuC;?3b%v~8s>rlwW+&0iGqFd@Uzskc>~Sa6ozVQn(hb^nXweW}0Jb(uu8Sdt{Ork* zLft}#i<|O=6l0#r&b;+~ca)cpOTrfjKULBY?W-*>u{BEPDBkp5Njx}aNZYVKNx;h~ zNgB`MuF~w3wT>)s3EItQDbCm1IdZ_OCZ>`=Xu7y_IQ6vSfowo1Kn+8 literal 0 HcmV?d00001 diff --git a/extensions/extension-editing/package.json b/extensions/extension-editing/package.json index c520abb2..d60c8420 100644 --- a/extensions/extension-editing/package.json +++ b/extensions/extension-editing/package.json @@ -8,6 +8,7 @@ "engines": { "vscode": "^1.4.0" }, + "icon": "images/icon.png", "activationEvents": [ "onLanguage:json", "onLanguage:markdown", @@ -15,13 +16,19 @@ ], "main": "./out/extensionEditingMain", "browser": "./dist/browser/extensionEditingBrowserMain", + "capabilities": { + "virtualWorkspaces": true, + "untrustedWorkspaces": { + "supported": true + } + }, "scripts": { "compile": "gulp compile-extension:extension-editing", "watch": "gulp watch-extension:extension-editing" }, "dependencies": { "jsonc-parser": "^2.2.1", - "markdown-it": "^8.3.1", + "markdown-it": "^12.0.4", "parse5": "^3.0.2", "vscode-nls": "^4.1.1" }, diff --git a/extensions/extension-editing/tsconfig.json b/extensions/extension-editing/tsconfig.json index 8aee88b5..5a23aaef 100644 --- a/extensions/extension-editing/tsconfig.json +++ b/extensions/extension-editing/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../shared.tsconfig.json", + "extends": "../tsconfig.base.json", "compilerOptions": { "outDir": "./out", "typeRoots": [ @@ -9,4 +9,4 @@ "include": [ "src/**/*" ] -} \ No newline at end of file +} diff --git a/extensions/extension-editing/yarn.lock b/extensions/extension-editing/yarn.lock index 0cc52354..3275b1f2 100644 --- a/extensions/extension-editing/yarn.lock +++ b/extensions/extension-editing/yarn.lock @@ -17,40 +17,38 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-6.0.78.tgz#5d4a3f579c1524e01ee21bf474e6fba09198f470" integrity sha512-+vD6E8ixntRzzZukoF3uP1iV+ZjVN3koTcaeK+BEoc/kSfGbLDIGC7RmCaUgVpUfN6cWvfczFRERCyKM9mkvXg== -argparse@^1.0.7: - version "1.0.9" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.9.tgz#73d83bc263f86e97f8cc4f6bae1b0e90a7d22c86" - integrity sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY= - dependencies: - sprintf-js "~1.0.2" +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== -entities@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0" - integrity sha1-blwtClYhtdra7O+AuQ7ftc13cvA= +entities@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5" + integrity sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w== jsonc-parser@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.2.1.tgz#db73cd59d78cce28723199466b2a03d1be1df2bc" integrity sha512-o6/yDBYccGvTz1+QFevz6l6OBZ2+fMVu2JZ9CIhzsYRX4mjaK5IyX9eldUdCmga16zlgQxyrj5pt9kzuj2C02w== -linkify-it@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-2.0.3.tgz#d94a4648f9b1c179d64fa97291268bdb6ce9434f" - integrity sha1-2UpGSPmxwXnWT6lykSaL22zpQ08= +linkify-it@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-3.0.2.tgz#f55eeb8bc1d3ae754049e124ab3bb56d97797fb8" + integrity sha512-gDBO4aHNZS6coiZCKVhSNh43F9ioIL4JwRjLZPkoLIY4yZFwg264Y5lu2x6rb1Js42Gh6Yqm2f6L2AJcnkzinQ== dependencies: uc.micro "^1.0.1" -markdown-it@^8.3.1: - version "8.4.0" - resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-8.4.0.tgz#e2400881bf171f7018ed1bd9da441dac8af6306d" - integrity sha512-tNuOCCfunY5v5uhcO2AUMArvKAyKMygX8tfup/JrgnsDqcCATQsAExBq7o5Ml9iMmO82bk6jYNLj6khcrl0JGA== +markdown-it@^12.0.4: + version "12.0.4" + resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-12.0.4.tgz#eec8247d296327eac3ba9746bdeec9cfcc751e33" + integrity sha512-34RwOXZT8kyuOJy25oJNJoulO8L0bTHYWXcdZBYZqFnjIy3NgjeoM3FmPXIOFQ26/lSHYMr8oc62B6adxXcb3Q== dependencies: - argparse "^1.0.7" - entities "~1.1.1" - linkify-it "^2.0.0" + argparse "^2.0.1" + entities "~2.1.0" + linkify-it "^3.0.1" mdurl "^1.0.1" - uc.micro "^1.0.3" + uc.micro "^1.0.5" mdurl@^1.0.1: version "1.0.1" @@ -64,16 +62,16 @@ parse5@^3.0.2: dependencies: "@types/node" "^6.0.46" -sprintf-js@~1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= - -uc.micro@^1.0.1, uc.micro@^1.0.3: +uc.micro@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.3.tgz#7ed50d5e0f9a9fb0a573379259f2a77458d50192" integrity sha1-ftUNXg+an7ClczeSWfKndFjVAZI= +uc.micro@^1.0.5: + version "1.0.6" + resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac" + integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA== + vscode-nls@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.1.1.tgz#f9916b64e4947b20322defb1e676a495861f133c" diff --git a/extensions/git-ui/.vscodeignore b/extensions/git-ui/.vscodeignore deleted file mode 100644 index 7462f744..00000000 --- a/extensions/git-ui/.vscodeignore +++ /dev/null @@ -1,8 +0,0 @@ -src/** -test/** -out/** -tsconfig.json -build/** -extension.webpack.config.js -cgmanifest.json -yarn.lock diff --git a/extensions/git-ui/README.md b/extensions/git-ui/README.md deleted file mode 100644 index d418425a..00000000 --- a/extensions/git-ui/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# Git UI integration for Visual Studio Code - -**Notice:** This extension is bundled with Visual Studio Code. It can be disabled but not uninstalled. - -## Features - -See [Git support in VS Code](https://code.visualstudio.com/docs/editor/versioncontrol#_git-support) to learn about the features of this extension. diff --git a/extensions/git-ui/cgmanifest.json b/extensions/git-ui/cgmanifest.json deleted file mode 100644 index f3071eb6..00000000 --- a/extensions/git-ui/cgmanifest.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "registrations": [], - "version": 1 -} \ No newline at end of file diff --git a/extensions/git-ui/extension.webpack.config.js b/extensions/git-ui/extension.webpack.config.js deleted file mode 100644 index 19c0ea30..00000000 --- a/extensions/git-ui/extension.webpack.config.js +++ /dev/null @@ -1,17 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -//@ts-check - -'use strict'; - -const withDefaults = require('../shared.webpack.config'); - -module.exports = withDefaults({ - context: __dirname, - entry: { - main: './src/main.ts' - } -}); diff --git a/extensions/git-ui/package.json b/extensions/git-ui/package.json deleted file mode 100644 index 67c6c940..00000000 --- a/extensions/git-ui/package.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "git-ui", - "displayName": "%displayName%", - "description": "%description%", - "publisher": "vscode", - "license": "MIT", - "version": "1.0.0", - "engines": { - "vscode": "^1.5.0" - }, - "extensionKind": [ - "ui" - ], - "aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217", - "enableProposedApi": true, - "categories": [ - "Other" - ], - "activationEvents": [ - "onCommand:git.credential" - ], - "main": "./out/main", - "icon": "resources/icons/git.png", - "scripts": { - "compile": "gulp compile-extension:git-ui", - "watch": "gulp watch-extension:git-ui" - }, - "devDependencies": { - "@types/node": "^12.19.9" - }, - "repository": { - "type": "git", - "url": "https://github.com/microsoft/vscode.git" - } -} diff --git a/extensions/git-ui/package.nls.json b/extensions/git-ui/package.nls.json deleted file mode 100644 index 413d891f..00000000 --- a/extensions/git-ui/package.nls.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "displayName": "Git UI", - "description": "Git SCM UI Integration" -} diff --git a/extensions/git-ui/resources/icons/git.png b/extensions/git-ui/resources/icons/git.png deleted file mode 100644 index 51f4ae5404fc79be09e69171bfc9d34d48810297..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2383 zcmb_edr(tX8b3FeJQNAIb;Tf>)JL(q3J4vLirl*vi*Kow20-DLSS9|3^Zwk7&^dy0_BKc76X2>}!()lOlVnV}tr z%7&jSS|mYX4~n%NS=f@&X(b^Q(N+N%0zF~9g{A- zZfSW+-Hr{m9%u8|2k$3dtage|2Sl;$h{sF(-i|c>B<|Qtz%K^PbRz=513=(`{A&g* z;RKU3B@gTnOaDvupQ!DT9hv|fD?ePvQ@0F%CkIDtqH+r6-`Pof?+-KO$>7G7NXXf@Nusk$YC-Nu- z23d{+;0ha8oHXp(O9h@TmW@CbU`rV z;l_K-qf92mZ;_k&oa6Cfm$b0End8ChmVX)f^pq##^aeu^Al9jBsZfODGUaf3a1>BU z#)l8$FxyesvmRxYX%&I`xjrI)@}}{Yz?WIexeH}QYZaSD3*0t5$8`+Ut3z}uuEv_l zb~=$F*(jZi!&FC0jtaAaNV1^#;yj+uX?0ZYbnwg26}mtz%iwT$HXX)ck)u$l7-(c|flTERxKM;g%Wl*nxU4yuBeLE+%mLQk3j#Q<_)GxC!`o6owL0SxvRSsR*zM_= zM;TNgl4km?o7x7!8}u>19+UmMGd!jky3e24KfUA+5jayP8JB}vti|+79AKDU-2xDY zqn+Hu(=dH4_b`}DuK6stpXcU@G9Z(UVjJC3;j2(CH&^;lDwRFv=gGOes*l+Q;vj%R z1CjmXXlAdvD}6z?*_cB>EeMeU3vbyDzxWD8a=-Y{)zbaNRsI}cXmGi}n|<>UAbYG9 z;xUVTfTo<;jjje( z<5@yEyyg^r9lW4kDOmWi7F$h4OvgW4*hR#{_R2TkNomGiT2@BGExcp}eH{5=tvPPr znrdcAhi9x>Cm;X5CI-$%(YABf@VI-Er7q>WS-MM&t|)rN1|93ssE#gv_%ypPosiRO zHauI2pZ{7#4Cak`9Bm+Eea_1Ri=F@QCrUo01C6qy1LOxLv(y%#4Rb1Cchij&G zr#4JZU%q$h=`k1i`eKFEX0D35ZPMpXFU?6-DzEpNF}+yks$ZJ7-sJd`US6|5H&c)= z?g+u?LxEVi_P~Y{L{FuKKRsbd!pg{zU6$E&)tT=G*E)_wv{c$(%`Lu27Pz|UsdXoo zog#W>2g$8C_+Cx>O@=}tkFwdxlOcOdgU!`~l$ABWSbdW+ZsxSDuMA~-A?WTkY=Uc%5xpg0$kgN=(W^ez> zH_0Zh@CX}tY3=ThcqDg~OVlqDK(!IiGN zC}T7iq-;&)B2ASvfD#5%fx7n~mIN!?Q47j>Zp`9ZwCvXL{U~GNpN-6MZ!^s|bb5-u zl{43z!4-)KlMrXIEQx3vDV*3C+$6xH%iJ^_k9EUe041;&y!qhF5f0HAh zFXfj)ECZw`K31%^WqqQaPbqdu$R@JyV3>73;@C#tjq&C3al~h1#r9#_?ZT-$)5Nk^ zmUQOPgk&zS|FArSgk}Pw6l3UjcW!>`-=XfV0hI_S$2b4{BL82 z2k(D``e93+SzOn9`ydHEt@(u*!(K_su{EJ|-OXKk#a^R&C_m@uwUJZEka5TH48ge_ vp%EL>Wi@Nxn8V*2x$L2D+u#MyDSqcb+P#5YHS3sP9I$26&gfeklTQ2-d^Cga diff --git a/extensions/git-ui/src/main.ts b/extensions/git-ui/src/main.ts deleted file mode 100644 index 98fdd15c..00000000 --- a/extensions/git-ui/src/main.ts +++ /dev/null @@ -1,57 +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 { ExtensionContext, commands } from 'vscode'; - -import * as cp from 'child_process'; - -export async function deactivate(): Promise { -} - -export async function activate(context: ExtensionContext): Promise { - context.subscriptions.push(commands.registerCommand('git.credential', async (data: any) => { - try { - const { stdout, stderr } = await exec(`git credential ${data.command}`, { - stdin: data.stdin, - env: Object.assign(process.env, { GIT_TERMINAL_PROMPT: '0' }) - }); - return { stdout, stderr, code: 0 }; - } catch ({ stdout, stderr, error }) { - const code = error.code || 0; - if (stderr.indexOf('terminal prompts disabled') !== -1) { - stderr = ''; - } - return { stdout, stderr, code }; - } - })); -} - -export interface ExecResult { - error: Error | null; - stdout: string; - stderr: string; -} - - -export function exec(command: string, options: cp.ExecOptions & { stdin?: string } = {}) { - return new Promise((resolve, reject) => { - const child = cp.exec(command, options, (error, stdout, stderr) => { - (error ? reject : resolve)({ error, stdout, stderr }); - }); - if (options.stdin) { - child.stdin!.write(options.stdin, (err: any) => { - if (err) { - reject(err); - return; - } - child.stdin!.end((err: any) => { - if (err) { - reject(err); - } - }); - }); - } - }); -} diff --git a/extensions/git-ui/src/typings/refs.d.ts b/extensions/git-ui/src/typings/refs.d.ts deleted file mode 100644 index a9334312..00000000 --- a/extensions/git-ui/src/typings/refs.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -/// -/// -/// \ No newline at end of file diff --git a/extensions/git-ui/tsconfig.json b/extensions/git-ui/tsconfig.json deleted file mode 100644 index 27e9268b..00000000 --- a/extensions/git-ui/tsconfig.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "extends": "../shared.tsconfig.json", - "compilerOptions": { - "outDir": "./out", - "experimentalDecorators": true, - "typeRoots": [ - "./node_modules/@types" - ] - }, - "include": [ - "src/**/*" - ] -} \ No newline at end of file diff --git a/extensions/git-ui/yarn.lock b/extensions/git-ui/yarn.lock deleted file mode 100644 index e03bdd57..00000000 --- a/extensions/git-ui/yarn.lock +++ /dev/null @@ -1,8 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@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== diff --git a/extensions/git/package.json b/extensions/git/package.json index 33e16333..7d5e81b2 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -26,6 +26,12 @@ "update-grammar": "node ./build/update-grammars.js", "test": "node ../../node_modules/mocha/bin/mocha" }, + "capabilities": { + "virtualWorkspaces": true, + "untrustedWorkspaces": { + "supported": true + } + }, "contributes": { "commands": [ { @@ -1686,19 +1692,10 @@ "default": true }, "git.autofetch": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "string", - "enum": [ - "all" - ] - } - ], + "type": ["boolean", "string"], + "enum": [true, false, "all"], "scope": "resource", - "description": "%config.autofetch%", + "markdownDescription": "%config.autofetch%", "default": false, "tags": [ "usesOnlineServices" @@ -1707,7 +1704,7 @@ "git.autofetchPeriod": { "type": "number", "scope": "resource", - "description": "%config.autofetchPeriod%", + "markdownDescription": "%config.autofetchPeriod%", "default": 180 }, "git.branchValidationRegex": { @@ -2378,7 +2375,7 @@ "byline": "^5.0.0", "file-type": "^7.2.0", "iconv-lite-umd": "0.6.8", - "jschardet": "2.2.1", + "jschardet": "2.3.0", "vscode-extension-telemetry": "0.1.7", "vscode-nls": "^4.0.0", "vscode-uri": "^2.0.0", diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index 43f3228a..28f9ae5a 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -101,7 +101,7 @@ "config.autoRepositoryDetection.openEditors": "Scan for parent folders of open files.", "config.autorefresh": "Whether auto refreshing is enabled.", "config.autofetch": "When set to true, commits will automatically be fetched from the default remote of the current Git repository. Setting to `all` will fetch from all remotes.", - "config.autofetchPeriod": "Duration in seconds between each automatic git fetch, when `git.autofetch` is enabled.", + "config.autofetchPeriod": "Duration in seconds between each automatic git fetch, when `#git.autofetch#` is enabled.", "config.confirmSync": "Confirm before synchronizing git repositories.", "config.countBadge": "Controls the Git count badge.", "config.countBadge.all": "Count all changes.", diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 6c9d0755..ff780988 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -43,18 +43,18 @@ class CheckoutItem implements QuickPickItem { class CheckoutTagItem extends CheckoutItem { - get description(): string { + override get description(): string { return localize('tag at', "Tag at {0}", this.shortCommit); } } class CheckoutRemoteHeadItem extends CheckoutItem { - get description(): string { + override get description(): string { return localize('remote branch at', "Remote branch at {0}", this.shortCommit); } - async run(repository: Repository, opts?: { detached?: boolean }): Promise { + override async run(repository: Repository, opts?: { detached?: boolean }): Promise { if (!this.ref.name) { return; } @@ -797,7 +797,7 @@ export class CommandCenter { return; } - const from = path.relative(repository.root, fromUri.path); + const from = path.relative(repository.root, fromUri.fsPath); let to = await window.showInputBox({ value: from, valueSelection: [from.length - path.basename(from).length, from.length] diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index f78f2cc3..ae318036 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -11,7 +11,7 @@ import * as which from 'which'; import { EventEmitter } from 'events'; import * as iconv from 'iconv-lite-umd'; import * as filetype from 'file-type'; -import { assign, groupBy, IDisposable, toDisposable, dispose, mkdirp, readBytes, detectUnicodeEncoding, Encoding, onceEvent, splitInChunks, Limiter } from './util'; +import { assign, groupBy, IDisposable, toDisposable, dispose, mkdirp, readBytes, detectUnicodeEncoding, Encoding, onceEvent, splitInChunks, Limiter, Versions } from './util'; import { CancellationToken, Progress, Uri } from 'vscode'; import { detectEncoding } from './encoding'; import { Ref, RefType, Branch, Remote, ForcePushMode, GitErrorCodes, LogOptions, Change, Status, CommitOptions, BranchQuery } from './api/git'; @@ -377,6 +377,10 @@ export class Git { this.env = options.env || {}; } + compareGitVersionTo(version: string): -1 | 0 | 1 { + return Versions.compare(Versions.fromString(this.version), Versions.fromString(version)); + } + open(repository: string, dotGit: string): Repository { return new Repository(this, repository, dotGit); } @@ -1977,7 +1981,16 @@ export class Repository { return this.getHEAD(); } - const args = ['for-each-ref', '--format=%(refname)%00%(upstream:short)%00%(upstream:track)%00%(objectname)']; + const args = ['for-each-ref']; + + let supportsAheadBehind = true; + if (this._git.compareGitVersionTo('1.9.0') === -1) { + args.push('--format=%(refname)%00%(upstream:short)%00%(objectname)'); + supportsAheadBehind = false; + } else { + args.push('--format=%(refname)%00%(upstream:short)%00%(objectname)%00%(upstream:track)'); + } + if (/^refs\/(head|remotes)\//i.test(name)) { args.push(name); } else { @@ -1986,7 +1999,7 @@ export class Repository { const result = await this.exec(args); const branches: Branch[] = result.stdout.trim().split('\n').map(line => { - let [branchName, upstream, status, ref] = line.trim().split('\0'); + let [branchName, upstream, ref, status] = line.trim().split('\0'); if (branchName.startsWith('refs/heads/')) { branchName = branchName.substring(11); @@ -2026,7 +2039,19 @@ export class Repository { }).filter((b?: Branch): b is Branch => !!b); if (branches.length) { - return branches[0]; + const [branch] = branches; + + if (!supportsAheadBehind && branch.upstream) { + try { + const result = await this.exec(['rev-list', '--left-right', '--count', `${branch.name}...${branch.upstream.remote}/${branch.upstream.name}`]); + const [ahead, behind] = result.stdout.trim().split('\t'); + + (branch as any).ahead = Number(ahead) || 0; + (branch as any).behind = Number(behind) || 0; + } catch { } + } + + return branch; } return Promise.reject(new Error('No such branch')); diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index cefe2384..37c0eb94 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -1835,7 +1835,10 @@ export class Repository implements Disposable { // noop } - const sort = config.get<'alphabetically' | 'committerdate'>('branchSortOrder') || 'alphabetically'; + let sort = config.get<'alphabetically' | 'committerdate'>('branchSortOrder') || 'alphabetically'; + if (sort !== 'alphabetically' && sort !== 'committerdate') { + sort = 'alphabetically'; + } const [refs, remotes, submodules, rebaseCommit] = await Promise.all([this.repository.getRefs({ sort }), this.repository.getRemotes(), this.repository.getSubmodules(), this.getRebaseCommit()]); this._HEAD = HEAD; diff --git a/extensions/git/src/test/index.ts b/extensions/git/src/test/index.ts index 3fe682fc..bed72035 100644 --- a/extensions/git/src/test/index.ts +++ b/extensions/git/src/test/index.ts @@ -8,7 +8,7 @@ const testRunner = require('../../../../test/integration/electron/testrunner'); const options: any = { ui: 'tdd', - color: (!process.env.BUILD_ARTIFACTSTAGINGDIRECTORY && process.platform !== 'win32'), + color: true, timeout: 60000 }; diff --git a/extensions/git/src/test/smoke.test.ts b/extensions/git/src/test/smoke.test.ts index c8c761bc..ed6a5d50 100644 --- a/extensions/git/src/test/smoke.test.ts +++ b/extensions/git/src/test/smoke.test.ts @@ -45,8 +45,10 @@ suite('git smoke test', function () { cp.execSync('git init', { cwd }); cp.execSync('git config user.name testuser', { cwd }); cp.execSync('git config user.email monacotools@microsoft.com', { cwd }); + cp.execSync('git config commit.gpgsign false', { cwd }); cp.execSync('git add .', { cwd }); cp.execSync('git commit -m "initial commit"', { cwd }); + cp.execSync('git branch -m main', { cwd }); // make sure git is activated const ext = extensions.getExtension('vscode.git'); @@ -124,7 +126,7 @@ suite('git smoke test', function () { assert.equal(repository.state.workingTreeChanges.length, 0); assert.equal(repository.state.indexChanges.length, 0); }); - + test('rename/delete conflict', async function () { cp.execSync('git branch test', { cwd }); cp.execSync('git checkout test', { cwd }); @@ -133,16 +135,16 @@ suite('git smoke test', function () { cp.execSync('git add .', { cwd }); await repository.commit('commit on test'); - cp.execSync('git checkout master', { cwd }); + cp.execSync('git checkout main', { cwd }); fs.renameSync(file('app.js'), file('rename.js')); cp.execSync('git add .', { cwd }); - await repository.commit('commit on master'); + await repository.commit('commit on main'); try { cp.execSync('git merge test', { cwd }); } catch (e) { } - + setTimeout(() => { commands.executeCommand('workbench.scm.focus'); }, 2e3); diff --git a/extensions/git/src/util.ts b/extensions/git/src/util.ts index 89c7cbe6..b5a46554 100644 --- a/extensions/git/src/util.ts +++ b/extensions/git/src/util.ts @@ -414,3 +414,56 @@ export class PromiseSource { } } } + +export namespace Versions { + declare type VersionComparisonResult = -1 | 0 | 1; + + export interface Version { + major: number; + minor: number; + patch: number; + pre?: string; + } + + export function compare(v1: string | Version, v2: string | Version): VersionComparisonResult { + if (typeof v1 === 'string') { + v1 = fromString(v1); + } + if (typeof v2 === 'string') { + v2 = fromString(v2); + } + + if (v1.major > v2.major) { return 1; } + if (v1.major < v2.major) { return -1; } + + if (v1.minor > v2.minor) { return 1; } + if (v1.minor < v2.minor) { return -1; } + + if (v1.patch > v2.patch) { return 1; } + if (v1.patch < v2.patch) { return -1; } + + if (v1.pre === undefined && v2.pre !== undefined) { return 1; } + if (v1.pre !== undefined && v2.pre === undefined) { return -1; } + + if (v1.pre !== undefined && v2.pre !== undefined) { + return v1.pre.localeCompare(v2.pre) as VersionComparisonResult; + } + + return 0; + } + + export function from(major: string | number, minor: string | number, patch?: string | number, pre?: string): Version { + return { + major: typeof major === 'string' ? parseInt(major, 10) : major, + minor: typeof minor === 'string' ? parseInt(minor, 10) : minor, + patch: patch === undefined || patch === null ? 0 : typeof patch === 'string' ? parseInt(patch, 10) : patch, + pre: pre, + }; + } + + export function fromString(version: string): Version { + const [ver, pre] = version.split('-'); + const [major, minor, patch] = ver.split('.'); + return from(major, minor, patch, pre); + } +} diff --git a/extensions/git/tsconfig.json b/extensions/git/tsconfig.json index 27e9268b..4e4f1252 100644 --- a/extensions/git/tsconfig.json +++ b/extensions/git/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../shared.tsconfig.json", + "extends": "../tsconfig.base.json", "compilerOptions": { "outDir": "./out", "experimentalDecorators": true, @@ -10,4 +10,4 @@ "include": [ "src/**/*" ] -} \ No newline at end of file +} diff --git a/extensions/git/yarn.lock b/extensions/git/yarn.lock index c60310b0..c964f284 100644 --- a/extensions/git/yarn.lock +++ b/extensions/git/yarn.lock @@ -117,10 +117,10 @@ isexe@^2.0.0: resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= -jschardet@2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/jschardet/-/jschardet-2.2.1.tgz#03b0264669a90c7a5c436a68c5a7d4e4cb0c9823" - integrity sha512-Ks2JNuUJoc7PGaZ7bVFtSEvOcr0rBq6Q1J5/7+zKWLT+g+4zziL63O0jg7y2jxhzIa1LVsHUbPXrbaWmz9iwDw== +jschardet@2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/jschardet/-/jschardet-2.3.0.tgz#06e2636e16c8ada36feebbdc08aa34e6a9b3ff75" + integrity sha512-6I6xT7XN/7sBB7q8ObzKbmv5vN+blzLcboDE1BNEsEfmRXJValMxO6OIRT69ylPBRemS3rw6US+CMCar0OBc9g== semver@^5.3.0: version "5.5.0" diff --git a/extensions/github-authentication/images/icon.png b/extensions/github-authentication/images/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..d20a3fbf98902f270cc842b7afcd97cffa42199a GIT binary patch literal 3658 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 literal 0 HcmV?d00001 diff --git a/extensions/github-authentication/package.json b/extensions/github-authentication/package.json index 4d664152..4a18a5bd 100644 --- a/extensions/github-authentication/package.json +++ b/extensions/github-authentication/package.json @@ -8,6 +8,7 @@ "engines": { "vscode": "^1.41.0" }, + "icon": "images/icon.png", "enableProposedApi": true, "categories": [ "Other" @@ -20,6 +21,12 @@ "activationEvents": [ "onAuthenticationRequest:github" ], + "capabilities": { + "virtualWorkspaces": true, + "untrustedWorkspaces": { + "supported": true + } + }, "contributes": { "commands": [ { @@ -56,7 +63,8 @@ "node-fetch": "2.6.1", "uuid": "8.1.0", "vscode-extension-telemetry": "0.1.7", - "vscode-nls": "^4.1.2" + "vscode-nls": "^4.1.2", + "vscode-tas-client": "^0.1.22" }, "devDependencies": { "@types/node": "^12.19.9", diff --git a/extensions/github-authentication/src/experimentationService.ts b/extensions/github-authentication/src/experimentationService.ts new file mode 100644 index 00000000..8d66edbd --- /dev/null +++ b/extensions/github-authentication/src/experimentationService.ts @@ -0,0 +1,73 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import TelemetryReporter from 'vscode-extension-telemetry'; +import { getExperimentationService, IExperimentationService, IExperimentationTelemetry, TargetPopulation } from 'vscode-tas-client'; + +export class ExperimentationTelemetry implements IExperimentationTelemetry { + private sharedProperties: Record = {}; + + constructor(private baseReporter: TelemetryReporter) { } + + sendTelemetryEvent(eventName: string, properties?: Record, measurements?: Record) { + this.baseReporter.sendTelemetryEvent( + eventName, + { + ...this.sharedProperties, + ...properties, + }, + measurements, + ); + } + + sendTelemetryErrorEvent( + eventName: string, + properties?: Record, + _measurements?: Record, + ) { + this.baseReporter.sendTelemetryErrorEvent(eventName, { + ...this.sharedProperties, + ...properties, + }); + } + + setSharedProperty(name: string, value: string): void { + this.sharedProperties[name] = value; + } + + postEvent(eventName: string, props: Map): void { + const event: Record = {}; + for (const [key, value] of props) { + event[key] = value; + } + this.sendTelemetryEvent(eventName, event); + } + + dispose(): Promise { + return this.baseReporter.dispose(); + } +} + +function getTargetPopulation(): TargetPopulation { + switch (vscode.env.uriScheme) { + case 'vscode': + return TargetPopulation.Public; + case 'vscode-insiders': + return TargetPopulation.Insiders; + case 'vscode-exploration': + return TargetPopulation.Internal; + case 'code-oss': + return TargetPopulation.Team; + default: + return TargetPopulation.Public; + } +} + +export async function createExperimentationService(context: vscode.ExtensionContext, telemetry: ExperimentationTelemetry): Promise { + const id = context.extension.id; + const version = context.extension.packageJSON.version; + return getExperimentationService(id, version, getTargetPopulation(), telemetry, context.globalState); +} diff --git a/extensions/github-authentication/src/extension.ts b/extensions/github-authentication/src/extension.ts index 7ae1e1d2..253f3aec 100644 --- a/extensions/github-authentication/src/extension.ts +++ b/extensions/github-authentication/src/extension.ts @@ -8,10 +8,14 @@ import { GitHubAuthenticationProvider, onDidChangeSessions } from './github'; import { uriHandler } from './githubServer'; import Logger from './common/logger'; import TelemetryReporter from 'vscode-extension-telemetry'; +import { createExperimentationService, ExperimentationTelemetry } from './experimentationService'; export async function activate(context: vscode.ExtensionContext) { const { name, version, aiKey } = require('../package.json') as { name: string, version: string, aiKey: string }; - const telemetryReporter = new TelemetryReporter(name, version, aiKey); + const telemetryReporter = new ExperimentationTelemetry(new TelemetryReporter(name, version, aiKey)); + + const experimentationService = await createExperimentationService(context, telemetryReporter); + await experimentationService.initialFetch; context.subscriptions.push(vscode.window.registerUriHandler(uriHandler)); const loginService = new GitHubAuthenticationProvider(context, telemetryReporter); diff --git a/extensions/github-authentication/src/github.ts b/extensions/github-authentication/src/github.ts index 595d1314..4d3573a7 100644 --- a/extensions/github-authentication/src/github.ts +++ b/extensions/github-authentication/src/github.ts @@ -9,7 +9,7 @@ import { Keychain } from './common/keychain'; import { GitHubServer, NETWORK_ERROR } from './githubServer'; import Logger from './common/logger'; import { arrayEquals } from './common/utils'; -import TelemetryReporter from 'vscode-extension-telemetry'; +import { ExperimentationTelemetry } from './experimentationService'; export const onDidChangeSessions = new vscode.EventEmitter(); @@ -30,7 +30,7 @@ export class GitHubAuthenticationProvider { private _keychain: Keychain; - constructor(context: vscode.ExtensionContext, telemetryReporter: TelemetryReporter) { + constructor(context: vscode.ExtensionContext, telemetryReporter: ExperimentationTelemetry) { this._keychain = new Keychain(context); this._githubServer = new GitHubServer(telemetryReporter); } diff --git a/extensions/github-authentication/src/githubServer.ts b/extensions/github-authentication/src/githubServer.ts index 2d7dd3f8..40877eff 100644 --- a/extensions/github-authentication/src/githubServer.ts +++ b/extensions/github-authentication/src/githubServer.ts @@ -9,12 +9,13 @@ import fetch, { Response } from 'node-fetch'; import { v4 as uuid } from 'uuid'; import { PromiseAdapter, promiseFromEvent } from './common/utils'; import Logger from './common/logger'; -import TelemetryReporter from 'vscode-extension-telemetry'; +import { ExperimentationTelemetry } from './experimentationService'; const localize = nls.loadMessageBundle(); export const NETWORK_ERROR = 'network error'; const AUTH_RELAY_SERVER = 'vscode-auth.github.com'; +// const AUTH_RELAY_STAGING_SERVER = 'client-auth-staging-14a768b.herokuapp.com'; class UriEventHandler extends vscode.EventEmitter implements vscode.UriHandler { public handleUri(uri: vscode.Uri) { @@ -42,10 +43,16 @@ export class GitHubServer { private _pendingStates = new Map(); private _codeExchangePromises = new Map, cancel: vscode.EventEmitter }>(); - constructor(private readonly telemetryReporter: TelemetryReporter) { } + constructor(private readonly telemetryReporter: ExperimentationTelemetry) { } private isTestEnvironment(url: vscode.Uri): boolean { - return url.authority === 'vscode-web-test-playground.azurewebsites.net' || url.authority.startsWith('localhost:'); + return /\.azurewebsites\.net$/.test(url.authority) || url.authority.startsWith('localhost:'); + } + + // TODO@joaomoreno TODO@RMacfarlane + private async isNoCorsEnvironment(): Promise { + const uri = await vscode.env.asExternalUri(vscode.Uri.parse(`${vscode.env.uriScheme}://vscode.github-authentication/did-authenticate`)); + return uri.scheme === 'https' && /^vscode\./.test(uri.authority); } public async login(scopes: string): Promise { @@ -53,7 +60,10 @@ export class GitHubServer { this.updateStatusBarItem(true); const state = uuid(); - const callbackUri = await vscode.env.asExternalUri(vscode.Uri.parse(`${vscode.env.uriScheme}://vscode.github-authentication/did-authenticate`)); + + // TODO@joaomoreno TODO@RMacfarlane + const nocors = await this.isNoCorsEnvironment(); + const callbackUri = await vscode.env.asExternalUri(vscode.Uri.parse(`${vscode.env.uriScheme}://vscode.github-authentication/did-authenticate${nocors ? '?nocors=true' : ''}`)); if (this.isTestEnvironment(callbackUri)) { const token = await vscode.window.showInputBox({ prompt: 'GitHub Personal Access Token', ignoreFocusOut: true }); @@ -80,7 +90,7 @@ export class GitHubServer { const existingStates = this._pendingStates.get(scopes) || []; this._pendingStates.set(scopes, [...existingStates, state]); - const uri = vscode.Uri.parse(`https://${AUTH_RELAY_SERVER}/authorize/?callbackUri=${encodeURIComponent(callbackUri.toString())}&scope=${scopes}&state=${state}&responseType=code&authServer=https://github.com`); + const uri = vscode.Uri.parse(`https://${AUTH_RELAY_SERVER}/authorize/?callbackUri=${encodeURIComponent(callbackUri.toString())}&scope=${scopes}&state=${state}&responseType=code&authServer=https://github.com${nocors ? '&nocors=true' : ''}`); await vscode.env.openExternal(uri); } @@ -121,23 +131,36 @@ export class GitHubServer { return; } - try { - const result = await fetch(`https://${AUTH_RELAY_SERVER}/token?code=${code}&state=${query.state}`, { - method: 'POST', - headers: { - Accept: 'application/json' - } - }); + const url = `https://${AUTH_RELAY_SERVER}/token?code=${code}&state=${query.state}`; - if (result.ok) { - const json = await result.json(); + // TODO@joao: remove + if (query.nocors) { + try { + const json: any = await vscode.commands.executeCommand('_workbench.fetchJSON', url, 'POST'); Logger.info('Token exchange success!'); resolve(json.access_token); - } else { - reject(result.statusText); + } catch (err) { + reject(err); + } + } else { + try { + const result = await fetch(url, { + method: 'POST', + headers: { + Accept: 'application/json' + } + }); + + if (result.ok) { + const json = await result.json(); + Logger.info('Token exchange success!'); + resolve(json.access_token); + } else { + reject(result.statusText); + } + } catch (ex) { + reject(ex); } - } catch (ex) { - reject(ex); } }; @@ -222,6 +245,12 @@ export class GitHubServer { } public async checkIsEdu(token: string): Promise { + const nocors = await this.isNoCorsEnvironment(); + + if (nocors) { + return; + } + try { const result = await fetch('https://education.github.com/api/user', { headers: { diff --git a/extensions/github-authentication/tsconfig.json b/extensions/github-authentication/tsconfig.json index 12257093..4e4f1252 100644 --- a/extensions/github-authentication/tsconfig.json +++ b/extensions/github-authentication/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../shared.tsconfig.json", + "extends": "../tsconfig.base.json", "compilerOptions": { "outDir": "./out", "experimentalDecorators": true, diff --git a/extensions/github-authentication/yarn.lock b/extensions/github-authentication/yarn.lock index 4ba71269..8095e574 100644 --- a/extensions/github-authentication/yarn.lock +++ b/extensions/github-authentication/yarn.lock @@ -55,6 +55,13 @@ asynckit@^0.4.0: resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= +axios@^0.21.1: + version "0.21.1" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8" + integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA== + dependencies: + follow-redirects "^1.10.0" + cls-hooked@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/cls-hooked/-/cls-hooked-4.2.2.tgz#ad2e9a4092680cdaffeb2d3551da0e225eae1908" @@ -103,6 +110,11 @@ emitter-listener@^1.0.1, emitter-listener@^1.1.1: dependencies: shimmer "^1.2.0" +follow-redirects@^1.10.0: + version "1.13.3" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.3.tgz#e5598ad50174c1bc4e872301e82ac2cd97f90267" + integrity sha512-DUgl6+HDzB0iEptNQEXLx/KhTmDb8tZUHSeLqpnjpknR70H0nC2t9N73BK6fN4hOvJ84pKlIQVQ4k5FFlBedKA== + form-data@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.0.tgz#31b7e39c85f1355b7139ee0c647cf0de7f83c682" @@ -144,6 +156,13 @@ stack-chain@^1.3.7: resolved "https://registry.yarnpkg.com/stack-chain/-/stack-chain-1.3.7.tgz#d192c9ff4ea6a22c94c4dd459171e3f00cea1285" integrity sha1-0ZLJ/06moiyUxN1FkXHj8AzqEoU= +tas-client@0.1.21: + version "0.1.21" + resolved "https://registry.yarnpkg.com/tas-client/-/tas-client-0.1.21.tgz#62275d5f75266eaae408f7463364748cb92f220d" + integrity sha512-7UuIwOXarCYoCTrQHY5n7M+63XuwMC0sVUdbPQzxqDB9wMjIW0JF39dnp3yoJnxr4jJUVhPtvkkXZbAD0BxCcA== + dependencies: + axios "^0.21.1" + uuid@8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.1.0.tgz#6f1536eb43249f473abc6bd58ff983da1ca30d8d" @@ -160,3 +179,10 @@ vscode-nls@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.1.2.tgz#ca8bf8bb82a0987b32801f9fddfdd2fb9fd3c167" integrity sha512-7bOHxPsfyuCqmP+hZXscLhiHwe7CSuFE4hyhbs22xPIhQ4jv99FcR4eBzfYYVLP356HNFpdvz63FFb/xw6T4Iw== + +vscode-tas-client@^0.1.22: + version "0.1.22" + resolved "https://registry.yarnpkg.com/vscode-tas-client/-/vscode-tas-client-0.1.22.tgz#2dd674b21a94ff4e97db2b6545d9efda8b5f07c3" + integrity sha512-1sYH73nhiSRVQgfZkLQNJW7VzhKM9qNbCe8QyXgiKkLhH4GflDXRPAK4yy4P41jUgula+Fc9G7i5imj1dlKfaw== + dependencies: + tas-client "0.1.21" diff --git a/extensions/github/images/icon.png b/extensions/github/images/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..d706dc83e7a7fce76157a8dfd5be1720e2f9ceb1 GIT binary patch literal 3657 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 a6QCh=sf&*Aiq0&d+j)Goe(3H=XYWEMpL literal 0 HcmV?d00001 diff --git a/extensions/github/package.json b/extensions/github/package.json index f6e7cd6b..8f67314b 100644 --- a/extensions/github/package.json +++ b/extensions/github/package.json @@ -8,6 +8,7 @@ "engines": { "vscode": "^1.41.0" }, + "icon": "images/icon.png", "enableProposedApi": true, "categories": [ "Other" @@ -19,6 +20,12 @@ "vscode.git" ], "main": "./out/extension.js", + "capabilities": { + "virtualWorkspaces": false, + "untrustedWorkspaces": { + "supported": true + } + }, "contributes": { "commands": [ { diff --git a/extensions/github/src/pushErrorHandler.ts b/extensions/github/src/pushErrorHandler.ts index 30a5c64d..1df4beae 100644 --- a/extensions/github/src/pushErrorHandler.ts +++ b/extensions/github/src/pushErrorHandler.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { commands, env, ProgressLocation, UIKind, Uri, window } from 'vscode'; +import { commands, env, ProgressLocation, Uri, window } from 'vscode'; import * as nls from 'vscode-nls'; import { getOctokit } from './auth'; import { GitErrorCodes, PushErrorHandler, Remote, Repository } from './typings/git'; @@ -17,33 +17,6 @@ export function isInCodespaces(): boolean { } async function handlePushError(repository: Repository, remote: Remote, refspec: string, owner: string, repo: string): Promise { - const inCodespaces = isInCodespaces(); - let codespace: string | undefined; - if (inCodespaces) { - if (env.uiKind === UIKind.Web) { - // TODO@eamodio Find a better way to get the codespace id - // HACK to get the codespace id - try { - const codespaceUrl = (await env.asExternalUri(Uri.parse(`${env.uriScheme}://codespace/`))).authority; - if (codespaceUrl.endsWith('.github.dev')) { - codespace = codespaceUrl.slice(0, -11); - } else { - [codespace] = codespaceUrl.split('.'); - } - } catch { } - } else { - // Call into the codespaces extension to get the codespace id - const info = await commands.executeCommand<{ name: string } | undefined>('github.codespaces.getCurrentCodespace'); - codespace = info?.name; - } - - if (!codespace) { - const ok = localize('ok', "OK"); - await window.showErrorMessage(localize('fork unable', "You don't have permissions to push to '{0}/{1}' on GitHub.", owner, repo), ok); - return; - } - } - const yes = localize('create a fork', "Create Fork"); const no = localize('no', "No"); @@ -66,15 +39,17 @@ async function handlePushError(repository: Repository, remote: Remote, refspec: // Issue: what if the repo already exists? let ghRepository: CreateForkResponseData; try { - if (inCodespaces) { - const userResp = await octokit.users.getAuthenticated(); - const user = userResp.data.login; + if (isInCodespaces()) { + // Call into the codespaces extension to fork the repository + const resp = await commands.executeCommand<{ repository: CreateForkResponseData, ref: string }>('github.codespaces.forkRepository'); + if (!resp) { + throw new Error('Unable to fork respository'); + } - const resp = await octokit.request<{ repository: CreateForkResponseData, ref: string }>({ method: 'POST', url: `/vscs_internal/user/${user}/codespaces/${codespace}/fork_repo` }); - ghRepository = resp.data.repository; + ghRepository = resp.repository; - if (resp.data.ref) { - let ref = resp.data.ref; + if (resp.ref) { + let ref = resp.ref; if (ref.startsWith('refs/heads/')) { ref = ref.substr(11); } @@ -90,7 +65,6 @@ async function handlePushError(repository: Repository, remote: Remote, refspec: throw ex; } - progress.report({ message: localize('forking_pushing', "Pushing changes..."), increment: 33 }); // Issue: what if there's already an `upstream` repo? @@ -166,9 +140,7 @@ export class GithubPushErrorHandler implements PushErrorHandler { return false; } - const match = /^https:\/\/github\.com\/([^/]+)\/([^/]+)(?:\.git)?/i.exec(remoteUrl) - || /^git@github\.com:([^/]+)\/([^/]+)(?:\.git)?/i.exec(remoteUrl); - + const match = /^(?:https:\/\/github\.com\/|git@github\.com:)([^/]+)\/([^/.]+)(?:\.git)?$/i.exec(remoteUrl); if (!match) { return false; } diff --git a/extensions/github/src/remoteSourceProvider.ts b/extensions/github/src/remoteSourceProvider.ts index 7b41c729..072f4e51 100644 --- a/extensions/github/src/remoteSourceProvider.ts +++ b/extensions/github/src/remoteSourceProvider.ts @@ -45,8 +45,8 @@ export class GithubRemoteSourceProvider implements RemoteSourceProvider { } const all = await Promise.all([ + this.getQueryRemoteSources(octokit, query), this.getUserRemoteSources(octokit, query), - this.getQueryRemoteSources(octokit, query) ]); const map = new Map(); diff --git a/extensions/github/tsconfig.json b/extensions/github/tsconfig.json index 12257093..4e4f1252 100644 --- a/extensions/github/tsconfig.json +++ b/extensions/github/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../shared.tsconfig.json", + "extends": "../tsconfig.base.json", "compilerOptions": { "outDir": "./out", "experimentalDecorators": true, diff --git a/extensions/grunt/package.json b/extensions/grunt/package.json index b5a46f91..d7fbd50e 100644 --- a/extensions/grunt/package.json +++ b/extensions/grunt/package.json @@ -26,6 +26,12 @@ "activationEvents": [ "onCommand:workbench.action.tasks.runTask" ], + "capabilities": { + "virtualWorkspaces": false, + "untrustedWorkspaces": { + "supported": true + } + }, "contributes": { "configuration": { "id": "grunt", @@ -63,7 +69,8 @@ "type": "string", "description": "%grunt.taskDefinition.file.description%" } - } + }, + "when": "shellExecutionSupported" } ] }, diff --git a/extensions/grunt/tsconfig.json b/extensions/grunt/tsconfig.json index 296ddb38..070854d6 100644 --- a/extensions/grunt/tsconfig.json +++ b/extensions/grunt/tsconfig.json @@ -1,9 +1,9 @@ { - "extends": "../shared.tsconfig.json", + "extends": "../tsconfig.base.json", "compilerOptions": { "outDir": "./out" }, "include": [ "src/**/*" ] -} \ No newline at end of file +} diff --git a/extensions/gulp/package.json b/extensions/gulp/package.json index 116e1c55..2fb8d583 100644 --- a/extensions/gulp/package.json +++ b/extensions/gulp/package.json @@ -26,6 +26,12 @@ "activationEvents": [ "onCommand:workbench.action.tasks.runTask" ], + "capabilities": { + "virtualWorkspaces": false, + "untrustedWorkspaces": { + "supported": true + } + }, "contributes": { "configuration": { "id": "gulp", @@ -59,7 +65,8 @@ "type": "string", "description": "%gulp.taskDefinition.file.description%" } - } + }, + "when": "shellExecutionSupported" } ] }, diff --git a/extensions/gulp/tsconfig.json b/extensions/gulp/tsconfig.json index 296ddb38..070854d6 100644 --- a/extensions/gulp/tsconfig.json +++ b/extensions/gulp/tsconfig.json @@ -1,9 +1,9 @@ { - "extends": "../shared.tsconfig.json", + "extends": "../tsconfig.base.json", "compilerOptions": { "outDir": "./out" }, "include": [ "src/**/*" ] -} \ No newline at end of file +} diff --git a/extensions/html-language-features/client/tsconfig.json b/extensions/html-language-features/client/tsconfig.json index 3203105c..8b4aedde 100644 --- a/extensions/html-language-features/client/tsconfig.json +++ b/extensions/html-language-features/client/tsconfig.json @@ -1,9 +1,9 @@ { - "extends": "../../shared.tsconfig.json", + "extends": "../../tsconfig.base.json", "compilerOptions": { "outDir": "./out" }, "include": [ "src/**/*" ] -} \ No newline at end of file +} diff --git a/extensions/html-language-features/package.json b/extensions/html-language-features/package.json index a6b0aab9..9fa6bc31 100644 --- a/extensions/html-language-features/package.json +++ b/extensions/html-language-features/package.json @@ -17,6 +17,12 @@ ], "main": "./client/out/node/htmlClientMain", "browser": "./client/dist/browser/htmlClientMain", + "capabilities": { + "virtualWorkspaces": true, + "untrustedWorkspaces": { + "supported": true + } + }, "scripts": { "compile": "npx gulp compile-extension:html-language-features-client compile-extension:html-language-features-server", "watch": "npx gulp watch-extension:html-language-features-client watch-extension:html-language-features-server", diff --git a/extensions/html-language-features/server/package.json b/extensions/html-language-features/server/package.json index efb68785..fdb29285 100644 --- a/extensions/html-language-features/server/package.json +++ b/extensions/html-language-features/server/package.json @@ -9,10 +9,10 @@ }, "main": "./out/node/htmlServerMain", "dependencies": { - "vscode-css-languageservice": "^5.1.0", - "vscode-html-languageservice": "^4.0.2", - "vscode-languageserver-textdocument": "^1.0.1", + "vscode-css-languageservice": "^5.1.1", + "vscode-html-languageservice": "^4.0.3", "vscode-languageserver": "^7.0.0", + "vscode-languageserver-textdocument": "^1.0.1", "vscode-nls": "^5.0.0", "vscode-uri": "^3.0.2" }, diff --git a/extensions/html-language-features/server/src/modes/javascriptMode.ts b/extensions/html-language-features/server/src/modes/javascriptMode.ts index a17cd4c8..01e0b381 100644 --- a/extensions/html-language-features/server/src/modes/javascriptMode.ts +++ b/extensions/html-language-features/server/src/modes/javascriptMode.ts @@ -127,7 +127,8 @@ export function getJavaScriptMode(documentRegions: LanguageModelCache { const jsDocument = jsDocuments.get(document); const jsLanguageService = await host.getLanguageService(jsDocument); - let details = jsLanguageService.getCompletionEntryDetails(jsDocument.uri, item.data.offset, item.label, undefined, undefined, undefined); + // @ts-expect-error until 4.3 protocol update + let details = jsLanguageService.getCompletionEntryDetails(jsDocument.uri, item.data.offset, item.label, undefined, undefined, undefined, undefined); if (details) { item.detail = ts.displayPartsToString(details.displayParts); item.documentation = ts.displayPartsToString(details.documentation); diff --git a/extensions/html-language-features/server/test/index.js b/extensions/html-language-features/server/test/index.js index 5f7aa21e..50e250b7 100644 --- a/extensions/html-language-features/server/test/index.js +++ b/extensions/html-language-features/server/test/index.js @@ -11,7 +11,7 @@ const suite = 'Integration HTML Extension Tests'; const options = { ui: 'tdd', - useColors: (!process.env.BUILD_ARTIFACTSTAGINGDIRECTORY && process.platform !== 'win32'), + color: true, timeout: 60000 }; diff --git a/extensions/html-language-features/server/tsconfig.json b/extensions/html-language-features/server/tsconfig.json index 3203105c..8b4aedde 100644 --- a/extensions/html-language-features/server/tsconfig.json +++ b/extensions/html-language-features/server/tsconfig.json @@ -1,9 +1,9 @@ { - "extends": "../../shared.tsconfig.json", + "extends": "../../tsconfig.base.json", "compilerOptions": { "outDir": "./out" }, "include": [ "src/**/*" ] -} \ No newline at end of file +} diff --git a/extensions/html-language-features/server/yarn.lock b/extensions/html-language-features/server/yarn.lock index 8925944a..ca035d1c 100644 --- a/extensions/html-language-features/server/yarn.lock +++ b/extensions/html-language-features/server/yarn.lock @@ -12,20 +12,20 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-12.19.9.tgz#990ad687ad8b26ef6dcc34a4f69c33d40c95b679" integrity sha512-yj0DOaQeUrk3nJ0bd3Y5PeDRJ6W0r+kilosLA+dzF3dola/o9hxhMSg2sFvVcA2UHS5JSOsZp4S0c1OEXc4m1Q== -vscode-css-languageservice@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-5.1.0.tgz#cd172d13e9e7ae23ba567c73778aee10475ff716" - integrity sha512-iLHd/WjRKgaZBXMNeUooHG+r0qlhJBkXa+3MpQQR6Rpm928cis/3OV2Mp1R80yAQevIMeDL32RIJfHoJCT/RRg== +vscode-css-languageservice@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-5.1.1.tgz#d68a22ea0b34a8356c169cafc7d32564c2ff6e87" + integrity sha512-QW0oe/g2y5E2AbVqY7FJNr2Q8uHiAHNSFpptI6xB8Y0KgzVKppOcIVrgmBNzXhFp9IswAwptkdqr8ExSJbqPkQ== dependencies: vscode-languageserver-textdocument "^1.0.1" vscode-languageserver-types "^3.16.0" vscode-nls "^5.0.0" vscode-uri "^3.0.2" -vscode-html-languageservice@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/vscode-html-languageservice/-/vscode-html-languageservice-4.0.2.tgz#e0a02975b7795b409b13a545b8954c0b6e5e8345" - integrity sha512-Kin07sTZ9FkZNcNMXtoUkmmDtZHsp1hShatFUvqx07ySz1BeXDyWyvdWY/fxk2Mc/ApfQ9rmK8YyhqUotLZ8GQ== +vscode-html-languageservice@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/vscode-html-languageservice/-/vscode-html-languageservice-4.0.3.tgz#3b7e7d3cfee75d47da0181dd638b5f459456a913" + integrity sha512-34KPIgRHVInT+TiFNmfiPFDrUAOOLuySNP2h0pMxBu1ObAbSixSqB3BMQFxIHz9hrGd3X0DEvi5YkobDxs4rWw== dependencies: vscode-languageserver-textdocument "^1.0.1" vscode-languageserver-types "^3.16.0" diff --git a/extensions/image-preview/icon.png b/extensions/image-preview/icon.png index 64dcf7d463c2678de271c4bae155092982e9bd50..7c90a41cd4dc7029862b19b832fd61567c6a048f 100644 GIT binary patch literal 1889 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 diff --git a/extensions/image-preview/package.json b/extensions/image-preview/package.json index b61b9be4..844d2554 100644 --- a/extensions/image-preview/package.json +++ b/extensions/image-preview/package.json @@ -26,6 +26,12 @@ "onCommand:imagePreview.zoomIn", "onCommand:imagePreview.zoomOut" ], + "capabilities": { + "virtualWorkspaces": true, + "untrustedWorkspaces": { + "supported": true + } + }, "contributes": { "customEditors": [ { diff --git a/extensions/image-preview/src/preview.ts b/extensions/image-preview/src/preview.ts index f4b589e0..6d61c989 100644 --- a/extensions/image-preview/src/preview.ts +++ b/extensions/image-preview/src/preview.ts @@ -212,6 +212,7 @@ class Preview extends Disposable { const nonce = Date.now().toString(); + const cspSource = this.webviewEditor.webview.cspSource; return /* html */` @@ -225,7 +226,7 @@ class Preview extends Disposable { - + diff --git a/extensions/image-preview/tsconfig.json b/extensions/image-preview/tsconfig.json index d0797aff..f34c085e 100644 --- a/extensions/image-preview/tsconfig.json +++ b/extensions/image-preview/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../shared.tsconfig.json", + "extends": "../tsconfig.base.json", "compilerOptions": { "outDir": "./out", "experimentalDecorators": true diff --git a/extensions/jake/package.json b/extensions/jake/package.json index 0c1f0c97..ce19974e 100644 --- a/extensions/jake/package.json +++ b/extensions/jake/package.json @@ -26,6 +26,12 @@ "activationEvents": [ "onCommand:workbench.action.tasks.runTask" ], + "capabilities": { + "virtualWorkspaces": false, + "untrustedWorkspaces": { + "supported": true + } + }, "contributes": { "configuration": { "id": "jake", @@ -59,7 +65,8 @@ "type": "string", "description": "%jake.taskDefinition.file.description%" } - } + }, + "when": "shellExecutionSupported" } ] }, diff --git a/extensions/jake/tsconfig.json b/extensions/jake/tsconfig.json index 296ddb38..070854d6 100644 --- a/extensions/jake/tsconfig.json +++ b/extensions/jake/tsconfig.json @@ -1,9 +1,9 @@ { - "extends": "../shared.tsconfig.json", + "extends": "../tsconfig.base.json", "compilerOptions": { "outDir": "./out" }, "include": [ "src/**/*" ] -} \ No newline at end of file +} diff --git a/extensions/javascript/syntaxes/JavaScript.tmLanguage.json b/extensions/javascript/syntaxes/JavaScript.tmLanguage.json index 2d7a18e6..a287aaf1 100644 --- a/extensions/javascript/syntaxes/JavaScript.tmLanguage.json +++ b/extensions/javascript/syntaxes/JavaScript.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/microsoft/TypeScript-TmLanguage/commit/d3c203d25a1df5def83082a47d1fe20fda2e80df", + "version": "https://github.com/microsoft/TypeScript-TmLanguage/commit/634a12f4fa2b2843c83353c0a395852c3a0c04c4", "name": "JavaScript (with React support)", "scopeName": "source.js", "patterns": [ @@ -873,7 +873,7 @@ "parameter-name": { "patterns": [ { - "match": "(?)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "match": "(?x)(?:(?)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "captures": { "1": { "name": "storage.modifier.js" @@ -901,7 +901,7 @@ } }, { - "match": "(?x)(?:(?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*))?[\\(])", + "begin": "(?x)(?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*))?[\\(])", "beginCaptures": { "1": { "name": "storage.modifier.js" @@ -1313,12 +1316,15 @@ "name": "storage.modifier.js" }, "3": { - "name": "storage.modifier.async.js" + "name": "storage.modifier.js" }, "4": { - "name": "keyword.operator.new.js" + "name": "storage.modifier.async.js" }, "5": { + "name": "keyword.operator.new.js" + }, + "6": { "name": "keyword.generator.asterisk.js" } }, @@ -1334,7 +1340,7 @@ }, { "name": "meta.method.declaration.js", - "begin": "(?x)(?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*))?[\\(])", + "begin": "(?x)(?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*))?[\\(])", "beginCaptures": { "1": { "name": "storage.modifier.js" @@ -1343,12 +1349,15 @@ "name": "storage.modifier.js" }, "3": { - "name": "storage.modifier.async.js" + "name": "storage.modifier.js" }, "4": { - "name": "storage.type.property.js" + "name": "storage.modifier.async.js" }, "5": { + "name": "storage.type.property.js" + }, + "6": { "name": "keyword.generator.asterisk.js" } }, @@ -1849,7 +1858,7 @@ }, "access-modifier": { "name": "storage.modifier.js", - "match": "(?)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "match": "(?x)(?:(?)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "captures": { "1": { "name": "storage.modifier.js" @@ -3112,7 +3121,7 @@ } }, { - "match": "(?x)(?:(?]|$|;|^\\s*$|(?:^\\s*(?:abstract|async|class|const|declare|enum|export|function|import|interface|let|module|namespace|return|type|var)\\b))", + "end": "(?=[,);}\\]=>:&|{\\?]|$|;|^\\s*$|(?:^\\s*(?:abstract|async|class|const|declare|enum|export|function|import|interface|let|module|namespace|return|type|var)\\b))", "patterns": [ { "include": "#expression" @@ -4264,9 +4273,12 @@ "type-fn-type-parameters": { "patterns": [ { - "begin": "(?|&&|\\|\\||\\*\\/)\\s*(\\/)(?![\\/*])(?=(?:[^\\/\\\\\\[\\()]|\\\\.|\\[([^\\]\\\\]|\\\\.)+\\]|\\(([^\\)\\\\]|\\\\.)+\\))+\\/([gimsuy]+|(?![\\/\\*])|(?=\\/\\*))(?!\\s*[a-zA-Z0-9_$]))", + "begin": "(?|&&|\\|\\||\\*\\/)\\s*(\\/)(?![\\/*])(?=(?:[^\\/\\\\\\[\\()]|\\\\.|\\[([^\\]\\\\]|\\\\.)+\\]|\\(([^\\)\\\\]|\\\\.)+\\))+\\/([dgimsuy]+|(?![\\/\\*])|(?=\\/\\*))(?!\\s*[a-zA-Z0-9_$]))", "beginCaptures": { "1": { "name": "punctuation.definition.string.begin.js" } }, - "end": "(/)([gimsuy]*)", + "end": "(/)([dgimsuy]*)", "endCaptures": { "1": { "name": "punctuation.definition.string.end.js" @@ -4802,13 +4817,13 @@ }, { "name": "string.regexp.js", - "begin": "((?)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "match": "(?x)(?:(?)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "captures": { "1": { "name": "storage.modifier.js.jsx" @@ -901,7 +901,7 @@ } }, { - "match": "(?x)(?:(?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*))?[\\(])", + "begin": "(?x)(?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*))?[\\(])", "beginCaptures": { "1": { "name": "storage.modifier.js.jsx" @@ -1313,12 +1316,15 @@ "name": "storage.modifier.js.jsx" }, "3": { - "name": "storage.modifier.async.js.jsx" + "name": "storage.modifier.js.jsx" }, "4": { - "name": "keyword.operator.new.js.jsx" + "name": "storage.modifier.async.js.jsx" }, "5": { + "name": "keyword.operator.new.js.jsx" + }, + "6": { "name": "keyword.generator.asterisk.js.jsx" } }, @@ -1334,7 +1340,7 @@ }, { "name": "meta.method.declaration.js.jsx", - "begin": "(?x)(?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*))?[\\(])", + "begin": "(?x)(?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*))?[\\(])", "beginCaptures": { "1": { "name": "storage.modifier.js.jsx" @@ -1343,12 +1349,15 @@ "name": "storage.modifier.js.jsx" }, "3": { - "name": "storage.modifier.async.js.jsx" + "name": "storage.modifier.js.jsx" }, "4": { - "name": "storage.type.property.js.jsx" + "name": "storage.modifier.async.js.jsx" }, "5": { + "name": "storage.type.property.js.jsx" + }, + "6": { "name": "keyword.generator.asterisk.js.jsx" } }, @@ -1849,7 +1858,7 @@ }, "access-modifier": { "name": "storage.modifier.js.jsx", - "match": "(?)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "match": "(?x)(?:(?)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "captures": { "1": { "name": "storage.modifier.js.jsx" @@ -3112,7 +3121,7 @@ } }, { - "match": "(?x)(?:(?]|$|;|^\\s*$|(?:^\\s*(?:abstract|async|class|const|declare|enum|export|function|import|interface|let|module|namespace|return|type|var)\\b))", + "end": "(?=[,);}\\]=>:&|{\\?]|$|;|^\\s*$|(?:^\\s*(?:abstract|async|class|const|declare|enum|export|function|import|interface|let|module|namespace|return|type|var)\\b))", "patterns": [ { "include": "#expression" @@ -4264,9 +4273,12 @@ "type-fn-type-parameters": { "patterns": [ { - "begin": "(?|&&|\\|\\||\\*\\/)\\s*(\\/)(?![\\/*])(?=(?:[^\\/\\\\\\[\\()]|\\\\.|\\[([^\\]\\\\]|\\\\.)+\\]|\\(([^\\)\\\\]|\\\\.)+\\))+\\/([gimsuy]+|(?![\\/\\*])|(?=\\/\\*))(?!\\s*[a-zA-Z0-9_$]))", + "begin": "(?|&&|\\|\\||\\*\\/)\\s*(\\/)(?![\\/*])(?=(?:[^\\/\\\\\\[\\()]|\\\\.|\\[([^\\]\\\\]|\\\\.)+\\]|\\(([^\\)\\\\]|\\\\.)+\\))+\\/([dgimsuy]+|(?![\\/\\*])|(?=\\/\\*))(?!\\s*[a-zA-Z0-9_$]))", "beginCaptures": { "1": { "name": "punctuation.definition.string.begin.js.jsx" } }, - "end": "(/)([gimsuy]*)", + "end": "(/)([dgimsuy]*)", "endCaptures": { "1": { "name": "punctuation.definition.string.end.js.jsx" @@ -4802,13 +4817,13 @@ }, { "name": "string.regexp.js.jsx", - "begin": "((?= 0; + +let outputRoot = __dirname; +const outputRootIndex = args.indexOf('--outputRoot'); +if (outputRootIndex >= 0) { + outputRoot = args[outputRootIndex + 1]; +} + +const outDir = path.join(outputRoot, 'notebook-out'); + +esbuild.build({ + entryPoints: [ + path.join(__dirname, 'notebook', 'index.ts'), + ], + bundle: true, + minify: true, + sourcemap: false, + format: 'esm', + outdir: outDir, + platform: 'browser', + target: ['es2020'], + incremental: isWatch, +}).catch(() => process.exit(1)); diff --git a/extensions/markdown-language-features/notebook/index.ts b/extensions/markdown-language-features/notebook/index.ts index 1dcb131a..1d7c6523 100644 --- a/extensions/markdown-language-features/notebook/index.ts +++ b/extensions/markdown-language-features/notebook/index.ts @@ -3,26 +3,37 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as MarkdownIt from 'markdown-it'; +const MarkdownIt = require('markdown-it'); -declare const acquireNotebookRendererApi: any; -type extendMarkdownItFnType = ( - (f: (md: MarkdownIt.MarkdownIt) => void) => void -); - -(function () { - const markdownIt = new MarkdownIt({ +export async function activate(ctx: { + dependencies: ReadonlyArray<{ entrypoint: string }> +}) { + let markdownIt = new MarkdownIt({ html: true }); - (globalThis as any).extendMarkdownIt = ((f: (md: MarkdownIt.MarkdownIt) => void) => { - f(markdownIt); - }) as extendMarkdownItFnType; + // Should we load the deps before this point? + // Also could we await inside `renderMarkup`? + await Promise.all(ctx.dependencies.map(async (dep) => { + try { + const api = await import(dep.entrypoint); + if (api?.extendMarkdownIt) { + markdownIt = api.extendMarkdownIt(markdownIt); + } + } catch (e) { + console.error('Could not load markdown entryPoint', e); + } + })); - const notebook = acquireNotebookRendererApi('notebookCoreTestRenderer'); + return { + renderMarkup: (context: { element: HTMLElement, content: string }) => { + const rendered = markdownIt.render(context.content); + context.element.innerHTML = rendered; - notebook.onDidCreateMarkdown(({ element, content }: any) => { - const rendered = markdownIt.render(content); - element.innerHTML = rendered; - }); -}()); + // Insert styles into markdown preview shadow dom so that they are applied + for (const markdownStyleNode of document.getElementsByClassName('markdown-style')) { + context.element.appendChild(markdownStyleNode.cloneNode(true)); + } + } + }; +} diff --git a/extensions/markdown-language-features/notebook/tsconfig.json b/extensions/markdown-language-features/notebook/tsconfig.json new file mode 100644 index 00000000..2d704f32 --- /dev/null +++ b/extensions/markdown-language-features/notebook/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./dist/", + "jsx": "react", + "module": "es2020", + "lib": [ + "es2018", + "DOM", + "DOM.Iterable" + ] + } +} diff --git a/extensions/markdown-language-features/package.json b/extensions/markdown-language-features/package.json index b86d4e41..19d1354c 100644 --- a/extensions/markdown-language-features/package.json +++ b/extensions/markdown-language-features/package.json @@ -29,12 +29,25 @@ "onWebviewPanel:markdown.preview", "onCustomEditor:vscode.markdown.preview.editor" ], + "capabilities": { + "virtualWorkspaces": true, + "untrustedWorkspaces": { + "supported": "limited", + "description": "%workspaceTrust%", + "restrictedConfigurations": [ + "markdown.styles" + ] + } + }, "contributes": { - "notebookMarkdownRenderer": [ + "notebookMarkupRenderers": [ { "id": "markdownItRenderer", "displayName": "Markdown it renderer", - "entrypoint": "./notebook-out/index.js" + "entrypoint": "./notebook-out/index.js", + "mimeTypes": [ + "text/markdown" + ] } ], "commands": [ @@ -207,6 +220,12 @@ "description": "%markdown.preview.linkify%", "scope": "resource" }, + "markdown.preview.typographer": { + "type": "boolean", + "default": false, + "description": "%markdown.preview.typographer%", + "scope": "resource" + }, "markdown.preview.fontFamily": { "type": "string", "default": "-apple-system, BlinkMacSystemFont, 'Segoe WPC', 'Segoe UI', system-ui, 'Ubuntu', 'Droid Sans', sans-serif", @@ -327,7 +346,7 @@ "vscode:prepublish": "npm run build-ext && npm run build-preview", "build-ext": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:markdown-language-features ./tsconfig.json", "build-preview": "npx webpack-cli --mode production", - "build-notebook": "npx webpack-cli --config webpack.notebook --mode production", + "build-notebook": "node ./esbuild", "compile-web": "npx webpack-cli --config extension-browser.webpack.config --mode none", "watch-web": "npx webpack-cli --config extension-browser.webpack.config --mode none --watch --info-verbosity verbose" }, diff --git a/extensions/markdown-language-features/package.nls.json b/extensions/markdown-language-features/package.nls.json index ff874da0..5bab556b 100644 --- a/extensions/markdown-language-features/package.nls.json +++ b/extensions/markdown-language-features/package.nls.json @@ -3,6 +3,7 @@ "description": "Provides rich language support for Markdown.", "markdown.preview.breaks.desc": "Sets how line-breaks are rendered in the Markdown preview. Setting it to 'true' creates a
for newlines inside paragraphs.", "markdown.preview.linkify": "Enable or disable conversion of URL-like text to links in the Markdown preview.", + "markdown.preview.typographer": "Enable or disable some language-neutral replacement and quotes beautification in the Markdown preview.", "markdown.preview.doubleClickToSwitchToEditor.desc": "Double click in the Markdown preview to switch to the editor.", "markdown.preview.fontFamily.desc": "Controls the font family used in the Markdown preview.", "markdown.preview.fontSize.desc": "Controls the font size in pixels used in the Markdown preview.", @@ -24,5 +25,6 @@ "configuration.markdown.preview.openMarkdownLinks.inPreview": "Try to open links in the Markdown preview.", "configuration.markdown.links.openLocation.description": "Controls where links in Markdown files should be opened.", "configuration.markdown.links.openLocation.currentGroup": "Open links in the active editor group.", - "configuration.markdown.links.openLocation.beside": "Open links beside the active editor." + "configuration.markdown.links.openLocation.beside": "Open links beside the active editor.", + "workspaceTrust": "Required for loading styles configured in the workspace." } diff --git a/extensions/markdown-language-features/preview-src/index.ts b/extensions/markdown-language-features/preview-src/index.ts index 90e96b6b..4d57d5f3 100644 --- a/extensions/markdown-language-features/preview-src/index.ts +++ b/extensions/markdown-language-features/preview-src/index.ts @@ -41,7 +41,7 @@ window.onload = () => { function doAfterImagesLoaded(cb: () => void) { const imgElements = document.getElementsByTagName('img'); if (imgElements.length > 0) { - const ps = Array.from(imgElements).map(e => { + const ps = Array.from(imgElements, e => { if (e.complete) { return Promise.resolve(); } else { diff --git a/extensions/markdown-language-features/preview-src/tsconfig.json b/extensions/markdown-language-features/preview-src/tsconfig.json index e19cd4a6..b1bede72 100644 --- a/extensions/markdown-language-features/preview-src/tsconfig.json +++ b/extensions/markdown-language-features/preview-src/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../shared.tsconfig.json", + "extends": "../../tsconfig.base.json", "compilerOptions": { "outDir": "./dist/", "jsx": "react", diff --git a/extensions/markdown-language-features/src/features/preview.ts b/extensions/markdown-language-features/src/features/preview.ts index 22c512db..42d6f08f 100644 --- a/extensions/markdown-language-features/src/features/preview.ts +++ b/extensions/markdown-language-features/src/features/preview.ts @@ -207,7 +207,7 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider { this.updatePreview(); } - dispose() { + override dispose() { super.dispose(); this._disposed = true; clearTimeout(this.throttleTimer); @@ -434,14 +434,14 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider { private async onDidClickPreviewLink(href: string) { let [hrefPath, fragment] = decodeURIComponent(href).split('#'); - // We perviously already resolve absolute paths. - // Now make sure we handle relative file paths if (hrefPath[0] !== '/') { - // Fix #93691, use this.resource.fsPath instead of this.resource.path - hrefPath = path.join(path.dirname(this.resource.fsPath), hrefPath); + // We perviously already resolve absolute paths. + // Now make sure we handle relative file paths + const dirnameUri = vscode.Uri.parse(path.dirname(this.resource.path)); + hrefPath = vscode.Uri.joinPath(dirnameUri, hrefPath).path; } else { // Handle any normalized file paths - hrefPath = vscode.Uri.parse(hrefPath.replace('/file', '')).fsPath; + hrefPath = vscode.Uri.parse(hrefPath.replace('/file', '')).path; } const config = vscode.workspace.getConfiguration('markdown', this.resource); @@ -537,7 +537,7 @@ export class StaticMarkdownPreview extends Disposable implements ManagedMarkdown private readonly _onDidChangeViewState = this._register(new vscode.EventEmitter()); public readonly onDidChangeViewState = this._onDidChangeViewState.event; - dispose() { + override dispose() { this._onDispose.fire(); super.dispose(); } @@ -682,7 +682,7 @@ export class DynamicMarkdownPreview extends Disposable implements ManagedMarkdow private readonly _onDidChangeViewStateEmitter = this._register(new vscode.EventEmitter()); public readonly onDidChangeViewState = this._onDidChangeViewStateEmitter.event; - dispose() { + override dispose() { this._preview.dispose(); this._webviewPanel.dispose(); diff --git a/extensions/markdown-language-features/src/features/previewManager.ts b/extensions/markdown-language-features/src/features/previewManager.ts index 1b0dc660..05729e08 100644 --- a/extensions/markdown-language-features/src/features/previewManager.ts +++ b/extensions/markdown-language-features/src/features/previewManager.ts @@ -23,7 +23,7 @@ class PreviewStore extends Disposable { private readonly _previews = new Set(); - public dispose(): void { + public override dispose(): void { super.dispose(); for (const preview of this._previews) { preview.dispose(); diff --git a/extensions/markdown-language-features/src/features/workspaceSymbolProvider.ts b/extensions/markdown-language-features/src/features/workspaceSymbolProvider.ts index 91f9dfcd..b4b57592 100644 --- a/extensions/markdown-language-features/src/features/workspaceSymbolProvider.ts +++ b/extensions/markdown-language-features/src/features/workspaceSymbolProvider.ts @@ -134,7 +134,7 @@ export default class MarkdownWorkspaceSymbolProvider extends Disposable implemen this._workspaceMarkdownDocumentProvider.onDidDeleteMarkdownDocument(this.onDidDeleteDocument, this, this._disposables); } - const allSymbolsSets = await Promise.all(Array.from(this._symbolCache.values()).map(x => x.value)); + const allSymbolsSets = await Promise.all(Array.from(this._symbolCache.values(), x => x.value)); const allSymbols = allSymbolsSets.flat(); return allSymbols.filter(symbolInformation => symbolInformation.name.toLowerCase().indexOf(query.toLowerCase()) !== -1); } diff --git a/extensions/markdown-language-features/src/markdownEngine.ts b/extensions/markdown-language-features/src/markdownEngine.ts index 5e010755..3d84a24a 100644 --- a/extensions/markdown-language-features/src/markdownEngine.ts +++ b/extensions/markdown-language-features/src/markdownEngine.ts @@ -17,6 +17,7 @@ const UNICODE_NEWLINE_REGEX = /\u2028|\u2029/g; interface MarkdownItConfig { readonly breaks: boolean; readonly linkify: boolean; + readonly typographer: boolean; } class TokenCache { @@ -187,7 +188,8 @@ export class MarkdownEngine { const config = vscode.workspace.getConfiguration('markdown', resource); return { breaks: config.get('preview.breaks', false), - linkify: config.get('preview.linkify', true) + linkify: config.get('preview.linkify', true), + typographer: config.get('preview.typographer', false) }; } diff --git a/extensions/markdown-language-features/src/test/index.ts b/extensions/markdown-language-features/src/test/index.ts index 4589f10f..2f00d0d5 100644 --- a/extensions/markdown-language-features/src/test/index.ts +++ b/extensions/markdown-language-features/src/test/index.ts @@ -8,7 +8,7 @@ const testRunner = require('../../../../test/integration/electron/testrunner'); const options: any = { ui: 'tdd', - color: (!process.env.BUILD_ARTIFACTSTAGINGDIRECTORY && process.platform !== 'win32'), + color: true, timeout: 60000 }; diff --git a/extensions/markdown-language-features/tsconfig.json b/extensions/markdown-language-features/tsconfig.json index b02362c2..1decd91e 100644 --- a/extensions/markdown-language-features/tsconfig.json +++ b/extensions/markdown-language-features/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../shared.tsconfig.json", + "extends": "../tsconfig.base.json", "compilerOptions": { "outDir": "./out", "experimentalDecorators": true, @@ -14,4 +14,4 @@ "include": [ "src/**/*" ] -} \ No newline at end of file +} diff --git a/extensions/markdown-language-features/webpack.notebook.js b/extensions/markdown-language-features/webpack.notebook.js deleted file mode 100644 index 3783b8a6..00000000 --- a/extensions/markdown-language-features/webpack.notebook.js +++ /dev/null @@ -1,28 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -const path = require('path'); - -module.exports = { - mode: 'production', - entry: { - index: path.join(__dirname, 'notebook', 'index.ts') - }, - module: { - rules: [ - { - test: /\.tsx?$/, - use: 'ts-loader', - exclude: /node_modules/ - } - ] - }, - resolve: { - extensions: ['.tsx', '.ts', '.js'] - }, - output: { - filename: '[name].js', - path: path.resolve(__dirname, 'notebook-out') - } -}; diff --git a/extensions/merge-conflict/media/icon.png b/extensions/merge-conflict/media/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ace1157de495c9790094ec8d07863eb92b394eb8 GIT binary patch literal 2439 zcmYM0dpy+X7stPIHO64v$|X!}2r(pD!VKeC$32{uHG z-Z>zS)A?bUHMqbY7O-Clq{AhH^U{TsT0Smo2+tfQ6uzN0O_0k*0dU(DRA$-e?wY-!rzkN|B)-EdT%nxpAqG#n3ha=c6Jj*^h*AbeM zBvx8X3>7O~Mv^Q{%sNufKfe(vUlNE+S!3=yc~sT63|~i+QR+mYGledUSl@_eqH(U8 zGHru-As`qRh+TR&hN|*cuNvyyx=^Bjf9HXkt#`WcVuX?e9t2{4^9!XC7`?iZQ`cFn z+Qk*7T1rYoH`n`waPt{@H|?q++llSm@gO78t7ETB<__CUf#b}8dthdBO3Ej9+9^eaO1Atz0OPRqSc@$ zc7;@(o7k97)oI7nvR%8pVZDMz8R((h!VBRyK^8KTUo8{TZ5S`!z*iL_O7v1H10!WO zZa;xGO1xHD3!zuUDD_w$f|DmZ92%))w!5#+7wFurahVLrr~d4R$eu$p&?8r=sO&Zq z3d!+$@uK6LVF{iau40p>m5frb!!+E#=(uia=PB z-1=`GuF!Mt4bGl~R+-O1kC0DzQ{C26>URlD&!G*#VAYdgi})PWC{th3Uw8D1OClA1 z+|2qnc*u~Q(gnqZa|)>rtM)k4R0n%Sm5r{tDdo0w)3Xg_B3&zchoR3+bNVr4JV&&1X(jMjghPbRbYEfSNCK!eX&I z7I1YGJ<^B8epV&urWS+IkmnET0f=JW`K#Q*9X%~6*Cp@J z)OGIBttL!ZXN`dS#o9so0$;(70oQPxtg9)9%*S|k%*}7<=jWWtMn9Im% zcWkezHDbG2LliSdwO-ZRVR`z65xQ>XN9JE^j}4?*tj3G}IuiXU(iVRorfdY%>8uMh z-73;tE|Uyw>N_u;q;hWc6)gc2V&j6fGE#@wN~)QsX;#IYKs@RFMt=={j1bcFuLGAe5 znC&#KVVXGAfR4VI?W~|#unR07c^bBeXiy%F{c-uuCYPk{QaGa zZ;S&~ZqY_&O><3+nu>Qn?EP4D=Ov~x{qWB80o8Gn2!ltMzEXRIFUH@Qs?gVtl2MjN zoCQU`{+;A23H7eNN&?;bhEPl3@D%?>|lAF~~8N z!;)UHsTQ91Amn{@^J#NJi9q819&FeGqw}#}?aSkgyROy0^nti0?YWDl zw2TdULqdM`SoZ<5vr6-zq@Z6rRB>ZBrpf@7I#~xamU?`d49fa9A;g_Y%VWdK)66EV zl1IS9XD=MN%{Blw*#=+~+W^UOn3j8A-3F;cp);cTmLJedNUNlthp*-RT`Juur$cNg z_lh>v_^<4=!TT$=z@1zLaB_D(?+MyZ{9`0Rc+jd8GG0s}+If)V2vivpubqUo=QNq+ zC99H_cU_Vl*)%1uG#USi1lgB$CaW^QMn;-r+=!h3(nnJ_PPt~wRjgVOf&Yh5aLT9H zQxKEqlU3k>7hBdy~?cn-OaQI~DE(x;XU9@W!MT>hn z@3Uj+@tOLtdav7#`?~Od@*zCghOD@CVBqXFKa#oJ@3ZM4h*IWygg^(jpYNx pJfG~n_x`z5sXBS~1wNW3g83~i><>dbcx?ZuJY2j;bxxGDe*s(Wh)Vzf literal 0 HcmV?d00001 diff --git a/extensions/merge-conflict/package.json b/extensions/merge-conflict/package.json index f53c6ae2..74f7b848 100644 --- a/extensions/merge-conflict/package.json +++ b/extensions/merge-conflict/package.json @@ -3,7 +3,7 @@ "publisher": "vscode", "displayName": "%displayName%", "description": "%description%", - "icon": "resources/icons/merge-conflict.png", + "icon": "media/icon.png", "version": "1.0.0", "license": "MIT", "aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217", @@ -13,6 +13,12 @@ "categories": [ "Other" ], + "capabilities": { + "virtualWorkspaces": false, + "untrustedWorkspaces": { + "supported": true + } + }, "activationEvents": [ "onStartupFinished" ], @@ -70,13 +76,15 @@ "category": "%command.category%", "title": "%command.next%", "original": "Next Conflict", - "command": "merge-conflict.next" + "command": "merge-conflict.next", + "icon": "$(arrow-down)" }, { "category": "%command.category%", "title": "%command.previous%", "original": "Previous Conflict", - "command": "merge-conflict.previous" + "command": "merge-conflict.previous", + "icon": "$(arrow-up)" }, { "category": "%command.category%", @@ -97,6 +105,18 @@ "when": "scmProvider == git && scmResourceGroup == merge", "group": "1_modification" } + ], + "editor/title": [ + { + "command": "merge-conflict.previous", + "group": "navigation@1", + "when": "mergeConflictsCount && mergeConflictsCount != 0" + }, + { + "command": "merge-conflict.next", + "group": "navigation@2", + "when": "mergeConflictsCount && mergeConflictsCount != 0" + } ] }, "configuration": { diff --git a/extensions/merge-conflict/resources/icons/merge-conflict.png b/extensions/merge-conflict/resources/icons/merge-conflict.png deleted file mode 100644 index 3cf8d7b80bd4ef674179b5f8aa2eaf490d90ab50..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2149 zcmeHIX;9Ng6yF2_DVH2_gOG@b2$+P^7(;^LCJ2!Mv`7;ma>*44mmC2_UWZ{u!q;?T3D9Kf3dFfAen2m=uQ9tzUk)TVY{=lgSzRCsek6!x$T0kx6w*!*G{{;OLgQg8lj7jK%9S;|IWZ})82MQ-E@PfMmG>4 zsJ22%s% zvB5aSwaZE?Ax})gZwR!Ou-g=>`~9|iO?U6q*VS-o7B^KU{Fc`}Wkooflg^G`#+E$& z!&^p;-pUxkSoW6|b?f_XMiAcj9h{Fh(wMDIw6}e^r93k^+KYlUJ-}vVW);AidQZsq z;uS|vD^Je6^j6u@m>sN6q}x+$akTdb=Lh1A?h^Wjp(rv?Fq{6;5iiTSB(+w?U)k0@ zCp`SdES_7ryM5RGpykoe&nl?fOIU%{jqoQXDq(BwJ z{G9D6APb@G04b6&j#OL8S!s#A#;VEYwR8Z)>>Md%kNEMG@j#zkcP(C27lB#quDAb! z?;%Qd{lqf*!gJs5se9-EN_DFpswlrbqrTb8Vr+eMDMOXGnG`M%SsxcfhnHI}Zfs6$ zPk$gRmI;#GTh5~k9P|{kDW4z*XDSHw=-5I?o$*#N%yHQV3~UUP?>C!xPUwe%1IW^b zgl;2Hgk}|+_;;z29L8AlA~>?ft-zF%?^JwL3|aG7lT`awB0ps4sF;JSb|9z;7O0aP zjHkdX_!uSGH9mZW{Te@1CtRNG=RMZH2c8rYI~kko+=;zn8g+1aXTz%OfU~E{X(UrN z=r5Cq{Jyw8^qLoPEoiK#bvSIiB~BZRhjx}iNNMKjNCY7mR7vwItV8v1M+l=R9tn10%!9BxCHa< zsYtt`z>$!Hr<(2Ahd#Zc7p-Cj@9SgWtht$aYx{z&4UOCD_89d^k-|F+O?qC46QsGY zp9CeC`I6^m(52?Uohqm5mX2RyJZmSmiq$ z)t_+;D@Gp)588NoxIChb>H&LyX%!)uB92Q|&;43yEt55t%4@xtDW`KZ(dc?8b#nU~ zF)gozhKed;>WOFTD4rvGUFmkja#5iq0=U+3bzG-$f&Gongs)8zk#cz4;t?MgC#R00 z_%UnTaOAwBgMoc`+eE?sj^Of_{A)NY=0Nc=^^A*cDbKN7R+jsH{<<}Ih65a@Le{b-Q z!Xy21NzHx};%I2d4#e1c#{@89n0S8{69Rz4V6BX?SYwQZ2M&WbGsojh4`VQR3}zv@ zr{f(wK~Zgs5&N^!Eec MXzNP3V?$5>6AIae4*&oF diff --git a/extensions/merge-conflict/src/codelensProvider.ts b/extensions/merge-conflict/src/codelensProvider.ts index ce10e484..bd5242b8 100644 --- a/extensions/merge-conflict/src/codelensProvider.ts +++ b/extensions/merge-conflict/src/codelensProvider.ts @@ -53,8 +53,10 @@ export default class MergeConflictCodeLensProvider implements vscode.CodeLensPro } let conflicts = await this.tracker.getConflicts(document); + const conflictsCount = conflicts?.length ?? 0; + vscode.commands.executeCommand('setContext', 'mergeConflictsCount', conflictsCount); - if (!conflicts || conflicts.length === 0) { + if (!conflictsCount) { return null; } @@ -99,6 +101,7 @@ export default class MergeConflictCodeLensProvider implements vscode.CodeLensPro private registerCodeLensProvider() { this.codeLensRegistrationHandle = vscode.languages.registerCodeLensProvider([ { scheme: 'file' }, + { scheme: 'vscode-vfs' }, { scheme: 'untitled' }, { scheme: 'vscode-userdata' }, ], this); diff --git a/extensions/merge-conflict/src/commandHandler.ts b/extensions/merge-conflict/src/commandHandler.ts index ca45b9a1..c6c9eaa8 100644 --- a/extensions/merge-conflict/src/commandHandler.ts +++ b/extensions/merge-conflict/src/commandHandler.ts @@ -339,18 +339,21 @@ export default class CommandHandler implements vscode.Disposable { let predicate: (_conflict: any) => boolean; let fallback: () => interfaces.IDocumentMergeConflict; + let scanOrder: interfaces.IDocumentMergeConflict[]; if (direction === NavigationDirection.Forwards) { predicate = (conflict) => selection.isBefore(conflict.range.start); fallback = () => conflicts![0]; + scanOrder = conflicts; } else if (direction === NavigationDirection.Backwards) { predicate = (conflict) => selection.isAfter(conflict.range.start); fallback = () => conflicts![conflicts!.length - 1]; + scanOrder = conflicts.slice().reverse(); } else { throw new Error(`Unsupported direction ${direction}`); } - for (const conflict of conflicts) { + for (const conflict of scanOrder) { if (predicate(conflict) && !conflict.range.contains(selection)) { return { canNavigate: true, diff --git a/extensions/merge-conflict/tsconfig.json b/extensions/merge-conflict/tsconfig.json index 296ddb38..070854d6 100644 --- a/extensions/merge-conflict/tsconfig.json +++ b/extensions/merge-conflict/tsconfig.json @@ -1,9 +1,9 @@ { - "extends": "../shared.tsconfig.json", + "extends": "../tsconfig.base.json", "compilerOptions": { "outDir": "./out" }, "include": [ "src/**/*" ] -} \ No newline at end of file +} diff --git a/extensions/microsoft-authentication/media/icon.png b/extensions/microsoft-authentication/media/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..c179f87a7119eed57e5780296b957325e1a93bbe GIT binary patch literal 3818 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 B3MBvl literal 0 HcmV?d00001 diff --git a/extensions/microsoft-authentication/package.json b/extensions/microsoft-authentication/package.json index 8424c450..7b47354d 100644 --- a/extensions/microsoft-authentication/package.json +++ b/extensions/microsoft-authentication/package.json @@ -8,6 +8,7 @@ "engines": { "vscode": "^1.42.0" }, + "icon": "media/icon.png", "categories": [ "Other" ], @@ -15,6 +16,12 @@ "activationEvents": [ "onAuthenticationRequest:microsoft" ], + "capabilities": { + "virtualWorkspaces": true, + "untrustedWorkspaces": { + "supported": true + } + }, "extensionKind": [ "ui", "workspace", diff --git a/extensions/notebook-markdown-extensions/esbuild.js b/extensions/notebook-markdown-extensions/esbuild.js new file mode 100644 index 00000000..f1bb454f --- /dev/null +++ b/extensions/notebook-markdown-extensions/esbuild.js @@ -0,0 +1,41 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +const path = require('path'); +const fse = require('fs-extra'); +const esbuild = require('esbuild'); + +const args = process.argv.slice(2); + +const isWatch = args.indexOf('--watch') >= 0; + +let outputRoot = __dirname; +const outputRootIndex = args.indexOf('--outputRoot'); +if (outputRootIndex >= 0) { + outputRoot = args[outputRootIndex + 1]; +} + +const outDir = path.join(outputRoot, 'notebook-out'); +esbuild.build({ + entryPoints: [ + path.join(__dirname, 'notebook', 'katex.ts'), + path.join(__dirname, 'notebook', 'emoji.ts') + ], + bundle: true, + minify: true, + sourcemap: false, + format: 'esm', + outdir: outDir, + platform: 'browser', + target: ['es2020'], + incremental: isWatch, +}).catch(() => process.exit(1)); + +fse.copySync( + path.join(__dirname, 'node_modules/katex/dist/katex.min.css'), + path.join(outDir, 'katex.min.css')); + +fse.copySync( + path.join(__dirname, 'node_modules/katex/dist/fonts'), + path.join(outDir, 'fonts/')); diff --git a/extensions/notebook-markdown-extensions/notebook/emoji.ts b/extensions/notebook-markdown-extensions/notebook/emoji.ts index 45c02d31..bf82f98b 100644 --- a/extensions/notebook-markdown-extensions/notebook/emoji.ts +++ b/extensions/notebook-markdown-extensions/notebook/emoji.ts @@ -4,17 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import type * as markdownIt from 'markdown-it'; -declare const extendMarkdownIt: undefined | ( - (f: (md: markdownIt.MarkdownIt) => void) => void -); - -(function () { - if (typeof extendMarkdownIt !== 'undefined') { - const emoji = require('markdown-it-emoji'); - - extendMarkdownIt((md: markdownIt.MarkdownIt) => { - md.use(emoji); - }); - } -}()); +const emoji = require('markdown-it-emoji'); +export function extendMarkdownIt(md: markdownIt.MarkdownIt) { + return md.use(emoji); +} diff --git a/extensions/notebook-markdown-extensions/notebook/katex.ts b/extensions/notebook-markdown-extensions/notebook/katex.ts index 8e8fce08..f862fd94 100644 --- a/extensions/notebook-markdown-extensions/notebook/katex.ts +++ b/extensions/notebook-markdown-extensions/notebook/katex.ts @@ -4,24 +4,17 @@ *--------------------------------------------------------------------------------------------*/ import type * as markdownIt from 'markdown-it'; -declare const extendMarkdownIt: undefined | ( - (f: (md: markdownIt.MarkdownIt) => void) => void -); - -const styleHref = (document.currentScript as any).src.replace(/katex.js$/, 'katex.min.css'); +const styleHref = import.meta.url.replace(/katex.js$/, 'katex.min.css'); const link = document.createElement('link'); link.rel = 'stylesheet'; +link.classList.add('markdown-style'); link.href = styleHref; document.head.append(link); -(function () { - const katex = require('@iktakahiro/markdown-it-katex'); - if (typeof extendMarkdownIt !== 'undefined') { +const katex = require('@iktakahiro/markdown-it-katex'); - extendMarkdownIt((md: markdownIt.MarkdownIt) => { - md.use(katex); - }); - } -}()); +export function extendMarkdownIt(md: markdownIt.MarkdownIt) { + return md.use(katex); +} diff --git a/extensions/notebook-markdown-extensions/notebook/tsconfig.json b/extensions/notebook-markdown-extensions/notebook/tsconfig.json index e19cd4a6..2d704f32 100644 --- a/extensions/notebook-markdown-extensions/notebook/tsconfig.json +++ b/extensions/notebook-markdown-extensions/notebook/tsconfig.json @@ -1,8 +1,9 @@ { - "extends": "../../shared.tsconfig.json", + "extends": "../../tsconfig.base.json", "compilerOptions": { "outDir": "./dist/", "jsx": "react", + "module": "es2020", "lib": [ "es2018", "DOM", diff --git a/extensions/notebook-markdown-extensions/package.json b/extensions/notebook-markdown-extensions/package.json index 4cefc019..16226598 100644 --- a/extensions/notebook-markdown-extensions/package.json +++ b/extensions/notebook-markdown-extensions/package.json @@ -14,27 +14,32 @@ "categories": [ "Other" ], + "capabilities": { + "virtualWorkspaces": false + }, "contributes": { - "notebookMarkdownRenderer": [ + "notebookMarkupRenderers": [ { "id": "markdownItRenderer-katex", "displayName": "Markdown it katex renderer", - "entrypoint": "./notebook-out/katex.js" + "entrypoint": "./notebook-out/katex.js", + "dependsOn": "markdownItRenderer" }, { "id": "markdownItRenderer-emoji", "displayName": "Markdown it emoji renderer", - "entrypoint": "./notebook-out/emoji.js" + "entrypoint": "./notebook-out/emoji.js", + "dependsOn": "markdownItRenderer" } ] }, "scripts": { "compile": "npm run build-notebook", "watch": "npm run build-notebook", - "build-notebook": "npx webpack-cli --config webpack.notebook.js --mode production" + "build-notebook": "node ./esbuild" }, "devDependencies": { - "@iktakahiro/markdown-it-katex": "^4.0.1", + "@iktakahiro/markdown-it-katex": "https://github.com/mjbvz/markdown-it-katex.git", "@types/markdown-it": "^0.0.0", "markdown-it": "^12.0.4", "markdown-it-emoji": "^2.0.0" diff --git a/extensions/notebook-markdown-extensions/webpack.notebook.js b/extensions/notebook-markdown-extensions/webpack.notebook.js deleted file mode 100644 index de8fe9af..00000000 --- a/extensions/notebook-markdown-extensions/webpack.notebook.js +++ /dev/null @@ -1,50 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * 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 = { - context: path.resolve(__dirname), - mode: 'production', - performance: { - hints: false, - maxEntrypointSize: 512000, - maxAssetSize: 512000 - }, - entry: { - katex: './notebook/katex.ts', - emoji: './notebook/emoji.ts', - }, - module: { - rules: [ - { - test: /\.tsx?$/, - use: 'ts-loader', - }, - ], - }, - resolve: { - extensions: ['.tsx', '.ts', '.js'] - }, - 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/notebook-markdown-extensions/yarn.lock b/extensions/notebook-markdown-extensions/yarn.lock index 557d01e9..58952667 100644 --- a/extensions/notebook-markdown-extensions/yarn.lock +++ b/extensions/notebook-markdown-extensions/yarn.lock @@ -2,12 +2,11 @@ # yarn lockfile v1 -"@iktakahiro/markdown-it-katex@^4.0.1": +"@iktakahiro/markdown-it-katex@https://github.com/mjbvz/markdown-it-katex.git": version "4.0.1" - resolved "https://registry.yarnpkg.com/@iktakahiro/markdown-it-katex/-/markdown-it-katex-4.0.1.tgz#65ff9d12afd4c0b7684dd247abe7ce42fc1edac3" - integrity sha512-kGFooO7fIOgY34PSG8ZNVsUlKhhNoqhzW2kq94TNGa8COzh73PO4KsEoPOsQVG1mEAe8tg7GqG0FoVao0aMHaw== + resolved "https://github.com/mjbvz/markdown-it-katex.git#e88925a7cb3fd593a14ed117fb43627c4ba910b6" dependencies: - katex "^0.12.0" + katex "^0.13.0" "@types/markdown-it@^0.0.0": version "0.0.0" @@ -19,22 +18,22 @@ argparse@^2.0.1: resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== -commander@^2.19.0: - version "2.20.3" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" - integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== +commander@^6.0.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" + integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA== entities@~2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/entities/-/entities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5" integrity sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w== -katex@^0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/katex/-/katex-0.12.0.tgz#2fb1c665dbd2b043edcf8a1f5c555f46beaa0cb9" - integrity sha512-y+8btoc/CK70XqcHqjxiGWBOeIL8upbS0peTPXTvgrh21n1RiWWcIpSWM+4uXq+IAgNh9YYQWdc7LVDPDAEEAg== +katex@^0.13.0: + version "0.13.0" + resolved "https://registry.yarnpkg.com/katex/-/katex-0.13.0.tgz#62900e56c1ad8fdf7da23399e50d7a7b690b39ab" + integrity sha512-6cHbzbegYgS9vvVGuH8UA+o97X+ZshtboSqJJCdq7trBYzuD75JNwr7Ef606xkUjecPPhFnyB+afx1dVafielg== dependencies: - commander "^2.19.0" + commander "^6.0.0" linkify-it@^3.0.1: version "3.0.2" diff --git a/extensions/npm/extension-browser.webpack.config.js b/extensions/npm/extension-browser.webpack.config.js index b0c70c96..b9f00ed5 100644 --- a/extensions/npm/extension-browser.webpack.config.js +++ b/extensions/npm/extension-browser.webpack.config.js @@ -18,6 +18,7 @@ module.exports = withBrowserDefaults({ filename: 'npmBrowserMain.js' }, node: { - 'child_process': 'empty' + 'child_process': 'empty', + 'which': 'empty' } }); diff --git a/extensions/npm/package.json b/extensions/npm/package.json index d943ce51..ab75bdf3 100644 --- a/extensions/npm/package.json +++ b/extensions/npm/package.json @@ -24,11 +24,13 @@ "minimatch": "^3.0.4", "request-light": "^0.4.0", "vscode-nls": "^4.1.1", + "which": "^2.0.2", "which-pm": "^2.0.0" }, "devDependencies": { "@types/minimatch": "^3.0.3", - "@types/node": "^12.19.9" + "@types/node": "^12.19.9", + "@types/which": "^2.0.0" }, "resolutions": { "which-pm/load-yaml-file/**/argparse": "1.0.9" @@ -42,6 +44,13 @@ "workspaceContains:package.json", "onView:npm" ], + "capabilities": { + "virtualWorkspaces": false, + "untrustedWorkspaces": { + "supported": "limited", + "description": "%workspaceTrust%" + } + }, "contributes": { "languages": [ { diff --git a/extensions/npm/package.nls.json b/extensions/npm/package.nls.json index 53ea1848..b8570d0d 100644 --- a/extensions/npm/package.nls.json +++ b/extensions/npm/package.nls.json @@ -1,6 +1,7 @@ { "description": "Extension to add task support for npm scripts.", "displayName": "NPM support for VS Code", + "workspaceTrust": "This extension calls the `tasks.executeTask()` API, which requires trust to run.", "config.npm.autoDetect": "Controls whether npm scripts should be automatically detected.", "config.npm.runSilent": "Run npm commands with the `--silent` option.", "config.npm.packageManager": "The package manager used to run scripts.", diff --git a/extensions/npm/src/features/jsonContributions.ts b/extensions/npm/src/features/jsonContributions.ts index 3954c235..bae32a5c 100644 --- a/extensions/npm/src/features/jsonContributions.ts +++ b/extensions/npm/src/features/jsonContributions.ts @@ -29,8 +29,8 @@ export interface IJSONContribution { resolveSuggestion?(resourceUri: Uri | undefined, item: CompletionItem): Thenable | null; } -export function addJSONProviders(xhr: XHRRequest, canRunNPM: boolean): Disposable { - const contributions = [new PackageJSONContribution(xhr, canRunNPM), new BowerJSONContribution(xhr)]; +export function addJSONProviders(xhr: XHRRequest, npmCommandPath: string | undefined): Disposable { + const contributions = [new PackageJSONContribution(xhr, npmCommandPath), new BowerJSONContribution(xhr)]; const subscriptions: Disposable[] = []; contributions.forEach(contribution => { const selector = contribution.getDocumentSelector(); @@ -136,7 +136,7 @@ export class JSONCompletionItemProvider implements CompletionItemProvider { } if (collectPromise) { return collectPromise.then(() => { - if (items.length > 0) { + if (items.length > 0 || isIncomplete) { return new CompletionList(items, isIncomplete); } return null; diff --git a/extensions/npm/src/features/packageJSONContribution.ts b/extensions/npm/src/features/packageJSONContribution.ts index 3249cb76..3afbf982 100644 --- a/extensions/npm/src/features/packageJSONContribution.ts +++ b/extensions/npm/src/features/packageJSONContribution.ts @@ -14,7 +14,6 @@ import { dirname } from 'path'; const localize = nls.loadMessageBundle(); const LIMIT = 40; -const SCOPED_LIMIT = 250; const USER_AGENT = 'Visual Studio Code'; @@ -33,7 +32,7 @@ export class PackageJSONContribution implements IJSONContribution { return [{ language: 'json', scheme: '*', pattern: '**/package.json' }]; } - public constructor(private xhr: XHRRequest, private canRunNPM: boolean) { + public constructor(private xhr: XHRRequest, private npmCommandPath: string | undefined) { } public collectDefaultSuggestions(_resource: Uri, result: ISuggestionsCollector): Thenable { @@ -53,7 +52,7 @@ export class PackageJSONContribution implements IJSONContribution { } private isEnabled() { - return this.canRunNPM || this.onlineEnabled(); + return this.npmCommandPath || this.onlineEnabled(); } private onlineEnabled() { @@ -94,7 +93,7 @@ export class PackageJSONContribution implements IJSONContribution { collector.setAsIncomplete(); } - queryUrl = `https://api.npms.io/v2/search/suggestions?size=${LIMIT}&q=${encodeURIComponent(currentWord)}`; + queryUrl = `https://registry.npmjs.org/-/v1/search?size=${LIMIT}&text=${encodeURIComponent(currentWord)}`; return this.xhr({ url: queryUrl, agent: USER_AGENT @@ -102,18 +101,17 @@ export class PackageJSONContribution implements IJSONContribution { if (success.status === 200) { try { const obj = JSON.parse(success.responseText); - if (obj && Array.isArray(obj)) { - const results = <{ package: SearchPackageInfo; }[]>obj; + if (obj && obj.objects && Array.isArray(obj.objects)) { + const results = <{ package: SearchPackageInfo; }[]>obj.objects; for (const result of results) { this.processPackage(result.package, addValue, isLast, collector); } - if (results.length === LIMIT) { - collector.setAsIncomplete(); - } + } } catch (e) { // ignore } + collector.setAsIncomplete(); } else { collector.error(localize('json.npm.error.repoaccess', 'Request to the NPM repository failed: {0}', success.responseText)); return 0; @@ -155,7 +153,7 @@ export class PackageJSONContribution implements IJSONContribution { if (name.length < 4) { name = ''; } - let queryUrl = `https://api.npms.io/v2/search?q=scope:${scope}%20${name}&size=250`; + let queryUrl = `https://registry.npmjs.com/-/v1/search?text=scope:${scope}%20${name}&size=250`; return this.xhr({ url: queryUrl, agent: USER_AGENT @@ -163,18 +161,16 @@ export class PackageJSONContribution implements IJSONContribution { if (success.status === 200) { try { const obj = JSON.parse(success.responseText); - if (obj && Array.isArray(obj.results)) { - const objects = <{ package: SearchPackageInfo }[]>obj.results; + if (obj && Array.isArray(obj.objects)) { + const objects = <{ package: SearchPackageInfo; }[]>obj.objects; for (let object of objects) { this.processPackage(object.package, addValue, isLast, collector); } - if (objects.length === SCOPED_LIMIT) { - collector.setAsIncomplete(); - } } } catch (e) { // ignore } + collector.setAsIncomplete(); } else { collector.error(localize('json.npm.error.repoaccess', 'Request to the NPM repository failed: {0}', success.responseText)); } @@ -272,8 +268,8 @@ export class PackageJSONContribution implements IJSONContribution { return undefined; // avoid unnecessary lookups } let info: ViewPackageInfo | undefined; - if (this.canRunNPM) { - info = await this.npmView(pack, resource); + if (this.npmCommandPath) { + info = await this.npmView(this.npmCommandPath, pack, resource); } if (!info && this.onlineEnabled()) { info = await this.npmjsView(pack); @@ -281,11 +277,11 @@ export class PackageJSONContribution implements IJSONContribution { return info; } - private npmView(pack: string, resource: Uri | undefined): Promise { + private npmView(npmCommandPath: string, pack: string, resource: Uri | undefined): Promise { return new Promise((resolve, _reject) => { const args = ['view', '--json', pack, 'description', 'dist-tags.latest', 'homepage', 'version']; let cwd = resource && resource.scheme === 'file' ? dirname(resource.fsPath) : undefined; - cp.execFile(process.platform === 'win32' ? 'npm.cmd' : 'npm', args, { cwd }, (error, stdout) => { + cp.execFile(npmCommandPath, args, { cwd }, (error, stdout) => { if (!error) { try { const content = JSON.parse(stdout); @@ -305,21 +301,18 @@ export class PackageJSONContribution implements IJSONContribution { } private async npmjsView(pack: string): Promise { - const queryUrl = 'https://api.npms.io/v2/package/' + encodeURIComponent(pack); + const queryUrl = 'https://registry.npmjs.org/' + encodeURIComponent(pack); try { const success = await this.xhr({ url: queryUrl, agent: USER_AGENT }); const obj = JSON.parse(success.responseText); - const metadata = obj?.collected?.metadata; - if (metadata) { - return { - description: metadata.description || '', - version: metadata.version, - homepage: metadata.links?.homepage || '' - }; - } + return { + description: obj.description || '', + version: Object.keys(obj.versions).pop(), + homepage: obj.homepage || '' + }; } catch (e) { //ignore diff --git a/extensions/npm/src/npmBrowserMain.ts b/extensions/npm/src/npmBrowserMain.ts index 96cfe579..c562b5a4 100644 --- a/extensions/npm/src/npmBrowserMain.ts +++ b/extensions/npm/src/npmBrowserMain.ts @@ -8,7 +8,7 @@ import * as vscode from 'vscode'; import { addJSONProviders } from './features/jsonContributions'; export async function activate(context: vscode.ExtensionContext): Promise { - context.subscriptions.push(addJSONProviders(httpRequest.xhr, false)); + context.subscriptions.push(addJSONProviders(httpRequest.xhr, undefined)); } export function deactivate(): void { diff --git a/extensions/npm/src/npmMain.ts b/extensions/npm/src/npmMain.ts index c5d442a4..6969b7d6 100644 --- a/extensions/npm/src/npmMain.ts +++ b/extensions/npm/src/npmMain.ts @@ -11,6 +11,7 @@ import { NpmScriptsTreeDataProvider } from './npmView'; import { getPackageManager, invalidateTasksCache, NpmTaskProvider, hasPackageJson } from './tasks'; import { invalidateHoverScriptsCache, NpmScriptHoverProvider } from './scriptHover'; import { NpmScriptLensProvider } from './npmScriptLens'; +import * as which from 'which'; let treeDataProvider: NpmScriptsTreeDataProvider | undefined; @@ -30,8 +31,8 @@ export async function activate(context: vscode.ExtensionContext): Promise } })); - const canRunNPM = canRunNpmInCurrentWorkspace(); - context.subscriptions.push(addJSONProviders(httpRequest.xhr, canRunNPM)); + const npmCommandPath = await getNPMCommandPath(); + context.subscriptions.push(addJSONProviders(httpRequest.xhr, npmCommandPath)); registerTaskProvider(context); treeDataProvider = registerExplorer(context); @@ -71,6 +72,17 @@ export async function activate(context: vscode.ExtensionContext): Promise context.subscriptions.push(new NpmScriptLensProvider()); } +async function getNPMCommandPath(): Promise { + if (canRunNpmInCurrentWorkspace()) { + try { + return await which(process.platform === 'win32' ? 'npm.cmd' : 'npm'); + } catch (e) { + return undefined; + } + } + return undefined; +} + function canRunNpmInCurrentWorkspace() { if (vscode.workspace.workspaceFolders) { return vscode.workspace.workspaceFolders.some(f => f.uri.scheme === 'file'); diff --git a/extensions/npm/src/tasks.ts b/extensions/npm/src/tasks.ts index 5a3d1a53..911d66fb 100644 --- a/extensions/npm/src/tasks.ts +++ b/extensions/npm/src/tasks.ts @@ -370,7 +370,23 @@ export async function hasPackageJson(): Promise { const timeout = setTimeout(() => token.cancel(), 1000); const files = await workspace.findFiles('**/package.json', undefined, 1, token.token); clearTimeout(timeout); - return files.length > 0; + return files.length > 0 || await hasRootPackageJson(); +} + +async function hasRootPackageJson(): Promise { + let folders = workspace.workspaceFolders; + if (!folders) { + return false; + } + for (const folder of folders) { + if (folder.uri.scheme === 'file') { + let packageJson = path.join(folder.uri.fsPath, 'package.json'); + if (await exists(packageJson)) { + return true; + } + } + } + return false; } async function exists(file: string): Promise { diff --git a/extensions/npm/tsconfig.json b/extensions/npm/tsconfig.json index a50348dc..070854d6 100644 --- a/extensions/npm/tsconfig.json +++ b/extensions/npm/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../shared.tsconfig.json", + "extends": "../tsconfig.base.json", "compilerOptions": { "outDir": "./out" }, diff --git a/extensions/npm/yarn.lock b/extensions/npm/yarn.lock index c8ca31e3..6b653dec 100644 --- a/extensions/npm/yarn.lock +++ b/extensions/npm/yarn.lock @@ -12,6 +12,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-12.19.9.tgz#990ad687ad8b26ef6dcc34a4f69c33d40c95b679" integrity sha512-yj0DOaQeUrk3nJ0bd3Y5PeDRJ6W0r+kilosLA+dzF3dola/o9hxhMSg2sFvVcA2UHS5JSOsZp4S0c1OEXc4m1Q== +"@types/which@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@types/which/-/which-2.0.0.tgz#446d35586611dee657120de8e0457382a658fc25" + integrity sha512-JHTNOEpZnACQdsTojWggn+SQ8IucfqEhtz7g8Z0G67WdSj4x3F0X5I2c/CVcl8z/QukGrIHeQ/N49v1au74XFQ== + agent-base@4: version "4.2.0" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.0.tgz#9838b5c3392b962bad031e6a4c5e1024abec45ce" @@ -130,6 +135,11 @@ is-number@^7.0.0: resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= + js-yaml@^3.13.0: version "3.14.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482" @@ -257,3 +267,10 @@ which-pm@^2.0.0: dependencies: load-yaml-file "^0.2.0" path-exists "^4.0.0" + +which@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" diff --git a/extensions/package.json b/extensions/package.json index d2a33e7e..645ea5c2 100644 --- a/extensions/package.json +++ b/extensions/package.json @@ -4,12 +4,13 @@ "license": "MIT", "description": "Dependencies shared by all extensions", "dependencies": { - "typescript": "4.2.3" + "typescript": "4.2.4" }, "scripts": { "postinstall": "node ./postinstall" }, "devDependencies": { + "esbuild": "^0.11.12", "vscode-grammar-updater": "^1.0.3" } } diff --git a/extensions/perl/cgmanifest.json b/extensions/perl/cgmanifest.json index 83d91107..cd175abe 100644 --- a/extensions/perl/cgmanifest.json +++ b/extensions/perl/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "textmate/perl.tmbundle", "repositoryUrl": "https://github.com/textmate/perl.tmbundle", - "commitHash": "80826abe75250286c2a1a07958e50e8551d3f50c" + "commitHash": "a85927a902d6e5d7805f56a653f324d34dfad53a" } }, "licenseDetail": [ diff --git a/extensions/perl/syntaxes/perl.tmLanguage.json b/extensions/perl/syntaxes/perl.tmLanguage.json index 741a4019..a6a84a54 100644 --- a/extensions/perl/syntaxes/perl.tmLanguage.json +++ b/extensions/perl/syntaxes/perl.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/textmate/perl.tmbundle/commit/80826abe75250286c2a1a07958e50e8551d3f50c", + "version": "https://github.com/textmate/perl.tmbundle/commit/a85927a902d6e5d7805f56a653f324d34dfad53a", "name": "Perl", "scopeName": "source.perl", "comment": "\n\tTODO:\tInclude RegExp syntax\n", @@ -917,685 +917,7 @@ "name": "keyword.operator.comparison.perl" }, { - "begin": "(((<<) *\"HTML\"))(.*)\\n?", - "captures": { - "1": { - "name": "punctuation.definition.string.perl" - }, - "2": { - "name": "string.unquoted.heredoc.doublequote.perl" - }, - "3": { - "name": "punctuation.definition.heredoc.perl" - }, - "4": { - "patterns": [ - { - "include": "$self" - } - ] - } - }, - "contentName": "text.html.embedded.perl", - "end": "(^HTML$)", - "patterns": [ - { - "include": "#escaped_char" - }, - { - "include": "#variable" - }, - { - "include": "text.html.basic" - } - ] - }, - { - "begin": "(((<<) *\"XML\"))(.*)\\n?", - "captures": { - "1": { - "name": "punctuation.definition.string.perl" - }, - "2": { - "name": "string.unquoted.heredoc.doublequote.perl" - }, - "3": { - "name": "punctuation.definition.heredoc.perl" - }, - "4": { - "patterns": [ - { - "include": "$self" - } - ] - } - }, - "contentName": "text.xml.embedded.perl", - "end": "(^XML$)", - "patterns": [ - { - "include": "#escaped_char" - }, - { - "include": "#variable" - }, - { - "include": "text.xml" - } - ] - }, - { - "begin": "(((<<) *\"CSS\"))(.*)\\n?", - "captures": { - "1": { - "name": "punctuation.definition.string.perl" - }, - "2": { - "name": "string.unquoted.heredoc.doublequote.perl" - }, - "3": { - "name": "punctuation.definition.heredoc.perl" - }, - "4": { - "patterns": [ - { - "include": "$self" - } - ] - } - }, - "contentName": "text.css.embedded.perl", - "end": "(^CSS$)", - "patterns": [ - { - "include": "#escaped_char" - }, - { - "include": "#variable" - }, - { - "include": "source.css" - } - ] - }, - { - "begin": "(((<<) *\"JAVASCRIPT\"))(.*)\\n?", - "captures": { - "1": { - "name": "punctuation.definition.string.perl" - }, - "2": { - "name": "string.unquoted.heredoc.doublequote.perl" - }, - "3": { - "name": "punctuation.definition.heredoc.perl" - }, - "4": { - "patterns": [ - { - "include": "$self" - } - ] - } - }, - "contentName": "text.js.embedded.perl", - "end": "(^JAVASCRIPT$)", - "patterns": [ - { - "include": "#escaped_char" - }, - { - "include": "#variable" - }, - { - "include": "source.js" - } - ] - }, - { - "begin": "(((<<) *\"SQL\"))(.*)\\n?", - "captures": { - "1": { - "name": "punctuation.definition.string.perl" - }, - "2": { - "name": "string.unquoted.heredoc.doublequote.perl" - }, - "3": { - "name": "punctuation.definition.heredoc.perl" - }, - "4": { - "patterns": [ - { - "include": "$self" - } - ] - } - }, - "contentName": "source.sql.embedded.perl", - "end": "(^SQL$)", - "patterns": [ - { - "include": "#escaped_char" - }, - { - "include": "#variable" - }, - { - "include": "source.sql" - } - ] - }, - { - "begin": "(((<<) *\"POSTSCRIPT\"))(.*)\\n?", - "captures": { - "1": { - "name": "punctuation.definition.string.perl" - }, - "2": { - "name": "string.unquoted.heredoc.doublequote.perl" - }, - "3": { - "name": "punctuation.definition.heredoc.perl" - }, - "4": { - "patterns": [ - { - "include": "$self" - } - ] - } - }, - "contentName": "text.postscript.embedded.perl", - "end": "(^POSTSCRIPT$)", - "patterns": [ - { - "include": "#escaped_char" - }, - { - "include": "#variable" - }, - { - "include": "source.postscript" - } - ] - }, - { - "begin": "(((<<) *\"([^\"]*)\"))(.*)\\n?", - "captures": { - "1": { - "name": "punctuation.definition.string.perl" - }, - "2": { - "name": "string.unquoted.heredoc.doublequote.perl" - }, - "3": { - "name": "punctuation.definition.heredoc.perl" - }, - "4": { - "patterns": [ - { - "include": "$self" - } - ] - } - }, - "contentName": "string.unquoted.heredoc.doublequote.perl", - "end": "(^\\4$)", - "patterns": [ - { - "include": "#escaped_char" - }, - { - "include": "#variable" - } - ] - }, - { - "begin": "(((<<) *'HTML'))(.*)\\n?", - "captures": { - "1": { - "name": "punctuation.definition.string.perl" - }, - "2": { - "name": "string.unquoted.heredoc.quote.perl" - }, - "3": { - "name": "punctuation.definition.heredoc.perl" - }, - "4": { - "patterns": [ - { - "include": "$self" - } - ] - } - }, - "contentName": "text.html.embedded.perl", - "end": "(^HTML$)", - "patterns": [ - { - "include": "text.html.basic" - } - ] - }, - { - "begin": "(((<<) *'XML'))(.*)\\n?", - "captures": { - "1": { - "name": "punctuation.definition.string.perl" - }, - "2": { - "name": "string.unquoted.heredoc.quote.perl" - }, - "3": { - "name": "punctuation.definition.heredoc.perl" - }, - "4": { - "patterns": [ - { - "include": "$self" - } - ] - } - }, - "contentName": "text.xml.embedded.perl", - "end": "(^XML$)", - "patterns": [ - { - "include": "text.xml" - } - ] - }, - { - "begin": "(((<<) *'CSS'))(.*)\\n?", - "captures": { - "1": { - "name": "punctuation.definition.string.perl" - }, - "2": { - "name": "string.unquoted.heredoc.quote.perl" - }, - "3": { - "name": "punctuation.definition.heredoc.perl" - }, - "4": { - "patterns": [ - { - "include": "$self" - } - ] - } - }, - "contentName": "text.css.embedded.perl", - "end": "(^CSS$)", - "patterns": [ - { - "include": "source.css" - } - ] - }, - { - "begin": "(((<<) *'JAVASCRIPT'))(.*)\\n?", - "captures": { - "1": { - "name": "punctuation.definition.string.perl" - }, - "2": { - "name": "string.unquoted.heredoc.quote.perl" - }, - "3": { - "name": "punctuation.definition.heredoc.perl" - }, - "4": { - "patterns": [ - { - "include": "$self" - } - ] - } - }, - "contentName": "text.js.embedded.perl", - "end": "(^JAVASCRIPT$)", - "patterns": [ - { - "include": "source.js" - } - ] - }, - { - "begin": "(((<<) *'SQL'))(.*)\\n?", - "captures": { - "1": { - "name": "punctuation.definition.string.perl" - }, - "2": { - "name": "string.unquoted.heredoc.quote.perl" - }, - "3": { - "name": "punctuation.definition.heredoc.perl" - }, - "4": { - "patterns": [ - { - "include": "$self" - } - ] - } - }, - "contentName": "source.sql.embedded.perl", - "end": "(^SQL$)", - "patterns": [ - { - "include": "source.sql" - } - ] - }, - { - "begin": "(((<<) *'POSTSCRIPT'))(.*)\\n?", - "captures": { - "1": { - "name": "punctuation.definition.string.perl" - }, - "2": { - "name": "string.unquoted.heredoc.quote.perl" - }, - "3": { - "name": "punctuation.definition.heredoc.perl" - }, - "4": { - "patterns": [ - { - "include": "$self" - } - ] - } - }, - "contentName": "source.postscript.embedded.perl", - "end": "(^POSTSCRIPT$)", - "patterns": [ - { - "include": "source.postscript" - } - ] - }, - { - "begin": "(((<<) *'([^']*)'))(.*)\\n?", - "captures": { - "1": { - "name": "punctuation.definition.string.perl" - }, - "2": { - "name": "string.unquoted.heredoc.quote.perl" - }, - "3": { - "name": "punctuation.definition.heredoc.perl" - }, - "4": { - "patterns": [ - { - "include": "$self" - } - ] - } - }, - "contentName": "string.unquoted.heredoc.quote.perl", - "end": "(^\\4$)" - }, - { - "begin": "(((<<) *\\\\((?![=\\d\\$\\( ])[^;,'\"`\\s\\)]*)))(.*)\\n?", - "captures": { - "1": { - "name": "punctuation.definition.string.perl" - }, - "2": { - "name": "string.unquoted.heredoc.quote.perl" - }, - "3": { - "name": "punctuation.definition.heredoc.perl" - }, - "4": { - "patterns": [ - { - "include": "$self" - } - ] - } - }, - "contentName": "string.unquoted.heredoc.quote.perl", - "end": "(^\\4$)" - }, - { - "begin": "(((<<) *`([^`]*)`))(.*)\\n?", - "captures": { - "1": { - "name": "punctuation.definition.string.perl" - }, - "2": { - "name": "string.unquoted.heredoc.backtick.perl" - }, - "3": { - "name": "punctuation.definition.heredoc.perl" - }, - "4": { - "patterns": [ - { - "include": "$self" - } - ] - } - }, - "contentName": "string.unquoted.heredoc.backtick.perl", - "end": "(^\\4$)", - "patterns": [ - { - "include": "#escaped_char" - }, - { - "include": "#variable" - } - ] - }, - { - "begin": "(((<<) *HTML\\b))(.*)\\n?", - "captures": { - "1": { - "name": "punctuation.definition.string.perl" - }, - "2": { - "name": "string.unquoted.heredoc.perl" - }, - "3": { - "name": "punctuation.definition.heredoc.perl" - }, - "4": { - "patterns": [ - { - "include": "$self" - } - ] - } - }, - "contentName": "text.html.embedded.perl", - "end": "(^HTML$)", - "patterns": [ - { - "include": "#escaped_char" - }, - { - "include": "#variable" - }, - { - "include": "text.html.basic" - } - ] - }, - { - "begin": "(((<<) *XML\\b))(.*)\\n?", - "captures": { - "1": { - "name": "punctuation.definition.string.perl" - }, - "2": { - "name": "string.unquoted.heredoc.perl" - }, - "3": { - "name": "punctuation.definition.heredoc.perl" - }, - "4": { - "patterns": [ - { - "include": "$self" - } - ] - } - }, - "contentName": "text.xml.embedded.perl", - "end": "(^XML$)", - "patterns": [ - { - "include": "#escaped_char" - }, - { - "include": "#variable" - }, - { - "include": "text.xml" - } - ] - }, - { - "begin": "(((<<) *JAVASCRIPT\\b))(.*)\\n?", - "captures": { - "1": { - "name": "punctuation.definition.string.perl" - }, - "2": { - "name": "string.unquoted.heredoc.perl" - }, - "3": { - "name": "punctuation.definition.heredoc.perl" - }, - "4": { - "patterns": [ - { - "include": "$self" - } - ] - } - }, - "contentName": "source.js.embedded.perl", - "end": "(^JAVASCRIPT$)", - "patterns": [ - { - "include": "#escaped_char" - }, - { - "include": "#variable" - }, - { - "include": "source.js" - } - ] - }, - { - "begin": "(((<<) *SQL\\b))(.*)\\n?", - "captures": { - "1": { - "name": "punctuation.definition.string.perl" - }, - "2": { - "name": "string.unquoted.heredoc.perl" - }, - "3": { - "name": "punctuation.definition.heredoc.perl" - }, - "4": { - "patterns": [ - { - "include": "$self" - } - ] - } - }, - "contentName": "source.sql.embedded.perl", - "end": "(^SQL$)", - "patterns": [ - { - "include": "#escaped_char" - }, - { - "include": "#variable" - }, - { - "include": "source.sql" - } - ] - }, - { - "begin": "(((<<) *POSTSCRIPT\\b))(.*)\\n?", - "captures": { - "1": { - "name": "punctuation.definition.string.perl" - }, - "2": { - "name": "string.unquoted.heredoc.perl" - }, - "3": { - "name": "punctuation.definition.heredoc.perl" - }, - "4": { - "patterns": [ - { - "include": "$self" - } - ] - } - }, - "contentName": "source.postscript.embedded.perl", - "end": "(^POSTSCRIPT$)", - "patterns": [ - { - "include": "#escaped_char" - }, - { - "include": "#variable" - }, - { - "include": "source.postscript" - } - ] - }, - { - "begin": "(((<<) *((?![=\\d\\$\\( ])[^;,'\"`\\s\\)]*)))(.*)\\n?", - "captures": { - "1": { - "name": "punctuation.definition.string.perl" - }, - "2": { - "name": "string.unquoted.heredoc.perl" - }, - "3": { - "name": "punctuation.definition.heredoc.perl" - }, - "5": { - "patterns": [ - { - "include": "$self" - } - ] - } - }, - "contentName": "string.unquoted.heredoc.perl", - "end": "(^\\4$)", - "patterns": [ - { - "include": "#escaped_char" - }, - { - "include": "#variable" - } - ] + "include": "#heredoc" }, { "begin": "\\bqq\\s*([^\\(\\{\\[\\<\\w\\s])", @@ -2150,6 +1472,1122 @@ } ] }, + "heredoc": { + "patterns": [ + { + "begin": "((((<<(~)?) *')(HTML)(')))(.*)\\n?", + "beginCaptures": { + "1": { + "name": "string.unquoted.heredoc.raw.perl" + }, + "2": { + "name": "punctuation.definition.string.begin.perl" + }, + "3": { + "name": "punctuation.definition.delimiter.begin.perl" + }, + "7": { + "name": "punctuation.definition.delimiter.end.perl" + }, + "8": { + "patterns": [ + { + "include": "$self" + } + ] + } + }, + "contentName": "string.unquoted.heredoc.raw.perl", + "end": "^((?!\\5)\\s+)?((\\6))$", + "endCaptures": { + "2": { + "name": "string.unquoted.heredoc.raw.perl" + }, + "3": { + "name": "punctuation.definition.string.end.perl" + } + }, + "name": "meta.embedded.block.html", + "patterns": [ + { + "begin": "^", + "end": "\\n", + "name": "text.html.basic", + "patterns": [ + { + "include": "text.html.basic" + } + ] + } + ] + }, + { + "begin": "((((<<(~)?) *')(XML)(')))(.*)\\n?", + "beginCaptures": { + "1": { + "name": "string.unquoted.heredoc.raw.perl" + }, + "2": { + "name": "punctuation.definition.string.begin.perl" + }, + "3": { + "name": "punctuation.definition.delimiter.begin.perl" + }, + "7": { + "name": "punctuation.definition.delimiter.end.perl" + }, + "8": { + "patterns": [ + { + "include": "$self" + } + ] + } + }, + "contentName": "string.unquoted.heredoc.raw.perl", + "end": "^((?!\\5)\\s+)?((\\6))$", + "endCaptures": { + "2": { + "name": "string.unquoted.heredoc.raw.perl" + }, + "3": { + "name": "punctuation.definition.string.end.perl" + } + }, + "name": "meta.embedded.block.xml", + "patterns": [ + { + "begin": "^", + "end": "\\n", + "name": "text.xml", + "patterns": [ + { + "include": "text.xml" + } + ] + } + ] + }, + { + "begin": "((((<<(~)?) *')(CSS)(')))(.*)\\n?", + "beginCaptures": { + "1": { + "name": "string.unquoted.heredoc.raw.perl" + }, + "2": { + "name": "punctuation.definition.string.begin.perl" + }, + "3": { + "name": "punctuation.definition.delimiter.begin.perl" + }, + "7": { + "name": "punctuation.definition.delimiter.end.perl" + }, + "8": { + "patterns": [ + { + "include": "$self" + } + ] + } + }, + "contentName": "string.unquoted.heredoc.raw.perl", + "end": "^((?!\\5)\\s+)?((\\6))$", + "endCaptures": { + "2": { + "name": "string.unquoted.heredoc.raw.perl" + }, + "3": { + "name": "punctuation.definition.string.end.perl" + } + }, + "name": "meta.embedded.block.css", + "patterns": [ + { + "begin": "^", + "end": "\\n", + "name": "source.css", + "patterns": [ + { + "include": "source.css" + } + ] + } + ] + }, + { + "begin": "((((<<(~)?) *')(JAVASCRIPT)(')))(.*)\\n?", + "beginCaptures": { + "1": { + "name": "string.unquoted.heredoc.raw.perl" + }, + "2": { + "name": "punctuation.definition.string.begin.perl" + }, + "3": { + "name": "punctuation.definition.delimiter.begin.perl" + }, + "7": { + "name": "punctuation.definition.delimiter.end.perl" + }, + "8": { + "patterns": [ + { + "include": "$self" + } + ] + } + }, + "contentName": "string.unquoted.heredoc.raw.perl", + "end": "^((?!\\5)\\s+)?((\\6))$", + "endCaptures": { + "2": { + "name": "string.unquoted.heredoc.raw.perl" + }, + "3": { + "name": "punctuation.definition.string.end.perl" + } + }, + "name": "meta.embedded.block.js", + "patterns": [ + { + "begin": "^", + "end": "\\n", + "name": "source.js", + "patterns": [ + { + "include": "source.js" + } + ] + } + ] + }, + { + "begin": "((((<<(~)?) *')(SQL)(')))(.*)\\n?", + "beginCaptures": { + "1": { + "name": "string.unquoted.heredoc.raw.perl" + }, + "2": { + "name": "punctuation.definition.string.begin.perl" + }, + "3": { + "name": "punctuation.definition.delimiter.begin.perl" + }, + "7": { + "name": "punctuation.definition.delimiter.end.perl" + }, + "8": { + "patterns": [ + { + "include": "$self" + } + ] + } + }, + "contentName": "string.unquoted.heredoc.raw.perl", + "end": "^((?!\\5)\\s+)?((\\6))$", + "endCaptures": { + "2": { + "name": "string.unquoted.heredoc.raw.perl" + }, + "3": { + "name": "punctuation.definition.string.end.perl" + } + }, + "name": "meta.embedded.block.sql", + "patterns": [ + { + "begin": "^", + "end": "\\n", + "name": "source.sql", + "patterns": [ + { + "include": "source.sql" + } + ] + } + ] + }, + { + "begin": "((((<<(~)?) *')(POSTSCRIPT)(')))(.*)\\n?", + "beginCaptures": { + "1": { + "name": "string.unquoted.heredoc.raw.perl" + }, + "2": { + "name": "punctuation.definition.string.begin.perl" + }, + "3": { + "name": "punctuation.definition.delimiter.begin.perl" + }, + "7": { + "name": "punctuation.definition.delimiter.end.perl" + }, + "8": { + "patterns": [ + { + "include": "$self" + } + ] + } + }, + "contentName": "string.unquoted.heredoc.raw.perl", + "end": "^((?!\\5)\\s+)?((\\6))$", + "endCaptures": { + "2": { + "name": "string.unquoted.heredoc.raw.perl" + }, + "3": { + "name": "punctuation.definition.string.end.perl" + } + }, + "name": "meta.embedded.block.postscript", + "patterns": [ + { + "begin": "^", + "end": "\\n", + "name": "source.postscript", + "patterns": [ + { + "include": "source.postscript" + } + ] + } + ] + }, + { + "begin": "((((<<(~)?) *')([^']*)(')))(.*)\\n?", + "beginCaptures": { + "1": { + "name": "string.unquoted.heredoc.raw.perl" + }, + "2": { + "name": "punctuation.definition.string.begin.perl" + }, + "3": { + "name": "punctuation.definition.delimiter.begin.perl" + }, + "7": { + "name": "punctuation.definition.delimiter.end.perl" + }, + "8": { + "patterns": [ + { + "include": "$self" + } + ] + } + }, + "contentName": "string.unquoted.heredoc.raw.perl", + "end": "^((?!\\5)\\s+)?((\\6))$", + "endCaptures": { + "2": { + "name": "string.unquoted.heredoc.raw.perl" + }, + "3": { + "name": "punctuation.definition.string.end.perl" + } + } + }, + { + "begin": "((((<<(~)?) *\\\\)((?![=\\d\\$\\( ])[^;,'\"`\\s\\)]*)()))(.*)\\n?", + "beginCaptures": { + "1": { + "name": "string.unquoted.heredoc.raw.perl" + }, + "2": { + "name": "punctuation.definition.string.begin.perl" + }, + "3": { + "name": "punctuation.definition.delimiter.begin.perl" + }, + "7": { + "name": "punctuation.definition.delimiter.end.perl" + }, + "8": { + "patterns": [ + { + "include": "$self" + } + ] + } + }, + "contentName": "string.unquoted.heredoc.raw.perl", + "end": "^((?!\\5)\\s+)?((\\6))$", + "endCaptures": { + "2": { + "name": "string.unquoted.heredoc.raw.perl" + }, + "3": { + "name": "punctuation.definition.string.end.perl" + } + } + }, + { + "begin": "((((<<(~)?) *\")(HTML)(\")))(.*)\\n?", + "beginCaptures": { + "1": { + "name": "string.unquoted.heredoc.interpolated.perl" + }, + "2": { + "name": "punctuation.definition.string.begin.perl" + }, + "3": { + "name": "punctuation.definition.delimiter.begin.perl" + }, + "7": { + "name": "punctuation.definition.delimiter.end.perl" + }, + "8": { + "patterns": [ + { + "include": "$self" + } + ] + } + }, + "contentName": "string.unquoted.heredoc.interpolated.perl", + "end": "^((?!\\5)\\s+)?((\\6))$", + "endCaptures": { + "2": { + "name": "string.unquoted.heredoc.interpolated.perl" + }, + "3": { + "name": "punctuation.definition.string.end.perl" + } + }, + "name": "meta.embedded.block.html", + "patterns": [ + { + "begin": "^", + "end": "\\n", + "name": "text.html.basic", + "patterns": [ + { + "include": "#escaped_char" + }, + { + "include": "#variable" + }, + { + "include": "text.html.basic" + } + ] + } + ] + }, + { + "begin": "((((<<(~)?) *\")(XML)(\")))(.*)\\n?", + "beginCaptures": { + "1": { + "name": "string.unquoted.heredoc.interpolated.perl" + }, + "2": { + "name": "punctuation.definition.string.begin.perl" + }, + "3": { + "name": "punctuation.definition.delimiter.begin.perl" + }, + "7": { + "name": "punctuation.definition.delimiter.end.perl" + }, + "8": { + "patterns": [ + { + "include": "$self" + } + ] + } + }, + "contentName": "string.unquoted.heredoc.interpolated.perl", + "end": "^((?!\\5)\\s+)?((\\6))$", + "endCaptures": { + "2": { + "name": "string.unquoted.heredoc.interpolated.perl" + }, + "3": { + "name": "punctuation.definition.string.end.perl" + } + }, + "name": "meta.embedded.block.xml", + "patterns": [ + { + "begin": "^", + "end": "\\n", + "name": "text.xml", + "patterns": [ + { + "include": "#escaped_char" + }, + { + "include": "#variable" + }, + { + "include": "text.xml" + } + ] + } + ] + }, + { + "begin": "((((<<(~)?) *\")(CSS)(\")))(.*)\\n?", + "beginCaptures": { + "1": { + "name": "string.unquoted.heredoc.interpolated.perl" + }, + "2": { + "name": "punctuation.definition.string.begin.perl" + }, + "3": { + "name": "punctuation.definition.delimiter.begin.perl" + }, + "7": { + "name": "punctuation.definition.delimiter.end.perl" + }, + "8": { + "patterns": [ + { + "include": "$self" + } + ] + } + }, + "contentName": "string.unquoted.heredoc.interpolated.perl", + "end": "^((?!\\5)\\s+)?((\\6))$", + "endCaptures": { + "2": { + "name": "string.unquoted.heredoc.interpolated.perl" + }, + "3": { + "name": "punctuation.definition.string.end.perl" + } + }, + "name": "meta.embedded.block.css", + "patterns": [ + { + "begin": "^", + "end": "\\n", + "name": "source.css", + "patterns": [ + { + "include": "#escaped_char" + }, + { + "include": "#variable" + }, + { + "include": "source.css" + } + ] + } + ] + }, + { + "begin": "((((<<(~)?) *\")(JAVASCRIPT)(\")))(.*)\\n?", + "beginCaptures": { + "1": { + "name": "string.unquoted.heredoc.interpolated.perl" + }, + "2": { + "name": "punctuation.definition.string.begin.perl" + }, + "3": { + "name": "punctuation.definition.delimiter.begin.perl" + }, + "7": { + "name": "punctuation.definition.delimiter.end.perl" + }, + "8": { + "patterns": [ + { + "include": "$self" + } + ] + } + }, + "contentName": "string.unquoted.heredoc.interpolated.perl", + "end": "^((?!\\5)\\s+)?((\\6))$", + "endCaptures": { + "2": { + "name": "string.unquoted.heredoc.interpolated.perl" + }, + "3": { + "name": "punctuation.definition.string.end.perl" + } + }, + "name": "meta.embedded.block.js", + "patterns": [ + { + "begin": "^", + "end": "\\n", + "name": "source.js", + "patterns": [ + { + "include": "#escaped_char" + }, + { + "include": "#variable" + }, + { + "include": "source.js" + } + ] + } + ] + }, + { + "begin": "((((<<(~)?) *\")(SQL)(\")))(.*)\\n?", + "beginCaptures": { + "1": { + "name": "string.unquoted.heredoc.interpolated.perl" + }, + "2": { + "name": "punctuation.definition.string.begin.perl" + }, + "3": { + "name": "punctuation.definition.delimiter.begin.perl" + }, + "7": { + "name": "punctuation.definition.delimiter.end.perl" + }, + "8": { + "patterns": [ + { + "include": "$self" + } + ] + } + }, + "contentName": "string.unquoted.heredoc.interpolated.perl", + "end": "^((?!\\5)\\s+)?((\\6))$", + "endCaptures": { + "2": { + "name": "string.unquoted.heredoc.interpolated.perl" + }, + "3": { + "name": "punctuation.definition.string.end.perl" + } + }, + "name": "meta.embedded.block.sql", + "patterns": [ + { + "begin": "^", + "end": "\\n", + "name": "source.sql", + "patterns": [ + { + "include": "#escaped_char" + }, + { + "include": "#variable" + }, + { + "include": "source.sql" + } + ] + } + ] + }, + { + "begin": "((((<<(~)?) *\")(POSTSCRIPT)(\")))(.*)\\n?", + "beginCaptures": { + "1": { + "name": "string.unquoted.heredoc.interpolated.perl" + }, + "2": { + "name": "punctuation.definition.string.begin.perl" + }, + "3": { + "name": "punctuation.definition.delimiter.begin.perl" + }, + "7": { + "name": "punctuation.definition.delimiter.end.perl" + }, + "8": { + "patterns": [ + { + "include": "$self" + } + ] + } + }, + "contentName": "string.unquoted.heredoc.interpolated.perl", + "end": "^((?!\\5)\\s+)?((\\6))$", + "endCaptures": { + "2": { + "name": "string.unquoted.heredoc.interpolated.perl" + }, + "3": { + "name": "punctuation.definition.string.end.perl" + } + }, + "name": "meta.embedded.block.postscript", + "patterns": [ + { + "begin": "^", + "end": "\\n", + "name": "source.postscript", + "patterns": [ + { + "include": "#escaped_char" + }, + { + "include": "#variable" + }, + { + "include": "source.postscript" + } + ] + } + ] + }, + { + "begin": "((((<<(~)?) *\")([^\"]*)(\")))(.*)\\n?", + "beginCaptures": { + "1": { + "name": "string.unquoted.heredoc.interpolated.perl" + }, + "2": { + "name": "punctuation.definition.string.begin.perl" + }, + "3": { + "name": "punctuation.definition.delimiter.begin.perl" + }, + "7": { + "name": "punctuation.definition.delimiter.end.perl" + }, + "8": { + "patterns": [ + { + "include": "$self" + } + ] + } + }, + "contentName": "string.unquoted.heredoc.interpolated.perl", + "end": "^((?!\\5)\\s+)?((\\6))$", + "endCaptures": { + "2": { + "name": "string.unquoted.heredoc.interpolated.perl" + }, + "3": { + "name": "punctuation.definition.string.end.perl" + } + }, + "patterns": [ + { + "include": "#escaped_char" + }, + { + "include": "#variable" + } + ] + }, + { + "begin": "((((<<(~)?) *)(HTML)()))(.*)\\n?", + "beginCaptures": { + "1": { + "name": "string.unquoted.heredoc.interpolated.perl" + }, + "2": { + "name": "punctuation.definition.string.begin.perl" + }, + "3": { + "name": "punctuation.definition.delimiter.begin.perl" + }, + "7": { + "name": "punctuation.definition.delimiter.end.perl" + }, + "8": { + "patterns": [ + { + "include": "$self" + } + ] + } + }, + "contentName": "string.unquoted.heredoc.interpolated.perl", + "end": "^((?!\\5)\\s+)?((\\6))$", + "endCaptures": { + "2": { + "name": "string.unquoted.heredoc.interpolated.perl" + }, + "3": { + "name": "punctuation.definition.string.end.perl" + } + }, + "name": "meta.embedded.block.html", + "patterns": [ + { + "begin": "^", + "end": "\\n", + "name": "text.html.basic", + "patterns": [ + { + "include": "#escaped_char" + }, + { + "include": "#variable" + }, + { + "include": "text.html.basic" + } + ] + } + ] + }, + { + "begin": "((((<<(~)?) *)(XML)()))(.*)\\n?", + "beginCaptures": { + "1": { + "name": "string.unquoted.heredoc.interpolated.perl" + }, + "2": { + "name": "punctuation.definition.string.begin.perl" + }, + "3": { + "name": "punctuation.definition.delimiter.begin.perl" + }, + "7": { + "name": "punctuation.definition.delimiter.end.perl" + }, + "8": { + "patterns": [ + { + "include": "$self" + } + ] + } + }, + "contentName": "string.unquoted.heredoc.interpolated.perl", + "end": "^((?!\\5)\\s+)?((\\6))$", + "endCaptures": { + "2": { + "name": "string.unquoted.heredoc.interpolated.perl" + }, + "3": { + "name": "punctuation.definition.string.end.perl" + } + }, + "name": "meta.embedded.block.xml", + "patterns": [ + { + "begin": "^", + "end": "\\n", + "name": "text.xml", + "patterns": [ + { + "include": "#escaped_char" + }, + { + "include": "#variable" + }, + { + "include": "text.xml" + } + ] + } + ] + }, + { + "begin": "((((<<(~)?) *)(CSS)()))(.*)\\n?", + "beginCaptures": { + "1": { + "name": "string.unquoted.heredoc.interpolated.perl" + }, + "2": { + "name": "punctuation.definition.string.begin.perl" + }, + "3": { + "name": "punctuation.definition.delimiter.begin.perl" + }, + "7": { + "name": "punctuation.definition.delimiter.end.perl" + }, + "8": { + "patterns": [ + { + "include": "$self" + } + ] + } + }, + "contentName": "string.unquoted.heredoc.interpolated.perl", + "end": "^((?!\\5)\\s+)?((\\6))$", + "endCaptures": { + "2": { + "name": "string.unquoted.heredoc.interpolated.perl" + }, + "3": { + "name": "punctuation.definition.string.end.perl" + } + }, + "name": "meta.embedded.block.css", + "patterns": [ + { + "begin": "^", + "end": "\\n", + "name": "source.css", + "patterns": [ + { + "include": "#escaped_char" + }, + { + "include": "#variable" + }, + { + "include": "source.css" + } + ] + } + ] + }, + { + "begin": "((((<<(~)?) *)(JAVASCRIPT)()))(.*)\\n?", + "beginCaptures": { + "1": { + "name": "string.unquoted.heredoc.interpolated.perl" + }, + "2": { + "name": "punctuation.definition.string.begin.perl" + }, + "3": { + "name": "punctuation.definition.delimiter.begin.perl" + }, + "7": { + "name": "punctuation.definition.delimiter.end.perl" + }, + "8": { + "patterns": [ + { + "include": "$self" + } + ] + } + }, + "contentName": "string.unquoted.heredoc.interpolated.perl", + "end": "^((?!\\5)\\s+)?((\\6))$", + "endCaptures": { + "2": { + "name": "string.unquoted.heredoc.interpolated.perl" + }, + "3": { + "name": "punctuation.definition.string.end.perl" + } + }, + "name": "meta.embedded.block.js", + "patterns": [ + { + "begin": "^", + "end": "\\n", + "name": "source.js", + "patterns": [ + { + "include": "#escaped_char" + }, + { + "include": "#variable" + }, + { + "include": "source.js" + } + ] + } + ] + }, + { + "begin": "((((<<(~)?) *)(SQL)()))(.*)\\n?", + "beginCaptures": { + "1": { + "name": "string.unquoted.heredoc.interpolated.perl" + }, + "2": { + "name": "punctuation.definition.string.begin.perl" + }, + "3": { + "name": "punctuation.definition.delimiter.begin.perl" + }, + "7": { + "name": "punctuation.definition.delimiter.end.perl" + }, + "8": { + "patterns": [ + { + "include": "$self" + } + ] + } + }, + "contentName": "string.unquoted.heredoc.interpolated.perl", + "end": "^((?!\\5)\\s+)?((\\6))$", + "endCaptures": { + "2": { + "name": "string.unquoted.heredoc.interpolated.perl" + }, + "3": { + "name": "punctuation.definition.string.end.perl" + } + }, + "name": "meta.embedded.block.sql", + "patterns": [ + { + "begin": "^", + "end": "\\n", + "name": "source.sql", + "patterns": [ + { + "include": "#escaped_char" + }, + { + "include": "#variable" + }, + { + "include": "source.sql" + } + ] + } + ] + }, + { + "begin": "((((<<(~)?) *)(POSTSCRIPT)()))(.*)\\n?", + "beginCaptures": { + "1": { + "name": "string.unquoted.heredoc.interpolated.perl" + }, + "2": { + "name": "punctuation.definition.string.begin.perl" + }, + "3": { + "name": "punctuation.definition.delimiter.begin.perl" + }, + "7": { + "name": "punctuation.definition.delimiter.end.perl" + }, + "8": { + "patterns": [ + { + "include": "$self" + } + ] + } + }, + "contentName": "string.unquoted.heredoc.interpolated.perl", + "end": "^((?!\\5)\\s+)?((\\6))$", + "endCaptures": { + "2": { + "name": "string.unquoted.heredoc.interpolated.perl" + }, + "3": { + "name": "punctuation.definition.string.end.perl" + } + }, + "name": "meta.embedded.block.postscript", + "patterns": [ + { + "begin": "^", + "end": "\\n", + "name": "source.postscript", + "patterns": [ + { + "include": "#escaped_char" + }, + { + "include": "#variable" + }, + { + "include": "source.postscript" + } + ] + } + ] + }, + { + "begin": "((((<<(~)?) *)((?![=\\d\\$\\( ])[^;,'\"`\\s\\)]*)()))(.*)\\n?", + "beginCaptures": { + "1": { + "name": "string.unquoted.heredoc.interpolated.perl" + }, + "2": { + "name": "punctuation.definition.string.begin.perl" + }, + "3": { + "name": "punctuation.definition.delimiter.begin.perl" + }, + "7": { + "name": "punctuation.definition.delimiter.end.perl" + }, + "8": { + "patterns": [ + { + "include": "$self" + } + ] + } + }, + "contentName": "string.unquoted.heredoc.interpolated.perl", + "end": "^((?!\\5)\\s+)?((\\6))$", + "endCaptures": { + "2": { + "name": "string.unquoted.heredoc.interpolated.perl" + }, + "3": { + "name": "punctuation.definition.string.end.perl" + } + }, + "patterns": [ + { + "include": "#escaped_char" + }, + { + "include": "#variable" + } + ] + }, + { + "begin": "((((<<(~)?) *`)([^`]*)(`)))(.*)\\n?", + "beginCaptures": { + "1": { + "name": "string.unquoted.heredoc.interpolated.perl" + }, + "2": { + "name": "punctuation.definition.string.begin.perl" + }, + "3": { + "name": "punctuation.definition.delimiter.begin.perl" + }, + "7": { + "name": "punctuation.definition.delimiter.end.perl" + }, + "8": { + "patterns": [ + { + "include": "$self" + } + ] + } + }, + "contentName": "string.unquoted.heredoc.shell.perl", + "end": "^((?!\\5)\\s+)?((\\6))$", + "endCaptures": { + "2": { + "name": "string.unquoted.heredoc.interpolated.perl" + }, + "3": { + "name": "punctuation.definition.string.end.perl" + } + }, + "patterns": [ + { + "include": "#escaped_char" + }, + { + "include": "#variable" + } + ] + } + ] + }, "line_comment": { "patterns": [ { diff --git a/extensions/php-language-features/package.json b/extensions/php-language-features/package.json index 712c0379..3763949d 100644 --- a/extensions/php-language-features/package.json +++ b/extensions/php-language-features/package.json @@ -9,6 +9,7 @@ "engines": { "vscode": "0.10.x" }, + "enableProposedApi": true, "activationEvents": [ "onLanguage:php" ], @@ -16,6 +17,16 @@ "categories": [ "Programming Languages" ], + "capabilities": { + "virtualWorkspaces": false, + "untrustedWorkspaces": { + "supported": "limited", + "description": "%workspaceTrust%", + "restrictedConfigurations": [ + "php.validate.executablePath" + ] + } + }, "contributes": { "configuration": { "title": "%configuration.title%", @@ -79,10 +90,12 @@ "watch": "npx gulp watch-extension:php-language-features" }, "dependencies": { - "vscode-nls": "^4.0.0" + "vscode-nls": "^4.0.0", + "which": "^2.0.2" }, "devDependencies": { - "@types/node": "^12.19.9" + "@types/node": "^12.19.9", + "@types/which": "^2.0.0" }, "repository": { "type": "git", diff --git a/extensions/php-language-features/package.nls.json b/extensions/php-language-features/package.nls.json index 720b69f2..1ebf76ea 100644 --- a/extensions/php-language-features/package.nls.json +++ b/extensions/php-language-features/package.nls.json @@ -7,5 +7,6 @@ "commands.categroy.php": "PHP", "command.untrustValidationExecutable": "Disallow PHP validation executable (defined as workspace setting)", "displayName": "PHP Language Features", - "description": "Provides rich language support for PHP files." + "description": "Provides rich language support for PHP files.", + "workspaceTrust": "The extension requires workspace trust when the `php.validate.executablePath` setting will load a version of PHP in the workspace." } diff --git a/extensions/php-language-features/src/features/utils/async.ts b/extensions/php-language-features/src/features/utils/async.ts index 866118be..7364553e 100644 --- a/extensions/php-language-features/src/features/utils/async.ts +++ b/extensions/php-language-features/src/features/utils/async.ts @@ -179,7 +179,7 @@ export class ThrottledDelayer extends Delayer> { this.throttler = new Throttler(); } - public trigger(promiseFactory: ITask>, delay?: number): Promise> { + public override trigger(promiseFactory: ITask>, delay?: number): Promise> { return super.trigger(() => this.throttler.queue(promiseFactory), delay); } } diff --git a/extensions/php-language-features/src/features/validationProvider.ts b/extensions/php-language-features/src/features/validationProvider.ts index 5c9d34af..acc6beaf 100644 --- a/extensions/php-language-features/src/features/validationProvider.ts +++ b/extensions/php-language-features/src/features/validationProvider.ts @@ -5,11 +5,10 @@ import * as cp from 'child_process'; import { StringDecoder } from 'string_decoder'; - +import * as which from 'which'; +import * as path from 'path'; import * as vscode from 'vscode'; - import { ThrottledDelayer } from './utils/async'; - import * as nls from 'vscode-nls'; let localize = nls.loadMessageBundle(); @@ -92,27 +91,24 @@ export default class PHPValidationProvider { private static FileArgs: string[] = ['-l', '-n', '-d', 'display_errors=On', '-d', 'log_errors=Off', '-f']; private validationEnabled: boolean; - private executableIsUserDefined: boolean | undefined; - private executable: string | undefined; - private trigger: RunTrigger; private pauseValidation: boolean; + private config: IPhpConfig | undefined; + private loadConfigP: Promise; private documentListener: vscode.Disposable | null = null; private diagnosticCollection?: vscode.DiagnosticCollection; private delayers?: { [key: string]: ThrottledDelayer }; constructor(private workspaceStore: vscode.Memento) { - this.executable = undefined; this.validationEnabled = true; - this.trigger = RunTrigger.onSave; this.pauseValidation = false; + this.loadConfigP = this.loadConfiguration(); } public activate(subscriptions: vscode.Disposable[]) { this.diagnosticCollection = vscode.languages.createDiagnosticCollection(); subscriptions.push(this); - vscode.workspace.onDidChangeConfiguration(this.loadConfiguration, this, subscriptions); - this.loadConfiguration(); + subscriptions.push(vscode.workspace.onDidChangeConfiguration(() => this.loadConfigP = this.loadConfiguration())); vscode.workspace.onDidOpenTextDocument(this.triggerValidate, this, subscriptions); vscode.workspace.onDidCloseTextDocument((textDocument) => { @@ -133,30 +129,25 @@ export default class PHPValidationProvider { } } - private loadConfiguration(): void { - let section = vscode.workspace.getConfiguration(); - let oldExecutable = this.executable; - if (section) { - this.validationEnabled = section.get(Setting.Enable, true); - let inspect = section.inspect(Setting.ExecutablePath); - if (inspect && inspect.workspaceValue) { - this.executable = inspect.workspaceValue; - this.executableIsUserDefined = false; - } else if (inspect && inspect.globalValue) { - this.executable = inspect.globalValue; - this.executableIsUserDefined = true; - } else { - this.executable = undefined; - this.executableIsUserDefined = undefined; - } - this.trigger = RunTrigger.from(section.get(Setting.Run, RunTrigger.strings.onSave)); - } - if (this.executableIsUserDefined !== true && this.workspaceStore.get(Setting.CheckedExecutablePath, undefined) !== undefined) { + private async loadConfiguration(): Promise { + const section = vscode.workspace.getConfiguration(); + const oldExecutable = this.config?.executable; + this.validationEnabled = section.get(Setting.Enable, true); + + this.config = await getConfig(); + + if (this.config.executableIsUserDefined !== true && this.workspaceStore.get(Setting.CheckedExecutablePath, undefined) !== undefined) { vscode.commands.executeCommand('setContext', 'php.untrustValidationExecutableContext', true); } + + const trustEnabled = vscode.workspace.getConfiguration().get('security.workspace.trust.enabled'); + if (trustEnabled) { + vscode.workspace.requestWorkspaceTrust(); + } + this.delayers = Object.create(null); if (this.pauseValidation) { - this.pauseValidation = oldExecutable === this.executable; + this.pauseValidation = oldExecutable === this.config.executable; } if (this.documentListener) { this.documentListener.dispose(); @@ -164,7 +155,7 @@ export default class PHPValidationProvider { } this.diagnosticCollection!.clear(); if (this.validationEnabled) { - if (this.trigger === RunTrigger.onType) { + if (this.config.trigger === RunTrigger.onType) { this.documentListener = vscode.workspace.onDidChangeTextDocument((e) => { this.triggerValidate(e.document); }); @@ -181,57 +172,85 @@ export default class PHPValidationProvider { vscode.commands.executeCommand('setContext', 'php.untrustValidationExecutableContext', false); } - private triggerValidate(textDocument: vscode.TextDocument): void { + private async triggerValidate(textDocument: vscode.TextDocument): Promise { + await this.loadConfigP; if (textDocument.languageId !== 'php' || this.pauseValidation || !this.validationEnabled) { return; } - interface MessageItem extends vscode.MessageItem { - id: string; - } let trigger = () => { let key = textDocument.uri.toString(); let delayer = this.delayers![key]; if (!delayer) { - delayer = new ThrottledDelayer(this.trigger === RunTrigger.onType ? 250 : 0); + delayer = new ThrottledDelayer(this.config?.trigger === RunTrigger.onType ? 250 : 0); this.delayers![key] = delayer; } delayer.trigger(() => this.doValidate(textDocument)); }; - if (this.executableIsUserDefined !== undefined && !this.executableIsUserDefined) { - let checkedExecutablePath = this.workspaceStore.get(Setting.CheckedExecutablePath, undefined); - if (!checkedExecutablePath || checkedExecutablePath !== this.executable) { - vscode.window.showInformationMessage( - localize('php.useExecutablePath', 'Do you allow {0} (defined as a workspace setting) to be executed to lint PHP files?', this.executable), - { - title: localize('php.yes', 'Allow'), - id: 'yes' - }, - { - title: localize('php.no', 'Disallow'), - isCloseAffordance: true, - id: 'no' - } - ).then(selected => { - if (!selected || selected.id === 'no') { - this.pauseValidation = true; - } else if (selected.id === 'yes') { - this.workspaceStore.update(Setting.CheckedExecutablePath, this.executable); - vscode.commands.executeCommand('setContext', 'php.untrustValidationExecutableContext', true); - trigger(); - } - }); - return; + const trustEnabled = vscode.workspace.getConfiguration().get('security.workspace.trust.enabled'); + if (trustEnabled) { + if (vscode.workspace.isTrusted) { + trigger(); } + } else if (this.config!.executableIsUserDefined !== undefined && !this.config!.executableIsUserDefined) { + const checkedExecutablePath = this.workspaceStore.get(Setting.CheckedExecutablePath, undefined); + if (!checkedExecutablePath || checkedExecutablePath !== this.config!.executable) { + if (await this.showCustomTrustDialog()) { + this.workspaceStore.update(Setting.CheckedExecutablePath, this.config!.executable); + vscode.commands.executeCommand('setContext', 'php.untrustValidationExecutableContext', true); + } else { + this.pauseValidation = true; + return; + } + } + + trigger(); } - trigger(); + } + + private async showCustomTrustDialog(): Promise { + interface MessageItem extends vscode.MessageItem { + id: string; + } + + const selected = await vscode.window.showInformationMessage( + localize('php.useExecutablePath', 'Do you allow {0} (defined as a workspace setting) to be executed to lint PHP files?', this.config!.executable), + { + title: localize('php.yes', 'Allow'), + id: 'yes' + }, + { + title: localize('php.no', 'Disallow'), + isCloseAffordance: true, + id: 'no' + } + ); + + if (selected && selected.id === 'yes') { + return true; + } + + return false; } private doValidate(textDocument: vscode.TextDocument): Promise { - return new Promise((resolve) => { - let executable = this.executable || 'php'; + return new Promise(async (resolve) => { + const executable = this.config!.executable; + if (!executable) { + this.showErrorMessage(localize('noPhp', 'Cannot validate since a PHP installation could not be found. Use the setting \'php.validate.executablePath\' to configure the PHP executable.')); + this.pauseValidation = true; + resolve(); + return; + } + + if (!path.isAbsolute(executable)) { + // executable should either be resolved to an absolute path or undefined. + // This is just to be sure. + return; + } + let decoder = new LineDecoder(); let diagnostics: vscode.Diagnostic[] = []; let processLine = (line: string) => { @@ -249,7 +268,7 @@ export default class PHPValidationProvider { let options = (vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders[0]) ? { cwd: vscode.workspace.workspaceFolders[0].uri.fsPath } : undefined; let args: string[]; - if (this.trigger === RunTrigger.onSave) { + if (this.config!.trigger === RunTrigger.onSave) { args = PHPValidationProvider.FileArgs.slice(0); args.push(textDocument.fileName); } else { @@ -267,7 +286,7 @@ export default class PHPValidationProvider { resolve(); }); if (childProcess.pid) { - if (this.trigger === RunTrigger.onType) { + if (this.config!.trigger === RunTrigger.onType) { childProcess.stdin.write(textDocument.getText()); childProcess.stdin.end(); } @@ -294,7 +313,7 @@ export default class PHPValidationProvider { private async showError(error: any, executable: string): Promise { let message: string | null = null; if (error.code === 'ENOENT') { - if (this.executable) { + if (this.config!.executable) { message = localize('wrongExecutable', 'Cannot validate since {0} is not a valid php executable. Use the setting \'php.validate.executablePath\' to configure the PHP executable.', executable); } else { message = localize('noExecutable', 'Cannot validate since no PHP executable is set. Use the setting \'php.validate.executablePath\' to configure the PHP executable.'); @@ -306,9 +325,63 @@ export default class PHPValidationProvider { return; } + return this.showErrorMessage(message); + } + + private async showErrorMessage(message: string): Promise { const openSettings = localize('goToSetting', 'Open Settings'); if (await vscode.window.showInformationMessage(message, openSettings) === openSettings) { vscode.commands.executeCommand('workbench.action.openSettings', Setting.ExecutablePath); } } } + +interface IPhpConfig { + readonly executable: string | undefined; + readonly executableIsUserDefined: boolean | undefined; + readonly trigger: RunTrigger; +} + +async function getConfig(): Promise { + const section = vscode.workspace.getConfiguration(); + + let executable: string | undefined; + let executableIsUserDefined: boolean | undefined; + const inspect = section.inspect(Setting.ExecutablePath); + if (inspect && inspect.workspaceValue) { + executable = inspect.workspaceValue; + executableIsUserDefined = false; + } else if (inspect && inspect.globalValue) { + executable = inspect.globalValue; + executableIsUserDefined = true; + } else { + executable = undefined; + executableIsUserDefined = undefined; + } + + if (executable && !path.isAbsolute(executable)) { + const first = vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders[0]; + if (first) { + executable = vscode.Uri.joinPath(first.uri, executable).fsPath; + } else { + executable = undefined; + } + } else if (!executable) { + executable = await getPhpPath(); + } + + const trigger = RunTrigger.from(section.get(Setting.Run, RunTrigger.strings.onSave)); + return { + executable, + executableIsUserDefined, + trigger + }; +} + +async function getPhpPath(): Promise { + try { + return await which('php'); + } catch (e) { + return undefined; + } +} diff --git a/extensions/php-language-features/src/typings/refs.d.ts b/extensions/php-language-features/src/typings/refs.d.ts index bc057c55..57b50520 100644 --- a/extensions/php-language-features/src/typings/refs.d.ts +++ b/extensions/php-language-features/src/typings/refs.d.ts @@ -5,3 +5,4 @@ /// /// +/// diff --git a/extensions/php-language-features/tsconfig.json b/extensions/php-language-features/tsconfig.json index 296ddb38..070854d6 100644 --- a/extensions/php-language-features/tsconfig.json +++ b/extensions/php-language-features/tsconfig.json @@ -1,9 +1,9 @@ { - "extends": "../shared.tsconfig.json", + "extends": "../tsconfig.base.json", "compilerOptions": { "outDir": "./out" }, "include": [ "src/**/*" ] -} \ No newline at end of file +} diff --git a/extensions/php-language-features/yarn.lock b/extensions/php-language-features/yarn.lock index 687f15f4..b0fa5625 100644 --- a/extensions/php-language-features/yarn.lock +++ b/extensions/php-language-features/yarn.lock @@ -7,7 +7,24 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-12.19.9.tgz#990ad687ad8b26ef6dcc34a4f69c33d40c95b679" integrity sha512-yj0DOaQeUrk3nJ0bd3Y5PeDRJ6W0r+kilosLA+dzF3dola/o9hxhMSg2sFvVcA2UHS5JSOsZp4S0c1OEXc4m1Q== +"@types/which@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@types/which/-/which-2.0.0.tgz#446d35586611dee657120de8e0457382a658fc25" + integrity sha512-JHTNOEpZnACQdsTojWggn+SQ8IucfqEhtz7g8Z0G67WdSj4x3F0X5I2c/CVcl8z/QukGrIHeQ/N49v1au74XFQ== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= + vscode-nls@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.0.0.tgz#4001c8a6caba5cedb23a9c5ce1090395c0e44002" integrity sha512-qCfdzcH+0LgQnBpZA53bA32kzp9rpq/f66Som577ObeuDlFIrtbEJ+A/+CCxjIh4G8dpJYNCKIsxpRAHIfsbNw== + +which@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" diff --git a/extensions/php/syntaxes/php.tmLanguage.json b/extensions/php/syntaxes/php.tmLanguage.json index d6341cce..0dcfc829 100644 --- a/extensions/php/syntaxes/php.tmLanguage.json +++ b/extensions/php/syntaxes/php.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/atom/language-php/commit/72739e6341b1b4bf4aa185e928932983baca449e", + "version": "https://github.com/atom/language-php/commit/5fae657cf989701e9594912772daff33249839b3", "scopeName": "source.php", "patterns": [ { @@ -750,7 +750,7 @@ ] }, { - "match": "(?xi)\n((?:(?:public|private|protected|static)(?:\\s+|(?=\\?)))+) # At least one modifier\n(\n (?:\\?\\s*)? [a-z0-9_\\x{7f}-\\x{7fffffff}\\\\]+ | # nullable type\n [a-z0-9_\\x{7f}-\\x{7fffffff}\\\\]+ (?: \\s*\\|\\s* [a-z0-9_\\x{7f}-\\x{7fffffff}\\\\]+)+ # union type\n)?\n\\s+ ((\\$)[a-z_\\x{7f}-\\x{7fffffff}][a-z0-9_\\x{7f}-\\x{7fffffff}]*) # Variable name", + "match": "(?xi)\n((?:(?:public|private|protected|static)(?:\\s+|(?=\\?)))++) # At least one modifier\n(\n (?:\\?\\s*)? [a-z0-9_\\x{7f}-\\x{7fffffff}\\\\]+ | # nullable type\n [a-z0-9_\\x{7f}-\\x{7fffffff}\\\\]+ (?: \\s*\\|\\s* [a-z0-9_\\x{7f}-\\x{7fffffff}\\\\]+)+ # union type\n)?\n\\s+ ((\\$)[a-z_\\x{7f}-\\x{7fffffff}][a-z0-9_\\x{7f}-\\x{7fffffff}]*) # Variable name", "captures": { "1": { "patterns": [ @@ -1304,6 +1304,9 @@ }, "function-parameters": { "patterns": [ + { + "include": "#attribute" + }, { "include": "#comments" }, @@ -3682,4 +3685,4 @@ "name": "keyword.operator.null-coalescing.php" } } -} \ No newline at end of file +} diff --git a/extensions/powershell/cgmanifest.json b/extensions/powershell/cgmanifest.json index 04444f36..13eee1db 100644 --- a/extensions/powershell/cgmanifest.json +++ b/extensions/powershell/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "PowerShell/EditorSyntax", "repositoryUrl": "https://github.com/PowerShell/EditorSyntax", - "commitHash": "d10ae29c0d3ceb248172c383a159ae43b9ccfb4d" + "commitHash": "c150c15cca30cafd2159e3f53514f93ccf4c5649" } }, "license": "MIT", diff --git a/extensions/powershell/syntaxes/powershell.tmLanguage.json b/extensions/powershell/syntaxes/powershell.tmLanguage.json index de473e19..61267a44 100644 --- a/extensions/powershell/syntaxes/powershell.tmLanguage.json +++ b/extensions/powershell/syntaxes/powershell.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/PowerShell/EditorSyntax/commit/d10ae29c0d3ceb248172c383a159ae43b9ccfb4d", + "version": "https://github.com/PowerShell/EditorSyntax/commit/c150c15cca30cafd2159e3f53514f93ccf4c5649", "name": "PowerShell", "scopeName": "source.powershell", "patterns": [ @@ -42,7 +42,7 @@ "include": "#variable" }, { - "include": "#interpolatedStringContent" + "include": "#subexpression" }, { "include": "#function" @@ -70,13 +70,14 @@ "include": "#doubleQuotedStringEscapes" }, { - "begin": "(?MH1dntRQHDK}5xnhA7l_AY3RGq!6S6iGlop%0u9i zR!1!=gsNcC!GKUjKmr#Ck3^_o6v#jjrS!@ps6<2{H`+h$%-#8B&-u=LyL)zLceaF1 z+mFLqV*voT17RTy03a=efG$!CCvHbGwNOnDXYSJ?fQJEh5OAa)d+re6N&!y>PHWRo z!TY5A%{fB=mH@8oQ%J#S8PF=)%F|N%HHAi_nO=HND1GdG?#c4Ijo~@9NWR=T^_t!C zoN{?wC|iI#UV_s@fF~97&a-Y$-;pnaZ0%U>{IcP>RRI6(VF+YPrkB>>_BpK%Wk<@j z4tM;(phW^>Z#oMWRCi8mD8tKl*aZG`TE$4LP`mE`8k?@=0#UWbTPPxEG^}qbsoI^R z%+TD}#Sgq3$1x|Ku=$=$%eYGmIkG}KvhPaAHvYn7 zlltG$mX3Q&s1L5@wkUEk&!*{-KID|YD95eUu!c{rJo>uV3!SxODcf_XXWBT@^J0$q z25I0WqeJ3yv>ugotRHVu9EdjwYqPZS1i^2+^C=A$>u)MU7rj4}_|a3GGU-xLY=hZ7TDFaO!L zOzn#e?ujj^Q!i0>(_%`yRi;kmH#|@Q*WWGeRro*JjGfekpJkXujGD1lrt%(!*@T#W zI3U35S0=67&m;+5|LQnsH?wxmAiulB+fo&bag>U5A`&RczpqrU*)OigLldhKMDwc( z^mk9~^#ymxos7dXn};I**O7bfF2?E39C;C;M!p#OYI8qmA@2P80Y4Kt!JoA7YI2oK*R z>;^wMw;^vN=T}e;q0c<28Z6#Dv5MlU+jU~vrI!jgv0X78xqCzi3%B{;+vIDomC%-w`x=mPmN=;-?T?e4OUiOJq%lRU{$otMzk#z-X4!MAW-BMY+I;OffJyz zH8atBY9Ze#^uq7Zp8iJOge%-j=EUiW67+*!>d`$~F$|RPnX%)w07H_P5eVt7;ZRSV zlfVr=yq^#S3so+MEhxzw15IerfF41gfbG2X9$~fd==H~3sVAs{WL83qj*myBcNyGlEe1<#>u4-e4mqpcH#(rz~I)?*-^6K(pHHS zmjQnbGUfmVPBipJvSqenBwY%Ll-bDh(f75UOV3p9q?DtFZN%}xD;T!-JAH zXe;RXTKoF-qDl|AubN5FADQvG;3jt7nrlT!1-jvo^>u?t*qYR9CPXss1wo#5v3Eb; z{PhNZ#U1inn(;^Y-LII_6{dShjD=mBepFEItT+nAXf)9=X?S6mFL#3+$&4ojssZl8 zn|LE(W4>J$jJ;*n)KJlZWnWCkZH)2+=q2BVyh%QO)4zFjr=48&6(&mQc^px@FoIQl z8-&Q1Q+9udkYrV@I4n$;mHpf`ad2A==9OE*kGoRDA20epc7rc16PSC8a5)#g{H?LE zwJfYuMsm6K2Qj5a|2mSe?&?(2=uA~(1ohe{l@bx~7_vH5;5G6?KLgdELm3EgE)@;^_;!mX6ixHP77M-*w{oP&*qWOrSX z-#2yYU$55c#=ha!AMr064(1ZaimfWTW(|uH3jH?qzUZ1on9OeXF1Fg4=fQTLNXKcI zSya_RY(v}P(2`4#j@-#HeB`^gMVakG6?)MZ?UN2=sw1~T4TZ1g?=)mKMRgJ1D*h^8 zI3&VTM;4D;KRXuZ{eUaMr94>WsDni<-GtvpFEe~wu*B zTB58h`N{RZ{n(nIq*FEC$LduR%`Wm?e?GUrmm#le4z0%LzSKaTwaq>p(}a(|891([ 'localhost', - '127.0.0.1' + // localhost IPv4 + '127.0.0.1', + // localhost IPv6 + '0:0:0:0:0:0:0:1', + '::1', + // all interfaces IPv4 + '0.0.0.0', + // all interfaces IPv6 + '0:0:0:0:0:0:0:0', + '::' ]); const openerId = 'simpleBrowser.open'; diff --git a/extensions/simple-browser/src/simpleBrowserView.ts b/extensions/simple-browser/src/simpleBrowserView.ts index ef491d60..5fabf32a 100644 --- a/extensions/simple-browser/src/simpleBrowserView.ts +++ b/extensions/simple-browser/src/simpleBrowserView.ts @@ -72,7 +72,7 @@ export class SimpleBrowserView extends Disposable { this.show(url); } - public dispose() { + public override dispose() { this._onDidDispose.fire(); super.dispose(); } @@ -90,7 +90,6 @@ export class SimpleBrowserView extends Disposable { const mainJs = this.extensionResourceUrl('media', 'index.js'); const mainCss = this.extensionResourceUrl('media', 'main.css'); const codiconsUri = this.extensionResourceUrl('media', 'codicon.css'); - const codiconsFontUri = this.extensionResourceUrl('media', 'codicon.ttf'); return /* html */ ` @@ -99,7 +98,7 @@ export class SimpleBrowserView extends Disposable { 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 delta 35768 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\n )) |\n ((async\\s*)?(\n ((<\\s*$)|((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "match": "(?x)(?:(?)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "captures": { "1": { "name": "storage.modifier.ts" @@ -898,7 +898,7 @@ } }, { - "match": "(?x)(?:(?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*))?[\\(])", + "begin": "(?x)(?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*))?[\\(])", "beginCaptures": { "1": { "name": "storage.modifier.ts" @@ -1310,12 +1313,15 @@ "name": "storage.modifier.ts" }, "3": { - "name": "storage.modifier.async.ts" + "name": "storage.modifier.ts" }, "4": { - "name": "keyword.operator.new.ts" + "name": "storage.modifier.async.ts" }, "5": { + "name": "keyword.operator.new.ts" + }, + "6": { "name": "keyword.generator.asterisk.ts" } }, @@ -1331,7 +1337,7 @@ }, { "name": "meta.method.declaration.ts", - "begin": "(?x)(?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*))?[\\(])", + "begin": "(?x)(?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*))?[\\(])", "beginCaptures": { "1": { "name": "storage.modifier.ts" @@ -1340,12 +1346,15 @@ "name": "storage.modifier.ts" }, "3": { - "name": "storage.modifier.async.ts" + "name": "storage.modifier.ts" }, "4": { - "name": "storage.type.property.ts" + "name": "storage.modifier.async.ts" }, "5": { + "name": "storage.type.property.ts" + }, + "6": { "name": "keyword.generator.asterisk.ts" } }, @@ -1846,7 +1855,7 @@ }, "access-modifier": { "name": "storage.modifier.ts", - "match": "(?)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "match": "(?x)(?:(?)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "captures": { "1": { "name": "storage.modifier.ts" @@ -3109,7 +3118,7 @@ } }, { - "match": "(?x)(?:(?]|$|;|^\\s*$|(?:^\\s*(?:abstract|async|class|const|declare|enum|export|function|import|interface|let|module|namespace|return|type|var)\\b))", + "end": "(?=[,);}\\]=>:&|{\\?]|$|;|^\\s*$|(?:^\\s*(?:abstract|async|class|const|declare|enum|export|function|import|interface|let|module|namespace|return|type|var)\\b))", "patterns": [ { "include": "#expression" @@ -4313,9 +4322,12 @@ "type-fn-type-parameters": { "patterns": [ { - "begin": "(?|&&|\\|\\||\\*\\/)\\s*(\\/)(?![\\/*])(?=(?:[^\\/\\\\\\[\\()]|\\\\.|\\[([^\\]\\\\]|\\\\.)+\\]|\\(([^\\)\\\\]|\\\\.)+\\))+\\/([gimsuy]+|(?![\\/\\*])|(?=\\/\\*))(?!\\s*[a-zA-Z0-9_$]))", + "begin": "(?|&&|\\|\\||\\*\\/)\\s*(\\/)(?![\\/*])(?=(?:[^\\/\\\\\\[\\()]|\\\\.|\\[([^\\]\\\\]|\\\\.)+\\]|\\(([^\\)\\\\]|\\\\.)+\\))+\\/([dgimsuy]+|(?![\\/\\*])|(?=\\/\\*))(?!\\s*[a-zA-Z0-9_$]))", "beginCaptures": { "1": { "name": "punctuation.definition.string.begin.ts" } }, - "end": "(/)([gimsuy]*)", + "end": "(/)([dgimsuy]*)", "endCaptures": { "1": { "name": "punctuation.definition.string.end.ts" @@ -4851,13 +4866,13 @@ }, { "name": "string.regexp.ts", - "begin": "((?)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "match": "(?x)(?:(?)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "captures": { "1": { "name": "storage.modifier.tsx" @@ -901,7 +901,7 @@ } }, { - "match": "(?x)(?:(?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*))?[\\(])", + "begin": "(?x)(?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*))?[\\(])", "beginCaptures": { "1": { "name": "storage.modifier.tsx" @@ -1313,12 +1316,15 @@ "name": "storage.modifier.tsx" }, "3": { - "name": "storage.modifier.async.tsx" + "name": "storage.modifier.tsx" }, "4": { - "name": "keyword.operator.new.tsx" + "name": "storage.modifier.async.tsx" }, "5": { + "name": "keyword.operator.new.tsx" + }, + "6": { "name": "keyword.generator.asterisk.tsx" } }, @@ -1334,7 +1340,7 @@ }, { "name": "meta.method.declaration.tsx", - "begin": "(?x)(?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*))?[\\(])", + "begin": "(?x)(?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*))?[\\(])", "beginCaptures": { "1": { "name": "storage.modifier.tsx" @@ -1343,12 +1349,15 @@ "name": "storage.modifier.tsx" }, "3": { - "name": "storage.modifier.async.tsx" + "name": "storage.modifier.tsx" }, "4": { - "name": "storage.type.property.tsx" + "name": "storage.modifier.async.tsx" }, "5": { + "name": "storage.type.property.tsx" + }, + "6": { "name": "keyword.generator.asterisk.tsx" } }, @@ -1849,7 +1858,7 @@ }, "access-modifier": { "name": "storage.modifier.tsx", - "match": "(?)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "match": "(?x)(?:(?)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "captures": { "1": { "name": "storage.modifier.tsx" @@ -3112,7 +3121,7 @@ } }, { - "match": "(?x)(?:(?]|$|;|^\\s*$|(?:^\\s*(?:abstract|async|class|const|declare|enum|export|function|import|interface|let|module|namespace|return|type|var)\\b))", + "end": "(?=[,);}\\]=>:&|{\\?]|$|;|^\\s*$|(?:^\\s*(?:abstract|async|class|const|declare|enum|export|function|import|interface|let|module|namespace|return|type|var)\\b))", "patterns": [ { "include": "#expression" @@ -4264,9 +4273,12 @@ "type-fn-type-parameters": { "patterns": [ { - "begin": "(?|&&|\\|\\||\\*\\/)\\s*(\\/)(?![\\/*])(?=(?:[^\\/\\\\\\[\\()]|\\\\.|\\[([^\\]\\\\]|\\\\.)+\\]|\\(([^\\)\\\\]|\\\\.)+\\))+\\/([gimsuy]+|(?![\\/\\*])|(?=\\/\\*))(?!\\s*[a-zA-Z0-9_$]))", + "begin": "(?|&&|\\|\\||\\*\\/)\\s*(\\/)(?![\\/*])(?=(?:[^\\/\\\\\\[\\()]|\\\\.|\\[([^\\]\\\\]|\\\\.)+\\]|\\(([^\\)\\\\]|\\\\.)+\\))+\\/([dgimsuy]+|(?![\\/\\*])|(?=\\/\\*))(?!\\s*[a-zA-Z0-9_$]))", "beginCaptures": { "1": { "name": "punctuation.definition.string.begin.tsx" } }, - "end": "(/)([gimsuy]*)", + "end": "(/)([dgimsuy]*)", "endCaptures": { "1": { "name": "punctuation.definition.string.end.tsx" @@ -4802,13 +4817,13 @@ }, { "name": "string.regexp.tsx", - "begin": "((?XwCQgatu{x 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 bs#KC6>pk`0nsVH<{}c4heqI%u*eU-9bRG~H literal 0 HcmV?d00001 diff --git a/extensions/typescript-language-features/package.json b/extensions/typescript-language-features/package.json index 1bff4648..738dc68e 100644 --- a/extensions/typescript-language-features/package.json +++ b/extensions/typescript-language-features/package.json @@ -3,18 +3,27 @@ "description": "%description%", "displayName": "%displayName%", "version": "1.0.0", - "icon": "icon.png", "author": "vscode", "publisher": "vscode", "license": "MIT", "aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217", "enableProposedApi": true, - "workspaceTrust": { - "required": "onDemand" + "capabilities": { + "virtualWorkspaces": true, + "untrustedWorkspaces": { + "supported": "limited", + "description": "%workspaceTrust%", + "restrictedConfigurations": [ + "typescript.tsdk", + "typescript.tsserver.pluginPaths", + "typescript.npm" + ] + } }, "engines": { "vscode": "^1.30.0" }, + "icon": "media/icon.png", "categories": [ "Programming Languages" ], @@ -138,7 +147,7 @@ "typescript.disableAutomaticTypeAcquisition": { "type": "boolean", "default": false, - "description": "%typescript.disableAutomaticTypeAcquisition%", + "markdownDescription": "%typescript.disableAutomaticTypeAcquisition%", "scope": "window", "tags": [ "usesOnlineServices" @@ -156,13 +165,13 @@ "null" ], "default": null, - "description": "%typescript.npm%", + "markdownDescription": "%typescript.npm%", "scope": "machine" }, "typescript.check.npmIsInstalled": { "type": "boolean", "default": true, - "description": "%typescript.check.npmIsInstalled%", + "markdownDescription": "%typescript.check.npmIsInstalled%", "scope": "window" }, "javascript.referencesCodeLens.enabled": { @@ -258,6 +267,24 @@ "description": "%configuration.suggest.includeAutomaticOptionalChainCompletions%", "scope": "resource" }, + "javascript.suggest.includeCompletionsForImportStatements": { + "type": "boolean", + "default": true, + "description": "%configuration.suggest.includeCompletionsForImportStatements%", + "scope": "resource" + }, + "typescript.suggest.includeCompletionsForImportStatements": { + "type": "boolean", + "default": true, + "description": "%configuration.suggest.includeCompletionsForImportStatements%", + "scope": "resource" + }, + "typescript.suggest.includeCompletionsWithSnippetText": { + "type": "boolean", + "default": true, + "description": "%configuration.suggest.includeCompletionsWithSnippetText%", + "scope": "resource" + }, "typescript.reportStyleChecksAsWarnings": { "type": "boolean", "default": true, @@ -1095,7 +1122,8 @@ "option": { "type": "string" } - } + }, + "when": "shellExecutionSupported" } ], "problemPatterns": [ @@ -1137,10 +1165,10 @@ "background": { "activeOnStart": true, "beginsPattern": { - "regexp": "^\\s*(?:message TS6032:|\\[?\\D*\\d{1,2}[:.]\\d{1,2}[:.]\\d{1,2}\\D*(├\\D*\\d{1,2}\\D+┤)?(?:\\]| -)) File change detected\\. Starting incremental compilation\\.\\.\\." + "regexp": "^\\s*(?:message TS6032:|\\[?\\D*.{1,2}[:.].{1,2}[:.].{1,2}\\D*(├\\D*\\d{1,2}\\D+┤)?(?:\\]| -)) File change detected\\. Starting incremental compilation\\.\\.\\." }, "endsPattern": { - "regexp": "^\\s*(?:message TS6042:|\\[?\\D*\\d{1,2}[:.]\\d{1,2}[:.]\\d{1,2}\\D*(├\\D*\\d{1,2}\\D+┤)?(?:\\]| -)) (?:Compilation complete\\.|Found \\d+ errors?\\.) Watching for file changes\\." + "regexp": "^\\s*(?:message TS6042:|\\[?\\D*.{1,2}[:.].{1,2}[:.].{1,2}\\D*(├\\D*\\d{1,2}\\D+┤)?(?:\\]| -)) (?:Compilation complete\\.|Found \\d+ errors?\\.) Watching for file changes\\." } } } diff --git a/extensions/typescript-language-features/package.nls.json b/extensions/typescript-language-features/package.nls.json index 0bdc8d5f..3f400cf4 100644 --- a/extensions/typescript-language-features/package.nls.json +++ b/extensions/typescript-language-features/package.nls.json @@ -1,12 +1,15 @@ { "displayName": "TypeScript and JavaScript Language Features", "description": "Provides rich language support for JavaScript and TypeScript.", + "workspaceTrust": "The extension requires workspace trust when the workspace version is used because it executes code specified by the workspace.", "reloadProjects.title": "Reload Project", "configuration.typescript": "TypeScript", "configuration.suggest.completeFunctionCalls": "Complete functions with their parameter signature.", "configuration.suggest.includeAutomaticOptionalChainCompletions": "Enable/disable showing completions on potentially undefined values that insert an optional chain call. Requires TS 3.7+ and strict null checks to be enabled.", - "typescript.tsdk.desc": "Specifies the folder path to the tsserver and lib*.d.ts files under a TypeScript install to use for IntelliSense, for example: `./node_modules/typescript/lib`.\n\n- When specified as a user setting, the TypeScript version from `typescript.tsdk` automatically replaces the built-in TypeScript version.\n- When specified as a workspace setting, `typescript.tsdk` allows you to switch to use that workspace version of TypeScript for IntelliSense with the `TypeScript: Select TypeScript version` command.\n\nSee the [TypeScript documentation](https://code.visualstudio.com/docs/typescript/typescript-compiling#_using-newer-typescript-versions) for more detail about managing TypeScript versions.", - "typescript.disableAutomaticTypeAcquisition": "Disables automatic type acquisition. Automatic type acquisition fetches `@types` packages from npm to improve IntelliSense for external libraries.", + "configuration.suggest.includeCompletionsForImportStatements": "Enable/disable auto-import-style completions on partially-typed import statements. Requires using TypeScript 4.3+ in the workspace.", + "configuration.suggest.includeCompletionsWithSnippetText": "Enable/disable snippet completions from TS Server. Requires using TypeScript 4.3+ in the workspace.", + "typescript.tsdk.desc": "Specifies the folder path to the tsserver and `lib*.d.ts` files under a TypeScript install to use for IntelliSense, for example: `./node_modules/typescript/lib`.\n\n- When specified as a user setting, the TypeScript version from `typescript.tsdk` automatically replaces the built-in TypeScript version.\n- When specified as a workspace setting, `typescript.tsdk` allows you to switch to use that workspace version of TypeScript for IntelliSense with the `TypeScript: Select TypeScript version` command.\n\nSee the [TypeScript documentation](https://code.visualstudio.com/docs/typescript/typescript-compiling#_using-newer-typescript-versions) for more detail about managing TypeScript versions.", + "typescript.disableAutomaticTypeAcquisition": "Disables [automatic type acquisition](https://code.visualstudio.com/docs/nodejs/working-with-javascript#_typings-and-automatic-type-acquisition). Automatic type acquisition fetches `@types` packages from npm to improve IntelliSense for external libraries.", "typescript.enablePromptUseWorkspaceTsdk": "Enables prompting of users to use the TypeScript version configured in the workspace for Intellisense.", "typescript.tsserver.enableTracing": "Enables tracing TS server performance to a directory. These trace files can be used to diagnose TS Server performance issues. The log may contain file paths, source code, and other potentially sensitive information from your project.", "typescript.tsserver.log": "Enables logging of the TS server to a file. This log can be used to diagnose TS Server issues. The log may contain file paths, source code, and other potentially sensitive information from your project.", @@ -47,8 +50,8 @@ "typescript.restartTsServer": "Restart TS server", "typescript.selectTypeScriptVersion.title": "Select TypeScript Version...", "typescript.reportStyleChecksAsWarnings": "Report style checks as warnings.", - "typescript.npm": "Specifies the path to the npm executable used for Automatic Type Acquisition.", - "typescript.check.npmIsInstalled": "Check if npm is installed for Automatic Type Acquisition.", + "typescript.npm": "Specifies the path to the npm executable used for [Automatic Type Acquisition](https://code.visualstudio.com/docs/nodejs/working-with-javascript#_typings-and-automatic-type-acquisition).", + "typescript.check.npmIsInstalled": "Check if npm is installed for [Automatic Type Acquisition](https://code.visualstudio.com/docs/nodejs/working-with-javascript#_typings-and-automatic-type-acquisition).", "configuration.suggest.names": "Enable/disable including unique names from the file in JavaScript suggestions. Note that name suggestions are always disabled in JavaScript code that is semantically checked using `@ts-check` or `checkJs`.", "typescript.tsc.autoDetect": "Controls auto detection of tsc tasks.", "typescript.tsc.autoDetect.off": "Disable this feature.", @@ -59,7 +62,7 @@ "typescript.problemMatchers.tscWatch.label": "TypeScript problems (watch mode)", "configuration.suggest.paths": "Enable/disable suggestions for paths in import statements and require calls.", "configuration.tsserver.useSeparateSyntaxServer": "Enable/disable spawning a separate TypeScript server that can more quickly respond to syntax related operations, such as calculating folding or computing document symbols. Requires using TypeScript 3.4.0 or newer in the workspace.", - "configuration.tsserver.maxTsServerMemory": "Set the maximum amount of memory (in MB) to allocate to the TypeScript server process", + "configuration.tsserver.maxTsServerMemory": "The maximum amount of memory (in MB) to allocate to the TypeScript server process.", "configuration.tsserver.experimental.enableProjectDiagnostics": "(Experimental) Enables project wide error reporting.", "typescript.locale": "Sets the locale used to report JavaScript and TypeScript errors. Default of `null` uses VS Code's locale.", "configuration.implicitProjectConfig.checkJs": "Enable/disable semantic checking of JavaScript files. Existing `jsconfig.json` or `tsconfig.json` files override this setting.", diff --git a/extensions/typescript-language-features/src/languageFeatures/codeLens/referencesCodeLens.ts b/extensions/typescript-language-features/src/languageFeatures/codeLens/referencesCodeLens.ts index bbb8e21e..d9097255 100644 --- a/extensions/typescript-language-features/src/languageFeatures/codeLens/referencesCodeLens.ts +++ b/extensions/typescript-language-features/src/languageFeatures/codeLens/referencesCodeLens.ts @@ -19,7 +19,7 @@ const localize = nls.loadMessageBundle(); export class TypeScriptReferencesCodeLensProvider extends TypeScriptBaseCodeLensProvider { public constructor( - protected client: ITypeScriptServiceClient, + client: ITypeScriptServiceClient, protected _cachedResponse: CachedResponse, private modeId: string ) { diff --git a/extensions/typescript-language-features/src/languageFeatures/completions.ts b/extensions/typescript-language-features/src/languageFeatures/completions.ts index 7412e7b4..37d32399 100644 --- a/extensions/typescript-language-features/src/languageFeatures/completions.ts +++ b/extensions/typescript-language-features/src/languageFeatures/completions.ts @@ -60,10 +60,11 @@ class MyCompletionItem extends vscode.CompletionItem { public readonly tsEntry: Proto.CompletionEntry, private readonly completionContext: CompletionContext, public readonly metadata: any | undefined, + client: ITypeScriptServiceClient, ) { super(tsEntry.name, MyCompletionItem.convertKind(tsEntry.kind)); - if (tsEntry.source) { + if (tsEntry.source && tsEntry.hasAction) { // De-prioritze auto-imports // https://github.com/microsoft/vscode/issues/40311 this.sortText = '\uffff' + tsEntry.sortText; @@ -78,16 +79,22 @@ class MyCompletionItem extends vscode.CompletionItem { this.sortText = tsEntry.sortText; } + // @ts-expect-error until 4.3 protocol update + const { sourceDisplay, isSnippet } = tsEntry; + if (sourceDisplay) { + this.label2 = { name: tsEntry.name, qualifier: Previewer.plainWithLinks(sourceDisplay, client) }; + } + this.preselect = tsEntry.isRecommended; this.position = position; this.useCodeSnippet = completionContext.useCodeSnippetsOnMethodSuggest && (this.kind === vscode.CompletionItemKind.Function || this.kind === vscode.CompletionItemKind.Method); this.range = this.getRangeFromReplacementSpan(tsEntry, completionContext); this.commitCharacters = MyCompletionItem.getCommitCharacters(completionContext, tsEntry); - this.insertText = tsEntry.insertText; + this.insertText = isSnippet && tsEntry.insertText ? new vscode.SnippetString(tsEntry.insertText) : tsEntry.insertText; this.filterText = this.getFilterText(completionContext.line, tsEntry.insertText); - if (completionContext.isMemberCompletion && completionContext.dotAccessorContext) { + if (completionContext.isMemberCompletion && completionContext.dotAccessorContext && !(this.insertText instanceof vscode.SnippetString)) { this.filterText = completionContext.dotAccessorContext.text + (this.insertText || this.label); if (!this.range) { const replacementRange = this.getFuzzyWordRange(); @@ -194,9 +201,9 @@ class MyCompletionItem extends vscode.CompletionItem { const detail = response.body[0]; if (!this.detail && detail.displayParts.length) { - this.detail = Previewer.plain(detail.displayParts); + this.detail = Previewer.plainWithLinks(detail.displayParts, client); } - this.documentation = this.getDocumentation(detail, this); + this.documentation = this.getDocumentation(client, detail, this); const codeAction = this.getCodeActions(detail, filepath); const commands: vscode.Command[] = [{ @@ -237,16 +244,17 @@ class MyCompletionItem extends vscode.CompletionItem { } private getDocumentation( + client: ITypeScriptServiceClient, detail: Proto.CompletionEntryDetails, item: MyCompletionItem ): vscode.MarkdownString | undefined { const documentation = new vscode.MarkdownString(); if (detail.source) { - const importPath = `'${Previewer.plain(detail.source)}'`; + const importPath = `'${Previewer.plainWithLinks(detail.source, client)}'`; const autoImportLabel = localize('autoImportLabel', 'Auto import from {0}', importPath); item.detail = `${autoImportLabel}\n${item.detail}`; } - Previewer.addMarkdownDocumentation(documentation, detail.documentation, detail.tags); + Previewer.addMarkdownDocumentation(documentation, detail.documentation, detail.tags, client); return documentation.value.length ? documentation : undefined; } @@ -545,6 +553,7 @@ class CompletionAcceptedCommand implements Command { /* __GDPR__ "completions.accept" : { "isPackageJsonImport" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "isImportStatementCompletion" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, "${include}": [ "${TypeScriptCommonProperties}" ] @@ -552,6 +561,8 @@ class CompletionAcceptedCommand implements Command { */ this.telemetryReporter.logTelemetry('completions.accept', { isPackageJsonImport: item.tsEntry.isPackageJsonImport ? 'true' : undefined, + // @ts-expect-error until 4.3 protocol update + isImportStatementCompletion: item.tsEntry.isImportStatementCompletion ? 'true' : undefined, }); } } @@ -628,6 +639,7 @@ interface CompletionConfiguration { readonly nameSuggestions: boolean; readonly pathSuggestions: boolean; readonly autoImportSuggestions: boolean; + readonly importStatementSuggestions: boolean; } namespace CompletionConfiguration { @@ -635,6 +647,7 @@ namespace CompletionConfiguration { export const nameSuggestions = 'suggest.names'; export const pathSuggestions = 'suggest.paths'; export const autoImportSuggestions = 'suggest.autoImports'; + export const importStatementSuggestions = 'suggest.importStatements'; export function getConfigurationForResource( modeId: string, @@ -646,13 +659,14 @@ namespace CompletionConfiguration { pathSuggestions: config.get(CompletionConfiguration.pathSuggestions, true), autoImportSuggestions: config.get(CompletionConfiguration.autoImportSuggestions, true), nameSuggestions: config.get(CompletionConfiguration.nameSuggestions, true), + importStatementSuggestions: config.get(CompletionConfiguration.nameSuggestions, true), }; } } class TypeScriptCompletionItemProvider implements vscode.CompletionItemProvider { - public static readonly triggerCharacters = ['.', '"', '\'', '`', '/', '@', '<', '#']; + public static readonly triggerCharacters = ['.', '"', '\'', '`', '/', '@', '<', '#', ' ']; constructor( private readonly client: ITypeScriptServiceClient, @@ -694,7 +708,7 @@ class TypeScriptCompletionItemProvider implements vscode.CompletionItemProvider< const line = document.lineAt(position.line); const completionConfiguration = CompletionConfiguration.getConfigurationForResource(this.modeId, document.uri); - if (!this.shouldTrigger(context, line, position)) { + if (!this.shouldTrigger(context, line, position, completionConfiguration)) { return undefined; } @@ -739,7 +753,8 @@ class TypeScriptCompletionItemProvider implements vscode.CompletionItemProvider< dotAccessorContext = { range, text }; } } - isIncomplete = (response as any).metadata && (response as any).metadata.isIncomplete; + // @ts-expect-error until 4.3 protocol update + isIncomplete = !!response.body.isIncomplete || (response as any).metadata && (response as any).metadata.isIncomplete; entries = response.body.entries; metadata = response.metadata; } else { @@ -765,21 +780,24 @@ class TypeScriptCompletionItemProvider implements vscode.CompletionItemProvider< }; let includesPackageJsonImport = false; + let includesImportStatementCompletion = false; const items: MyCompletionItem[] = []; for (const entry of entries) { if (!shouldExcludeCompletionEntry(entry, completionConfiguration)) { - const item = new MyCompletionItem(position, document, entry, completionContext, metadata); + const item = new MyCompletionItem(position, document, entry, completionContext, metadata, this.client); item.command = { command: ApplyCompletionCommand.ID, title: '', arguments: [item] }; items.push(item); - includesPackageJsonImport = !!entry.isPackageJsonImport; + includesPackageJsonImport = includesPackageJsonImport || !!entry.isPackageJsonImport; + // @ts-expect-error until 4.3 protocol update + includesImportStatementCompletion = includesImportStatementCompletion || !!entry.isImportStatementCompletion; } } if (duration !== undefined) { - this.logCompletionsTelemetry(duration, response, includesPackageJsonImport); + this.logCompletionsTelemetry(duration, response, includesPackageJsonImport, includesImportStatementCompletion); } return new vscode.CompletionList(items, isIncomplete); } @@ -787,7 +805,8 @@ class TypeScriptCompletionItemProvider implements vscode.CompletionItemProvider< private logCompletionsTelemetry( duration: number, response: ServerResponse.Response | undefined, - includesPackageJsonImport?: boolean + includesPackageJsonImport?: boolean, + includesImportStatementCompletion?: boolean, ) { /* __GDPR__ "completions.execute" : { @@ -797,6 +816,7 @@ class TypeScriptCompletionItemProvider implements vscode.CompletionItemProvider< "updateGraphDurationMs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, "createAutoImportProviderProgramDurationMs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, "includesPackageJsonImport" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "includesImportStatementCompletion" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, "${include}": [ "${TypeScriptCommonProperties}" ] @@ -809,6 +829,7 @@ class TypeScriptCompletionItemProvider implements vscode.CompletionItemProvider< updateGraphDurationMs: response?.type === 'response' ? response.performanceData?.updateGraphDurationMs : undefined, createAutoImportProviderProgramDurationMs: response?.type === 'response' ? response.performanceData?.createAutoImportProviderProgramDurationMs : undefined, includesPackageJsonImport: includesPackageJsonImport ? 'true' : undefined, + includesImportStatementCompletion: includesImportStatementCompletion ? 'true' : undefined, }); } @@ -820,6 +841,11 @@ class TypeScriptCompletionItemProvider implements vscode.CompletionItemProvider< case '#': // Workaround for https://github.com/microsoft/TypeScript/issues/36367 return this.client.apiVersion.lt(API.v381) ? undefined : '#'; + case ' ': + // @ts-expect-error until 4.3.0 protocol update + const space: Proto.CompletionsTriggerCharacter = ' '; + return this.client.apiVersion.gte(API.v430) ? space : undefined; + case '.': case '"': case '\'': @@ -862,7 +888,8 @@ class TypeScriptCompletionItemProvider implements vscode.CompletionItemProvider< private shouldTrigger( context: vscode.CompletionContext, line: vscode.TextLine, - position: vscode.Position + position: vscode.Position, + configuration: CompletionConfiguration, ): boolean { if (context.triggerCharacter && this.client.apiVersion.lt(API.v290)) { if ((context.triggerCharacter === '"' || context.triggerCharacter === '\'')) { @@ -893,7 +920,13 @@ class TypeScriptCompletionItemProvider implements vscode.CompletionItemProvider< return false; } } - + if (context.triggerCharacter === ' ') { + if (!configuration.importStatementSuggestions || this.client.apiVersion.lt(API.v430)) { + return false; + } + const pre = line.text.slice(0, position.character); + return pre === 'import'; + } return true; } } diff --git a/extensions/typescript-language-features/src/languageFeatures/diagnostics.ts b/extensions/typescript-language-features/src/languageFeatures/diagnostics.ts index 25a9591c..0fd8dec5 100644 --- a/extensions/typescript-language-features/src/languageFeatures/diagnostics.ts +++ b/extensions/typescript-language-features/src/languageFeatures/diagnostics.ts @@ -160,7 +160,7 @@ export class DiagnosticsManager extends Disposable { this._currentDiagnostics = this._register(vscode.languages.createDiagnosticCollection(owner)); } - public dispose() { + public override dispose() { super.dispose(); for (const value of this._pendingUpdates.values) { diff --git a/extensions/typescript-language-features/src/languageFeatures/fileConfigurationManager.ts b/extensions/typescript-language-features/src/languageFeatures/fileConfigurationManager.ts index f0c41541..69cb3526 100644 --- a/extensions/typescript-language-features/src/languageFeatures/fileConfigurationManager.ts +++ b/extensions/typescript-language-features/src/languageFeatures/fileConfigurationManager.ts @@ -173,7 +173,7 @@ export default class FileConfigurationManager extends Disposable { isTypeScriptDocument(document) ? 'typescript.preferences' : 'javascript.preferences', document.uri); - const preferences: Proto.UserPreferences = { + const preferences: Proto.UserPreferences & { displayPartsForJSDoc: true } = { quotePreference: this.getQuoteStylePreference(preferencesConfig), importModuleSpecifierPreference: getImportModuleSpecifierPreference(preferencesConfig), importModuleSpecifierEnding: getImportModuleSpecifierEndingPreference(preferencesConfig), @@ -183,6 +183,10 @@ export default class FileConfigurationManager extends Disposable { includeAutomaticOptionalChainCompletions: config.get('suggest.includeAutomaticOptionalChainCompletions', true), provideRefactorNotApplicableReason: true, generateReturnInDocTemplate: config.get('suggest.jsdoc.generateReturns', true), + // @ts-expect-error until 4.3 protocol update + includeCompletionsForImportStatements: config.get('suggest.includeCompletionsForImportStatements', true), + includeCompletionsWithSnippetText: config.get('suggest.includeCompletionsWithSnippetText', true), + displayPartsForJSDoc: true, }; return preferences; diff --git a/extensions/typescript-language-features/src/languageFeatures/hover.ts b/extensions/typescript-language-features/src/languageFeatures/hover.ts index 236ef694..36f39971 100644 --- a/extensions/typescript-language-features/src/languageFeatures/hover.ts +++ b/extensions/typescript-language-features/src/languageFeatures/hover.ts @@ -11,12 +11,14 @@ import { conditionalRegistration, requireSomeCapability } from '../utils/depende import { DocumentSelector } from '../utils/documentSelector'; import { markdownDocumentation } from '../utils/previewer'; import * as typeConverters from '../utils/typeConverters'; +import FileConfigurationManager from './fileConfigurationManager'; class TypeScriptHoverProvider implements vscode.HoverProvider { public constructor( - private readonly client: ITypeScriptServiceClient + private readonly client: ITypeScriptServiceClient, + private readonly fileConfigurationManager: FileConfigurationManager, ) { } public async provideHover( @@ -29,8 +31,13 @@ class TypeScriptHoverProvider implements vscode.HoverProvider { return undefined; } - const args = typeConverters.Position.toFileLocationRequestArgs(filepath, position); - const response = await this.client.interruptGetErr(() => this.client.execute('quickinfo', args, token)); + const response = await this.client.interruptGetErr(async () => { + await this.fileConfigurationManager.ensureConfigurationForDocument(document, token); + + const args = typeConverters.Position.toFileLocationRequestArgs(filepath, position); + return this.client.execute('quickinfo', args, token); + }); + if (response.type !== 'response' || !response.body) { return undefined; } @@ -62,19 +69,20 @@ class TypeScriptHoverProvider implements vscode.HoverProvider { parts.push({ language: 'typescript', value: displayParts.join(' ') }); } - parts.push(markdownDocumentation(data.documentation, data.tags)); + parts.push(markdownDocumentation(data.documentation, data.tags, this.client)); return parts; } } export function register( selector: DocumentSelector, - client: ITypeScriptServiceClient + client: ITypeScriptServiceClient, + fileConfigurationManager: FileConfigurationManager, ): vscode.Disposable { return conditionalRegistration([ requireSomeCapability(client, ClientCapability.EnhancedSyntax, ClientCapability.Semantic), ], () => { return vscode.languages.registerHoverProvider(selector.syntax, - new TypeScriptHoverProvider(client)); + new TypeScriptHoverProvider(client, fileConfigurationManager)); }); } diff --git a/extensions/typescript-language-features/src/languageFeatures/organizeImports.ts b/extensions/typescript-language-features/src/languageFeatures/organizeImports.ts index 218cda41..3b8111b7 100644 --- a/extensions/typescript-language-features/src/languageFeatures/organizeImports.ts +++ b/extensions/typescript-language-features/src/languageFeatures/organizeImports.ts @@ -13,7 +13,7 @@ import { Command, CommandManager } from '../commands/commandManager'; import { conditionalRegistration, requireMinVersion, requireSomeCapability } from '../utils/dependentRegistration'; import { DocumentSelector } from '../utils/documentSelector'; import { TelemetryReporter } from '../utils/telemetry'; -import * as typeconverts from '../utils/typeConverters'; +import * as typeConverters from '../utils/typeConverters'; import FileConfigurationManager from './fileConfigurationManager'; const localize = nls.loadMessageBundle(); @@ -52,7 +52,7 @@ class OrganizeImportsCommand implements Command { return false; } - const edits = typeconverts.WorkspaceEdit.fromFileCodeEdits(this.client, response.body); + const edits = typeConverters.WorkspaceEdit.fromFileCodeEdits(this.client, response.body); return vscode.workspace.applyEdit(edits); } } diff --git a/extensions/typescript-language-features/src/languageFeatures/quickFix.ts b/extensions/typescript-language-features/src/languageFeatures/quickFix.ts index 7b69b7aa..4cb76bad 100644 --- a/extensions/typescript-language-features/src/languageFeatures/quickFix.ts +++ b/extensions/typescript-language-features/src/languageFeatures/quickFix.ts @@ -382,6 +382,7 @@ const preferredFixes = new Map x.name !== 'param'))); + Previewer.plainWithLinks(item.prefixDisplayParts, this.client), + Previewer.markdownDocumentation(item.documentation, item.tags.filter(x => x.name !== 'param'), this.client)); let textIndex = signature.label.length; - const separatorLabel = Previewer.plain(item.separatorDisplayParts); + const separatorLabel = Previewer.plainWithLinks(item.separatorDisplayParts, this.client); for (let i = 0; i < item.parameters.length; ++i) { const parameter = item.parameters[i]; - const label = Previewer.plain(parameter.displayParts); + const label = Previewer.plainWithLinks(parameter.displayParts, this.client); signature.parameters.push( new vscode.ParameterInformation( [textIndex, textIndex + label.length], - Previewer.markdownDocumentation(parameter.documentation, []))); + Previewer.markdownDocumentation(parameter.documentation, [], this.client))); textIndex += label.length; signature.label += label; @@ -95,7 +95,7 @@ class TypeScriptSignatureHelpProvider implements vscode.SignatureHelpProvider { } } - signature.label += Previewer.plain(item.suffixDisplayParts); + signature.label += Previewer.plainWithLinks(item.suffixDisplayParts, this.client); return signature; } } diff --git a/extensions/typescript-language-features/src/languageFeatures/tagClosing.ts b/extensions/typescript-language-features/src/languageFeatures/tagClosing.ts index 289ce73b..c1d53a03 100644 --- a/extensions/typescript-language-features/src/languageFeatures/tagClosing.ts +++ b/extensions/typescript-language-features/src/languageFeatures/tagClosing.ts @@ -29,7 +29,7 @@ class TagClosing extends Disposable { this._disposables); } - public dispose() { + public override dispose() { super.dispose(); this._disposed = true; diff --git a/extensions/typescript-language-features/src/languageProvider.ts b/extensions/typescript-language-features/src/languageProvider.ts index 2cb36eb9..3af617ea 100644 --- a/extensions/typescript-language-features/src/languageProvider.ts +++ b/extensions/typescript-language-features/src/languageProvider.ts @@ -70,7 +70,7 @@ export default class LanguageProvider extends Disposable { import('./languageFeatures/fixAll').then(provider => this._register(provider.register(selector, this.client, this.fileConfigurationManager, this.client.diagnosticsManager))), import('./languageFeatures/folding').then(provider => this._register(provider.register(selector, this.client))), import('./languageFeatures/formatting').then(provider => this._register(provider.register(selector, this.description.id, this.client, this.fileConfigurationManager))), - import('./languageFeatures/hover').then(provider => this._register(provider.register(selector, this.client))), + import('./languageFeatures/hover').then(provider => this._register(provider.register(selector, this.client, this.fileConfigurationManager))), import('./languageFeatures/implementations').then(provider => this._register(provider.register(selector, this.client))), import('./languageFeatures/jsDocCompletions').then(provider => this._register(provider.register(selector, this.description.id, this.client, this.fileConfigurationManager))), import('./languageFeatures/organizeImports').then(provider => this._register(provider.register(selector, this.client, this.commandManager, this.fileConfigurationManager, this.telemetryReporter))), diff --git a/extensions/typescript-language-features/src/protocol.d.ts b/extensions/typescript-language-features/src/protocol.d.ts index 9cf1f851..853fd543 100644 --- a/extensions/typescript-language-features/src/protocol.d.ts +++ b/extensions/typescript-language-features/src/protocol.d.ts @@ -10,5 +10,9 @@ declare module 'typescript/lib/protocol' { interface Response { readonly _serverType?: ServerType; } + + interface JSDocLinkDisplayPart { + target: Proto.FileSpan; + } } diff --git a/extensions/typescript-language-features/src/test-all.ts b/extensions/typescript-language-features/src/test-all.ts index ef6cc04c..71e88e4a 100644 --- a/extensions/typescript-language-features/src/test-all.ts +++ b/extensions/typescript-language-features/src/test-all.ts @@ -21,7 +21,7 @@ const testRunner = require('../../../test/integration/electron/testrunner'); // See https://github.com/mochajs/mocha/wiki/Using-mocha-programmatically#set-options for more info testRunner.configure({ ui: 'tdd', // the TDD UI is being used in extension.test.ts (suite, test, etc.) - color: (!process.env.BUILD_ARTIFACTSTAGINGDIRECTORY && process.platform !== 'win32'), // colored output from test results (only windows cannot handle) + color: true, timeout: 60000, }); diff --git a/extensions/typescript-language-features/src/test/index.ts b/extensions/typescript-language-features/src/test/index.ts index 11204e86..1d2d4f89 100644 --- a/extensions/typescript-language-features/src/test/index.ts +++ b/extensions/typescript-language-features/src/test/index.ts @@ -21,7 +21,7 @@ const testRunner = require('../../../../test/integration/electron/testrunner'); // See https://github.com/mochajs/mocha/wiki/Using-mocha-programmatically#set-options for more info testRunner.configure({ ui: 'tdd', // the TDD UI is being used in extension.test.ts (suite, test, etc.) - color: (!process.env.BUILD_ARTIFACTSTAGINGDIRECTORY && process.platform !== 'win32'), // colored output from test results (only windows cannot handle) + color: true, timeout: 60000, }); diff --git a/extensions/typescript-language-features/src/test/smoke/index.ts b/extensions/typescript-language-features/src/test/smoke/index.ts index ab3d8756..16d163fa 100644 --- a/extensions/typescript-language-features/src/test/smoke/index.ts +++ b/extensions/typescript-language-features/src/test/smoke/index.ts @@ -21,7 +21,7 @@ const testRunner = require('../../../../../test/integration/electron/testrunner' // See https://github.com/mochajs/mocha/wiki/Using-mocha-programmatically#set-options for more info testRunner.configure({ ui: 'tdd', // the TDD UI is being used in extension.test.ts (suite, test, etc.) - color: (!process.env.BUILD_ARTIFACTSTAGINGDIRECTORY && process.platform !== 'win32'), // colored output from test results (only windows cannot handle) + color: true, timeout: 60000, }); diff --git a/extensions/typescript-language-features/src/test/unit/index.ts b/extensions/typescript-language-features/src/test/unit/index.ts index ab3d8756..16d163fa 100644 --- a/extensions/typescript-language-features/src/test/unit/index.ts +++ b/extensions/typescript-language-features/src/test/unit/index.ts @@ -21,7 +21,7 @@ const testRunner = require('../../../../../test/integration/electron/testrunner' // See https://github.com/mochajs/mocha/wiki/Using-mocha-programmatically#set-options for more info testRunner.configure({ ui: 'tdd', // the TDD UI is being used in extension.test.ts (suite, test, etc.) - color: (!process.env.BUILD_ARTIFACTSTAGINGDIRECTORY && process.platform !== 'win32'), // colored output from test results (only windows cannot handle) + color: true, timeout: 60000, }); diff --git a/extensions/typescript-language-features/src/test/unit/previewer.test.ts b/extensions/typescript-language-features/src/test/unit/previewer.test.ts index 38bfc14e..bdbbf103 100644 --- a/extensions/typescript-language-features/src/test/unit/previewer.test.ts +++ b/extensions/typescript-language-features/src/test/unit/previewer.test.ts @@ -5,7 +5,12 @@ import * as assert from 'assert'; import 'mocha'; -import { tagsMarkdownPreview, markdownDocumentation } from '../../utils/previewer'; +import { Uri } from 'vscode'; +import { tagsMarkdownPreview, markdownDocumentation, IFilePathToResourceConverter } from '../../utils/previewer'; + +const noopToResource: IFilePathToResourceConverter = { + toResource: (path) => Uri.file(path) +}; suite('typescript.previewer', () => { test('Should ignore hyphens after a param tag', async () => { @@ -15,25 +20,37 @@ suite('typescript.previewer', () => { name: 'param', text: 'a - b' } - ]), + ], noopToResource), '*@param* `a` — b'); }); test('Should parse url jsdoc @link', async () => { assert.strictEqual( - markdownDocumentation('x {@link http://www.example.com/foo} y {@link https://api.jquery.com/bind/#bind-eventType-eventData-handler} z', []).value, + markdownDocumentation( + 'x {@link http://www.example.com/foo} y {@link https://api.jquery.com/bind/#bind-eventType-eventData-handler} z', + [], + noopToResource + ).value, 'x [http://www.example.com/foo](http://www.example.com/foo) y [https://api.jquery.com/bind/#bind-eventType-eventData-handler](https://api.jquery.com/bind/#bind-eventType-eventData-handler) z'); }); test('Should parse url jsdoc @link with text', async () => { assert.strictEqual( - markdownDocumentation('x {@link http://www.example.com/foo abc xyz} y {@link http://www.example.com/bar|b a z} z', []).value, + markdownDocumentation( + 'x {@link http://www.example.com/foo abc xyz} y {@link http://www.example.com/bar|b a z} z', + [], + noopToResource + ).value, 'x [abc xyz](http://www.example.com/foo) y [b a z](http://www.example.com/bar) z'); }); test('Should treat @linkcode jsdocs links as monospace', async () => { assert.strictEqual( - markdownDocumentation('x {@linkcode http://www.example.com/foo} y {@linkplain http://www.example.com/bar} z', []).value, + markdownDocumentation( + 'x {@linkcode http://www.example.com/foo} y {@linkplain http://www.example.com/bar} z', + [], + noopToResource + ).value, 'x [`http://www.example.com/foo`](http://www.example.com/foo) y [http://www.example.com/bar](http://www.example.com/bar) z'); }); @@ -44,13 +61,17 @@ suite('typescript.previewer', () => { name: 'param', text: 'a x {@link http://www.example.com/foo abc xyz} y {@link http://www.example.com/bar|b a z} z' } - ]), + ], noopToResource), '*@param* `a` — x [abc xyz](http://www.example.com/foo) y [b a z](http://www.example.com/bar) z'); }); test('Should ignore unclosed jsdocs @link', async () => { assert.strictEqual( - markdownDocumentation('x {@link http://www.example.com/foo y {@link http://www.example.com/bar bar} z', []).value, + markdownDocumentation( + 'x {@link http://www.example.com/foo y {@link http://www.example.com/bar bar} z', + [], + noopToResource + ).value, 'x {@link http://www.example.com/foo y [bar](http://www.example.com/bar) z'); }); @@ -61,7 +82,7 @@ suite('typescript.previewer', () => { name: 'param', text: 'parámetroConDiacríticos this will not' } - ]), + ], noopToResource), '*@param* `parámetroConDiacríticos` — this will not'); }); }); diff --git a/extensions/typescript-language-features/src/tsServer/server.ts b/extensions/typescript-language-features/src/tsServer/server.ts index 4ca6fbe4..5ede4ef4 100644 --- a/extensions/typescript-language-features/src/tsServer/server.ts +++ b/extensions/typescript-language-features/src/tsServer/server.ts @@ -117,7 +117,7 @@ export class ProcessBasedTsServer extends Disposable implements ITypeScriptServe this._process.write(serverRequest); } - public dispose() { + public override dispose() { super.dispose(); this._callbacks.destroy('server disposed'); this._pendingResponses.clear(); diff --git a/extensions/typescript-language-features/src/tsServer/serverError.ts b/extensions/typescript-language-features/src/tsServer/serverError.ts index 2653360c..be9a44d2 100644 --- a/extensions/typescript-language-features/src/tsServer/serverError.ts +++ b/extensions/typescript-language-features/src/tsServer/serverError.ts @@ -39,13 +39,15 @@ export class TypeScriptServerError extends Error { "TypeScriptRequestErrorProperties" : { "command" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, "serverid" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, - "sanitizedstack" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" } + "sanitizedstack" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "badclient" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" } } */ return { command: this.serverCommand, serverid: this.serverId, sanitizedstack: this.sanitizedStack || '', + badclient: /\bBADCLIENT\b/.test(this.stack || ''), } as const; } diff --git a/extensions/typescript-language-features/src/tsServer/versionManager.ts b/extensions/typescript-language-features/src/tsServer/versionManager.ts index cdd85fb0..54d88c11 100644 --- a/extensions/typescript-language-features/src/tsServer/versionManager.ts +++ b/extensions/typescript-language-features/src/tsServer/versionManager.ts @@ -32,22 +32,17 @@ export class TypeScriptVersionManager extends Disposable { this._currentVersion = this.versionProvider.defaultVersion; if (this.useWorkspaceTsdkSetting) { - if (this.isWorkspaceTrusted) { + if (vscode.workspace.isTrusted) { const localVersion = this.versionProvider.localVersion; if (localVersion) { this._currentVersion = localVersion; } } else { - setImmediate(() => { - vscode.workspace.requireWorkspaceTrust({ modal: false }) - .then(trustState => { - if (trustState === vscode.WorkspaceTrustState.Trusted && this.versionProvider.localVersion) { - this.updateActiveVersion(this.versionProvider.localVersion); - } else { - this.updateActiveVersion(this.versionProvider.defaultVersion); - } - }); - }); + this._disposables.push(vscode.workspace.onDidGrantWorkspaceTrust(() => { + if (this.versionProvider.localVersion) { + this.updateActiveVersion(this.versionProvider.localVersion); + } + })); } } @@ -99,7 +94,7 @@ export class TypeScriptVersionManager extends Disposable { private getBundledPickItem(): QuickPickItem { const bundledVersion = this.versionProvider.defaultVersion; return { - label: (!this.useWorkspaceTsdkSetting || !this.isWorkspaceTrusted + label: (!this.useWorkspaceTsdkSetting || !vscode.workspace.isTrusted ? '• ' : '') + localize('useVSCodeVersionOption', "Use VS Code's Version"), description: bundledVersion.displayName, @@ -114,14 +109,14 @@ export class TypeScriptVersionManager extends Disposable { private getLocalPickItems(): QuickPickItem[] { return this.versionProvider.localVersions.map(version => { return { - label: (this.useWorkspaceTsdkSetting && this.isWorkspaceTrusted && this.currentVersion.eq(version) + label: (this.useWorkspaceTsdkSetting && vscode.workspace.isTrusted && this.currentVersion.eq(version) ? '• ' : '') + localize('useWorkspaceVersionOption', "Use Workspace Version"), description: version.displayName, detail: version.pathLabel, run: async () => { - const trustState = await vscode.workspace.requireWorkspaceTrust(); - if (trustState === vscode.WorkspaceTrustState.Trusted) { + const trusted = await vscode.workspace.requestWorkspaceTrust({ modal: true }); + if (trusted) { await this.workspaceState.update(useWorkspaceTsdkStorageKey, true); const tsConfig = vscode.workspace.getConfiguration('typescript'); await tsConfig.update('tsdk', version.pathLabel, false); @@ -165,10 +160,6 @@ export class TypeScriptVersionManager extends Disposable { } } - private get isWorkspaceTrusted(): boolean { - return vscode.workspace.trustState === vscode.WorkspaceTrustState.Trusted; - } - private get useWorkspaceTsdkSetting(): boolean { return this.workspaceState.get(useWorkspaceTsdkStorageKey, false); } diff --git a/extensions/typescript-language-features/src/typescriptServiceClient.ts b/extensions/typescript-language-features/src/typescriptServiceClient.ts index 62533a9d..2bdf9d85 100644 --- a/extensions/typescript-language-features/src/typescriptServiceClient.ts +++ b/extensions/typescript-language-features/src/typescriptServiceClient.ts @@ -261,7 +261,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType return this._configuration; } - public dispose() { + public override dispose() { super.dispose(); this.bufferSyncSupport.dispose(); @@ -710,16 +710,20 @@ export default class TypeScriptServiceClient extends Disposable implements IType return undefined; } - if (resource.scheme === fileSchemes.file || resource.scheme === fileSchemes.untitled) { - for (const root of roots.sort((a, b) => a.uri.fsPath.length - b.uri.fsPath.length)) { - if (resource.fsPath.startsWith(root.uri.fsPath + path.sep)) { - return root.uri.fsPath; + switch (resource.scheme) { + case fileSchemes.file: + case fileSchemes.untitled: + case fileSchemes.vscodeNotebookCell: + for (const root of roots.sort((a, b) => a.uri.fsPath.length - b.uri.fsPath.length)) { + if (resource.fsPath.startsWith(root.uri.fsPath + path.sep)) { + return root.uri.fsPath; + } } - } - return roots[0].uri.fsPath; - } + return roots[0].uri.fsPath; - return undefined; + default: + return undefined; + } } public execute(command: keyof TypeScriptRequests, args: any, token: vscode.CancellationToken, config?: ExecConfig): Promise> { @@ -763,11 +767,18 @@ export default class TypeScriptServiceClient extends Disposable implements IType } public executeWithoutWaitingForResponse(command: keyof TypeScriptRequests, args: any): void { - this.executeImpl(command, args, { + const promise = this.executeImpl(command, args, { isAsync: false, token: undefined, - expectsResult: false + expectsResult: command === 'updateOpen' }); + + if (command === 'updateOpen') { + // If update open has completed, consider that the project has loaded + promise.then(() => { + this.loadingIndicator.reset(); + }); + } } public executeAsync(command: keyof TypeScriptRequests, args: Proto.GeterrRequestArgs, token: vscode.CancellationToken): Promise> { diff --git a/extensions/typescript-language-features/src/utils/api.ts b/extensions/typescript-language-features/src/utils/api.ts index 2282791a..5ff6572b 100644 --- a/extensions/typescript-language-features/src/utils/api.ts +++ b/extensions/typescript-language-features/src/utils/api.ts @@ -35,6 +35,7 @@ export default class API { public static readonly v400 = API.fromSimpleString('4.0.0'); public static readonly v401 = API.fromSimpleString('4.0.1'); public static readonly v420 = API.fromSimpleString('4.2.0'); + public static readonly v430 = API.fromSimpleString('4.3.0'); public static fromVersionString(versionString: string): API { let version = semver.valid(versionString); diff --git a/extensions/typescript-language-features/src/utils/fixNames.ts b/extensions/typescript-language-features/src/utils/fixNames.ts index 98e47f0e..c2f8ce67 100644 --- a/extensions/typescript-language-features/src/utils/fixNames.ts +++ b/extensions/typescript-language-features/src/utils/fixNames.ts @@ -15,3 +15,4 @@ export const forgottenThisPropertyAccess = 'forgottenThisPropertyAccess'; export const spelling = 'spelling'; export const fixImport = 'import'; export const addMissingAwait = 'addMissingAwait'; +export const addMissingOverride = 'fixOverrideModifier'; diff --git a/extensions/typescript-language-features/src/utils/previewer.ts b/extensions/typescript-language-features/src/utils/previewer.ts index 3d059dca..cfbbfe4e 100644 --- a/extensions/typescript-language-features/src/utils/previewer.ts +++ b/extensions/typescript-language-features/src/utils/previewer.ts @@ -6,6 +6,13 @@ import * as vscode from 'vscode'; import type * as Proto from '../protocol'; +export interface IFilePathToResourceConverter { + /** + * Convert a typescript filepath to a VS Code resource. + */ + toResource(filepath: string): vscode.Uri; +} + function replaceLinks(text: string): string { return text // Http(s) links @@ -24,7 +31,10 @@ function processInlineTags(text: string): string { return replaceLinks(text); } -function getTagBodyText(tag: Proto.JSDocTagInfo): string | undefined { +function getTagBodyText( + tag: Proto.JSDocTagInfo, + filePathConverter: IFilePathToResourceConverter, +): string | undefined { if (!tag.text) { return undefined; } @@ -37,38 +47,42 @@ function getTagBodyText(tag: Proto.JSDocTagInfo): string | undefined { return '```\n' + text + '\n```'; } + const text = convertLinkTags(tag.text, filePathConverter); switch (tag.name) { case 'example': // check for caption tags, fix for #79704 - const captionTagMatches = tag.text.match(/(.*?)<\/caption>\s*(\r\n|\n)/); + const captionTagMatches = text.match(/(.*?)<\/caption>\s*(\r\n|\n)/); if (captionTagMatches && captionTagMatches.index === 0) { - return captionTagMatches[1] + '\n\n' + makeCodeblock(tag.text.substr(captionTagMatches[0].length)); + return captionTagMatches[1] + '\n\n' + makeCodeblock(text.substr(captionTagMatches[0].length)); } else { - return makeCodeblock(tag.text); + return makeCodeblock(text); } case 'author': // fix obsucated email address, #80898 - const emailMatch = tag.text.match(/(.+)\s<([-.\w]+@[-.\w]+)>/); + const emailMatch = text.match(/(.+)\s<([-.\w]+@[-.\w]+)>/); if (emailMatch === null) { - return tag.text; + return text; } else { return `${emailMatch[1]} ${emailMatch[2]}`; } case 'default': - return makeCodeblock(tag.text); + return makeCodeblock(text); } - return processInlineTags(tag.text); + return processInlineTags(text); } -function getTagDocumentation(tag: Proto.JSDocTagInfo): string | undefined { +function getTagDocumentation( + tag: Proto.JSDocTagInfo, + filePathConverter: IFilePathToResourceConverter, +): string | undefined { switch (tag.name) { case 'augments': case 'extends': case 'param': case 'template': - const body = (tag.text || '').split(/^(\S+)\s*-?\s*/); + const body = (convertLinkTags(tag.text, filePathConverter)).split(/^(\S+)\s*-?\s*/); if (body?.length === 3) { const param = body[1]; const doc = body[2]; @@ -82,44 +96,112 @@ function getTagDocumentation(tag: Proto.JSDocTagInfo): string | undefined { // Generic tag const label = `*@${tag.name}*`; - const text = getTagBodyText(tag); + const text = getTagBodyText(tag, filePathConverter); if (!text) { return label; } return label + (text.match(/\r\n|\n/g) ? ' \n' + text : ` — ${text}`); } -export function plain(parts: Proto.SymbolDisplayPart[] | string): string { - return processInlineTags( - typeof parts === 'string' - ? parts - : parts.map(part => part.text).join('')); +export function plainWithLinks( + parts: readonly Proto.SymbolDisplayPart[] | string, + filePathConverter: IFilePathToResourceConverter, +): string { + return processInlineTags(convertLinkTags(parts, filePathConverter)); } -export function tagsMarkdownPreview(tags: Proto.JSDocTagInfo[]): string { - return tags.map(getTagDocumentation).join(' \n\n'); +/** + * Convert `@link` inline tags to markdown links + */ +function convertLinkTags( + parts: readonly Proto.SymbolDisplayPart[] | string | undefined, + filePathConverter: IFilePathToResourceConverter, +): string { + if (!parts) { + return ''; + } + + if (typeof parts === 'string') { + return parts; + } + + const out: string[] = []; + + let currentLink: { name?: string, target?: Proto.FileSpan, text?: string } | undefined; + for (const part of parts) { + switch (part.kind) { + case 'link': + if (currentLink) { + const text = currentLink.text ?? currentLink.name; + if (currentLink.target) { + const link = filePathConverter.toResource(currentLink.target.file) + .with({ + fragment: `L${currentLink.target.start.line},${currentLink.target.start.offset}` + }); + + out.push(`[${text}](${link.toString(true)})`); + } else { + if (text) { + out.push(text); + } + } + currentLink = undefined; + } else { + currentLink = {}; + } + break; + + case 'linkName': + if (currentLink) { + currentLink.name = part.text; + // TODO: remove cast once we pick up TS 4.3 + currentLink.target = (part as any as Proto.JSDocLinkDisplayPart).target; + } + break; + + case 'linkText': + if (currentLink) { + currentLink.text = part.text; + } + break; + + default: + out.push(part.text); + break; + } + } + return processInlineTags(out.join('')); +} + +export function tagsMarkdownPreview( + tags: readonly Proto.JSDocTagInfo[], + filePathConverter: IFilePathToResourceConverter, +): string { + return tags.map(tag => getTagDocumentation(tag, filePathConverter)).join(' \n\n'); } export function markdownDocumentation( documentation: Proto.SymbolDisplayPart[] | string, - tags: Proto.JSDocTagInfo[] + tags: Proto.JSDocTagInfo[], + filePathConverter: IFilePathToResourceConverter, ): vscode.MarkdownString { const out = new vscode.MarkdownString(); - addMarkdownDocumentation(out, documentation, tags); + addMarkdownDocumentation(out, documentation, tags, filePathConverter); return out; } export function addMarkdownDocumentation( out: vscode.MarkdownString, documentation: Proto.SymbolDisplayPart[] | string | undefined, - tags: Proto.JSDocTagInfo[] | undefined + tags: Proto.JSDocTagInfo[] | undefined, + converter: IFilePathToResourceConverter, ): vscode.MarkdownString { if (documentation) { - out.appendMarkdown(plain(documentation)); + out.appendMarkdown(plainWithLinks(documentation, converter)); } if (tags) { - const tagsPreview = tagsMarkdownPreview(tags); + const tagsPreview = tagsMarkdownPreview(tags, converter); if (tagsPreview) { out.appendMarkdown('\n\n' + tagsPreview); } diff --git a/extensions/typescript-language-features/src/utils/resourceMap.ts b/extensions/typescript-language-features/src/utils/resourceMap.ts index 1b7285aa..b6d056b7 100644 --- a/extensions/typescript-language-features/src/utils/resourceMap.ts +++ b/extensions/typescript-language-features/src/utils/resourceMap.ts @@ -73,7 +73,7 @@ export class ResourceMap { } public get values(): Iterable { - return Array.from(this._map.values()).map(x => x.value); + return Array.from(this._map.values(), x => x.value); } public get entries(): Iterable<{ resource: vscode.Uri, value: T }> { diff --git a/extensions/typescript-language-features/src/utils/telemetry.ts b/extensions/typescript-language-features/src/utils/telemetry.ts index 13527cae..5231c0fb 100644 --- a/extensions/typescript-language-features/src/utils/telemetry.ts +++ b/extensions/typescript-language-features/src/utils/telemetry.ts @@ -14,7 +14,7 @@ interface PackageInfo { } export interface TelemetryProperties { - readonly [prop: string]: string | number | undefined; + readonly [prop: string]: string | number | boolean | undefined; } export interface TelemetryReporter { diff --git a/extensions/typescript-language-features/src/utils/tsconfig.ts b/extensions/typescript-language-features/src/utils/tsconfig.ts index 9d1c309e..bf312a25 100644 --- a/extensions/typescript-language-features/src/utils/tsconfig.ts +++ b/extensions/typescript-language-features/src/utils/tsconfig.ts @@ -28,7 +28,7 @@ export function inferredProjectCompilerOptions( ): Proto.ExternalProjectCompilerOptions { const projectConfig: Proto.ExternalProjectCompilerOptions = { module: 'commonjs' as Proto.ModuleKind, - target: 'es2016' as Proto.ScriptTarget, + target: 'es2020' as Proto.ScriptTarget, jsx: 'preserve' as Proto.JsxEmit, }; diff --git a/extensions/typescript-language-features/src/utils/typingsStatus.ts b/extensions/typescript-language-features/src/utils/typingsStatus.ts index efb9a540..c9b9a909 100644 --- a/extensions/typescript-language-features/src/utils/typingsStatus.ts +++ b/extensions/typescript-language-features/src/utils/typingsStatus.ts @@ -27,7 +27,7 @@ export default class TypingsStatus extends Disposable { this._client.onDidEndInstallTypings(event => this.onEndInstallTypings(event.eventId))); } - public dispose(): void { + public override dispose(): void { super.dispose(); for (const timeout of this._acquiringTypings.values()) { @@ -68,7 +68,7 @@ export class AtaProgressReporter extends Disposable { this._register(client.onTypesInstallerInitializationFailed(_ => this.onTypesInstallerInitializationFailed())); } - dispose(): void { + override dispose(): void { super.dispose(); this._promises.forEach(value => value()); } diff --git a/extensions/typescript-language-features/tsconfig.json b/extensions/typescript-language-features/tsconfig.json index 0efbb232..691bb523 100644 --- a/extensions/typescript-language-features/tsconfig.json +++ b/extensions/typescript-language-features/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../shared.tsconfig.json", + "extends": "../tsconfig.base.json", "compilerOptions": { "outDir": "./out", "experimentalDecorators": true, diff --git a/extensions/vscode-api-tests/media/icon.png b/extensions/vscode-api-tests/media/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..f785ef7031624d04d821a12d5f1534ecccfef491 GIT binary patch literal 2210 zcmY*bc{tSD8$aI}!x-BnOVKh?jjlCH3uR;sS}es?-O7+kNe0PS=WFSfa0#VCqq?E8 zrYwaSx|S@zQ9@eGSh8fYMPrzm?;Y-6zw^A$`F!5xInU>P-t(O2#5+6MDdDtm001Qi zd-7obASs0a7A*zef}^fdc;S4|)mDlC=76vk5Zbo1hqZ#RHW1Mc!lmxAMJz}DdxhBCV00mfSD~OY7m5z6hd3z%kkzuSRqHWL>=NFrX zeXiWg2oCDIhM&@n5V1A>r3@QE*~O`wpQYHZ*J}Q%w#BCqzIm$NkZlms5rs^i=?Kf zQznejSKBf)$LVPn3bl4wdFYOe_zsC8lCqe^NhwSOMbx{p-9Pie@ooN3QC;3%5@ZZ9 zdk%g7(OqqH8XQ9)@3n6*v6X=;fazL~`oO|+RyE%hVcOiW3M7FRcGbZR3}~uwSY91{ z?}ak(#70$w+#mNFw+CyRVI@V(^k>&&FNP|&dhYJ5T9j~JR&We!(;B1AI_5>~#X*Ub z!-`03xI@Wq==#-n@+7iF4@t2wDU)Q`TbB`fJ-lttGkrp7Nq}Y|@%Tw7zHwB;p8O0A zPf_GRz{s#Hdx3uBGv5BW3hX++tY$49M;!0j%@=xC zHuj3t{hJnj72u>Vs`RLL;<~>CJsO1l2GK)KCuSrqC~smguKGK@Jl`CYAI@V-rZdFb zWv{m>;tfh{gQ59Hq=W%XdA_Z|S3;IwAhM!grrbHyq3n28J|EdN zrlWNxXA@iRz;2A%!1=Waq}Zz;gEQQ+WIlOGvcID)@0u#eV*NsK4irk&Vz#ZndSgM~ zm^aiznkjzB-p60_(L+EyHM&g|j~3!M||zCrSL>*Ba#2~4SH#(Dgd>_)GFn?e)UDhTzJ zc{j`X~7EJ;}49U_HW-!ej40r_ZQVWFRDtMiTm!U@HsPW^_9YoSTA!?|Y zxls++HF6JvHTpmEfL_DVTUL->Hca;)No4u2!owG)~xc#Zz;a$j%uhXCE z9a037`EV&1DrS*lj(+fnjK-E0NE=_#r}4`jp-tg89kr^NaEc(+DB|Oy^lCiCcCH73 z@tn={p(M#@|G|rm{&Yr7$W?9nriuYSI-^0wE}M6QH7K6jyY8An<=_uGBO96#F`Fa! zk{0RcOxdc`35U_IwoO|fE&%3ea6Ae_kv7`#$D1u?`MCW(h8H_3Q9q&(`-xP#pu!^#JAeIA9eZ+^0vgL7p41q?8?Z1z{nYW zr)S{-GrziyB~y06T$)e1d=&9+WXP+j4JDK=%RHc@(J%6HZ%_riz|~06k&|p_g@W*z z5k49&PiGCI)?sN4B6ZC>JY#Yw!z+(2i?{CcSnTZAAkmaB8igVWL4{_dOUf5tO*}V6 zu#CLyF(*tdpUbrGRERkD=G$F~D=csicBiKbt7U{opgs>bcm&1GATS03IE(rUG|= z$_$zL3%4mmP`dQaP=itU1CLh1cT6YLLs7E2O%F6yC7xKRIQ644F8s|}Ic{Bgk`ed^ zNj!m`meD1G`?r!_Q!R~Fp9Jw+dMoAsDQh-22lsO&I|i|_nGfWkn43x|#G`Y>7&pd- z|AT=K%@R(~?NZf1w1GxZ;)DS((5y7G-ueaCRTk)+O7#|wOs`f?+oN`Fzj>}zvIY6E zqVlCbI}>ql4e=Uzbz=ETsniQ|JyzHWW8SblMIu^|v(Dn#^`|8@FwcegdQXnr-P^CS z(Fiu#sp5r>tCI<_9IYdEE~K;n-rN7^W)-Kix&e`0u{@1z&a+B2cv_r2K8gXO!erx!e z?Z=K9nngudTWX2w>-q*Xl2qQfByQQX5R|7j_zo}XZO=qxGT}IST0OaF>9gTr>qsuz HM~(Rx0c1bk literal 0 HcmV?d00001 diff --git a/extensions/vscode-api-tests/package.json b/extensions/vscode-api-tests/package.json index 6d39b43c..e1c16dac 100644 --- a/extensions/vscode-api-tests/package.json +++ b/extensions/vscode-api-tests/package.json @@ -5,15 +5,13 @@ "publisher": "vscode", "license": "MIT", "enableProposedApi": true, - "workspaceTrust": { - "required": "onDemand" - }, "private": true, "activationEvents": [], "main": "./out/extension", "engines": { - "vscode": "^1.25.0" + "vscode": "^1.55.0" }, + "icon": "media/icon.png", "contributes": { "configuration": { "type": "object", @@ -138,6 +136,15 @@ "filenamePattern": "**/*.nbdtest" } ] + }, + { + "viewType": "notebook.nbdserializer", + "displayName": "notebook.nbdserializer", + "selector": [ + { + "filenamePattern": "**/*.nbdserializer" + } + ] } ] }, diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/env.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/env.test.ts index e3f0106f..42e25baf 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/env.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/env.test.ts @@ -32,17 +32,19 @@ suite('vscode API - env', () => { test('env.remoteName', function () { const remoteName = env.remoteName; const knownWorkspaceExtension = extensions.getExtension('vscode.git'); - const knownUiExtension = extensions.getExtension('vscode.git-ui'); + const knownUiAndWorkspaceExtension = extensions.getExtension('vscode.image-preview'); if (typeof remoteName === 'undefined') { // not running in remote, so we expect both extensions assert.ok(knownWorkspaceExtension); - assert.ok(knownUiExtension); - assert.equal(ExtensionKind.UI, knownUiExtension!.extensionKind); + assert.ok(knownUiAndWorkspaceExtension); + assert.equal(ExtensionKind.UI, knownUiAndWorkspaceExtension!.extensionKind); } else if (typeof remoteName === 'string') { // running in remote, so we only expect workspace extensions assert.ok(knownWorkspaceExtension); if (env.uiKind === UIKind.Desktop) { - assert.ok(!knownUiExtension); // we currently can only access extensions that run on same host + assert.ok(!knownUiAndWorkspaceExtension); // we currently can only access extensions that run on same host + } else { + assert.ok(knownUiAndWorkspaceExtension); } assert.equal(ExtensionKind.Workspace, knownWorkspaceExtension!.extensionKind); } else { diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/index.ts b/extensions/vscode-api-tests/src/singlefolder-tests/index.ts index 2b283c39..de9ebf36 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/index.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/index.ts @@ -8,7 +8,7 @@ const testRunner = require('../../../../test/integration/electron/testrunner'); const options: any = { ui: 'tdd', - color: (!process.env.BUILD_ARTIFACTSTAGINGDIRECTORY && process.platform !== 'win32'), + color: true, timeout: 60000 }; diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.document.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.document.test.ts index d1fc3604..70e6e704 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.document.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.document.test.ts @@ -9,16 +9,25 @@ import * as utils from '../utils'; suite('Notebook Document', function () { - const contentProvider = new class implements vscode.NotebookContentProvider { + const simpleContentProvider = new class implements vscode.NotebookSerializer { + deserializeNotebook(_data: Uint8Array): vscode.NotebookData | Thenable { + return new vscode.NotebookData( + [new vscode.NotebookCellData(vscode.NotebookCellKind.Code, '// SIMPLE', 'javascript')], + new vscode.NotebookDocumentMetadata() + ); + } + serializeNotebook(_data: vscode.NotebookData): Uint8Array | Thenable { + return new Uint8Array(); + } + }; + + const complexContentProvider = new class implements vscode.NotebookContentProvider { async openNotebook(uri: vscode.Uri, _openContext: vscode.NotebookDocumentOpenContext): Promise { return new vscode.NotebookData( [new vscode.NotebookCellData(vscode.NotebookCellKind.Code, uri.toString(), 'javascript')], new vscode.NotebookDocumentMetadata() ); } - async resolveNotebook(_document: vscode.NotebookDocument, _webview: vscode.NotebookCommunication) { - // - } async saveNotebook(_document: vscode.NotebookDocument, _cancellation: vscode.CancellationToken) { // } @@ -45,13 +54,17 @@ suite('Notebook Document', function () { }); suiteSetup(function () { - disposables.push(vscode.notebook.registerNotebookContentProvider('notebook.nbdtest', contentProvider)); + disposables.push(vscode.notebook.registerNotebookContentProvider('notebook.nbdtest', complexContentProvider)); + disposables.push(vscode.notebook.registerNotebookSerializer('notebook.nbdserializer', simpleContentProvider)); }); test('cannot register sample provider multiple times', function () { assert.throws(() => { - vscode.notebook.registerNotebookContentProvider('notebook.nbdtest', contentProvider); + vscode.notebook.registerNotebookContentProvider('notebook.nbdtest', complexContentProvider); }); + // assert.throws(() => { + // vscode.notebook.registerNotebookSerializer('notebook.nbdserializer', simpleContentProvider); + // }); }); test('cannot open unknown types', async function () { @@ -70,7 +83,7 @@ suite('Notebook Document', function () { assert.strictEqual(notebook.uri.toString(), uri.toString()); assert.strictEqual(notebook.isDirty, false); assert.strictEqual(notebook.isUntitled, false); - assert.strictEqual(notebook.cells.length, 1); + assert.strictEqual(notebook.cellCount, 1); assert.strictEqual(notebook.viewType, 'notebook.nbdtest'); }); @@ -83,7 +96,7 @@ suite('Notebook Document', function () { return; } const notebook = vscode.notebook.notebookDocuments.find(notebook => { - const cell = notebook.cells.find(cell => cell.document === doc); + const cell = notebook.getCells().find(cell => cell.document === doc); return Boolean(cell); }); assert.ok(notebook, `notebook for cell ${doc.uri} NOT found`); @@ -99,7 +112,9 @@ suite('Notebook Document', function () { const uri = await utils.createRandomFile(undefined, undefined, '.nbdtest'); const p = utils.asPromise(vscode.notebook.onDidOpenNotebookDocument).then(notebook => { - for (let cell of notebook.cells) { + for (let i = 0; i < notebook.cellCount; i++) { + let cell = notebook.cellAt(i); + const doc = vscode.workspace.textDocuments.find(doc => doc.uri.toString() === cell.document.uri.toString()); assert.ok(doc); assert.strictEqual(doc.notebook === notebook, true); @@ -119,12 +134,12 @@ suite('Notebook Document', function () { const uri = await utils.createRandomFile(undefined, undefined, '.nbdtest'); const document = await vscode.notebook.openNotebookDocument(uri); - assert.strictEqual(document.cells.length, 1); + assert.strictEqual(document.cellCount, 1); // inserting two new cells { const edit = new vscode.WorkspaceEdit(); - edit.replaceNotebookCells(document.uri, 0, 0, [{ + edit.replaceNotebookCells(document.uri, new vscode.NotebookRange(0, 0), [{ kind: vscode.NotebookCellKind.Markdown, language: 'markdown', metadata: undefined, @@ -142,26 +157,26 @@ suite('Notebook Document', function () { assert.strictEqual(success, true); } - assert.strictEqual(document.cells.length, 3); - assert.strictEqual(document.cells[0].document.getText(), 'new_markdown'); - assert.strictEqual(document.cells[1].document.getText(), 'new_code'); + assert.strictEqual(document.cellCount, 3); + assert.strictEqual(document.cellAt(0).document.getText(), 'new_markdown'); + assert.strictEqual(document.cellAt(1).document.getText(), 'new_code'); // deleting cell 1 and 3 { const edit = new vscode.WorkspaceEdit(); - edit.replaceNotebookCells(document.uri, 0, 1, []); - edit.replaceNotebookCells(document.uri, 2, 3, []); + edit.replaceNotebookCells(document.uri, new vscode.NotebookRange(0, 1), []); + edit.replaceNotebookCells(document.uri, new vscode.NotebookRange(2, 3), []); const success = await vscode.workspace.applyEdit(edit); assert.strictEqual(success, true); } - assert.strictEqual(document.cells.length, 1); - assert.strictEqual(document.cells[0].document.getText(), 'new_code'); + assert.strictEqual(document.cellCount, 1); + assert.strictEqual(document.cellAt(0).document.getText(), 'new_code'); // replacing all cells { const edit = new vscode.WorkspaceEdit(); - edit.replaceNotebookCells(document.uri, 0, 1, [{ + edit.replaceNotebookCells(document.uri, new vscode.NotebookRange(0, 1), [{ kind: vscode.NotebookCellKind.Markdown, language: 'markdown', metadata: undefined, @@ -177,27 +192,27 @@ suite('Notebook Document', function () { const success = await vscode.workspace.applyEdit(edit); assert.strictEqual(success, true); } - assert.strictEqual(document.cells.length, 2); - assert.strictEqual(document.cells[0].document.getText(), 'new2_markdown'); - assert.strictEqual(document.cells[1].document.getText(), 'new2_code'); + assert.strictEqual(document.cellCount, 2); + assert.strictEqual(document.cellAt(0).document.getText(), 'new2_markdown'); + assert.strictEqual(document.cellAt(1).document.getText(), 'new2_code'); // remove all cells { const edit = new vscode.WorkspaceEdit(); - edit.replaceNotebookCells(document.uri, 0, document.cells.length, []); + edit.replaceNotebookCells(document.uri, new vscode.NotebookRange(0, document.cellCount), []); const success = await vscode.workspace.applyEdit(edit); assert.strictEqual(success, true); } - assert.strictEqual(document.cells.length, 0); + assert.strictEqual(document.cellCount, 0); }); test('workspace edit API (replaceCells, event)', async function () { const uri = await utils.createRandomFile(undefined, undefined, '.nbdtest'); const document = await vscode.notebook.openNotebookDocument(uri); - assert.strictEqual(document.cells.length, 1); + assert.strictEqual(document.cellCount, 1); const edit = new vscode.WorkspaceEdit(); - edit.replaceNotebookCells(document.uri, 0, 0, [{ + edit.replaceNotebookCells(document.uri, new vscode.NotebookRange(0, 0), [{ kind: vscode.NotebookCellKind.Markdown, language: 'markdown', metadata: undefined, @@ -219,9 +234,9 @@ suite('Notebook Document', function () { const data = await event; // check document - assert.strictEqual(document.cells.length, 3); - assert.strictEqual(document.cells[0].document.getText(), 'new_markdown'); - assert.strictEqual(document.cells[1].document.getText(), 'new_code'); + assert.strictEqual(document.cellCount, 3); + assert.strictEqual(document.cellAt(0).document.getText(), 'new_markdown'); + assert.strictEqual(document.cellAt(1).document.getText(), 'new_code'); // check event data assert.strictEqual(data.document === document, true); @@ -229,69 +244,8 @@ suite('Notebook Document', function () { assert.strictEqual(data.changes[0].deletedCount, 0); assert.strictEqual(data.changes[0].deletedItems.length, 0); assert.strictEqual(data.changes[0].items.length, 2); - assert.strictEqual(data.changes[0].items[0], document.cells[0]); - assert.strictEqual(data.changes[0].items[1], document.cells[1]); - }); - - test('workspace edit API (appendNotebookCellOutput, replaceCellOutput, event)', async function () { - const uri = await utils.createRandomFile(undefined, undefined, '.nbdtest'); - const document = await vscode.notebook.openNotebookDocument(uri); - - const outputChangeEvent = utils.asPromise(vscode.notebook.onDidChangeCellOutputs); - const edit = new vscode.WorkspaceEdit(); - const firstCellOutput = new vscode.NotebookCellOutput([new vscode.NotebookCellOutputItem('foo', 'bar')]); - edit.appendNotebookCellOutput(document.uri, 0, [firstCellOutput]); - const success = await vscode.workspace.applyEdit(edit); - const data = await outputChangeEvent; - - assert.strictEqual(success, true); - assert.strictEqual(document.cells.length, 1); - assert.strictEqual(document.cells[0].outputs.length, 1); - assert.deepStrictEqual(document.cells[0].outputs, [firstCellOutput]); - - assert.strictEqual(data.document === document, true); - assert.strictEqual(data.cells.length, 1); - assert.strictEqual(data.cells[0].outputs.length, 1); - assert.deepStrictEqual(data.cells[0].outputs, [firstCellOutput]); - - - { - const outputChangeEvent = utils.asPromise(vscode.notebook.onDidChangeCellOutputs); - const edit = new vscode.WorkspaceEdit(); - const secondCellOutput = new vscode.NotebookCellOutput([new vscode.NotebookCellOutputItem('foo', 'baz')]); - edit.appendNotebookCellOutput(document.uri, 0, [secondCellOutput]); - const success = await vscode.workspace.applyEdit(edit); - const data = await outputChangeEvent; - - assert.strictEqual(success, true); - assert.strictEqual(document.cells.length, 1); - assert.strictEqual(document.cells[0].outputs.length, 2); - assert.deepStrictEqual(document.cells[0].outputs, [firstCellOutput, secondCellOutput]); - - assert.strictEqual(data.document === document, true); - assert.strictEqual(data.cells.length, 1); - assert.strictEqual(data.cells[0].outputs.length, 2); - assert.deepStrictEqual(data.cells[0].outputs, [firstCellOutput, secondCellOutput]); - } - - { - const outputChangeEvent = utils.asPromise(vscode.notebook.onDidChangeCellOutputs); - const edit = new vscode.WorkspaceEdit(); - const thirdOutput = new vscode.NotebookCellOutput([new vscode.NotebookCellOutputItem('foo', 'baz1')]); - edit.replaceNotebookCellOutput(document.uri, 0, [thirdOutput]); - const success = await vscode.workspace.applyEdit(edit); - const data = await outputChangeEvent; - - assert.strictEqual(success, true); - assert.strictEqual(document.cells.length, 1); - assert.strictEqual(document.cells[0].outputs.length, 1); - assert.deepStrictEqual(document.cells[0].outputs, [thirdOutput]); - - assert.strictEqual(data.document === document, true); - assert.strictEqual(data.cells.length, 1); - assert.strictEqual(data.cells[0].outputs.length, 1); - assert.deepStrictEqual(data.cells[0].outputs, [thirdOutput]); - } + assert.strictEqual(data.changes[0].items[0], document.cellAt(0)); + assert.strictEqual(data.changes[0].items[1], document.cellAt(1)); }); test('document save API', async function () { @@ -301,11 +255,11 @@ suite('Notebook Document', function () { assert.strictEqual(notebook.uri.toString(), uri.toString()); assert.strictEqual(notebook.isDirty, false); assert.strictEqual(notebook.isUntitled, false); - assert.strictEqual(notebook.cells.length, 1); + assert.strictEqual(notebook.cellCount, 1); assert.strictEqual(notebook.viewType, 'notebook.nbdtest'); const edit = new vscode.WorkspaceEdit(); - edit.replaceNotebookCells(notebook.uri, 0, 0, [{ + edit.replaceNotebookCells(notebook.uri, new vscode.NotebookRange(0, 0), [{ kind: vscode.NotebookCellKind.Markdown, language: 'markdown', metadata: undefined, @@ -332,7 +286,7 @@ suite('Notebook Document', function () { const uri = await utils.createRandomFile(undefined, undefined, '.nbdtest'); const notebook = await vscode.notebook.openNotebookDocument(uri); - const first = notebook.cells[0]; + const first = notebook.cellAt(0); assert.strictEqual(first.document.languageId, 'javascript'); const pclose = utils.asPromise(vscode.workspace.onDidCloseTextDocument); @@ -349,43 +303,44 @@ suite('Notebook Document', function () { assert.strictEqual(opened === closed, true); }); + test('setTextDocumentLanguage when notebook editor is not open', async function () { + const uri = await utils.createRandomFile('', undefined, '.nbdtest'); + const notebook = await vscode.notebook.openNotebookDocument(uri); + const firstCelUri = notebook.cellAt(0).document.uri; + await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); - test('#117273, Add multiple outputs', async function () { + let cellDoc = await vscode.workspace.openTextDocument(firstCelUri); + cellDoc = await vscode.languages.setTextDocumentLanguage(cellDoc, 'css'); + assert.strictEqual(cellDoc.languageId, 'css'); + }); + test('dirty state - complex', async function () { const resource = await utils.createRandomFile(undefined, undefined, '.nbdtest'); const document = await vscode.notebook.openNotebookDocument(resource); + assert.strictEqual(document.isDirty, false); const edit = new vscode.WorkspaceEdit(); - edit.replaceNotebookCellOutput(document.uri, 0, [ - new vscode.NotebookCellOutput( - [new vscode.NotebookCellOutputItem('application/x.notebook.stream', '1', { outputType: 'stream', streamName: 'stdout' })], - { outputType: 'stream', streamName: 'stdout' } - ) - ]); - let success = await vscode.workspace.applyEdit(edit); + edit.replaceNotebookCells(document.uri, new vscode.NotebookRange(0, document.cellCount), []); + assert.ok(await vscode.workspace.applyEdit(edit)); - assert.ok(success); - assert.strictEqual(document.cells[0].outputs.length, 1); - assert.strictEqual(document.cells[0].outputs[0].outputs.length, 1); - assert.deepStrictEqual(document.cells[0].outputs[0].metadata, { outputType: 'stream', streamName: 'stdout' }); - assert.deepStrictEqual(document.cells[0].outputs[0].outputs[0].metadata, { outputType: 'stream', streamName: 'stdout' }); + assert.strictEqual(document.isDirty, true); - const edit2 = new vscode.WorkspaceEdit(); - edit2.appendNotebookCellOutput(document.uri, 0, [ - new vscode.NotebookCellOutput( - [new vscode.NotebookCellOutputItem('hello', '1', { outputType: 'stream', streamName: 'stderr' })], - { outputType: 'stream', streamName: 'stderr' } - ) - ]); - success = await vscode.workspace.applyEdit(edit2); - assert.ok(success); + await document.save(); + assert.strictEqual(document.isDirty, false); + }); - assert.strictEqual(document.cells[0].outputs.length, 2); - assert.strictEqual(document.cells[0].outputs[0].outputs.length, 1); - assert.strictEqual(document.cells[0].outputs[1].outputs.length, 1); - assert.deepStrictEqual(document.cells[0].outputs[0].metadata, { outputType: 'stream', streamName: 'stdout' }); - assert.deepStrictEqual(document.cells[0].outputs[0].outputs[0].metadata, { outputType: 'stream', streamName: 'stdout' }); - assert.deepStrictEqual(document.cells[0].outputs[1].metadata, { outputType: 'stream', streamName: 'stderr' }); - assert.deepStrictEqual(document.cells[0].outputs[1].outputs[0].metadata, { outputType: 'stream', streamName: 'stderr' }); + test('dirty state - serializer', async function () { + const resource = await utils.createRandomFile(undefined, undefined, '.nbdserializer'); + const document = await vscode.notebook.openNotebookDocument(resource); + assert.strictEqual(document.isDirty, false); + + const edit = new vscode.WorkspaceEdit(); + edit.replaceNotebookCells(document.uri, new vscode.NotebookRange(0, document.cellCount), []); + assert.ok(await vscode.workspace.applyEdit(edit)); + + assert.strictEqual(document.isDirty, true); + + await document.save(); + assert.strictEqual(document.isDirty, false); }); }); 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 77ac6734..11792c4f 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 @@ -9,25 +9,15 @@ import * as utils from '../utils'; suite('Notebook Editor', function () { - const contentProvider = new class implements vscode.NotebookContentProvider { - async openNotebook(uri: vscode.Uri, _openContext: vscode.NotebookDocumentOpenContext): Promise { + const contentSerializer = new class implements vscode.NotebookSerializer { + deserializeNotebook() { return new vscode.NotebookData( - [new vscode.NotebookCellData(vscode.NotebookCellKind.Code, uri.toString(), 'javascript')], + [new vscode.NotebookCellData(vscode.NotebookCellKind.Code, '// code cell', 'javascript')], new vscode.NotebookDocumentMetadata() ); - } - async resolveNotebook(_document: vscode.NotebookDocument, _webview: vscode.NotebookCommunication) { - // - } - async saveNotebook(_document: vscode.NotebookDocument, _cancellation: vscode.CancellationToken) { - // - } - async saveNotebookAs(_targetResource: vscode.Uri, _document: vscode.NotebookDocument, _cancellation: vscode.CancellationToken) { - // - } - async backupNotebook(_document: vscode.NotebookDocument, _context: vscode.NotebookDocumentBackupContext, _cancellation: vscode.CancellationToken) { - return { id: '', delete() { } }; + serializeNotebook() { + return new Uint8Array(); } }; @@ -46,7 +36,7 @@ suite('Notebook Editor', function () { }); suiteSetup(function () { - disposables.push(vscode.notebook.registerNotebookContentProvider('notebook.nbdtest', contentProvider)); + disposables.push(vscode.notebook.registerNotebookSerializer('notebook.nbdtest', contentSerializer)); }); @@ -68,6 +58,18 @@ suite('Notebook Editor', function () { }); + test('notebook editor has viewColumn', async function () { + + const uri1 = await utils.createRandomFile(undefined, undefined, '.nbdtest'); + const editor1 = await vscode.window.showNotebookDocument(uri1); + + assert.strictEqual(editor1.viewColumn, vscode.ViewColumn.One); + + const uri2 = await utils.createRandomFile(undefined, undefined, '.nbdtest'); + const editor2 = await vscode.window.showNotebookDocument(uri2, { viewColumn: vscode.ViewColumn.Beside }); + assert.strictEqual(editor2.viewColumn, vscode.ViewColumn.Two); + }); + 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'); 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 5381bc67..1aff42f1 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts @@ -8,6 +8,10 @@ import * as assert from 'assert'; import * as vscode from 'vscode'; import { createRandomFile, asPromise, disposeAll, closeAllEditors, revertAllDirty, saveAllEditors, assertNoRpc } from '../utils'; +async function createRandomNotebookFile() { + return createRandomFile('', undefined, '.vsctestnb'); +} + // Since `workbench.action.splitEditor` command does await properly // Notebook editor/document events are not guaranteed to be sent to the ext host when promise resolves // The workaround here is waiting for the first visible notebook editor change event. @@ -53,44 +57,29 @@ async function withEvent(event: vscode.Event, callback: (e: Promise) => await callback(e); } -const kernel1 = new class implements vscode.NotebookKernel { - readonly id = 'mainKernel'; - readonly label = 'Notebook Test Kernel'; - readonly isPreferred = true; - readonly supportedLanguages = ['typescript', 'javascript']; - async executeCellsRequest(document: vscode.NotebookDocument, ranges: vscode.NotebookCellRange[]) { - if (ranges.length > 1 || ranges[0].start + 1 < ranges[0].end) { - // Keeping same behavior... if the full notebook is executed, just execute the first cell - const task = vscode.notebook.createNotebookCellExecutionTask(document.uri, 0, 'mainKernel'); - if (!task) { - return; - } +class Kernel { - task.start(); - await task.replaceOutput(new vscode.NotebookCellOutput([ - new vscode.NotebookCellOutputItem('text/plain', ['my output'], undefined) - ])); - task.end({ success: true }); - return; - } + readonly controller: vscode.NotebookController; - for (let range of ranges) { - for (let i = range.start; i < range.end; i++) { - await this.runCell(document, i); - } + constructor(id: string, label: string) { + this.controller = vscode.notebook.createNotebookController(id, 'notebookCoreTest', label); + this.controller.executeHandler = this._execute.bind(this); + this.controller.hasExecutionOrder = true; + this.controller.supportedLanguages = ['typescript', 'javascript']; + } + + protected async _execute(cells: vscode.NotebookCell[]): Promise { + for (let cell of cells) { + await this._runCell(cell); } } - private async runCell(document: vscode.NotebookDocument, idx: number) { - const task = vscode.notebook.createNotebookCellExecutionTask(document.uri, idx, 'mainKernel'); - if (!task) { - return; - } - + protected async _runCell(cell: vscode.NotebookCell) { + const task = this.controller.createNotebookCellExecutionTask(cell); task.start(); task.executionOrder = 1; - if (document.uri.path.endsWith('customRenderer.vsctestnb')) { + if (cell.notebook.uri.path.endsWith('customRenderer.vsctestnb')) { await task.replaceOutput([new vscode.NotebookCellOutput([ new vscode.NotebookCellOutputItem('text/custom', ['test'], undefined) ])]); @@ -102,82 +91,20 @@ const kernel1 = new class implements vscode.NotebookKernel { ])]); task.end({ success: true }); } -}; - -const kernel2 = new class implements vscode.NotebookKernel { - readonly id = 'secondaryKernel'; - readonly label = 'Notebook Secondary Test Kernel'; - readonly isPreferred = false; - readonly supportedLanguages = ['typescript', 'javascript']; - - async executeCellsRequest(document: vscode.NotebookDocument, ranges: vscode.NotebookCellRange[]) { - if (ranges.length > 1 || ranges[0].start + 1 < ranges[0].end) { - // Keeping same behavior... if the full notebook is executed, just execute the first cell - const task = vscode.notebook.createNotebookCellExecutionTask(document.uri, 0, 'secondaryKernel'); - if (!task) { - return; - } - - task.start(); - await task.replaceOutput([new vscode.NotebookCellOutput([ - new vscode.NotebookCellOutputItem('text/plain', ['my second output'], undefined) - ])]); - task.end({ success: true }); - return; - } - - for (let range of ranges) { - for (let i = range.start; i < range.end; i++) { - await this.runCell(document, i); - } - } - } - - private async runCell(document: vscode.NotebookDocument, idx: number) { - const task = vscode.notebook.createNotebookCellExecutionTask(document.uri, idx, 'mainKernel'); - if (!task) { - return; - } - - task.start(); - if (document.uri.path.endsWith('customRenderer.vsctestnb')) { - task.replaceOutput([new vscode.NotebookCellOutput([ - new vscode.NotebookCellOutputItem('text/custom', ['test 2'], undefined) - ])]); - task.end({ success: true }); - return; - } - - await task.replaceOutput([new vscode.NotebookCellOutput([ - new vscode.NotebookCellOutputItem('text/plain', ['my second output'], undefined) - ])]); - task.end({ success: true }); - } -}; - -class KernelProvider implements vscode.NotebookKernelProvider { - private _onDidChangeKernels = new vscode.EventEmitter(); - onDidChangeKernels = this._onDidChangeKernels.event; - - private _hasKernels = true; - private readonly _kernels: vscode.NotebookKernel[] = [kernel1, kernel2]; - - addKernel(kernel: vscode.NotebookKernel): void { - this._kernels.push(kernel); - this._onDidChangeKernels.fire(undefined); - } - - provideKernels(): vscode.ProviderResult { - return this._hasKernels ? this._kernels : []; - } - - setHasKernels(hasKernels: boolean): void { - this._hasKernels = hasKernels; - this._onDidChangeKernels.fire(undefined); - } } -let currentKernelProvider: KernelProvider; + +function getFocusedCell(editor?: vscode.NotebookEditor) { + return editor ? editor.document.cellAt(editor.selections[0].start) : undefined; +} + +async function assertKernel(controller: vscode.NotebookController): Promise { + const success = await vscode.commands.executeCommand('notebook.selectKernel', { + extension: 'vscode.vscode-api-tests', + id: controller.id + }); + assert.ok(success, `expected selected kernel to be ${controller.id}`); +} suite('Notebook API tests', function () { @@ -214,7 +141,7 @@ suite('Notebook API tests', function () { kind: vscode.NotebookCellKind.Code, outputs: [], metadata: new vscode.NotebookCellMetadata().with({ custom: { testCellMetadata: 123 } }), - latestExecutionSummary: { duration: 25 } + latestExecutionSummary: { startTime: 10, endTime: 20 } }, { source: 'test2', @@ -233,9 +160,6 @@ suite('Notebook API tests', function () { }; return dto; }, - resolveNotebook: async (_document: vscode.NotebookDocument) => { - return; - }, saveNotebook: async (_document: vscode.NotebookDocument, _cancellation: vscode.CancellationToken) => { return; }, @@ -251,9 +175,40 @@ suite('Notebook API tests', function () { })); }); + let kernel1: Kernel; + let kernel2: Kernel; + setup(() => { - currentKernelProvider = new KernelProvider(); - testDisposables.push(vscode.notebook.registerNotebookKernelProvider({ filenamePattern: '*.vsctestnb' }, currentKernelProvider)); + + kernel1 = new Kernel('mainKernel', 'Notebook Primary Test Kernel'); + + const listener = vscode.notebook.onDidOpenNotebookDocument(async notebook => { + if (notebook.viewType === kernel1.controller.viewType) { + await vscode.commands.executeCommand('notebook.selectKernel', { + extension: 'vscode.vscode-api-tests', + id: kernel1.controller.id + }); + } + }); + + + kernel2 = new class extends Kernel { + constructor() { + super('secondaryKernel', 'Notebook Secondary Test Kernel'); + this.controller.hasExecutionOrder = false; + } + + override async _runCell(cell: vscode.NotebookCell) { + const task = this.controller.createNotebookCellExecutionTask(cell); + task.start(); + await task.replaceOutput([new vscode.NotebookCellOutput([ + new vscode.NotebookCellOutputItem('text/plain', ['my second output'], undefined) + ])]); + task.end({ success: true }); + } + }; + + testDisposables.push(kernel1.controller, listener, kernel2.controller); }); teardown(() => { @@ -262,7 +217,7 @@ suite('Notebook API tests', function () { }); test('shared document in notebook editors', async function () { - const resource = await createRandomFile(undefined, undefined, '.vsctestnb'); + const resource = await createRandomNotebookFile(); let counter = 0; const disposables: vscode.Disposable[] = []; disposables.push(vscode.notebook.onDidOpenNotebookDocument(() => { @@ -283,7 +238,7 @@ suite('Notebook API tests', function () { }); test('editor open/close event', async function () { - const resource = await createRandomFile('', undefined, '.vsctestnb'); + const resource = await createRandomNotebookFile(); const firstEditorOpen = asPromise(vscode.window.onDidChangeVisibleNotebookEditors); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); await firstEditorOpen; @@ -294,7 +249,7 @@ suite('Notebook API tests', function () { }); test('editor open/close event 2', async function () { - const resource = await createRandomFile('', undefined, '.vsctestnb'); + const resource = await createRandomNotebookFile(); let count = 0; const disposables: vscode.Disposable[] = []; disposables.push(vscode.window.onDidChangeVisibleNotebookEditors(() => { @@ -311,8 +266,27 @@ suite('Notebook API tests', function () { assert.strictEqual(count, 0); }); + test('correct cell selection on undo/redo of cell creation', async function () { + const resource = await createRandomNotebookFile(); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); + await vscode.commands.executeCommand('undo'); + const selectionUndo = [...vscode.window.activeNotebookEditor!.selections]; + await vscode.commands.executeCommand('redo'); + const selectionRedo = vscode.window.activeNotebookEditor!.selections; + + // On undo, the selected cell must be the upper cell, ie the first one + assert.strictEqual(selectionUndo.length, 1); + assert.strictEqual(selectionUndo[0].start, 0); + assert.strictEqual(selectionUndo[0].end, 1); + // On redo, the selected cell must be the new cell, ie the second one + assert.strictEqual(selectionRedo.length, 1); + assert.strictEqual(selectionRedo[0].start, 1); + assert.strictEqual(selectionRedo[0].end, 2); + }); + test('editor editing event 2', async function () { - const resource = await createRandomFile('', undefined, '.vsctestnb'); + const resource = await createRandomNotebookFile(); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); const cellsChangeEvent = asPromise(vscode.notebook.onDidChangeNotebookCells); @@ -325,7 +299,7 @@ suite('Notebook API tests', function () { deletedCount: 0, deletedItems: [], items: [ - vscode.window.activeNotebookEditor!.document.cells[1] + vscode.window.activeNotebookEditor!.document.cellAt(1) ] }); @@ -338,7 +312,7 @@ suite('Notebook API tests', function () { const cellOutputsAddedRet = await cellOutputChange; assert.deepStrictEqual(cellOutputsAddedRet, { document: vscode.window.activeNotebookEditor!.document, - cells: [vscode.window.activeNotebookEditor!.document.cells[0]] + cells: [vscode.window.activeNotebookEditor!.document.cellAt(0)] }); assert.strictEqual(cellOutputsAddedRet.cells[0].outputs.length, 1); @@ -347,7 +321,7 @@ suite('Notebook API tests', function () { const cellOutputsCleardRet = await cellOutputClear; assert.deepStrictEqual(cellOutputsCleardRet, { document: vscode.window.activeNotebookEditor!.document, - cells: [vscode.window.activeNotebookEditor!.document.cells[0]] + cells: [vscode.window.activeNotebookEditor!.document.cellAt(0)] }); assert.strictEqual(cellOutputsAddedRet.cells[0].outputs.length, 0); @@ -356,7 +330,7 @@ suite('Notebook API tests', function () { // const cellChangeLanguageRet = await cellChangeLanguage; // assert.deepStrictEqual(cellChangeLanguageRet, { // document: vscode.window.activeNotebookEditor!.document, - // cells: vscode.window.activeNotebookEditor!.document.cells[0], + // cells: vscode.window.activeNotebookEditor!.document.cellAt(0), // language: 'markdown' // }); @@ -364,14 +338,14 @@ suite('Notebook API tests', function () { }); test('editor move cell event', async function () { - const resource = await createRandomFile('', undefined, '.vsctestnb'); + const resource = await createRandomNotebookFile(); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove'); await vscode.commands.executeCommand('notebook.focusTop'); - const activeCell = vscode.window.activeNotebookEditor!.selection; - assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.indexOf(activeCell!), 0); + const activeCell = getFocusedCell(vscode.window.activeNotebookEditor); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.getCells().indexOf(activeCell!), 0); const moveChange = asPromise(vscode.notebook.onDidChangeNotebookCells); await vscode.commands.executeCommand('notebook.cell.moveDown'); await moveChange; @@ -380,12 +354,12 @@ suite('Notebook API tests', function () { await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); const firstEditor = vscode.window.activeNotebookEditor; - assert.strictEqual(firstEditor?.document.cells.length, 2); + assert.strictEqual(firstEditor?.document.cellCount, 2); await saveAllFilesAndCloseAll(undefined); }); test('notebook editor active/visible', async function () { - const resource = await createRandomFile('', undefined, '.vsctestnb'); + const resource = await createRandomNotebookFile(); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); const firstEditor = vscode.window.activeNotebookEditor; assert.strictEqual(firstEditor && vscode.window.visibleNotebookEditors.indexOf(firstEditor) >= 0, true); @@ -417,7 +391,7 @@ suite('Notebook API tests', function () { }); test('notebook active editor change', async function () { - const resource = await createRandomFile('', undefined, '.vsctestnb'); + const resource = await createRandomNotebookFile(); const firstEditorOpen = asPromise(vscode.window.onDidChangeActiveNotebookEditor); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); await firstEditorOpen; @@ -430,7 +404,7 @@ suite('Notebook API tests', function () { }); test('edit API (replaceMetadata)', async function () { - const resource = await createRandomFile('', undefined, '.vsctestnb'); + const resource = await createRandomNotebookFile(); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); await vscode.window.activeNotebookEditor!.edit(editBuilder => { @@ -438,15 +412,15 @@ suite('Notebook API tests', function () { }); const document = vscode.window.activeNotebookEditor?.document!; - assert.strictEqual(document.cells.length, 2); - assert.strictEqual(document.cells[0].metadata.inputCollapsed, true); + assert.strictEqual(document.cellCount, 2); + assert.strictEqual(document.cellAt(0).metadata.inputCollapsed, true); assert.strictEqual(document.isDirty, true); await saveFileAndCloseAll(resource); }); test('edit API (replaceMetadata, event)', async function () { - const resource = await createRandomFile('', undefined, '.vsctestnb'); + const resource = await createRandomNotebookFile(); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); const event = asPromise(vscode.notebook.onDidChangeCellMetadata); @@ -464,7 +438,7 @@ suite('Notebook API tests', function () { }); test('edit API batch edits', async function () { - const resource = await createRandomFile('', undefined, '.vsctestnb'); + const resource = await createRandomNotebookFile(); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); const cellsChangeEvent = asPromise(vscode.notebook.onDidChangeNotebookCells); @@ -472,7 +446,7 @@ suite('Notebook API tests', function () { const version = vscode.window.activeNotebookEditor!.document.version; await vscode.window.activeNotebookEditor!.edit(editBuilder => { editBuilder.replaceCells(1, 0, [{ kind: vscode.NotebookCellKind.Code, language: 'javascript', source: 'test 2', outputs: [], metadata: undefined }]); - editBuilder.replaceCellMetadata(0, new vscode.NotebookCellMetadata().with({ breakpointMargin: false })); + editBuilder.replaceCellMetadata(0, new vscode.NotebookCellMetadata().with({ inputCollapsed: false })); }); await cellsChangeEvent; @@ -482,7 +456,7 @@ suite('Notebook API tests', function () { }); test('edit API batch edits undo/redo', async function () { - const resource = await createRandomFile('', undefined, '.vsctestnb'); + const resource = await createRandomNotebookFile(); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); const cellsChangeEvent = asPromise(vscode.notebook.onDidChangeNotebookCells); @@ -490,25 +464,25 @@ suite('Notebook API tests', function () { const version = vscode.window.activeNotebookEditor!.document.version; await vscode.window.activeNotebookEditor!.edit(editBuilder => { editBuilder.replaceCells(1, 0, [{ kind: vscode.NotebookCellKind.Code, language: 'javascript', source: 'test 2', outputs: [], metadata: undefined }]); - editBuilder.replaceCellMetadata(0, new vscode.NotebookCellMetadata().with({ breakpointMargin: false })); + editBuilder.replaceCellMetadata(0, new vscode.NotebookCellMetadata().with({ inputCollapsed: false })); }); await cellsChangeEvent; await cellMetadataChangeEvent; - assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.length, 3); - assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells[0]?.metadata?.breakpointMargin, false); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cellCount, 3); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cellAt(0)?.metadata?.inputCollapsed, false); assert.strictEqual(version + 1, vscode.window.activeNotebookEditor!.document.version); await vscode.commands.executeCommand('undo'); assert.strictEqual(version + 2, vscode.window.activeNotebookEditor!.document.version); - assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells[0]?.metadata?.breakpointMargin, undefined); - assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.length, 2); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cellAt(0)?.metadata?.inputCollapsed, undefined); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cellCount, 2); await saveAllFilesAndCloseAll(resource); }); test('initialzation should not emit cell change events.', async function () { - const resource = await createRandomFile('', undefined, '.vsctestnb'); + const resource = await createRandomNotebookFile(); let count = 0; const disposables: vscode.Disposable[] = []; disposables.push(vscode.notebook.onDidChangeNotebookCells(() => { @@ -527,13 +501,13 @@ suite('Notebook API tests', function () { // suite('notebook workflow', () => { test('notebook open', async function () { - const resource = await createRandomFile('', undefined, '.vsctestnb'); + const resource = await createRandomNotebookFile(); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); - assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.document.getText(), 'test'); - assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.document.languageId, 'typescript'); + assert.strictEqual(getFocusedCell(vscode.window.activeNotebookEditor)?.document.getText(), 'test'); + assert.strictEqual(getFocusedCell(vscode.window.activeNotebookEditor)?.document.languageId, 'typescript'); - const secondCell = vscode.window.activeNotebookEditor!.document.cells[1]; + const secondCell = vscode.window.activeNotebookEditor!.document.cellAt(1); assert.strictEqual(secondCell!.outputs.length, 1); assert.deepStrictEqual(secondCell!.outputs[0].metadata, { testOutputMetadata: true }); assert.strictEqual(secondCell!.outputs[0].outputs.length, 1); @@ -544,85 +518,85 @@ suite('Notebook API tests', function () { assert.strictEqual(secondCell!.latestExecutionSummary?.success, true); await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); - assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.document.getText(), ''); + assert.strictEqual(getFocusedCell(vscode.window.activeNotebookEditor)?.document.getText(), ''); await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove'); - const activeCell = vscode.window.activeNotebookEditor!.selection; - assert.notEqual(vscode.window.activeNotebookEditor!.selection, undefined); + const activeCell = getFocusedCell(vscode.window.activeNotebookEditor); + assert.notEqual(getFocusedCell(vscode.window.activeNotebookEditor), undefined); assert.strictEqual(activeCell!.document.getText(), ''); - assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.length, 4); - assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.indexOf(activeCell!), 1); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cellCount, 4); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.getCells().indexOf(activeCell!), 1); await vscode.commands.executeCommand('workbench.action.files.save'); await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); }); test('notebook cell actions', async function () { - const resource = await createRandomFile('', undefined, '.vsctestnb'); + const resource = await createRandomNotebookFile(); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); - assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.document.getText(), 'test'); - assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.document.languageId, 'typescript'); + assert.strictEqual(getFocusedCell(vscode.window.activeNotebookEditor)?.document.getText(), 'test'); + assert.strictEqual(getFocusedCell(vscode.window.activeNotebookEditor)?.document.languageId, 'typescript'); // ---- insert cell below and focus ---- // await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); - assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.document.getText(), ''); + assert.strictEqual(getFocusedCell(vscode.window.activeNotebookEditor)?.document.getText(), ''); // ---- insert cell above and focus ---- // await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove'); - let activeCell = vscode.window.activeNotebookEditor!.selection; - assert.notEqual(vscode.window.activeNotebookEditor!.selection, undefined); + let activeCell = getFocusedCell(vscode.window.activeNotebookEditor); + assert.notEqual(getFocusedCell(vscode.window.activeNotebookEditor), undefined); assert.strictEqual(activeCell!.document.getText(), ''); - assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.length, 4); - assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.indexOf(activeCell!), 1); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cellCount, 4); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.getCells().indexOf(activeCell!), 1); // ---- focus bottom ---- // await vscode.commands.executeCommand('notebook.focusBottom'); - activeCell = vscode.window.activeNotebookEditor!.selection; - assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.indexOf(activeCell!), 3); + activeCell = getFocusedCell(vscode.window.activeNotebookEditor); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.getCells().indexOf(activeCell!), 3); // ---- focus top and then copy down ---- // await vscode.commands.executeCommand('notebook.focusTop'); - activeCell = vscode.window.activeNotebookEditor!.selection; - assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.indexOf(activeCell!), 0); + activeCell = getFocusedCell(vscode.window.activeNotebookEditor); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.getCells().indexOf(activeCell!), 0); await vscode.commands.executeCommand('notebook.cell.copyDown'); - activeCell = vscode.window.activeNotebookEditor!.selection; - assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.indexOf(activeCell!), 1); + activeCell = getFocusedCell(vscode.window.activeNotebookEditor); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.getCells().indexOf(activeCell!), 1); assert.strictEqual(activeCell?.document.getText(), 'test'); await vscode.commands.executeCommand('notebook.cell.delete'); - activeCell = vscode.window.activeNotebookEditor!.selection; - assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.indexOf(activeCell!), 1); + activeCell = getFocusedCell(vscode.window.activeNotebookEditor); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.getCells().indexOf(activeCell!), 1); assert.strictEqual(activeCell?.document.getText(), ''); // ---- focus top and then copy up ---- // await vscode.commands.executeCommand('notebook.focusTop'); await vscode.commands.executeCommand('notebook.cell.copyUp'); - assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.length, 5); - assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells[0].document.getText(), 'test'); - assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells[1].document.getText(), 'test'); - assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells[2].document.getText(), ''); - assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells[3].document.getText(), ''); - activeCell = vscode.window.activeNotebookEditor!.selection; - assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.indexOf(activeCell!), 0); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cellCount, 5); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cellAt(0).document.getText(), 'test'); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cellAt(1).document.getText(), 'test'); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cellAt(2).document.getText(), ''); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cellAt(3).document.getText(), ''); + activeCell = getFocusedCell(vscode.window.activeNotebookEditor); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.getCells().indexOf(activeCell!), 0); // ---- move up and down ---- // await vscode.commands.executeCommand('notebook.cell.moveDown'); - assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.indexOf(vscode.window.activeNotebookEditor!.selection!), 1, - `first move down, active cell ${vscode.window.activeNotebookEditor!.selection!.document.uri.toString()}, ${vscode.window.activeNotebookEditor!.selection!.document.getText()}`); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.getCells().indexOf(getFocusedCell(vscode.window.activeNotebookEditor)!), 1, + `first move down, active cell ${getFocusedCell(vscode.window.activeNotebookEditor)!.document.uri.toString()}, ${getFocusedCell(vscode.window.activeNotebookEditor)!.document.getText()}`); // await vscode.commands.executeCommand('notebook.cell.moveDown'); - // activeCell = vscode.window.activeNotebookEditor!.selection; + // activeCell = getFocusedCell(vscode.window.activeNotebookEditor); - // assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.indexOf(activeCell!), 2, - // `second move down, active cell ${vscode.window.activeNotebookEditor!.selection!.uri.toString()}, ${vscode.window.activeNotebookEditor!.selection!.document.getText()}`); - // assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells[0].document.getText(), 'test'); - // assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells[1].document.getText(), ''); - // assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells[2].document.getText(), 'test'); - // assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells[3].document.getText(), ''); + // assert.strictEqual(vscode.window.activeNotebookEditor!.document.getCells().indexOf(activeCell!), 2, + // `second move down, active cell ${getFocusedCell(vscode.window.activeNotebookEditor)!.uri.toString()}, ${getFocusedCell(vscode.window.activeNotebookEditor)!.document.getText()}`); + // assert.strictEqual(vscode.window.activeNotebookEditor!.document.cellAt(0).document.getText(), 'test'); + // assert.strictEqual(vscode.window.activeNotebookEditor!.document.cellAt(1).document.getText(), ''); + // assert.strictEqual(vscode.window.activeNotebookEditor!.document.cellAt(2).document.getText(), 'test'); + // assert.strictEqual(vscode.window.activeNotebookEditor!.document.cellAt(3).document.getText(), ''); // ---- ---- // @@ -631,78 +605,78 @@ suite('Notebook API tests', function () { }); test('notebook join cells', async function () { - const resource = await createRandomFile('', undefined, '.vsctestnb'); + const resource = await createRandomNotebookFile(); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); - assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.document.getText(), 'test'); - assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.document.languageId, 'typescript'); + assert.strictEqual(getFocusedCell(vscode.window.activeNotebookEditor)?.document.getText(), 'test'); + assert.strictEqual(getFocusedCell(vscode.window.activeNotebookEditor)?.document.languageId, 'typescript'); await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); - assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.document.getText(), ''); + assert.strictEqual(getFocusedCell(vscode.window.activeNotebookEditor)?.document.getText(), ''); const edit = new vscode.WorkspaceEdit(); - edit.insert(vscode.window.activeNotebookEditor!.selection!.document.uri, new vscode.Position(0, 0), 'var abc = 0;'); + edit.insert(getFocusedCell(vscode.window.activeNotebookEditor)!.document.uri, new vscode.Position(0, 0), 'var abc = 0;'); await vscode.workspace.applyEdit(edit); const cellsChangeEvent = asPromise(vscode.notebook.onDidChangeNotebookCells); await vscode.commands.executeCommand('notebook.cell.joinAbove'); await cellsChangeEvent; - assert.deepStrictEqual(vscode.window.activeNotebookEditor!.selection?.document.getText().split(/\r\n|\r|\n/), ['test', 'var abc = 0;']); + assert.deepStrictEqual(getFocusedCell(vscode.window.activeNotebookEditor)?.document.getText().split(/\r\n|\r|\n/), ['test', 'var abc = 0;']); await vscode.commands.executeCommand('workbench.action.files.save'); await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); }); test('move cells will not recreate cells in ExtHost', async function () { - const resource = await createRandomFile('', undefined, '.vsctestnb'); + const resource = await createRandomNotebookFile(); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove'); await vscode.commands.executeCommand('notebook.focusTop'); - const activeCell = vscode.window.activeNotebookEditor!.selection; - assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.indexOf(activeCell!), 0); + const activeCell = getFocusedCell(vscode.window.activeNotebookEditor); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.getCells().indexOf(activeCell!), 0); await vscode.commands.executeCommand('notebook.cell.moveDown'); await vscode.commands.executeCommand('notebook.cell.moveDown'); - const newActiveCell = vscode.window.activeNotebookEditor!.selection; + const newActiveCell = getFocusedCell(vscode.window.activeNotebookEditor); assert.deepStrictEqual(activeCell, newActiveCell); await saveFileAndCloseAll(resource); }); - test('document runnable based on kernel count', async () => { - const resource = await createRandomFile('', undefined, '.vsctestnb'); - await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); - const editor = vscode.window.activeNotebookEditor!; + // test('document runnable based on kernel count', async () => { + // const resource = await createRandomNotebookFile(); + // await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + // assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); + // const editor = vscode.window.activeNotebookEditor!; - const cell = editor.document.cells[0]; - assert.strictEqual(cell.outputs.length, 0); + // const cell = editor.document.cellAt(0); + // assert.strictEqual(cell.outputs.length, 0); - currentKernelProvider.setHasKernels(false); - await vscode.commands.executeCommand('notebook.execute'); - assert.strictEqual(cell.outputs.length, 0, 'should not execute'); // not runnable, didn't work + // currentKernelProvider.setHasKernels(false); + // await vscode.commands.executeCommand('notebook.execute'); + // assert.strictEqual(cell.outputs.length, 0, 'should not execute'); // not runnable, didn't work - currentKernelProvider.setHasKernels(true); + // currentKernelProvider.setHasKernels(true); - await withEvent(vscode.notebook.onDidChangeCellOutputs, async (event) => { - await vscode.commands.executeCommand('notebook.execute'); - await event; - assert.strictEqual(cell.outputs.length, 1, 'should execute'); // runnable, it worked - }); + // await withEvent(vscode.notebook.onDidChangeCellOutputs, async (event) => { + // await vscode.commands.executeCommand('notebook.execute'); + // await event; + // assert.strictEqual(cell.outputs.length, 1, 'should execute'); // runnable, it worked + // }); - await saveAllFilesAndCloseAll(undefined); - }); + // await saveAllFilesAndCloseAll(undefined); + // }); // TODO@rebornix this is wrong, `await vscode.commands.executeCommand('notebook.execute');` doesn't wait until the workspace edit is applied test.skip('cell execute command takes arguments', async () => { - const resource = await createRandomFile('', undefined, '.vsctestnb'); + const resource = await createRandomNotebookFile(); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); const editor = vscode.window.activeNotebookEditor!; - const cell = editor.document.cells[0]; + const cell = editor.document.cellAt(0); await vscode.commands.executeCommand('notebook.execute'); assert.strictEqual(cell.outputs.length, 0, 'should not execute'); // not runnable, didn't work @@ -711,11 +685,11 @@ suite('Notebook API tests', function () { }); test('cell execute command takes arguments 2', async () => { - const resource = await createRandomFile('', undefined, '.vsctestnb'); + const resource = await createRandomNotebookFile(); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); const editor = vscode.window.activeNotebookEditor!; - const cell = editor.document.cells[0]; + const cell = editor.document.cellAt(0); await withEvent(vscode.notebook.onDidChangeCellOutputs, async (event) => { await vscode.commands.executeCommand('notebook.execute'); @@ -729,7 +703,7 @@ suite('Notebook API tests', function () { assert.strictEqual(cell.outputs.length, 0, 'should clear'); }); - const secondResource = await createRandomFile('', undefined, '.vsctestnb'); + const secondResource = await createRandomNotebookFile(); await vscode.commands.executeCommand('vscode.openWith', secondResource, 'notebookCoreTest'); await withEvent(vscode.notebook.onDidChangeCellOutputs, async (event) => { @@ -743,11 +717,11 @@ suite('Notebook API tests', function () { }); test('document execute command takes arguments', async () => { - const resource = await createRandomFile('', undefined, '.vsctestnb'); + const resource = await createRandomNotebookFile(); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); const editor = vscode.window.activeNotebookEditor!; - const cell = editor.document.cells[0]; + const cell = editor.document.cellAt(0); await withEvent(vscode.notebook.onDidChangeCellOutputs, async (event) => { await vscode.commands.executeCommand('notebook.execute'); @@ -760,7 +734,7 @@ suite('Notebook API tests', function () { await clearChangeEvent; assert.strictEqual(cell.outputs.length, 0, 'should clear'); - const secondResource = await createRandomFile('', undefined, '.vsctestnb'); + const secondResource = await createRandomNotebookFile(); await vscode.commands.executeCommand('vscode.openWith', secondResource, 'notebookCoreTest'); await withEvent(vscode.notebook.onDidChangeCellOutputs, async (event) => { @@ -773,15 +747,15 @@ suite('Notebook API tests', function () { await saveAllFilesAndCloseAll(undefined); }); - test('cell execute and select kernel', async () => { - const resource = await createRandomFile('', undefined, '.vsctestnb'); + test('cell execute and select kernel', async function () { + const resource = await createRandomNotebookFile(); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); const editor = vscode.window.activeNotebookEditor!; - const cell = editor.document.cells[0]; + const cell = editor.document.cellAt(0); - vscode.commands.executeCommand('notebook.cell.execute'); await withEvent(vscode.notebook.onDidChangeCellOutputs, async (event) => { + await vscode.commands.executeCommand('notebook.cell.execute'); await event; assert.strictEqual(cell.outputs.length, 1, 'should execute'); // runnable, it worked assert.strictEqual(cell.outputs[0].outputs.length, 1); @@ -791,9 +765,9 @@ suite('Notebook API tests', function () { ]); }); - await vscode.commands.executeCommand('notebook.selectKernel', { extension: 'vscode.vscode-api-tests', id: 'secondaryKernel' }); - vscode.commands.executeCommand('notebook.cell.execute'); await withEvent(vscode.notebook.onDidChangeCellOutputs, async (event) => { + await assertKernel(kernel2.controller); + await vscode.commands.executeCommand('notebook.cell.execute'); await event; assert.strictEqual(cell.outputs.length, 1, 'should execute'); // runnable, it worked assert.strictEqual(cell.outputs[0].outputs.length, 1); @@ -807,37 +781,34 @@ suite('Notebook API tests', function () { }); test('set outputs on cancel', async () => { - const cancelableKernel = new class implements vscode.NotebookKernel { - readonly id = 'cancelableKernel'; - readonly label = 'Notebook Cancelable Test Kernel'; - readonly isPreferred = false; - readonly supportedLanguages = ['typescript', 'javascript']; - async executeCellsRequest(document: vscode.NotebookDocument, ranges: vscode.NotebookCellRange[]) { - const idx = ranges[0].start; + const cancelableKernel = new class extends Kernel { + + constructor() { + super('cancelableKernel', 'Notebook Cancelable Test Kernel'); + } + + override async _execute(cells: vscode.NotebookCell[]) { + for (const cell of cells) { + const task = this.controller.createNotebookCellExecutionTask(cell); + task.start(); + task.token.onCancellationRequested(async () => { + await task.replaceOutput([new vscode.NotebookCellOutput([ + new vscode.NotebookCellOutputItem('text/plain', ['Canceled'], undefined) + ])]); + task.end({}); + }); - const task = vscode.notebook.createNotebookCellExecutionTask(document.uri, idx, 'cancelableKernel'); - if (!task) { - return; } - - task.start(); - task.token.onCancellationRequested(async () => { - await task.replaceOutput([new vscode.NotebookCellOutput([ - new vscode.NotebookCellOutputItem('text/plain', ['Canceled'], undefined) - ])]); - task.end({}); - }); } }; - currentKernelProvider.addKernel(cancelableKernel); - const resource = await createRandomFile('', undefined, '.vsctestnb'); + const resource = await createRandomNotebookFile(); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); const editor = vscode.window.activeNotebookEditor!; - const cell = editor.document.cells[0]; + const cell = editor.document.cellAt(0); - await vscode.commands.executeCommand('notebook.selectKernel', { extension: 'vscode.vscode-api-tests', id: cancelableKernel.id }); + await assertKernel(cancelableKernel.controller); await withEvent(vscode.notebook.onDidChangeCellOutputs, async (event) => { await vscode.commands.executeCommand('notebook.cell.execute'); await vscode.commands.executeCommand('notebook.cell.cancelExecution'); @@ -850,30 +821,28 @@ suite('Notebook API tests', function () { ]); }); + cancelableKernel.controller.dispose(); await saveAllFilesAndCloseAll(undefined); }); test('set outputs on interrupt', async () => { - const interruptableKernel = new class implements vscode.NotebookKernel { - readonly id = 'interruptableKernel'; - readonly label = 'Notebook Interruptable Test Kernel'; - readonly isPreferred = false; - readonly supportedLanguages = ['typescript', 'javascript']; + const interruptableKernel = new class extends Kernel { + + + constructor() { + super('interruptableKernel', 'Notebook Interruptable Test Kernel'); + this.controller.interruptHandler = this.interrupt.bind(this); + } private _task: vscode.NotebookCellExecutionTask | undefined; - async executeCellsRequest(document: vscode.NotebookDocument, ranges: vscode.NotebookCellRange[]) { - const idx = ranges[0].start; - - this._task = vscode.notebook.createNotebookCellExecutionTask(document.uri, idx, 'interruptableKernel'); - if (!this._task) { - return; - } - + override async _execute(cells: vscode.NotebookCell[]) { + this._task = this.controller.createNotebookCellExecutionTask(cells[0]); this._task.start(); } - async interrupt(_document: vscode.NotebookDocument) { + + async interrupt() { await this._task!.replaceOutput([new vscode.NotebookCellOutput([ new vscode.NotebookCellOutputItem('text/plain', ['Interrupted'], undefined) ])]); @@ -881,13 +850,13 @@ suite('Notebook API tests', function () { } }; - currentKernelProvider.addKernel(interruptableKernel); - const resource = await createRandomFile('', undefined, '.vsctestnb'); + const resource = await createRandomNotebookFile(); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); const editor = vscode.window.activeNotebookEditor!; - const cell = editor.document.cells[0]; + const cell = editor.document.cellAt(0); + + await assertKernel(interruptableKernel.controller); - await vscode.commands.executeCommand('notebook.selectKernel', { extension: 'vscode.vscode-api-tests', id: interruptableKernel.id }); await withEvent(vscode.notebook.onDidChangeCellOutputs, async (event) => { await vscode.commands.executeCommand('notebook.cell.execute'); await vscode.commands.executeCommand('notebook.cell.cancelExecution'); @@ -900,14 +869,15 @@ suite('Notebook API tests', function () { ]); }); + interruptableKernel.controller.dispose(); await saveAllFilesAndCloseAll(undefined); }); test('onDidChangeCellExecutionState is fired', async () => { - const resource = await createRandomFile('', undefined, '.vsctestnb'); + const resource = await createRandomNotebookFile(); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); const editor = vscode.window.activeNotebookEditor!; - const cell = editor.document.cells[0]; + const cell = editor.document.cellAt(0); vscode.commands.executeCommand('notebook.cell.execute'); let eventCount = 0; @@ -935,21 +905,21 @@ suite('Notebook API tests', function () { // suite('notebook dirty state', () => { test('notebook open', async function () { - const resource = await createRandomFile('', undefined, '.vsctestnb'); + const resource = await createRandomNotebookFile(); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); - assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.document.getText(), 'test'); - assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.document.languageId, 'typescript'); + assert.strictEqual(getFocusedCell(vscode.window.activeNotebookEditor)?.document.getText(), 'test'); + assert.strictEqual(getFocusedCell(vscode.window.activeNotebookEditor)?.document.languageId, 'typescript'); await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); - assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.document.getText(), ''); + assert.strictEqual(getFocusedCell(vscode.window.activeNotebookEditor)?.document.getText(), ''); await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove'); - const activeCell = vscode.window.activeNotebookEditor!.selection; - assert.notStrictEqual(vscode.window.activeNotebookEditor!.selection, undefined); + const activeCell = getFocusedCell(vscode.window.activeNotebookEditor); + assert.notStrictEqual(getFocusedCell(vscode.window.activeNotebookEditor), undefined); assert.strictEqual(activeCell!.document.getText(), ''); - assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.length, 4); - assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.indexOf(activeCell!), 1); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cellCount, 4); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.getCells().indexOf(activeCell!), 1); await withEvent(vscode.workspace.onDidChangeTextDocument, async event => { const edit = new vscode.WorkspaceEdit(); @@ -957,9 +927,8 @@ suite('Notebook API tests', function () { await vscode.workspace.applyEdit(edit); await event; assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true); - assert.strictEqual(vscode.window.activeNotebookEditor?.selection !== undefined, true); - assert.deepStrictEqual(vscode.window.activeNotebookEditor?.document.cells[1], vscode.window.activeNotebookEditor?.selection); - assert.strictEqual(vscode.window.activeNotebookEditor?.selection?.document.getText(), 'var abc = 0;'); + assert.deepStrictEqual(vscode.window.activeNotebookEditor?.document.cellAt(1), getFocusedCell(vscode.window.activeNotebookEditor)); + assert.strictEqual(getFocusedCell(vscode.window.activeNotebookEditor)?.document.getText(), 'var abc = 0;'); }); await saveFileAndCloseAll(resource); @@ -968,120 +937,117 @@ suite('Notebook API tests', function () { // suite('notebook undo redo', () => { test('notebook open', async function () { - const resource = await createRandomFile('', undefined, '.vsctestnb'); + const resource = await createRandomNotebookFile(); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); - assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.document.getText(), 'test'); - assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.document.languageId, 'typescript'); + assert.strictEqual(getFocusedCell(vscode.window.activeNotebookEditor)?.document.getText(), 'test'); + assert.strictEqual(getFocusedCell(vscode.window.activeNotebookEditor)?.document.languageId, 'typescript'); await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); - assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.document.getText(), ''); + assert.strictEqual(getFocusedCell(vscode.window.activeNotebookEditor)?.document.getText(), ''); await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove'); - const activeCell = vscode.window.activeNotebookEditor!.selection; - assert.notStrictEqual(vscode.window.activeNotebookEditor!.selection, undefined); + const activeCell = getFocusedCell(vscode.window.activeNotebookEditor); + assert.notStrictEqual(getFocusedCell(vscode.window.activeNotebookEditor), undefined); assert.strictEqual(activeCell!.document.getText(), ''); - assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.length, 4); - assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.indexOf(activeCell!), 1); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cellCount, 4); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.getCells().indexOf(activeCell!), 1); // modify the second cell, delete it const edit = new vscode.WorkspaceEdit(); - edit.insert(vscode.window.activeNotebookEditor!.selection!.document.uri, new vscode.Position(0, 0), 'var abc = 0;'); + edit.insert(getFocusedCell(vscode.window.activeNotebookEditor)!.document.uri, new vscode.Position(0, 0), 'var abc = 0;'); await vscode.workspace.applyEdit(edit); await vscode.commands.executeCommand('notebook.cell.delete'); - assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.length, 3); - assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.indexOf(vscode.window.activeNotebookEditor!.selection!), 1); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cellCount, 3); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.getCells().indexOf(getFocusedCell(vscode.window.activeNotebookEditor)!), 1); // undo should bring back the deleted cell, and revert to previous content and selection await vscode.commands.executeCommand('undo'); - assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.length, 4); - assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.indexOf(vscode.window.activeNotebookEditor!.selection!), 1); - assert.strictEqual(vscode.window.activeNotebookEditor?.selection?.document.getText(), 'var abc = 0;'); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cellCount, 4); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.getCells().indexOf(getFocusedCell(vscode.window.activeNotebookEditor)!), 1); + assert.strictEqual(getFocusedCell(vscode.window.activeNotebookEditor)?.document.getText(), 'var abc = 0;'); // redo // await vscode.commands.executeCommand('notebook.redo'); - // assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.length, 2); - // assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.indexOf(vscode.window.activeNotebookEditor!.selection!), 1); + // assert.strictEqual(vscode.window.activeNotebookEditor!.document.cellCount, 2); + // assert.strictEqual(vscode.window.activeNotebookEditor!.document.getCells().indexOf(getFocusedCell(vscode.window.activeNotebookEditor)!), 1); // assert.strictEqual(vscode.window.activeNotebookEditor?.selection?.document.getText(), 'test'); await saveFileAndCloseAll(resource); }); test('multiple tabs: dirty + clean', async function () { - const resource = await createRandomFile('', undefined, '.vsctestnb'); + const resource = await createRandomNotebookFile(); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); - assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.document.getText(), ''); + assert.strictEqual(getFocusedCell(vscode.window.activeNotebookEditor)?.document.getText(), ''); await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove'); const edit = new vscode.WorkspaceEdit(); - edit.insert(vscode.window.activeNotebookEditor!.selection!.document.uri, new vscode.Position(0, 0), 'var abc = 0;'); + edit.insert(getFocusedCell(vscode.window.activeNotebookEditor)!.document.uri, new vscode.Position(0, 0), 'var abc = 0;'); await vscode.workspace.applyEdit(edit); - const secondResource = await createRandomFile('', undefined, '.vsctestnb'); + const secondResource = await createRandomNotebookFile(); await vscode.commands.executeCommand('vscode.openWith', secondResource, 'notebookCoreTest'); await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); // make sure that the previous dirty editor is still restored in the extension host and no data loss assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true); - assert.strictEqual(vscode.window.activeNotebookEditor?.selection !== undefined, true); - assert.deepStrictEqual(vscode.window.activeNotebookEditor?.document.cells[1], vscode.window.activeNotebookEditor?.selection); - assert.deepStrictEqual(vscode.window.activeNotebookEditor?.document.cells.length, 4); - assert.strictEqual(vscode.window.activeNotebookEditor?.selection?.document.getText(), 'var abc = 0;'); + assert.deepStrictEqual(vscode.window.activeNotebookEditor?.document.cellAt(1), getFocusedCell(vscode.window.activeNotebookEditor)); + assert.deepStrictEqual(vscode.window.activeNotebookEditor?.document.cellCount, 4); + assert.strictEqual(getFocusedCell(vscode.window.activeNotebookEditor)?.document.getText(), 'var abc = 0;'); await saveFileAndCloseAll(resource); }); test('multiple tabs: two dirty tabs and switching', async function () { - const resource = await createRandomFile('', undefined, '.vsctestnb'); + const resource = await createRandomNotebookFile(); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); - assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.document.getText(), ''); + assert.strictEqual(getFocusedCell(vscode.window.activeNotebookEditor)?.document.getText(), ''); await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove'); const edit = new vscode.WorkspaceEdit(); - edit.insert(vscode.window.activeNotebookEditor!.selection!.document.uri, new vscode.Position(0, 0), 'var abc = 0;'); + edit.insert(getFocusedCell(vscode.window.activeNotebookEditor)!.document.uri, new vscode.Position(0, 0), 'var abc = 0;'); await vscode.workspace.applyEdit(edit); - const secondResource = await createRandomFile('', undefined, '.vsctestnb'); + const secondResource = await createRandomNotebookFile(); await vscode.commands.executeCommand('vscode.openWith', secondResource, 'notebookCoreTest'); await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); - assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.document.getText(), ''); + assert.strictEqual(getFocusedCell(vscode.window.activeNotebookEditor)?.document.getText(), ''); // switch to the first editor await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true); - assert.strictEqual(vscode.window.activeNotebookEditor?.selection !== undefined, true); - assert.deepStrictEqual(vscode.window.activeNotebookEditor?.document.cells[1], vscode.window.activeNotebookEditor?.selection); - assert.deepStrictEqual(vscode.window.activeNotebookEditor?.document.cells.length, 4); - assert.strictEqual(vscode.window.activeNotebookEditor?.selection?.document.getText(), 'var abc = 0;'); + assert.deepStrictEqual(vscode.window.activeNotebookEditor?.document.cellAt(1), getFocusedCell(vscode.window.activeNotebookEditor)); + assert.deepStrictEqual(vscode.window.activeNotebookEditor?.document.cellCount, 4); + assert.strictEqual(getFocusedCell(vscode.window.activeNotebookEditor)?.document.getText(), 'var abc = 0;'); // switch to the second editor await vscode.commands.executeCommand('vscode.openWith', secondResource, 'notebookCoreTest'); assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true); - assert.strictEqual(vscode.window.activeNotebookEditor?.selection !== undefined, true); - assert.deepStrictEqual(vscode.window.activeNotebookEditor?.document.cells[1], vscode.window.activeNotebookEditor?.selection); - assert.deepStrictEqual(vscode.window.activeNotebookEditor?.document.cells.length, 3); - assert.strictEqual(vscode.window.activeNotebookEditor?.selection?.document.getText(), ''); + assert.deepStrictEqual(vscode.window.activeNotebookEditor?.document.cellAt(1), getFocusedCell(vscode.window.activeNotebookEditor)); + assert.deepStrictEqual(vscode.window.activeNotebookEditor?.document.cellCount, 3); + assert.strictEqual(getFocusedCell(vscode.window.activeNotebookEditor)?.document.getText(), ''); await saveAllFilesAndCloseAll(secondResource); }); test.skip('multiple tabs: different editors with same document', async function () { - const resource = await createRandomFile('', undefined, '.vsctestnb'); + const resource = await createRandomNotebookFile(); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); const firstNotebookEditor = vscode.window.activeNotebookEditor; assert.strictEqual(firstNotebookEditor !== undefined, true, 'notebook first'); - assert.strictEqual(firstNotebookEditor!.selection?.document.getText(), 'test'); - assert.strictEqual(firstNotebookEditor!.selection?.document.languageId, 'typescript'); + assert.strictEqual(getFocusedCell(vscode.window.activeNotebookEditor!)?.document.getText(), 'test'); + assert.strictEqual(getFocusedCell(vscode.window.activeNotebookEditor!)?.document.languageId, 'typescript'); await splitEditor(); const secondNotebookEditor = vscode.window.activeNotebookEditor; assert.strictEqual(secondNotebookEditor !== undefined, true, 'notebook first'); - assert.strictEqual(secondNotebookEditor!.selection?.document.getText(), 'test'); - assert.strictEqual(secondNotebookEditor!.selection?.document.languageId, 'typescript'); + assert.strictEqual(getFocusedCell(vscode.window.activeNotebookEditor!)?.document.getText(), 'test'); + assert.strictEqual(getFocusedCell(vscode.window.activeNotebookEditor!)?.document.languageId, 'typescript'); assert.notEqual(firstNotebookEditor, secondNotebookEditor); assert.strictEqual(firstNotebookEditor?.document, secondNotebookEditor?.document, 'split notebook editors share the same document'); @@ -1090,12 +1056,12 @@ suite('Notebook API tests', function () { }); test('custom metadata should be supported', async function () { - const resource = await createRandomFile('', undefined, '.vsctestnb'); + const resource = await createRandomNotebookFile(); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); assert.strictEqual(vscode.window.activeNotebookEditor!.document.metadata.custom!['testMetadata'] as boolean, false); - assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.metadata.custom!['testCellMetadata'] as number, 123); - assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.document.languageId, 'typescript'); + assert.strictEqual(getFocusedCell(vscode.window.activeNotebookEditor)?.metadata.custom!['testCellMetadata'] as number, 123); + assert.strictEqual(getFocusedCell(vscode.window.activeNotebookEditor)?.document.languageId, 'typescript'); await saveFileAndCloseAll(resource); }); @@ -1103,17 +1069,17 @@ suite('Notebook API tests', function () { // TODO@rebornix skip as it crashes the process all the time test.skip('custom metadata should be supported 2', async function () { - const resource = await createRandomFile('', undefined, '.vsctestnb'); + const resource = await createRandomNotebookFile(); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); assert.strictEqual(vscode.window.activeNotebookEditor!.document.metadata.custom!['testMetadata'] as boolean, false); - assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.metadata.custom!['testCellMetadata'] as number, 123); - assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.document.languageId, 'typescript'); + assert.strictEqual(getFocusedCell(vscode.window.activeNotebookEditor)?.metadata.custom!['testCellMetadata'] as number, 123); + assert.strictEqual(getFocusedCell(vscode.window.activeNotebookEditor)?.document.languageId, 'typescript'); // TODO see #101462 // await vscode.commands.executeCommand('notebook.cell.copyDown'); - // const activeCell = vscode.window.activeNotebookEditor!.selection; - // assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.indexOf(activeCell!), 1); + // const activeCell = getFocusedCell(vscode.window.activeNotebookEditor); + // assert.strictEqual(vscode.window.activeNotebookEditor!.document.getCells().indexOf(activeCell!), 1); // assert.strictEqual(activeCell?.metadata.custom!['testCellMetadata'] as number, 123); await saveFileAndCloseAll(resource); @@ -1121,11 +1087,11 @@ suite('Notebook API tests', function () { test('#106657. Opening a notebook from markers view is broken ', async function () { - const resource = await createRandomFile('', undefined, '.vsctestnb'); + const resource = await createRandomNotebookFile(); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); const document = vscode.window.activeNotebookEditor?.document!; - const [cell] = document.cells; + const [cell] = document.getCells(); await saveAllFilesAndCloseAll(document.uri); assert.strictEqual(vscode.window.activeNotebookEditor, undefined); @@ -1138,11 +1104,11 @@ suite('Notebook API tests', function () { }); test.skip('Cannot open notebook from cell-uri with vscode.open-command', async function () { - const resource = await createRandomFile('', undefined, '.vsctestnb'); + const resource = await createRandomNotebookFile(); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); const document = vscode.window.activeNotebookEditor?.document!; - const [cell] = document.cells; + const [cell] = document.getCells(); await saveAllFilesAndCloseAll(document.uri); assert.strictEqual(vscode.window.activeNotebookEditor, undefined); @@ -1155,19 +1121,19 @@ suite('Notebook API tests', function () { }); test('#97830, #97764. Support switch to other editor types', async function () { - const resource = await createRandomFile('', undefined, '.vsctestnb'); + const resource = await createRandomNotebookFile(); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); const edit = new vscode.WorkspaceEdit(); - edit.insert(vscode.window.activeNotebookEditor!.selection!.document.uri, new vscode.Position(0, 0), 'var abc = 0;'); + edit.insert(getFocusedCell(vscode.window.activeNotebookEditor)!.document.uri, new vscode.Position(0, 0), 'var abc = 0;'); await vscode.workspace.applyEdit(edit); assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); - assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.document.getText(), 'var abc = 0;'); + assert.strictEqual(getFocusedCell(vscode.window.activeNotebookEditor)?.document.getText(), 'var abc = 0;'); // no kernel -> no default language // assert.strictEqual(vscode.window.activeNotebookEditor!.kernel, undefined); - assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.document.languageId, 'typescript'); + assert.strictEqual(getFocusedCell(vscode.window.activeNotebookEditor)?.document.languageId, 'typescript'); await vscode.commands.executeCommand('vscode.openWith', resource, 'default'); assert.strictEqual(vscode.window.activeTextEditor?.document.uri.path, resource.path); @@ -1177,7 +1143,7 @@ suite('Notebook API tests', function () { // open text editor, pin, and then open a notebook test('#96105 - dirty editors', async function () { - const resource = await createRandomFile('', undefined, '.vsctestnb'); + const resource = await createRandomNotebookFile(); await vscode.commands.executeCommand('vscode.openWith', resource, 'default'); const edit = new vscode.WorkspaceEdit(); edit.insert(resource, new vscode.Position(0, 0), 'var abc = 0;'); @@ -1199,57 +1165,30 @@ suite('Notebook API tests', function () { }); test('#102423 - copy/paste shares the same text buffer', async function () { - const resource = await createRandomFile('', undefined, '.vsctestnb'); + const resource = await createRandomNotebookFile(); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - let activeCell = vscode.window.activeNotebookEditor!.selection; + let activeCell = getFocusedCell(vscode.window.activeNotebookEditor); assert.strictEqual(activeCell?.document.getText(), 'test'); await vscode.commands.executeCommand('notebook.cell.copyDown'); await vscode.commands.executeCommand('notebook.cell.edit'); - activeCell = vscode.window.activeNotebookEditor!.selection; - assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.indexOf(activeCell!), 1); + activeCell = getFocusedCell(vscode.window.activeNotebookEditor); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.getCells().indexOf(activeCell!), 1); assert.strictEqual(activeCell?.document.getText(), 'test'); const edit = new vscode.WorkspaceEdit(); - edit.insert(vscode.window.activeNotebookEditor!.selection!.document.uri, new vscode.Position(0, 0), 'var abc = 0;'); + edit.insert(getFocusedCell(vscode.window.activeNotebookEditor)!.document.uri, new vscode.Position(0, 0), 'var abc = 0;'); await vscode.workspace.applyEdit(edit); - assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.length, 3); - assert.notEqual(vscode.window.activeNotebookEditor!.document.cells[0].document.getText(), vscode.window.activeNotebookEditor!.document.cells[1].document.getText()); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.getCells().length, 3); + assert.notEqual(vscode.window.activeNotebookEditor!.document.cellAt(0).document.getText(), vscode.window.activeNotebookEditor!.document.cellAt(1).document.getText()); await closeAllEditors(); }); - test('#116598, output items change event.', async function () { - - const resource = await createRandomFile('', undefined, '.vsctestnb'); - await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - - const edit = new vscode.WorkspaceEdit(); - edit.appendNotebookCellOutput(resource, 0, [new vscode.NotebookCellOutput([ - new vscode.NotebookCellOutputItem('application/foo', 'bar'), - new vscode.NotebookCellOutputItem('application/json', { data: true }, { metadata: true }), - ])]); - await vscode.workspace.applyEdit(edit); - assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells[0].outputs.length, 1); - assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells[0].outputs[0].outputs.length, 2); - - const appendEdit = new vscode.WorkspaceEdit(); - const newItem = new vscode.NotebookCellOutputItem('text/plain', '1'); - appendEdit.appendNotebookCellOutputItems( - resource, - 0, - vscode.window.activeNotebookEditor!.document.cells[0].outputs[0].id, - [newItem] - ); - await vscode.workspace.applyEdit(appendEdit); - assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells[0].outputs[0].outputs.length, 3); - assert.deepStrictEqual(vscode.window.activeNotebookEditor!.document.cells[0].outputs[0].outputs[2], newItem); - }); - test('#115855 onDidSaveNotebookDocument', async function () { - const resource = await createRandomFile(undefined, undefined, '.vsctestnb'); + const resource = await createRandomNotebookFile(); const notebook = await vscode.notebook.openNotebookDocument(resource); const editor = await vscode.window.showNotebookDocument(notebook); @@ -1270,59 +1209,40 @@ suite('Notebook API tests', function () { assert.strictEqual(notebook.isDirty, false); }); - test('#116808, active kernel should not be undefined', async function () { - const resource = await createRandomFile('', undefined, '.vsctestnb'); - await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - - await withEvent(vscode.notebook.onDidChangeActiveNotebookKernel, async event => { - await event; - assert.notStrictEqual(vscode.window.activeNotebookEditor?.kernel, undefined); - assert.strictEqual(vscode.window.activeNotebookEditor?.kernel?.id, 'mainKernel'); - }); - - await saveAllFilesAndCloseAll(resource); - }); - test('Output changes are applied once the promise resolves', async function () { - const verifyOutputSyncKernel = new class implements vscode.NotebookKernel { - readonly id = 'verifyOutputSyncKernel'; - readonly label = ''; - readonly isPreferred = false; - readonly supportedLanguages = ['typescript', 'javascript']; + const verifyOutputSyncKernel = new class extends Kernel { - async executeCellsRequest(document: vscode.NotebookDocument, ranges: vscode.NotebookCellRange[]) { - const idx = ranges[0].start; - - const task = vscode.notebook.createNotebookCellExecutionTask(document.uri, idx, this.id); - if (!task) { - return; - } + constructor() { + super('verifyOutputSyncKernel', ''); + } + override async _execute(cells: vscode.NotebookCell[]) { + const [cell] = cells; + const task = this.controller.createNotebookCellExecutionTask(cell); task.start(); await task.replaceOutput([new vscode.NotebookCellOutput([ new vscode.NotebookCellOutputItem('text/plain', ['Some output'], undefined) ])]); - assert.strictEqual(document.cells[0].outputs.length, 1); - assert.deepStrictEqual(document.cells[0].outputs[0].outputs[0].value, ['Some output']); + assert.strictEqual(cell.notebook.cellAt(0).outputs.length, 1); + assert.deepStrictEqual(cell.notebook.cellAt(0).outputs[0].outputs[0].value, ['Some output']); task.end({}); } }; - currentKernelProvider.addKernel(verifyOutputSyncKernel); - - const resource = await createRandomFile('', undefined, '.vsctestnb'); + const resource = await createRandomNotebookFile(); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - await vscode.commands.executeCommand('notebook.selectKernel', { extension: 'vscode.vscode-api-tests', id: verifyOutputSyncKernel.id }); + await assertKernel(verifyOutputSyncKernel.controller); await vscode.commands.executeCommand('notebook.cell.execute'); await saveAllFilesAndCloseAll(undefined); + verifyOutputSyncKernel.controller.dispose(); }); test('latestExecutionSummary', async () => { - const resource = await createRandomFile('', undefined, '.vsctestnb'); + const resource = await createRandomNotebookFile(); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); const editor = vscode.window.activeNotebookEditor!; - const cell = editor.document.cells[0]; + const cell = editor.document.cellAt(0); assert.strictEqual(cell.latestExecutionSummary?.success, undefined); assert.strictEqual(cell.latestExecutionSummary?.executionOrder, undefined); @@ -1336,51 +1256,42 @@ suite('Notebook API tests', function () { }); test('initialize latestExecutionSummary', async () => { - const resource = await createRandomFile('', undefined, '.vsctestnb'); + const resource = await createRandomNotebookFile(); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); const editor = vscode.window.activeNotebookEditor!; - const cell = editor.document.cells[0]; + const cell = editor.document.cellAt(0); assert.strictEqual(cell.latestExecutionSummary?.success, undefined); - assert.strictEqual(cell.latestExecutionSummary?.duration, 25); + assert.strictEqual(cell.latestExecutionSummary?.startTime, 10); + assert.strictEqual(cell.latestExecutionSummary?.endTime, 20); await saveAllFilesAndCloseAll(undefined); }); - test('Throws errors for invalid execution tasks', async function () { - let missedError: string | undefined; - const invalidKernel = new class implements vscode.NotebookKernel { - readonly id = 'invalidKernel'; - readonly label = ''; - readonly isPreferred = false; - readonly supportedLanguages = ['typescript', 'javascript']; + suite('statusbar', () => { + const emitter = new vscode.EventEmitter(); + const onDidCallProvide = emitter.event; + suiteSetup(() => { + vscode.notebook.registerNotebookCellStatusBarItemProvider({ viewType: 'notebookCoreTest' }, { + async provideCellStatusBarItems(cell: vscode.NotebookCell, _token: vscode.CancellationToken): Promise { + emitter.fire(cell); + return []; + } + }); + }); - async executeCellsRequest(document: vscode.NotebookDocument, _ranges: vscode.NotebookCellRange[]) { - try { - vscode.notebook.createNotebookCellExecutionTask(document.uri, 1000, this.id); - missedError = 'Expected to throw for invalid index'; - return; - } catch (e) { } + test('provideCellStatusBarItems called on metadata change', async function () { + const provideCalled = asPromise(onDidCallProvide); + const resource = await createRandomNotebookFile(); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + await provideCalled; - try { - vscode.notebook.createNotebookCellExecutionTask(vscode.Uri.file('slkdf'), 0, this.id); - missedError = 'Expected to throw for invalid uri'; - return; - } catch (e) { } - } - }; - - currentKernelProvider.addKernel(invalidKernel); - - const resource = await createRandomFile('', undefined, '.vsctestnb'); - await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - await vscode.commands.executeCommand('notebook.selectKernel', { extension: 'vscode.vscode-api-tests', id: invalidKernel.id }); - await vscode.commands.executeCommand('notebook.cell.execute'); - - assert.strictEqual(missedError, undefined, missedError); - - await saveAllFilesAndCloseAll(undefined); + const edit = new vscode.WorkspaceEdit(); + edit.replaceNotebookCellMetadata(resource, 0, new vscode.NotebookCellMetadata().with({ inputCollapsed: true })); + vscode.workspace.applyEdit(edit); + await provideCalled; + }); }); // }); @@ -1392,7 +1303,7 @@ suite('Notebook API tests', function () { // return; // } - // const resource = await createRandomFile('', undefined, '.vsctestnb'); + // const resource = await createRandomNotebookFile(); // await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); // assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); // const uri = vscode.window.activeNotebookEditor!.asWebviewUri(vscode.Uri.file('./hello.png')); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts index d05b8f61..a23d3e55 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts @@ -23,7 +23,7 @@ import { assertNoRpc } from '../utils'; // Disable exit alerts as tests may trigger then and we're not testing the notifications await config.update('showExitAlert', false, ConfigurationTarget.Global); // Canvas may cause problems when running in a container - await config.update('rendererType', 'dom', ConfigurationTarget.Global); + await config.update('gpuAcceleration', 'off', ConfigurationTarget.Global); // Disable env var relaunch for tests to prevent terminals relaunching themselves await config.update('environmentChangesRelaunch', false, ConfigurationTarget.Global); }); @@ -468,7 +468,7 @@ import { assertNoRpc } from '../utils'; // const terminal = window.createTerminal({ name: 'foo', pty }); // }); - test('should respect dimension overrides', (done) => { + test.skip('should respect dimension overrides', (done) => { disposables.push(window.onDidOpenTerminal(term => { try { equal(terminal, term); @@ -633,8 +633,9 @@ import { assertNoRpc } from '../utils'; }); }); - suite('environmentVariableCollection', () => { - test.skip('should have collection variables apply to terminals immediately after setting', (done) => { + // https://github.com/microsoft/vscode/issues/119826 + suite.skip('environmentVariableCollection', () => { + test('should have collection variables apply to terminals immediately after setting', (done) => { // Text to match on before passing the test const expectedText = [ '~a2~', diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts index 14947da7..629fbaac 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts @@ -399,6 +399,118 @@ suite.skip('vscode API - webview', () => { assert.strictEqual(await vscode.env.clipboard.readText(), expectedText); }); } + + test('webviews should transfer ArrayBuffers to and from webviews', async () => { + const webview = _register(vscode.window.createWebviewPanel(webviewId, 'title', { viewColumn: vscode.ViewColumn.One }, { enableScripts: true, retainContextWhenHidden: true })); + const ready = getMessage(webview); + webview.webview.html = createHtmlDocumentWithBody(/*html*/` + `); + await ready; + + const responsePromise = getMessage(webview); + + const bufferLen = 100; + + { + const arrayBuffer = new ArrayBuffer(bufferLen); + const uint8Array = new Uint8Array(arrayBuffer); + for (let i = 0; i < bufferLen; ++i) { + uint8Array[i] = i; + } + webview.webview.postMessage({ + type: 'add1', + array: arrayBuffer + }); + } + { + const response = await responsePromise; + assert.ok(response.array instanceof ArrayBuffer); + + const uint8Array = new Uint8Array(response.array); + for (let i = 0; i < bufferLen; ++i) { + assert.strictEqual(uint8Array[i], i + 1); + } + } + }); + + test('webviews should transfer Typed arrays to and from webviews', async () => { + const webview = _register(vscode.window.createWebviewPanel(webviewId, 'title', { viewColumn: vscode.ViewColumn.One }, { enableScripts: true, retainContextWhenHidden: true })); + const ready = getMessage(webview); + webview.webview.html = createHtmlDocumentWithBody(/*html*/` + `); + await ready; + + const responsePromise = getMessage(webview); + + const bufferLen = 100; + { + const arrayBuffer = new ArrayBuffer(bufferLen); + const uint8Array = new Uint8Array(arrayBuffer); + const uint16Array = new Uint16Array(arrayBuffer); + for (let i = 0; i < uint16Array.length; ++i) { + uint16Array[i] = i; + } + + webview.webview.postMessage({ + type: 'add1', + array1: uint8Array, + array2: uint16Array, + }); + } + { + const response = await responsePromise; + + assert.ok(response.array1 instanceof Uint8Array); + assert.ok(response.array2 instanceof Uint16Array); + assert.ok(response.array1.buffer === response.array2.buffer); + + const uint8Array = response.array1; + for (let i = 0; i < bufferLen; ++i) { + if (i % 2 === 0) { + assert.strictEqual(uint8Array[i], Math.floor(i / 2) + 1); + } else { + assert.strictEqual(uint8Array[i], 0); + } + } + } + }); }); function createHtmlDocumentWithBody(body: string): string { diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts index 615bb79a..c30ab513 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts @@ -157,7 +157,7 @@ suite('vscode API - window', () => { return; } - if (process.env['BUILD_SOURCEVERSION']) { + if (process.env['BUILD_SOURCEVERSION'] || process.env['CI']) { this.skip(); return; } 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 8d3c8950..36790620 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 @@ -18,7 +18,7 @@ import { assertNoRpc } from '../utils'; // Disable exit alerts as tests may trigger then and we're not testing the notifications await config.update('showExitAlert', false, ConfigurationTarget.Global); // Canvas may cause problems when running in a container - await config.update('rendererType', 'dom', ConfigurationTarget.Global); + await config.update('gpuAcceleration', 'off', ConfigurationTarget.Global); // Disable env var relaunch for tests to prevent terminals relaunching themselves await config.update('environmentChangesRelaunch', false, ConfigurationTarget.Global); }); @@ -131,6 +131,7 @@ import { assertNoRpc } from '../utils'; suite('CustomExecution', () => { test('task should start and shutdown successfully', async () => { + window.terminals.forEach(terminal => terminal.dispose()); interface CustomTestingTaskDefinition extends TaskDefinition { /** * One of the task properties. This can be used to customize the task in the tasks.json @@ -203,7 +204,10 @@ import { assertNoRpc } from '../utils'; // Dispose the terminal await new Promise(r => { - disposables.push(window.onDidCloseTerminal(() => { + disposables.push(window.onDidCloseTerminal((e) => { + if (e !== terminal) { + return; + } assert.strictEqual(testOrder, TestOrder.TerminalWritten); testOrder = TestOrder.TerminalClosed; // Pseudoterminal.close should have fired by now, additionally we want diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts index ace2479b..e60998c2 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts @@ -619,7 +619,7 @@ suite('vscode API - workspace', () => { assert.equal(results.length, 1); const match = results[0]; assert(match.preview.text.indexOf('foo') >= 0); - assert.equal(vscode.workspace.asRelativePath(match.uri), '10linefile.ts'); + assert.equal(basename(vscode.workspace.asRelativePath(match.uri)), '10linefile.ts'); }); test('findTextInFiles, cancellation', async () => { diff --git a/extensions/vscode-api-tests/src/utils.ts b/extensions/vscode-api-tests/src/utils.ts index 8d94c8ea..a36b7549 100644 --- a/extensions/vscode-api-tests/src/utils.ts +++ b/extensions/vscode-api-tests/src/utils.ts @@ -122,7 +122,7 @@ export function assertNoRpcFromEntry(entry: [obj: any, name: string]) { assert.strictEqual(proxyPaths.length, 0, proxyPaths.join('\n')); // happens... } -export async function asPromise(event: vscode.Event, timeout = 5000): Promise { +export async function asPromise(event: vscode.Event, timeout = vscode.env.uiKind === vscode.UIKind.Desktop ? 5000 : 15000): Promise { return new Promise((resolve, reject) => { const handle = setTimeout(() => { diff --git a/extensions/vscode-api-tests/src/workspace-tests/index.ts b/extensions/vscode-api-tests/src/workspace-tests/index.ts index ba41f64f..9c23f586 100644 --- a/extensions/vscode-api-tests/src/workspace-tests/index.ts +++ b/extensions/vscode-api-tests/src/workspace-tests/index.ts @@ -8,7 +8,7 @@ const testRunner = require('../../../../test/integration/electron/testrunner'); const options: any = { ui: 'tdd', - color: (!process.env.BUILD_ARTIFACTSTAGINGDIRECTORY && process.platform !== 'win32'), + color: true, timeout: 60000 }; diff --git a/extensions/vscode-api-tests/tsconfig.json b/extensions/vscode-api-tests/tsconfig.json index 296ddb38..070854d6 100644 --- a/extensions/vscode-api-tests/tsconfig.json +++ b/extensions/vscode-api-tests/tsconfig.json @@ -1,9 +1,9 @@ { - "extends": "../shared.tsconfig.json", + "extends": "../tsconfig.base.json", "compilerOptions": { "outDir": "./out" }, "include": [ "src/**/*" ] -} \ No newline at end of file +} diff --git a/extensions/vscode-colorize-tests/media/icon.png b/extensions/vscode-colorize-tests/media/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..f785ef7031624d04d821a12d5f1534ecccfef491 GIT binary patch literal 2210 zcmY*bc{tSD8$aI}!x-BnOVKh?jjlCH3uR;sS}es?-O7+kNe0PS=WFSfa0#VCqq?E8 zrYwaSx|S@zQ9@eGSh8fYMPrzm?;Y-6zw^A$`F!5xInU>P-t(O2#5+6MDdDtm001Qi zd-7obASs0a7A*zef}^fdc;S4|)mDlC=76vk5Zbo1hqZ#RHW1Mc!lmxAMJz}DdxhBCV00mfSD~OY7m5z6hd3z%kkzuSRqHWL>=NFrX zeXiWg2oCDIhM&@n5V1A>r3@QE*~O`wpQYHZ*J}Q%w#BCqzIm$NkZlms5rs^i=?Kf zQznejSKBf)$LVPn3bl4wdFYOe_zsC8lCqe^NhwSOMbx{p-9Pie@ooN3QC;3%5@ZZ9 zdk%g7(OqqH8XQ9)@3n6*v6X=;fazL~`oO|+RyE%hVcOiW3M7FRcGbZR3}~uwSY91{ z?}ak(#70$w+#mNFw+CyRVI@V(^k>&&FNP|&dhYJ5T9j~JR&We!(;B1AI_5>~#X*Ub z!-`03xI@Wq==#-n@+7iF4@t2wDU)Q`TbB`fJ-lttGkrp7Nq}Y|@%Tw7zHwB;p8O0A zPf_GRz{s#Hdx3uBGv5BW3hX++tY$49M;!0j%@=xC zHuj3t{hJnj72u>Vs`RLL;<~>CJsO1l2GK)KCuSrqC~smguKGK@Jl`CYAI@V-rZdFb zWv{m>;tfh{gQ59Hq=W%XdA_Z|S3;IwAhM!grrbHyq3n28J|EdN zrlWNxXA@iRz;2A%!1=Waq}Zz;gEQQ+WIlOGvcID)@0u#eV*NsK4irk&Vz#ZndSgM~ zm^aiznkjzB-p60_(L+EyHM&g|j~3!M||zCrSL>*Ba#2~4SH#(Dgd>_)GFn?e)UDhTzJ zc{j`X~7EJ;}49U_HW-!ej40r_ZQVWFRDtMiTm!U@HsPW^_9YoSTA!?|Y zxls++HF6JvHTpmEfL_DVTUL->Hca;)No4u2!owG)~xc#Zz;a$j%uhXCE z9a037`EV&1DrS*lj(+fnjK-E0NE=_#r}4`jp-tg89kr^NaEc(+DB|Oy^lCiCcCH73 z@tn={p(M#@|G|rm{&Yr7$W?9nriuYSI-^0wE}M6QH7K6jyY8An<=_uGBO96#F`Fa! zk{0RcOxdc`35U_IwoO|fE&%3ea6Ae_kv7`#$D1u?`MCW(h8H_3Q9q&(`-xP#pu!^#JAeIA9eZ+^0vgL7p41q?8?Z1z{nYW zr)S{-GrziyB~y06T$)e1d=&9+WXP+j4JDK=%RHc@(J%6HZ%_riz|~06k&|p_g@W*z z5k49&PiGCI)?sN4B6ZC>JY#Yw!z+(2i?{CcSnTZAAkmaB8igVWL4{_dOUf5tO*}V6 zu#CLyF(*tdpUbrGRERkD=G$F~D=csicBiKbt7U{opgs>bcm&1GATS03IE(rUG|= z$_$zL3%4mmP`dQaP=itU1CLh1cT6YLLs7E2O%F6yC7xKRIQ644F8s|}Ic{Bgk`ed^ zNj!m`meD1G`?r!_Q!R~Fp9Jw+dMoAsDQh-22lsO&I|i|_nGfWkn43x|#G`Y>7&pd- z|AT=K%@R(~?NZf1w1GxZ;)DS((5y7G-ueaCRTk)+O7#|wOs`f?+oN`Fzj>}zvIY6E zqVlCbI}>ql4e=Uzbz=ETsniQ|JyzHWW8SblMIu^|v(Dn#^`|8@FwcegdQXnr-P^CS z(Fiu#sp5r>tCI<_9IYdEE~K;n-rN7^W)-Kix&e`0u{@1z&a+B2cv_r2K8gXO!erx!e z?Z=K9nngudTWX2w>-q*Xl2qQfByQQX5R|7j_zo}XZO=qxGT}IST0OaF>9gTr>qsuz HM~(Rx0c1bk literal 0 HcmV?d00001 diff --git a/extensions/vscode-colorize-tests/package.json b/extensions/vscode-colorize-tests/package.json index 21cb61f6..0ec286b3 100644 --- a/extensions/vscode-colorize-tests/package.json +++ b/extensions/vscode-colorize-tests/package.json @@ -13,6 +13,7 @@ "engines": { "vscode": "*" }, + "icon": "media/icon.png", "scripts": { "vscode:prepublish": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:vscode-colorize-tests ./tsconfig.json" }, diff --git a/extensions/vscode-colorize-tests/src/index.ts b/extensions/vscode-colorize-tests/src/index.ts index f8066005..4dcda1af 100644 --- a/extensions/vscode-colorize-tests/src/index.ts +++ b/extensions/vscode-colorize-tests/src/index.ts @@ -10,7 +10,7 @@ const suite = 'Integration Colorize Tests'; const options: any = { ui: 'tdd', - color: (!process.env.BUILD_ARTIFACTSTAGINGDIRECTORY && process.platform !== 'win32'), + color: true, timeout: 60000 }; diff --git a/extensions/vscode-colorize-tests/test/colorize-fixtures/test-92369.cpp b/extensions/vscode-colorize-tests/test/colorize-fixtures/test-92369.cpp deleted file mode 100644 index b91dec67..00000000 --- a/extensions/vscode-colorize-tests/test/colorize-fixtures/test-92369.cpp +++ /dev/null @@ -1,3 +0,0 @@ -std::tuple_element<0, std::pair, pugi::xml_node, std::less >, std::allocator, pugi::xml_node> > > > >::type dnode - -std::_Rb_tree_iterator, pugi::xml_node, std::less >, std::allocator, pugi::xml_node> > > > > > dnode_it = dnodes_.find(uid.position) diff --git a/extensions/vscode-colorize-tests/test/colorize-fixtures/test.cu b/extensions/vscode-colorize-tests/test/colorize-fixtures/test.cu new file mode 100644 index 00000000..e1327ccd --- /dev/null +++ b/extensions/vscode-colorize-tests/test/colorize-fixtures/test.cu @@ -0,0 +1,149 @@ +#include +#include +#include + +#include + +#if defined(assert) +#undef assert +#endif + +#define assert(c) \ + do { \ + if(!(c)) { \ + fprintf(stderr, "Assertion \"%s\" failed. (%s:%d)\n", \ + #c, __FILE__, __LINE__); \ + exit(1); \ + } \ + } while(0) + +#define assertSucceeded(c) \ + do { \ + unsigned __tmp = c; \ + if(__tmp != cudaSuccess) { \ + fprintf(stderr, "Operation \"%s\" failed with error code %x. (%s:%d)\n", \ + #c, (__tmp), __FILE__, __LINE__); \ + exit(__tmp); \ + } \ + } while(0) + +#define ARRAY_LENGTH(x) (sizeof(x) / sizeof(x[0])) + +constexpr int dataLength = 1 << 24; +constexpr int threadsPerBlock = 128; + +typedef unsigned char byte; + +struct TestType +{ + union { + struct + { + unsigned lowHalf; + unsigned highHalf; + } halfAndHalf; + + unsigned long long whole; + } takeYourPick; + + int arr[5]; + + struct { + char a; + char b; + } structArr[5]; + + float theFloats[2]; + double theDouble; +}; + +__global__ void cudaComputeHash(TestType* input, unsigned *results) +{ + int idx = blockIdx.x * threadsPerBlock + threadIdx.x; + TestType* myInput = input + idx; + + unsigned myResult = 0; + + myResult += myInput->takeYourPick.halfAndHalf.lowHalf - idx; + myResult += myInput->takeYourPick.halfAndHalf.highHalf - idx; + + for(size_t i = 0; i < ARRAY_LENGTH(myInput->arr); i++) + { + myResult += myInput->arr[i] - idx; + } + + for(size_t i = 0; i < sizeof(myInput->structArr); i++) + { + myResult += reinterpret_cast(myInput->structArr)[i] - '0'; + } + + __syncthreads(); + + results[idx] = myResult; +} + +int main() +{ + int cudaDeviceCount; + assertSucceeded(cudaGetDeviceCount(&cudaDeviceCount)); + assert(cudaDeviceCount > 0); + + assertSucceeded(cudaSetDevice(0)); + + TestType* input; + unsigned* results; + + assertSucceeded(cudaMallocManaged(&input, sizeof(TestType) * dataLength)); + assert(!!input); + + for (size_t i = 0; i < dataLength; i++) + { + input[i].takeYourPick.halfAndHalf.lowHalf = i + 1; + input[i].takeYourPick.halfAndHalf.highHalf = i + 3; + + for(size_t j = 0; j < ARRAY_LENGTH(input[i].arr); j++) + { + input[i].arr[j] = i + j + 2; + } + + for(size_t j = 0; j < sizeof(input[i].structArr); j++) + { + reinterpret_cast(input[i].structArr)[j] = '0' + static_cast((i + j) % 10); + } + + input[i].theFloats[0] = i + 1; + input[i].theFloats[1] = input[i].theFloats[0] / 2; + + input[i].theDouble = input[i].theFloats[1] + 1; + } + + assertSucceeded(cudaMallocManaged(reinterpret_cast(&results), sizeof(unsigned) * dataLength)); + assert(!!results); + + constexpr int blocks = dataLength / threadsPerBlock; + cudaComputeHash<<>>(input, results); + + assertSucceeded(cudaDeviceSynchronize()); + + const unsigned expectedResult = + 1 + + 3 + + ARRAY_LENGTH(input[0].arr) * (ARRAY_LENGTH(input[0].arr) - 1) / 2 + + ARRAY_LENGTH(input[0].arr) * 2 + + sizeof(input[0].structArr) * (sizeof(input[0].structArr) - 1) / 2; + + for (unsigned i = 0; i < dataLength; i++) + { + if (results[i] != expectedResult){ + fprintf(stderr, "results[%u] (%u) != %u\n", i, results[i], expectedResult); + exit(1); + } + } + + assertSucceeded(cudaFree(input)); + assertSucceeded(cudaFree(results)); + + fprintf(stderr, "Success\n"); + + exit(0); +} diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test-92369_cpp.json b/extensions/vscode-colorize-tests/test/colorize-results/test-92369_cpp.json deleted file mode 100644 index 6a3254db..00000000 --- a/extensions/vscode-colorize-tests/test/colorize-results/test-92369_cpp.json +++ /dev/null @@ -1,24 +0,0 @@ -[ - { - "c": "std::tuple_element<0, std::pair, pugi::xml_node, std::less >, std::allocator, pugi::xml_node> > > > >::type dnode", - "t": "source.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "std::_Rb_tree_iterator, pugi::xml_node, std::less >, std::allocator, pugi::xml_node> > > > > > dnode_it = dnodes_.find(uid.position)", - "t": "source.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - } -] \ No newline at end of file diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test_cshtml.json b/extensions/vscode-colorize-tests/test/colorize-results/test_cshtml.json index e542974c..18e2d5c6 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test_cshtml.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test_cshtml.json @@ -3695,4 +3695,4 @@ "hc_black": "punctuation.definition.tag: #808080" } } -] +] \ No newline at end of file diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test_cu.json b/extensions/vscode-colorize-tests/test/colorize-results/test_cu.json new file mode 100644 index 00000000..21c8a061 --- /dev/null +++ b/extensions/vscode-colorize-tests/test/colorize-results/test_cu.json @@ -0,0 +1,12047 @@ +[ + { + "c": "#", + "t": "source.cuda-cpp meta.preprocessor.include.cuda-cpp keyword.control.directive.include.cuda-cpp punctuation.definition.directive.cuda-cpp", + "r": { + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0" + } + }, + { + "c": "include", + "t": "source.cuda-cpp meta.preprocessor.include.cuda-cpp keyword.control.directive.include.cuda-cpp", + "r": { + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.preprocessor.include.cuda-cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "<", + "t": "source.cuda-cpp meta.preprocessor.include.cuda-cpp string.quoted.other.lt-gt.include.cuda-cpp punctuation.definition.string.begin.cuda-cpp", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "stdlib.h", + "t": "source.cuda-cpp meta.preprocessor.include.cuda-cpp string.quoted.other.lt-gt.include.cuda-cpp", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": ">", + "t": "source.cuda-cpp meta.preprocessor.include.cuda-cpp string.quoted.other.lt-gt.include.cuda-cpp punctuation.definition.string.end.cuda-cpp", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "#", + "t": "source.cuda-cpp meta.preprocessor.include.cuda-cpp keyword.control.directive.include.cuda-cpp punctuation.definition.directive.cuda-cpp", + "r": { + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0" + } + }, + { + "c": "include", + "t": "source.cuda-cpp meta.preprocessor.include.cuda-cpp keyword.control.directive.include.cuda-cpp", + "r": { + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.preprocessor.include.cuda-cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "<", + "t": "source.cuda-cpp meta.preprocessor.include.cuda-cpp string.quoted.other.lt-gt.include.cuda-cpp punctuation.definition.string.begin.cuda-cpp", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "stdio.h", + "t": "source.cuda-cpp meta.preprocessor.include.cuda-cpp string.quoted.other.lt-gt.include.cuda-cpp", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": ">", + "t": "source.cuda-cpp meta.preprocessor.include.cuda-cpp string.quoted.other.lt-gt.include.cuda-cpp punctuation.definition.string.end.cuda-cpp", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "#", + "t": "source.cuda-cpp meta.preprocessor.include.cuda-cpp keyword.control.directive.include.cuda-cpp punctuation.definition.directive.cuda-cpp", + "r": { + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0" + } + }, + { + "c": "include", + "t": "source.cuda-cpp meta.preprocessor.include.cuda-cpp keyword.control.directive.include.cuda-cpp", + "r": { + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.preprocessor.include.cuda-cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "<", + "t": "source.cuda-cpp meta.preprocessor.include.cuda-cpp string.quoted.other.lt-gt.include.cuda-cpp punctuation.definition.string.begin.cuda-cpp", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "unistd.h", + "t": "source.cuda-cpp meta.preprocessor.include.cuda-cpp string.quoted.other.lt-gt.include.cuda-cpp", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": ">", + "t": "source.cuda-cpp meta.preprocessor.include.cuda-cpp string.quoted.other.lt-gt.include.cuda-cpp punctuation.definition.string.end.cuda-cpp", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "#", + "t": "source.cuda-cpp meta.preprocessor.include.cuda-cpp keyword.control.directive.include.cuda-cpp punctuation.definition.directive.cuda-cpp", + "r": { + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0" + } + }, + { + "c": "include", + "t": "source.cuda-cpp meta.preprocessor.include.cuda-cpp keyword.control.directive.include.cuda-cpp", + "r": { + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.preprocessor.include.cuda-cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "<", + "t": "source.cuda-cpp meta.preprocessor.include.cuda-cpp string.quoted.other.lt-gt.include.cuda-cpp punctuation.definition.string.begin.cuda-cpp", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "cuda_runtime.h", + "t": "source.cuda-cpp meta.preprocessor.include.cuda-cpp string.quoted.other.lt-gt.include.cuda-cpp", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": ">", + "t": "source.cuda-cpp meta.preprocessor.include.cuda-cpp string.quoted.other.lt-gt.include.cuda-cpp punctuation.definition.string.end.cuda-cpp", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "#", + "t": "source.cuda-cpp keyword.control.directive.conditional.if.cuda-cpp punctuation.definition.directive.cuda-cpp", + "r": { + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0" + } + }, + { + "c": "if", + "t": "source.cuda-cpp keyword.control.directive.conditional.if.cuda-cpp", + "r": { + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.preprocessor.conditional.cuda-cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "defined", + "t": "source.cuda-cpp meta.preprocessor.conditional.cuda-cpp keyword.control.directive.conditional.defined.cuda-cpp", + "r": { + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0" + } + }, + { + "c": "(", + "t": "source.cuda-cpp meta.preprocessor.conditional.cuda-cpp punctuation.section.parens.control.defined.cuda-cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "assert", + "t": "source.cuda-cpp meta.preprocessor.conditional.cuda-cpp entity.name.function.preprocessor.cuda-cpp", + "r": { + "dark_plus": "entity.name.function.preprocessor: #569CD6", + "light_plus": "entity.name.function.preprocessor: #0000FF", + "dark_vs": "entity.name.function.preprocessor: #569CD6", + "light_vs": "entity.name.function.preprocessor: #0000FF", + "hc_black": "entity.name.function: #DCDCAA" + } + }, + { + "c": ")", + "t": "source.cuda-cpp meta.preprocessor.conditional.cuda-cpp punctuation.section.parens.control.defined.cuda-cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "#", + "t": "source.cuda-cpp meta.preprocessor.undef.cuda-cpp keyword.control.directive.undef.cuda-cpp punctuation.definition.directive.cuda-cpp", + "r": { + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0" + } + }, + { + "c": "undef", + "t": "source.cuda-cpp meta.preprocessor.undef.cuda-cpp keyword.control.directive.undef.cuda-cpp", + "r": { + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.preprocessor.undef.cuda-cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "assert", + "t": "source.cuda-cpp meta.preprocessor.undef.cuda-cpp entity.name.function.preprocessor.cuda-cpp", + "r": { + "dark_plus": "entity.name.function.preprocessor: #569CD6", + "light_plus": "entity.name.function.preprocessor: #0000FF", + "dark_vs": "entity.name.function.preprocessor: #569CD6", + "light_vs": "entity.name.function.preprocessor: #0000FF", + "hc_black": "entity.name.function: #DCDCAA" + } + }, + { + "c": "#", + "t": "source.cuda-cpp keyword.control.directive.endif.cuda-cpp punctuation.definition.directive.cuda-cpp", + "r": { + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0" + } + }, + { + "c": "endif", + "t": "source.cuda-cpp keyword.control.directive.endif.cuda-cpp", + "r": { + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0" + } + }, + { + "c": "#", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp keyword.control.directive.define.cuda-cpp punctuation.definition.directive.cuda-cpp", + "r": { + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0" + } + }, + { + "c": "define", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp keyword.control.directive.define.cuda-cpp", + "r": { + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "assert", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp entity.name.function.preprocessor.cuda-cpp", + "r": { + "dark_plus": "entity.name.function.preprocessor: #569CD6", + "light_plus": "entity.name.function.preprocessor: #0000FF", + "dark_vs": "entity.name.function.preprocessor: #569CD6", + "light_vs": "entity.name.function.preprocessor: #0000FF", + "hc_black": "entity.name.function: #DCDCAA" + } + }, + { + "c": "(", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp punctuation.definition.parameters.begin.preprocessor.cuda-cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "c", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp variable.parameter.preprocessor.cuda-cpp", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": ")", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp punctuation.definition.parameters.end.preprocessor.cuda-cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "\\", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp constant.character.escape.line-continuation.cpp", + "r": { + "dark_plus": "constant.character.escape: #D7BA7D", + "light_plus": "constant.character.escape: #EE0000", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "constant.character: #569CD6" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "do", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp keyword.control.do.cpp", + "r": { + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "{", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp punctuation.section.block.begin.bracket.curly.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "\\", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp constant.character.escape.line-continuation.cpp", + "r": { + "dark_plus": "constant.character.escape: #D7BA7D", + "light_plus": "constant.character.escape: #EE0000", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "constant.character: #569CD6" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "if", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp keyword.control.if.cpp", + "r": { + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0" + } + }, + { + "c": "(", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.parens.cpp punctuation.section.parens.begin.bracket.round.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "!", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.parens.cpp keyword.operator.logical.cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": "(", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.parens.cpp meta.parens.cpp punctuation.section.parens.begin.bracket.round.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "c", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.parens.cpp meta.parens.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": ")", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.parens.cpp meta.parens.cpp punctuation.section.parens.end.bracket.round.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": ")", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.parens.cpp punctuation.section.parens.end.bracket.round.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "{", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp punctuation.section.block.begin.bracket.curly.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "\\", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp constant.character.escape.line-continuation.cpp", + "r": { + "dark_plus": "constant.character.escape: #D7BA7D", + "light_plus": "constant.character.escape: #EE0000", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "constant.character: #569CD6" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "fprintf", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp entity.name.function.call.cpp", + "r": { + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "entity.name.function: #DCDCAA" + } + }, + { + "c": "(", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp punctuation.section.arguments.begin.bracket.round.function.call.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "stderr", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": ",", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp punctuation.separator.delimiter.comma.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "\"", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp string.quoted.double.cpp punctuation.definition.string.begin.cpp", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "Assertion ", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp string.quoted.double.cpp", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "\\\"", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp string.quoted.double.cpp constant.character.escape.cpp", + "r": { + "dark_plus": "constant.character.escape: #D7BA7D", + "light_plus": "constant.character.escape: #EE0000", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "constant.character: #569CD6" + } + }, + { + "c": "%s", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp string.quoted.double.cpp constant.other.placeholder", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "\\\"", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp string.quoted.double.cpp constant.character.escape.cpp", + "r": { + "dark_plus": "constant.character.escape: #D7BA7D", + "light_plus": "constant.character.escape: #EE0000", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "constant.character: #569CD6" + } + }, + { + "c": " failed. (", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp string.quoted.double.cpp", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "%s", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp string.quoted.double.cpp constant.other.placeholder", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": ":", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp string.quoted.double.cpp", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "%d", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp string.quoted.double.cpp constant.other.placeholder", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": ")", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp string.quoted.double.cpp", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "\\n", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp string.quoted.double.cpp constant.character.escape.cpp", + "r": { + "dark_plus": "constant.character.escape: #D7BA7D", + "light_plus": "constant.character.escape: #EE0000", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "constant.character: #569CD6" + } + }, + { + "c": "\"", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp string.quoted.double.cpp punctuation.definition.string.end.cpp", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": ",", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp punctuation.separator.delimiter.comma.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "\\", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp constant.character.escape.line-continuation.cpp", + "r": { + "dark_plus": "constant.character.escape: #D7BA7D", + "light_plus": "constant.character.escape: #EE0000", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "constant.character: #569CD6" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "#c", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp variable.other.macro.argument.cpp", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": ",", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp punctuation.separator.delimiter.comma.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "__FILE__", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp entity.name.other.preprocessor.macro.predefined.__FILE__.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": ",", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp punctuation.separator.delimiter.comma.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "__LINE__", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp entity.name.other.preprocessor.macro.predefined.__LINE__.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": ")", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp punctuation.section.arguments.end.bracket.round.function.call.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": ";", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp punctuation.terminator.statement.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "\\", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp constant.character.escape.line-continuation.cpp", + "r": { + "dark_plus": "constant.character.escape: #D7BA7D", + "light_plus": "constant.character.escape: #EE0000", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "constant.character: #569CD6" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "exit", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp entity.name.function.call.cpp", + "r": { + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "entity.name.function: #DCDCAA" + } + }, + { + "c": "(", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp punctuation.section.arguments.begin.bracket.round.function.call.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "1", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp constant.numeric.decimal.cpp", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #098658", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #098658", + "hc_black": "constant.numeric: #B5CEA8" + } + }, + { + "c": ")", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp punctuation.section.arguments.end.bracket.round.function.call.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": ";", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp punctuation.terminator.statement.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "\\", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp constant.character.escape.line-continuation.cpp", + "r": { + "dark_plus": "constant.character.escape: #D7BA7D", + "light_plus": "constant.character.escape: #EE0000", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "constant.character: #569CD6" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "}", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp punctuation.section.block.end.bracket.curly.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "\\", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp constant.character.escape.line-continuation.cpp", + "r": { + "dark_plus": "constant.character.escape: #D7BA7D", + "light_plus": "constant.character.escape: #EE0000", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "constant.character: #569CD6" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "}", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp punctuation.section.block.end.bracket.curly.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "while", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp keyword.control.while.cpp", + "r": { + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0" + } + }, + { + "c": "(", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.parens.cpp punctuation.section.parens.begin.bracket.round.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "0", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.parens.cpp constant.numeric.decimal.cpp", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #098658", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #098658", + "hc_black": "constant.numeric: #B5CEA8" + } + }, + { + "c": ")", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.parens.cpp punctuation.section.parens.end.bracket.round.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "#", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp keyword.control.directive.define.cuda-cpp punctuation.definition.directive.cuda-cpp", + "r": { + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0" + } + }, + { + "c": "define", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp keyword.control.directive.define.cuda-cpp", + "r": { + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "assertSucceeded", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp entity.name.function.preprocessor.cuda-cpp", + "r": { + "dark_plus": "entity.name.function.preprocessor: #569CD6", + "light_plus": "entity.name.function.preprocessor: #0000FF", + "dark_vs": "entity.name.function.preprocessor: #569CD6", + "light_vs": "entity.name.function.preprocessor: #0000FF", + "hc_black": "entity.name.function: #DCDCAA" + } + }, + { + "c": "(", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp punctuation.definition.parameters.begin.preprocessor.cuda-cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "c", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp variable.parameter.preprocessor.cuda-cpp", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": ")", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp punctuation.definition.parameters.end.preprocessor.cuda-cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "\\", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp constant.character.escape.line-continuation.cpp", + "r": { + "dark_plus": "constant.character.escape: #D7BA7D", + "light_plus": "constant.character.escape: #EE0000", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "constant.character: #569CD6" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "do", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp keyword.control.do.cpp", + "r": { + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "{", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp punctuation.section.block.begin.bracket.curly.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "\\", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp constant.character.escape.line-continuation.cpp", + "r": { + "dark_plus": "constant.character.escape: #D7BA7D", + "light_plus": "constant.character.escape: #EE0000", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "constant.character: #569CD6" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "unsigned", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp storage.type.primitive.cpp storage.type.built-in.primitive.cpp", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6" + } + }, + { + "c": " __tmp ", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "=", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp keyword.operator.assignment.cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " c", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": ";", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp punctuation.terminator.statement.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "\\", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp constant.character.escape.line-continuation.cpp", + "r": { + "dark_plus": "constant.character.escape: #D7BA7D", + "light_plus": "constant.character.escape: #EE0000", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "constant.character: #569CD6" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "if", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp keyword.control.if.cpp", + "r": { + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0" + } + }, + { + "c": "(", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.parens.cpp punctuation.section.parens.begin.bracket.round.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "__tmp ", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.parens.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "!=", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.parens.cpp keyword.operator.comparison.cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " cudaSuccess", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.parens.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": ")", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.parens.cpp punctuation.section.parens.end.bracket.round.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "{", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp punctuation.section.block.begin.bracket.curly.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "\\", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp constant.character.escape.line-continuation.cpp", + "r": { + "dark_plus": "constant.character.escape: #D7BA7D", + "light_plus": "constant.character.escape: #EE0000", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "constant.character: #569CD6" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "fprintf", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp entity.name.function.call.cpp", + "r": { + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "entity.name.function: #DCDCAA" + } + }, + { + "c": "(", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp punctuation.section.arguments.begin.bracket.round.function.call.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "stderr", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": ",", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp punctuation.separator.delimiter.comma.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "\"", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp string.quoted.double.cpp punctuation.definition.string.begin.cpp", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "Operation ", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp string.quoted.double.cpp", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "\\\"", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp string.quoted.double.cpp constant.character.escape.cpp", + "r": { + "dark_plus": "constant.character.escape: #D7BA7D", + "light_plus": "constant.character.escape: #EE0000", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "constant.character: #569CD6" + } + }, + { + "c": "%s", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp string.quoted.double.cpp constant.other.placeholder", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "\\\"", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp string.quoted.double.cpp constant.character.escape.cpp", + "r": { + "dark_plus": "constant.character.escape: #D7BA7D", + "light_plus": "constant.character.escape: #EE0000", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "constant.character: #569CD6" + } + }, + { + "c": " failed with error code ", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp string.quoted.double.cpp", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "%x", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp string.quoted.double.cpp constant.other.placeholder", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": ". (", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp string.quoted.double.cpp", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "%s", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp string.quoted.double.cpp constant.other.placeholder", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": ":", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp string.quoted.double.cpp", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "%d", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp string.quoted.double.cpp constant.other.placeholder", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": ")", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp string.quoted.double.cpp", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "\\n", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp string.quoted.double.cpp constant.character.escape.cpp", + "r": { + "dark_plus": "constant.character.escape: #D7BA7D", + "light_plus": "constant.character.escape: #EE0000", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "constant.character: #569CD6" + } + }, + { + "c": "\"", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp string.quoted.double.cpp punctuation.definition.string.end.cpp", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": ",", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp punctuation.separator.delimiter.comma.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "\\", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp constant.character.escape.line-continuation.cpp", + "r": { + "dark_plus": "constant.character.escape: #D7BA7D", + "light_plus": "constant.character.escape: #EE0000", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "constant.character: #569CD6" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "#c", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp variable.other.macro.argument.cpp", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": ",", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp punctuation.separator.delimiter.comma.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "(", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp meta.parens.cpp punctuation.section.parens.begin.bracket.round.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "__tmp", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp meta.parens.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": ")", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp meta.parens.cpp punctuation.section.parens.end.bracket.round.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": ",", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp punctuation.separator.delimiter.comma.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "__FILE__", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp entity.name.other.preprocessor.macro.predefined.__FILE__.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": ",", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp punctuation.separator.delimiter.comma.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "__LINE__", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp entity.name.other.preprocessor.macro.predefined.__LINE__.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": ")", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp punctuation.section.arguments.end.bracket.round.function.call.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": ";", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp punctuation.terminator.statement.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "\\", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp constant.character.escape.line-continuation.cpp", + "r": { + "dark_plus": "constant.character.escape: #D7BA7D", + "light_plus": "constant.character.escape: #EE0000", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "constant.character: #569CD6" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "exit", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp entity.name.function.call.cpp", + "r": { + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "entity.name.function: #DCDCAA" + } + }, + { + "c": "(", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp punctuation.section.arguments.begin.bracket.round.function.call.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "__tmp", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": ")", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp punctuation.section.arguments.end.bracket.round.function.call.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": ";", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp punctuation.terminator.statement.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "\\", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp constant.character.escape.line-continuation.cpp", + "r": { + "dark_plus": "constant.character.escape: #D7BA7D", + "light_plus": "constant.character.escape: #EE0000", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "constant.character: #569CD6" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "}", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp punctuation.section.block.end.bracket.curly.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "\\", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp constant.character.escape.line-continuation.cpp", + "r": { + "dark_plus": "constant.character.escape: #D7BA7D", + "light_plus": "constant.character.escape: #EE0000", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "constant.character: #569CD6" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "}", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp punctuation.section.block.end.bracket.curly.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "while", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp keyword.control.while.cpp", + "r": { + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0" + } + }, + { + "c": "(", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.parens.cpp punctuation.section.parens.begin.bracket.round.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "0", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.parens.cpp constant.numeric.decimal.cpp", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #098658", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #098658", + "hc_black": "constant.numeric: #B5CEA8" + } + }, + { + "c": ")", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.parens.cpp punctuation.section.parens.end.bracket.round.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "#", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp keyword.control.directive.define.cuda-cpp punctuation.definition.directive.cuda-cpp", + "r": { + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0" + } + }, + { + "c": "define", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp keyword.control.directive.define.cuda-cpp", + "r": { + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "ARRAY_LENGTH", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp entity.name.function.preprocessor.cuda-cpp", + "r": { + "dark_plus": "entity.name.function.preprocessor: #569CD6", + "light_plus": "entity.name.function.preprocessor: #0000FF", + "dark_vs": "entity.name.function.preprocessor: #569CD6", + "light_vs": "entity.name.function.preprocessor: #0000FF", + "hc_black": "entity.name.function: #DCDCAA" + } + }, + { + "c": "(", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp punctuation.definition.parameters.begin.preprocessor.cuda-cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "x", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp variable.parameter.preprocessor.cuda-cpp", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": ")", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp punctuation.definition.parameters.end.preprocessor.cuda-cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "(", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.parens.cpp punctuation.section.parens.begin.bracket.round.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "sizeof", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.parens.cpp keyword.operator.functionlike.cpp keyword.operator.sizeof.cpp", + "r": { + "dark_plus": "keyword.operator.sizeof: #569CD6", + "light_plus": "keyword.operator.sizeof: #0000FF", + "dark_vs": "keyword.operator.sizeof: #569CD6", + "light_vs": "keyword.operator.sizeof: #0000FF", + "hc_black": "keyword.operator.sizeof: #569CD6" + } + }, + { + "c": "(", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.parens.cpp punctuation.section.arguments.begin.bracket.round.operator.sizeof.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "x", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.parens.cpp meta.arguments.operator.sizeof", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": ")", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.parens.cpp punctuation.section.arguments.end.bracket.round.operator.sizeof.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.parens.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "/", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.parens.cpp keyword.operator.cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.parens.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "sizeof", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.parens.cpp keyword.operator.functionlike.cpp keyword.operator.sizeof.cpp", + "r": { + "dark_plus": "keyword.operator.sizeof: #569CD6", + "light_plus": "keyword.operator.sizeof: #0000FF", + "dark_vs": "keyword.operator.sizeof: #569CD6", + "light_vs": "keyword.operator.sizeof: #0000FF", + "hc_black": "keyword.operator.sizeof: #569CD6" + } + }, + { + "c": "(", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.parens.cpp punctuation.section.arguments.begin.bracket.round.operator.sizeof.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "x", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.parens.cpp meta.arguments.operator.sizeof meta.bracket.square.access variable.other.object", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": "[", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.parens.cpp meta.arguments.operator.sizeof meta.bracket.square.access punctuation.definition.begin.bracket.square", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "0", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.parens.cpp meta.arguments.operator.sizeof meta.bracket.square.access constant.numeric.decimal.cpp", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #098658", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #098658", + "hc_black": "constant.numeric: #B5CEA8" + } + }, + { + "c": "]", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.parens.cpp meta.arguments.operator.sizeof meta.bracket.square.access punctuation.definition.end.bracket.square", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": ")", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.parens.cpp punctuation.section.arguments.end.bracket.round.operator.sizeof.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": ")", + "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.parens.cpp punctuation.section.parens.end.bracket.round.cpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "constexpr", + "t": "source.cuda-cpp storage.modifier.specifier.functional.pre-parameters.constexpr.cuda-cpp", + "r": { + "dark_plus": "storage.modifier: #569CD6", + "light_plus": "storage.modifier: #0000FF", + "dark_vs": "storage.modifier: #569CD6", + "light_vs": "storage.modifier: #0000FF", + "hc_black": "storage.modifier: #569CD6" + } + }, + { + "c": " ", + "t": "source.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "int", + "t": "source.cuda-cpp storage.type.primitive.cuda-cpp storage.type.built-in.primitive.cuda-cpp", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6" + } + }, + { + "c": " dataLength ", + "t": "source.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "=", + "t": "source.cuda-cpp keyword.operator.assignment.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "1", + "t": "source.cuda-cpp constant.numeric.decimal.cuda-cpp", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #098658", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #098658", + "hc_black": "constant.numeric: #B5CEA8" + } + }, + { + "c": " ", + "t": "source.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "<<", + "t": "source.cuda-cpp keyword.operator.bitwise.shift.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "24", + "t": "source.cuda-cpp constant.numeric.decimal.cuda-cpp", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #098658", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #098658", + "hc_black": "constant.numeric: #B5CEA8" + } + }, + { + "c": ";", + "t": "source.cuda-cpp punctuation.terminator.statement.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "constexpr", + "t": "source.cuda-cpp storage.modifier.specifier.functional.pre-parameters.constexpr.cuda-cpp", + "r": { + "dark_plus": "storage.modifier: #569CD6", + "light_plus": "storage.modifier: #0000FF", + "dark_vs": "storage.modifier: #569CD6", + "light_vs": "storage.modifier: #0000FF", + "hc_black": "storage.modifier: #569CD6" + } + }, + { + "c": " ", + "t": "source.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "int", + "t": "source.cuda-cpp storage.type.primitive.cuda-cpp storage.type.built-in.primitive.cuda-cpp", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6" + } + }, + { + "c": " threadsPerBlock ", + "t": "source.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "=", + "t": "source.cuda-cpp keyword.operator.assignment.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "128", + "t": "source.cuda-cpp constant.numeric.decimal.cuda-cpp", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #098658", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #098658", + "hc_black": "constant.numeric: #B5CEA8" + } + }, + { + "c": ";", + "t": "source.cuda-cpp punctuation.terminator.statement.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "typedef", + "t": "source.cuda-cpp keyword.other.typedef.cuda-cpp", + "r": { + "dark_plus": "keyword: #569CD6", + "light_plus": "keyword: #0000FF", + "dark_vs": "keyword: #569CD6", + "light_vs": "keyword: #0000FF", + "hc_black": "keyword: #569CD6" + } + }, + { + "c": " ", + "t": "source.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "unsigned", + "t": "source.cuda-cpp storage.type.primitive.cuda-cpp storage.type.built-in.primitive.cuda-cpp", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6" + } + }, + { + "c": " ", + "t": "source.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "char", + "t": "source.cuda-cpp storage.type.primitive.cuda-cpp storage.type.built-in.primitive.cuda-cpp", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6" + } + }, + { + "c": " byte", + "t": "source.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ";", + "t": "source.cuda-cpp punctuation.terminator.statement.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "struct", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.head.struct.cuda-cpp storage.type.struct.cuda-cpp", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "TestType", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp entity.name.type.struct.cuda-cpp", + "r": { + "dark_plus": "entity.name.type: #4EC9B0", + "light_plus": "entity.name.type: #267F99", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.type: #4EC9B0" + } + }, + { + "c": "{", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.head.struct.cuda-cpp punctuation.section.block.begin.bracket.curly.struct.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "union", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp meta.block.union.cuda-cpp meta.head.union.cuda-cpp storage.type.union.cuda-cpp", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp meta.block.union.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "{", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp meta.block.union.cuda-cpp meta.head.union.cuda-cpp punctuation.section.block.begin.bracket.curly.union.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp meta.block.union.cuda-cpp meta.body.union.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "struct", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp meta.block.union.cuda-cpp meta.body.union.cuda-cpp meta.block.struct.cuda-cpp meta.head.struct.cuda-cpp storage.type.struct.cuda-cpp", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp meta.block.union.cuda-cpp meta.body.union.cuda-cpp meta.block.struct.cuda-cpp meta.head.struct.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "{", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp meta.block.union.cuda-cpp meta.body.union.cuda-cpp meta.block.struct.cuda-cpp meta.head.struct.cuda-cpp punctuation.section.block.begin.bracket.curly.struct.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp meta.block.union.cuda-cpp meta.body.union.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "unsigned", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp meta.block.union.cuda-cpp meta.body.union.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp storage.type.primitive.cuda-cpp storage.type.built-in.primitive.cuda-cpp", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6" + } + }, + { + "c": " lowHalf", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp meta.block.union.cuda-cpp meta.body.union.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ";", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp meta.block.union.cuda-cpp meta.body.union.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp punctuation.terminator.statement.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp meta.block.union.cuda-cpp meta.body.union.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "unsigned", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp meta.block.union.cuda-cpp meta.body.union.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp storage.type.primitive.cuda-cpp storage.type.built-in.primitive.cuda-cpp", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6" + } + }, + { + "c": " highHalf", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp meta.block.union.cuda-cpp meta.body.union.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ";", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp meta.block.union.cuda-cpp meta.body.union.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp punctuation.terminator.statement.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp meta.block.union.cuda-cpp meta.body.union.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "}", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp meta.block.union.cuda-cpp meta.body.union.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp punctuation.section.block.end.bracket.curly.struct.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " halfAndHalf", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp meta.block.union.cuda-cpp meta.body.union.cuda-cpp meta.block.struct.cuda-cpp meta.tail.struct.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ";", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp meta.block.union.cuda-cpp meta.body.union.cuda-cpp meta.block.struct.cuda-cpp punctuation.terminator.statement.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp meta.block.union.cuda-cpp meta.body.union.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "unsigned", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp meta.block.union.cuda-cpp meta.body.union.cuda-cpp storage.type.primitive.cuda-cpp storage.type.built-in.primitive.cuda-cpp", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp meta.block.union.cuda-cpp meta.body.union.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "long", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp meta.block.union.cuda-cpp meta.body.union.cuda-cpp storage.type.primitive.cuda-cpp storage.type.built-in.primitive.cuda-cpp", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp meta.block.union.cuda-cpp meta.body.union.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "long", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp meta.block.union.cuda-cpp meta.body.union.cuda-cpp storage.type.primitive.cuda-cpp storage.type.built-in.primitive.cuda-cpp", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6" + } + }, + { + "c": " whole", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp meta.block.union.cuda-cpp meta.body.union.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ";", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp meta.block.union.cuda-cpp meta.body.union.cuda-cpp punctuation.terminator.statement.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp meta.block.union.cuda-cpp meta.body.union.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "}", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp meta.block.union.cuda-cpp meta.body.union.cuda-cpp punctuation.section.block.end.bracket.curly.union.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " takeYourPick", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp meta.block.union.cuda-cpp meta.tail.union.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ";", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp meta.block.union.cuda-cpp punctuation.terminator.statement.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "int", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp storage.type.primitive.cuda-cpp storage.type.built-in.primitive.cuda-cpp", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "arr", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp meta.bracket.square.access variable.other.object", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": "[", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp meta.bracket.square.access punctuation.definition.begin.bracket.square", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "5", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp meta.bracket.square.access constant.numeric.decimal.cuda-cpp", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #098658", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #098658", + "hc_black": "constant.numeric: #B5CEA8" + } + }, + { + "c": "]", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp meta.bracket.square.access punctuation.definition.end.bracket.square", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ";", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp punctuation.terminator.statement.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "struct", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp meta.block.struct.cuda-cpp meta.head.struct.cuda-cpp storage.type.struct.cuda-cpp", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp meta.block.struct.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "{", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp meta.block.struct.cuda-cpp meta.head.struct.cuda-cpp punctuation.section.block.begin.bracket.curly.struct.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "char", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp storage.type.primitive.cuda-cpp storage.type.built-in.primitive.cuda-cpp", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6" + } + }, + { + "c": " a", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ";", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp punctuation.terminator.statement.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "char", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp storage.type.primitive.cuda-cpp storage.type.built-in.primitive.cuda-cpp", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6" + } + }, + { + "c": " b", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ";", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp punctuation.terminator.statement.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "}", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp punctuation.section.block.end.bracket.curly.struct.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp meta.block.struct.cuda-cpp meta.tail.struct.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "structArr", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp meta.block.struct.cuda-cpp meta.tail.struct.cuda-cpp meta.bracket.square.access variable.other.object", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": "[", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp meta.block.struct.cuda-cpp meta.tail.struct.cuda-cpp meta.bracket.square.access punctuation.definition.begin.bracket.square", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "5", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp meta.block.struct.cuda-cpp meta.tail.struct.cuda-cpp meta.bracket.square.access constant.numeric.decimal.cuda-cpp", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #098658", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #098658", + "hc_black": "constant.numeric: #B5CEA8" + } + }, + { + "c": "]", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp meta.block.struct.cuda-cpp meta.tail.struct.cuda-cpp meta.bracket.square.access punctuation.definition.end.bracket.square", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ";", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp meta.block.struct.cuda-cpp punctuation.terminator.statement.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "float", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp storage.type.primitive.cuda-cpp storage.type.built-in.primitive.cuda-cpp", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "theFloats", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp meta.bracket.square.access variable.other.object", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": "[", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp meta.bracket.square.access punctuation.definition.begin.bracket.square", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "2", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp meta.bracket.square.access constant.numeric.decimal.cuda-cpp", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #098658", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #098658", + "hc_black": "constant.numeric: #B5CEA8" + } + }, + { + "c": "]", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp meta.bracket.square.access punctuation.definition.end.bracket.square", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ";", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp punctuation.terminator.statement.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "double", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp storage.type.primitive.cuda-cpp storage.type.built-in.primitive.cuda-cpp", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6" + } + }, + { + "c": " theDouble", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ";", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp punctuation.terminator.statement.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "}", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp meta.body.struct.cuda-cpp punctuation.section.block.end.bracket.curly.struct.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ";", + "t": "source.cuda-cpp meta.block.struct.cuda-cpp punctuation.terminator.statement.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "__global__", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp storage.modifier.__global__.cuda-cpp", + "r": { + "dark_plus": "storage.modifier: #569CD6", + "light_plus": "storage.modifier: #0000FF", + "dark_vs": "storage.modifier: #569CD6", + "light_vs": "storage.modifier: #0000FF", + "hc_black": "storage.modifier: #569CD6" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "void", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.qualified_type.cuda-cpp storage.type.primitive.cuda-cpp storage.type.built-in.primitive.cuda-cpp", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "cudaComputeHash", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.head.function.definition.cuda-cpp entity.name.function.definition.cuda-cpp", + "r": { + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.function: #DCDCAA" + } + }, + { + "c": "(", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.head.function.definition.cuda-cpp punctuation.section.parameters.begin.bracket.round.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "TestType", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.head.function.definition.cuda-cpp meta.function.definition.parameters meta.parameter.cuda-cpp entity.name.type.parameter.cuda-cpp", + "r": { + "dark_plus": "entity.name.type: #4EC9B0", + "light_plus": "entity.name.type: #267F99", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.type: #4EC9B0" + } + }, + { + "c": "*", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.head.function.definition.cuda-cpp meta.function.definition.parameters meta.parameter.cuda-cpp storage.modifier.pointer.cuda-cpp", + "r": { + "dark_plus": "storage.modifier: #569CD6", + "light_plus": "storage.modifier: #0000FF", + "dark_vs": "storage.modifier: #569CD6", + "light_vs": "storage.modifier: #0000FF", + "hc_black": "storage.modifier: #569CD6" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.head.function.definition.cuda-cpp meta.function.definition.parameters meta.parameter.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "input", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.head.function.definition.cuda-cpp meta.function.definition.parameters meta.parameter.cuda-cpp variable.parameter.cuda-cpp", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": ",", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.head.function.definition.cuda-cpp meta.function.definition.parameters meta.parameter.cuda-cpp punctuation.separator.delimiter.comma.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.head.function.definition.cuda-cpp meta.function.definition.parameters meta.parameter.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "unsigned", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.head.function.definition.cuda-cpp meta.function.definition.parameters meta.parameter.cuda-cpp storage.type.primitive.cuda-cpp storage.type.built-in.primitive.cuda-cpp", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.head.function.definition.cuda-cpp meta.function.definition.parameters meta.parameter.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "*", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.head.function.definition.cuda-cpp meta.function.definition.parameters meta.parameter.cuda-cpp storage.modifier.pointer.cuda-cpp", + "r": { + "dark_plus": "storage.modifier: #569CD6", + "light_plus": "storage.modifier: #0000FF", + "dark_vs": "storage.modifier: #569CD6", + "light_vs": "storage.modifier: #0000FF", + "hc_black": "storage.modifier: #569CD6" + } + }, + { + "c": "results", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.head.function.definition.cuda-cpp meta.function.definition.parameters meta.parameter.cuda-cpp variable.parameter.cuda-cpp", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": ")", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.head.function.definition.cuda-cpp punctuation.section.parameters.end.bracket.round.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "{", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.head.function.definition.cuda-cpp punctuation.section.block.begin.bracket.curly.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "int", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp storage.type.primitive.cuda-cpp storage.type.built-in.primitive.cuda-cpp", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6" + } + }, + { + "c": " idx ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "=", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp keyword.operator.assignment.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "blockIdx", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp storage.type.primitive.cuda-cpp storage.type.built-in.primitive.cuda-cpp", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6" + } + }, + { + "c": ".x ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "*", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp keyword.operator.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " threadsPerBlock ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "+", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp keyword.operator.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "threadIdx", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp storage.type.primitive.cuda-cpp storage.type.built-in.primitive.cuda-cpp", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6" + } + }, + { + "c": ".x", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ";", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.terminator.statement.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " TestType", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "*", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp keyword.operator.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " myInput ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "=", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp keyword.operator.assignment.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " input ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "+", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp keyword.operator.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " idx", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ";", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.terminator.statement.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "unsigned", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp storage.type.primitive.cuda-cpp storage.type.built-in.primitive.cuda-cpp", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6" + } + }, + { + "c": " myResult ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "=", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp keyword.operator.assignment.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "0", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp constant.numeric.decimal.cuda-cpp", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #098658", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #098658", + "hc_black": "constant.numeric: #B5CEA8" + } + }, + { + "c": ";", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.terminator.statement.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " myResult ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "+=", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp keyword.operator.assignment.compound.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "myInput", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp variable.other.object.access.cuda-cpp", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": "->", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.separator.pointer-access.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "takeYourPick", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp variable.other.object.property.cuda-cpp", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": ".", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.separator.dot-access.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "halfAndHalf", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp variable.other.object.property.cuda-cpp", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": ".", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.separator.dot-access.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "lowHalf", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp variable.other.property.cuda-cpp", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "-", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp keyword.operator.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " idx", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ";", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.terminator.statement.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " myResult ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "+=", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp keyword.operator.assignment.compound.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "myInput", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp variable.other.object.access.cuda-cpp", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": "->", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.separator.pointer-access.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "takeYourPick", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp variable.other.object.property.cuda-cpp", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": ".", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.separator.dot-access.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "halfAndHalf", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp variable.other.object.property.cuda-cpp", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": ".", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.separator.dot-access.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "highHalf", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp variable.other.property.cuda-cpp", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "-", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp keyword.operator.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " idx", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ";", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.terminator.statement.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "for", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp keyword.control.for.cuda-cpp", + "r": { + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0" + } + }, + { + "c": "(", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp punctuation.section.parens.begin.bracket.round.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "size_t", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp storage.type.cuda-cpp storage.type.built-in.cuda-cpp", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6" + } + }, + { + "c": " i ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "=", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp keyword.operator.assignment.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "0", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp constant.numeric.decimal.cuda-cpp", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #098658", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #098658", + "hc_black": "constant.numeric: #B5CEA8" + } + }, + { + "c": ";", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp punctuation.terminator.statement.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " i ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "<", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp keyword.operator.comparison.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "ARRAY_LENGTH", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp entity.name.function.call.cuda-cpp", + "r": { + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.function: #DCDCAA" + } + }, + { + "c": "(", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp punctuation.section.arguments.begin.bracket.round.function.call.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "myInput", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp variable.other.object.access.cuda-cpp", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": "->", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp punctuation.separator.pointer-access.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "arr", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp variable.other.property.cuda-cpp", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": ")", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp punctuation.section.arguments.end.bracket.round.function.call.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ";", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp punctuation.terminator.statement.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " i", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "++", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp keyword.operator.increment.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": ")", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp punctuation.section.parens.end.bracket.round.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "{", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp punctuation.section.block.begin.bracket.curly.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " myResult ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "+=", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp keyword.operator.assignment.compound.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "myInput", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp variable.other.object.access.cuda-cpp", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": "->", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp punctuation.separator.pointer-access.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "arr", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp variable.other.property.cuda-cpp", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": "[", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.bracket.square.access punctuation.definition.begin.bracket.square", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "i", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.bracket.square.access", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "]", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.bracket.square.access punctuation.definition.end.bracket.square", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "-", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp keyword.operator.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " idx", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ";", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp punctuation.terminator.statement.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "}", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp punctuation.section.block.end.bracket.curly.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "for", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp keyword.control.for.cuda-cpp", + "r": { + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0" + } + }, + { + "c": "(", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp punctuation.section.parens.begin.bracket.round.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "size_t", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp storage.type.cuda-cpp storage.type.built-in.cuda-cpp", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6" + } + }, + { + "c": " i ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "=", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp keyword.operator.assignment.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "0", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp constant.numeric.decimal.cuda-cpp", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #098658", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #098658", + "hc_black": "constant.numeric: #B5CEA8" + } + }, + { + "c": ";", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp punctuation.terminator.statement.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " i ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "<", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp keyword.operator.comparison.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "sizeof", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp keyword.operator.functionlike.cuda-cpp keyword.operator.sizeof.cuda-cpp", + "r": { + "dark_plus": "keyword.operator.sizeof: #569CD6", + "light_plus": "keyword.operator.sizeof: #0000FF", + "dark_vs": "keyword.operator.sizeof: #569CD6", + "light_vs": "keyword.operator.sizeof: #0000FF", + "hc_black": "keyword.operator.sizeof: #569CD6" + } + }, + { + "c": "(", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp punctuation.section.arguments.begin.bracket.round.operator.sizeof.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "myInput", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp meta.arguments.operator.sizeof variable.other.object.access.cuda-cpp", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": "->", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp meta.arguments.operator.sizeof punctuation.separator.pointer-access.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "structArr", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp meta.arguments.operator.sizeof variable.other.property.cuda-cpp", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": ")", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp punctuation.section.arguments.end.bracket.round.operator.sizeof.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ";", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp punctuation.terminator.statement.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " i", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "++", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp keyword.operator.increment.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": ")", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp punctuation.section.parens.end.bracket.round.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "{", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp punctuation.section.block.begin.bracket.curly.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " myResult ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "+=", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp keyword.operator.assignment.compound.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "reinterpret_cast", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp keyword.operator.wordlike.cuda-cpp keyword.operator.cast.reinterpret_cast.cuda-cpp", + "r": { + "dark_plus": "keyword.operator.cast: #569CD6", + "light_plus": "keyword.operator.cast: #0000FF", + "dark_vs": "keyword.operator.cast: #569CD6", + "light_vs": "keyword.operator.cast: #0000FF", + "hc_black": "keyword.operator.cast: #569CD6" + } + }, + { + "c": "<", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp keyword.operator.comparison.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": "byte ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "*", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp keyword.operator.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": ">", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp keyword.operator.comparison.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": "(", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp punctuation.section.parens.begin.bracket.round.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "myInput", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp variable.other.object.access.cuda-cpp", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": "->", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp punctuation.separator.pointer-access.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "structArr", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp variable.other.property.cuda-cpp", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": ")", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp punctuation.section.parens.end.bracket.round.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "[", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.bracket.square.access punctuation.definition.begin.bracket.square", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "i", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.bracket.square.access", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "]", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.bracket.square.access punctuation.definition.end.bracket.square", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "-", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp keyword.operator.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "'", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp string.quoted.single.cuda-cpp punctuation.definition.string.begin.cuda-cpp", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "0", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp string.quoted.single.cuda-cpp", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "'", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp string.quoted.single.cuda-cpp punctuation.definition.string.end.cuda-cpp", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": ";", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp punctuation.terminator.statement.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "}", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp punctuation.section.block.end.bracket.curly.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "__syncthreads", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp entity.name.function.call.cuda-cpp", + "r": { + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.function: #DCDCAA" + } + }, + { + "c": "(", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.section.arguments.begin.bracket.round.function.call.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ")", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.section.arguments.end.bracket.round.function.call.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ";", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.terminator.statement.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "results", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.bracket.square.access variable.other.object", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": "[", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.bracket.square.access punctuation.definition.begin.bracket.square", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "idx", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.bracket.square.access", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "]", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.bracket.square.access punctuation.definition.end.bracket.square", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "=", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp keyword.operator.assignment.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " myResult", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ";", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.terminator.statement.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "}", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.section.block.end.bracket.curly.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "int", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.qualified_type.cuda-cpp storage.type.primitive.cuda-cpp storage.type.built-in.primitive.cuda-cpp", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "main", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.head.function.definition.cuda-cpp entity.name.function.definition.cuda-cpp", + "r": { + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.function: #DCDCAA" + } + }, + { + "c": "(", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.head.function.definition.cuda-cpp punctuation.section.parameters.begin.bracket.round.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ")", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.head.function.definition.cuda-cpp punctuation.section.parameters.end.bracket.round.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "{", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.head.function.definition.cuda-cpp punctuation.section.block.begin.bracket.curly.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "int", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp storage.type.primitive.cuda-cpp storage.type.built-in.primitive.cuda-cpp", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6" + } + }, + { + "c": " cudaDeviceCount", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ";", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.terminator.statement.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "assertSucceeded", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp entity.name.function.call.cuda-cpp", + "r": { + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.function: #DCDCAA" + } + }, + { + "c": "(", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.section.arguments.begin.bracket.round.function.call.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "cudaGetDeviceCount", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp entity.name.function.call.cuda-cpp", + "r": { + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.function: #DCDCAA" + } + }, + { + "c": "(", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.section.arguments.begin.bracket.round.function.call.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "&", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp keyword.operator.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": "cudaDeviceCount", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "))", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.section.arguments.end.bracket.round.function.call.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ";", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.terminator.statement.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "assert", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp entity.name.function.call.cuda-cpp", + "r": { + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.function: #DCDCAA" + } + }, + { + "c": "(", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.section.arguments.begin.bracket.round.function.call.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "cudaDeviceCount ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ">", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp keyword.operator.comparison.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "0", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp constant.numeric.decimal.cuda-cpp", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #098658", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #098658", + "hc_black": "constant.numeric: #B5CEA8" + } + }, + { + "c": ")", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.section.arguments.end.bracket.round.function.call.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ";", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.terminator.statement.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "assertSucceeded", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp entity.name.function.call.cuda-cpp", + "r": { + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.function: #DCDCAA" + } + }, + { + "c": "(", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.section.arguments.begin.bracket.round.function.call.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "cudaSetDevice", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp entity.name.function.call.cuda-cpp", + "r": { + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.function: #DCDCAA" + } + }, + { + "c": "(", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.section.arguments.begin.bracket.round.function.call.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "0", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp constant.numeric.decimal.cuda-cpp", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #098658", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #098658", + "hc_black": "constant.numeric: #B5CEA8" + } + }, + { + "c": "))", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.section.arguments.end.bracket.round.function.call.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ";", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.terminator.statement.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " TestType", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "*", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp keyword.operator.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " input", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ";", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.terminator.statement.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "unsigned", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp storage.type.primitive.cuda-cpp storage.type.built-in.primitive.cuda-cpp", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6" + } + }, + { + "c": "*", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp keyword.operator.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " results", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ";", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.terminator.statement.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "assertSucceeded", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp entity.name.function.call.cuda-cpp", + "r": { + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.function: #DCDCAA" + } + }, + { + "c": "(", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.section.arguments.begin.bracket.round.function.call.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "cudaMallocManaged", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp entity.name.function.call.cuda-cpp", + "r": { + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.function: #DCDCAA" + } + }, + { + "c": "(", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.section.arguments.begin.bracket.round.function.call.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "&", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp keyword.operator.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": "input", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ",", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.separator.delimiter.comma.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "sizeof", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp keyword.operator.functionlike.cuda-cpp keyword.operator.sizeof.cuda-cpp", + "r": { + "dark_plus": "keyword.operator.sizeof: #569CD6", + "light_plus": "keyword.operator.sizeof: #0000FF", + "dark_vs": "keyword.operator.sizeof: #569CD6", + "light_vs": "keyword.operator.sizeof: #0000FF", + "hc_black": "keyword.operator.sizeof: #569CD6" + } + }, + { + "c": "(", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.section.arguments.begin.bracket.round.operator.sizeof.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "TestType", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.arguments.operator.sizeof", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ")", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.section.arguments.end.bracket.round.operator.sizeof.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "*", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp keyword.operator.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " dataLength", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "))", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.section.arguments.end.bracket.round.function.call.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ";", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.terminator.statement.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "assert", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp entity.name.function.call.cuda-cpp", + "r": { + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.function: #DCDCAA" + } + }, + { + "c": "(", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.section.arguments.begin.bracket.round.function.call.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "!!", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp keyword.operator.logical.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": "input", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ")", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.section.arguments.end.bracket.round.function.call.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ";", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.terminator.statement.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "for", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp keyword.control.for.cuda-cpp", + "r": { + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "(", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp punctuation.section.parens.begin.bracket.round.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "size_t", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp storage.type.cuda-cpp storage.type.built-in.cuda-cpp", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6" + } + }, + { + "c": " i ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "=", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp keyword.operator.assignment.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "0", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp constant.numeric.decimal.cuda-cpp", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #098658", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #098658", + "hc_black": "constant.numeric: #B5CEA8" + } + }, + { + "c": ";", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp punctuation.terminator.statement.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " i ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "<", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp keyword.operator.comparison.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " dataLength", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ";", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp punctuation.terminator.statement.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " i", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "++", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp keyword.operator.increment.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": ")", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp punctuation.section.parens.end.bracket.round.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "{", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp punctuation.section.block.begin.bracket.curly.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "input", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.bracket.square.access variable.other.object", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": "[", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.bracket.square.access punctuation.definition.begin.bracket.square", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "i", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.bracket.square.access", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "]", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.bracket.square.access punctuation.definition.end.bracket.square", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ".", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp punctuation.separator.dot-access.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "takeYourPick", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp variable.other.object.property.cuda-cpp", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": ".", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp punctuation.separator.dot-access.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "halfAndHalf", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp variable.other.object.property.cuda-cpp", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": ".", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp punctuation.separator.dot-access.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "lowHalf", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp variable.other.property.cuda-cpp", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "=", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp keyword.operator.assignment.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " i ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "+", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp keyword.operator.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "1", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp constant.numeric.decimal.cuda-cpp", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #098658", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #098658", + "hc_black": "constant.numeric: #B5CEA8" + } + }, + { + "c": ";", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp punctuation.terminator.statement.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "input", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.bracket.square.access variable.other.object", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": "[", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.bracket.square.access punctuation.definition.begin.bracket.square", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "i", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.bracket.square.access", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "]", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.bracket.square.access punctuation.definition.end.bracket.square", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ".", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp punctuation.separator.dot-access.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "takeYourPick", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp variable.other.object.property.cuda-cpp", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": ".", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp punctuation.separator.dot-access.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "halfAndHalf", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp variable.other.object.property.cuda-cpp", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": ".", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp punctuation.separator.dot-access.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "highHalf", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp variable.other.property.cuda-cpp", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "=", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp keyword.operator.assignment.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " i ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "+", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp keyword.operator.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "3", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp constant.numeric.decimal.cuda-cpp", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #098658", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #098658", + "hc_black": "constant.numeric: #B5CEA8" + } + }, + { + "c": ";", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp punctuation.terminator.statement.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "for", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp keyword.control.for.cuda-cpp", + "r": { + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0" + } + }, + { + "c": "(", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp punctuation.section.parens.begin.bracket.round.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "size_t", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp storage.type.cuda-cpp storage.type.built-in.cuda-cpp", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6" + } + }, + { + "c": " j ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "=", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp keyword.operator.assignment.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "0", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp constant.numeric.decimal.cuda-cpp", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #098658", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #098658", + "hc_black": "constant.numeric: #B5CEA8" + } + }, + { + "c": ";", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp punctuation.terminator.statement.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " j ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "<", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp keyword.operator.comparison.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "ARRAY_LENGTH", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp entity.name.function.call.cuda-cpp", + "r": { + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.function: #DCDCAA" + } + }, + { + "c": "(", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp punctuation.section.arguments.begin.bracket.round.function.call.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "input", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp meta.bracket.square.access variable.other.object", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": "[", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp meta.bracket.square.access punctuation.definition.begin.bracket.square", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "i", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp meta.bracket.square.access", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "]", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp meta.bracket.square.access punctuation.definition.end.bracket.square", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ".", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp punctuation.separator.dot-access.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "arr", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp variable.other.property.cuda-cpp", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": ")", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp punctuation.section.arguments.end.bracket.round.function.call.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ";", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp punctuation.terminator.statement.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " j", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "++", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp keyword.operator.increment.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": ")", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp punctuation.section.parens.end.bracket.round.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "{", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp punctuation.section.block.begin.bracket.curly.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "input", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp meta.bracket.square.access variable.other.object", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": "[", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp meta.bracket.square.access punctuation.definition.begin.bracket.square", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "i", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp meta.bracket.square.access", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "]", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp meta.bracket.square.access punctuation.definition.end.bracket.square", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ".", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp punctuation.separator.dot-access.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "arr", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp variable.other.property.cuda-cpp", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": "[", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp meta.bracket.square.access punctuation.definition.begin.bracket.square", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "j", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp meta.bracket.square.access", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "]", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp meta.bracket.square.access punctuation.definition.end.bracket.square", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "=", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp keyword.operator.assignment.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " i ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "+", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp keyword.operator.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " j ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "+", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp keyword.operator.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "2", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp constant.numeric.decimal.cuda-cpp", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #098658", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #098658", + "hc_black": "constant.numeric: #B5CEA8" + } + }, + { + "c": ";", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp punctuation.terminator.statement.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "}", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp punctuation.section.block.end.bracket.curly.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "for", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp keyword.control.for.cuda-cpp", + "r": { + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0" + } + }, + { + "c": "(", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp punctuation.section.parens.begin.bracket.round.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "size_t", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp storage.type.cuda-cpp storage.type.built-in.cuda-cpp", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6" + } + }, + { + "c": " j ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "=", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp keyword.operator.assignment.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "0", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp constant.numeric.decimal.cuda-cpp", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #098658", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #098658", + "hc_black": "constant.numeric: #B5CEA8" + } + }, + { + "c": ";", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp punctuation.terminator.statement.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " j ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "<", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp keyword.operator.comparison.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "sizeof", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp keyword.operator.functionlike.cuda-cpp keyword.operator.sizeof.cuda-cpp", + "r": { + "dark_plus": "keyword.operator.sizeof: #569CD6", + "light_plus": "keyword.operator.sizeof: #0000FF", + "dark_vs": "keyword.operator.sizeof: #569CD6", + "light_vs": "keyword.operator.sizeof: #0000FF", + "hc_black": "keyword.operator.sizeof: #569CD6" + } + }, + { + "c": "(", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp punctuation.section.arguments.begin.bracket.round.operator.sizeof.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "input", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp meta.arguments.operator.sizeof meta.bracket.square.access variable.other.object", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": "[", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp meta.arguments.operator.sizeof meta.bracket.square.access punctuation.definition.begin.bracket.square", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "i", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp meta.arguments.operator.sizeof meta.bracket.square.access", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "]", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp meta.arguments.operator.sizeof meta.bracket.square.access punctuation.definition.end.bracket.square", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ".", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp meta.arguments.operator.sizeof punctuation.separator.dot-access.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "structArr", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp meta.arguments.operator.sizeof variable.other.property.cuda-cpp", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": ")", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp punctuation.section.arguments.end.bracket.round.operator.sizeof.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ";", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp punctuation.terminator.statement.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " j", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "++", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp keyword.operator.increment.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": ")", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp punctuation.section.parens.end.bracket.round.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "{", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp punctuation.section.block.begin.bracket.curly.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "reinterpret_cast", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp keyword.operator.wordlike.cuda-cpp keyword.operator.cast.reinterpret_cast.cuda-cpp", + "r": { + "dark_plus": "keyword.operator.cast: #569CD6", + "light_plus": "keyword.operator.cast: #0000FF", + "dark_vs": "keyword.operator.cast: #569CD6", + "light_vs": "keyword.operator.cast: #0000FF", + "hc_black": "keyword.operator.cast: #569CD6" + } + }, + { + "c": "<", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp keyword.operator.comparison.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": "byte ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "*", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp keyword.operator.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": ">", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp keyword.operator.comparison.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": "(", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp punctuation.section.parens.begin.bracket.round.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "input", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp meta.bracket.square.access variable.other.object", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": "[", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp meta.bracket.square.access punctuation.definition.begin.bracket.square", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "i", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp meta.bracket.square.access", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "]", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp meta.bracket.square.access punctuation.definition.end.bracket.square", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ".", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp punctuation.separator.dot-access.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "structArr", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp variable.other.property.cuda-cpp", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": ")", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp punctuation.section.parens.end.bracket.round.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "[", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp meta.bracket.square.access punctuation.definition.begin.bracket.square", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "j", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp meta.bracket.square.access", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "]", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp meta.bracket.square.access punctuation.definition.end.bracket.square", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "=", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp keyword.operator.assignment.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "'", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp string.quoted.single.cuda-cpp punctuation.definition.string.begin.cuda-cpp", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "0", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp string.quoted.single.cuda-cpp", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "'", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp string.quoted.single.cuda-cpp punctuation.definition.string.end.cuda-cpp", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "+", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp keyword.operator.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "static_cast", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp keyword.operator.wordlike.cuda-cpp keyword.operator.cast.static_cast.cuda-cpp", + "r": { + "dark_plus": "keyword.operator.cast: #569CD6", + "light_plus": "keyword.operator.cast: #0000FF", + "dark_vs": "keyword.operator.cast: #569CD6", + "light_vs": "keyword.operator.cast: #0000FF", + "hc_black": "keyword.operator.cast: #569CD6" + } + }, + { + "c": "<", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp keyword.operator.comparison.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": "char", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp storage.type.primitive.cuda-cpp storage.type.built-in.primitive.cuda-cpp", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6" + } + }, + { + "c": ">", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp keyword.operator.comparison.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": "(", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp punctuation.section.parens.begin.bracket.round.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "(", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp meta.parens.cuda-cpp punctuation.section.parens.begin.bracket.round.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "i ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp meta.parens.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "+", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp meta.parens.cuda-cpp keyword.operator.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " j", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp meta.parens.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ")", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp meta.parens.cuda-cpp punctuation.section.parens.end.bracket.round.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "%", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp keyword.operator.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "10", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp constant.numeric.decimal.cuda-cpp", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #098658", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #098658", + "hc_black": "constant.numeric: #B5CEA8" + } + }, + { + "c": ")", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp punctuation.section.parens.end.bracket.round.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ";", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp punctuation.terminator.statement.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "}", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp punctuation.section.block.end.bracket.curly.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "input", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.bracket.square.access variable.other.object", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": "[", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.bracket.square.access punctuation.definition.begin.bracket.square", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "i", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.bracket.square.access", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "]", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.bracket.square.access punctuation.definition.end.bracket.square", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ".", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp punctuation.separator.dot-access.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "theFloats", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp variable.other.property.cuda-cpp", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": "[", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.bracket.square.access punctuation.definition.begin.bracket.square", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "0", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.bracket.square.access constant.numeric.decimal.cuda-cpp", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #098658", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #098658", + "hc_black": "constant.numeric: #B5CEA8" + } + }, + { + "c": "]", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.bracket.square.access punctuation.definition.end.bracket.square", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "=", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp keyword.operator.assignment.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " i ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "+", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp keyword.operator.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "1", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp constant.numeric.decimal.cuda-cpp", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #098658", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #098658", + "hc_black": "constant.numeric: #B5CEA8" + } + }, + { + "c": ";", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp punctuation.terminator.statement.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "input", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.bracket.square.access variable.other.object", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": "[", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.bracket.square.access punctuation.definition.begin.bracket.square", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "i", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.bracket.square.access", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "]", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.bracket.square.access punctuation.definition.end.bracket.square", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ".", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp punctuation.separator.dot-access.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "theFloats", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp variable.other.property.cuda-cpp", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": "[", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.bracket.square.access punctuation.definition.begin.bracket.square", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "1", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.bracket.square.access constant.numeric.decimal.cuda-cpp", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #098658", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #098658", + "hc_black": "constant.numeric: #B5CEA8" + } + }, + { + "c": "]", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.bracket.square.access punctuation.definition.end.bracket.square", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "=", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp keyword.operator.assignment.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "input", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.bracket.square.access variable.other.object", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": "[", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.bracket.square.access punctuation.definition.begin.bracket.square", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "i", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.bracket.square.access", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "]", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.bracket.square.access punctuation.definition.end.bracket.square", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ".", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp punctuation.separator.dot-access.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "theFloats", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp variable.other.property.cuda-cpp", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": "[", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.bracket.square.access punctuation.definition.begin.bracket.square", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "0", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.bracket.square.access constant.numeric.decimal.cuda-cpp", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #098658", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #098658", + "hc_black": "constant.numeric: #B5CEA8" + } + }, + { + "c": "]", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.bracket.square.access punctuation.definition.end.bracket.square", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "/", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp keyword.operator.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "2", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp constant.numeric.decimal.cuda-cpp", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #098658", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #098658", + "hc_black": "constant.numeric: #B5CEA8" + } + }, + { + "c": ";", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp punctuation.terminator.statement.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "input", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.bracket.square.access variable.other.object", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": "[", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.bracket.square.access punctuation.definition.begin.bracket.square", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "i", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.bracket.square.access", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "]", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.bracket.square.access punctuation.definition.end.bracket.square", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ".", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp punctuation.separator.dot-access.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "theDouble", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp variable.other.property.cuda-cpp", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "=", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp keyword.operator.assignment.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "input", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.bracket.square.access variable.other.object", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": "[", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.bracket.square.access punctuation.definition.begin.bracket.square", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "i", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.bracket.square.access", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "]", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.bracket.square.access punctuation.definition.end.bracket.square", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ".", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp punctuation.separator.dot-access.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "theFloats", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp variable.other.property.cuda-cpp", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": "[", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.bracket.square.access punctuation.definition.begin.bracket.square", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "1", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.bracket.square.access constant.numeric.decimal.cuda-cpp", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #098658", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #098658", + "hc_black": "constant.numeric: #B5CEA8" + } + }, + { + "c": "]", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.bracket.square.access punctuation.definition.end.bracket.square", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "+", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp keyword.operator.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "1", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp constant.numeric.decimal.cuda-cpp", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #098658", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #098658", + "hc_black": "constant.numeric: #B5CEA8" + } + }, + { + "c": ";", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp punctuation.terminator.statement.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "}", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp punctuation.section.block.end.bracket.curly.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "assertSucceeded", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp entity.name.function.call.cuda-cpp", + "r": { + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.function: #DCDCAA" + } + }, + { + "c": "(", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.section.arguments.begin.bracket.round.function.call.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "cudaMallocManaged", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp entity.name.function.call.cuda-cpp", + "r": { + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.function: #DCDCAA" + } + }, + { + "c": "(", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.section.arguments.begin.bracket.round.function.call.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "reinterpret_cast", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp keyword.operator.wordlike.cuda-cpp keyword.operator.cast.reinterpret_cast.cuda-cpp", + "r": { + "dark_plus": "keyword.operator.cast: #569CD6", + "light_plus": "keyword.operator.cast: #0000FF", + "dark_vs": "keyword.operator.cast: #569CD6", + "light_vs": "keyword.operator.cast: #0000FF", + "hc_black": "keyword.operator.cast: #569CD6" + } + }, + { + "c": "<", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp keyword.operator.comparison.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": "void", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp storage.type.primitive.cuda-cpp storage.type.built-in.primitive.cuda-cpp", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "**", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp keyword.operator.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": ">", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp keyword.operator.comparison.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": "(", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp punctuation.section.parens.begin.bracket.round.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "&", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp keyword.operator.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": "results", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ")", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp punctuation.section.parens.end.bracket.round.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ",", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.separator.delimiter.comma.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "sizeof", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp keyword.operator.functionlike.cuda-cpp keyword.operator.sizeof.cuda-cpp", + "r": { + "dark_plus": "keyword.operator.sizeof: #569CD6", + "light_plus": "keyword.operator.sizeof: #0000FF", + "dark_vs": "keyword.operator.sizeof: #569CD6", + "light_vs": "keyword.operator.sizeof: #0000FF", + "hc_black": "keyword.operator.sizeof: #569CD6" + } + }, + { + "c": "(", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.section.arguments.begin.bracket.round.operator.sizeof.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "unsigned", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.arguments.operator.sizeof storage.type.primitive.cuda-cpp storage.type.built-in.primitive.cuda-cpp", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6" + } + }, + { + "c": ")", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.section.arguments.end.bracket.round.operator.sizeof.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "*", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp keyword.operator.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " dataLength", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "))", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.section.arguments.end.bracket.round.function.call.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ";", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.terminator.statement.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "assert", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp entity.name.function.call.cuda-cpp", + "r": { + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.function: #DCDCAA" + } + }, + { + "c": "(", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.section.arguments.begin.bracket.round.function.call.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "!!", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp keyword.operator.logical.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": "results", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ")", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.section.arguments.end.bracket.round.function.call.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ";", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.terminator.statement.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "constexpr", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp storage.modifier.specifier.functional.pre-parameters.constexpr.cuda-cpp", + "r": { + "dark_plus": "storage.modifier: #569CD6", + "light_plus": "storage.modifier: #0000FF", + "dark_vs": "storage.modifier: #569CD6", + "light_vs": "storage.modifier: #0000FF", + "hc_black": "storage.modifier: #569CD6" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "int", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp storage.type.primitive.cuda-cpp storage.type.built-in.primitive.cuda-cpp", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6" + } + }, + { + "c": " blocks ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "=", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp keyword.operator.assignment.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " dataLength ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "/", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp keyword.operator.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " threadsPerBlock", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ";", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.terminator.statement.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " cudaComputeHash", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "<<", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp keyword.operator.bitwise.shift.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": "<", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp keyword.operator.comparison.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": "blocks", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ",", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.separator.delimiter.comma.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " threadsPerBlock", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ">>", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp keyword.operator.bitwise.shift.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": ">", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp keyword.operator.comparison.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": "(", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp punctuation.section.parens.begin.bracket.round.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "input", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ",", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp punctuation.separator.delimiter.comma.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " results", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ")", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp punctuation.section.parens.end.bracket.round.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ";", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.terminator.statement.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "assertSucceeded", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp entity.name.function.call.cuda-cpp", + "r": { + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.function: #DCDCAA" + } + }, + { + "c": "(", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.section.arguments.begin.bracket.round.function.call.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "cudaDeviceSynchronize", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp entity.name.function.call.cuda-cpp", + "r": { + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.function: #DCDCAA" + } + }, + { + "c": "(", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.section.arguments.begin.bracket.round.function.call.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "))", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.section.arguments.end.bracket.round.function.call.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ";", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.terminator.statement.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "const", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp storage.modifier.specifier.const.cuda-cpp", + "r": { + "dark_plus": "storage.modifier: #569CD6", + "light_plus": "storage.modifier: #0000FF", + "dark_vs": "storage.modifier: #569CD6", + "light_vs": "storage.modifier: #0000FF", + "hc_black": "storage.modifier: #569CD6" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "unsigned", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp storage.type.primitive.cuda-cpp storage.type.built-in.primitive.cuda-cpp", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6" + } + }, + { + "c": " expectedResult ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "=", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp keyword.operator.assignment.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "1", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp constant.numeric.decimal.cuda-cpp", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #098658", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #098658", + "hc_black": "constant.numeric: #B5CEA8" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "+", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp keyword.operator.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "3", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp constant.numeric.decimal.cuda-cpp", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #098658", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #098658", + "hc_black": "constant.numeric: #B5CEA8" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "+", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp keyword.operator.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "ARRAY_LENGTH", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp entity.name.function.call.cuda-cpp", + "r": { + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.function: #DCDCAA" + } + }, + { + "c": "(", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.section.arguments.begin.bracket.round.function.call.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "input", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.bracket.square.access variable.other.object", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": "[", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.bracket.square.access punctuation.definition.begin.bracket.square", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "0", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.bracket.square.access constant.numeric.decimal.cuda-cpp", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #098658", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #098658", + "hc_black": "constant.numeric: #B5CEA8" + } + }, + { + "c": "]", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.bracket.square.access punctuation.definition.end.bracket.square", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ".", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.separator.dot-access.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "arr", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp variable.other.property.cuda-cpp", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": ")", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.section.arguments.end.bracket.round.function.call.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "*", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp keyword.operator.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "(", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp punctuation.section.parens.begin.bracket.round.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "ARRAY_LENGTH", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp entity.name.function.call.cuda-cpp", + "r": { + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.function: #DCDCAA" + } + }, + { + "c": "(", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp punctuation.section.arguments.begin.bracket.round.function.call.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "input", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp meta.bracket.square.access variable.other.object", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": "[", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp meta.bracket.square.access punctuation.definition.begin.bracket.square", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "0", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp meta.bracket.square.access constant.numeric.decimal.cuda-cpp", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #098658", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #098658", + "hc_black": "constant.numeric: #B5CEA8" + } + }, + { + "c": "]", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp meta.bracket.square.access punctuation.definition.end.bracket.square", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ".", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp punctuation.separator.dot-access.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "arr", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp variable.other.property.cuda-cpp", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": ")", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp punctuation.section.arguments.end.bracket.round.function.call.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "-", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp keyword.operator.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "1", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp constant.numeric.decimal.cuda-cpp", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #098658", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #098658", + "hc_black": "constant.numeric: #B5CEA8" + } + }, + { + "c": ")", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp punctuation.section.parens.end.bracket.round.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "/", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp keyword.operator.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "2", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp constant.numeric.decimal.cuda-cpp", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #098658", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #098658", + "hc_black": "constant.numeric: #B5CEA8" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "+", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp keyword.operator.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "ARRAY_LENGTH", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp entity.name.function.call.cuda-cpp", + "r": { + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.function: #DCDCAA" + } + }, + { + "c": "(", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.section.arguments.begin.bracket.round.function.call.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "input", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.bracket.square.access variable.other.object", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": "[", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.bracket.square.access punctuation.definition.begin.bracket.square", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "0", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.bracket.square.access constant.numeric.decimal.cuda-cpp", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #098658", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #098658", + "hc_black": "constant.numeric: #B5CEA8" + } + }, + { + "c": "]", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.bracket.square.access punctuation.definition.end.bracket.square", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ".", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.separator.dot-access.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "arr", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp variable.other.property.cuda-cpp", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": ")", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.section.arguments.end.bracket.round.function.call.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "*", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp keyword.operator.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "2", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp constant.numeric.decimal.cuda-cpp", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #098658", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #098658", + "hc_black": "constant.numeric: #B5CEA8" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "+", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp keyword.operator.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "sizeof", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp keyword.operator.functionlike.cuda-cpp keyword.operator.sizeof.cuda-cpp", + "r": { + "dark_plus": "keyword.operator.sizeof: #569CD6", + "light_plus": "keyword.operator.sizeof: #0000FF", + "dark_vs": "keyword.operator.sizeof: #569CD6", + "light_vs": "keyword.operator.sizeof: #0000FF", + "hc_black": "keyword.operator.sizeof: #569CD6" + } + }, + { + "c": "(", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.section.arguments.begin.bracket.round.operator.sizeof.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "input", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.arguments.operator.sizeof meta.bracket.square.access variable.other.object", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": "[", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.arguments.operator.sizeof meta.bracket.square.access punctuation.definition.begin.bracket.square", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "0", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.arguments.operator.sizeof meta.bracket.square.access constant.numeric.decimal.cuda-cpp", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #098658", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #098658", + "hc_black": "constant.numeric: #B5CEA8" + } + }, + { + "c": "]", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.arguments.operator.sizeof meta.bracket.square.access punctuation.definition.end.bracket.square", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ".", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.arguments.operator.sizeof punctuation.separator.dot-access.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "structArr", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.arguments.operator.sizeof variable.other.property.cuda-cpp", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": ")", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.section.arguments.end.bracket.round.operator.sizeof.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "*", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp keyword.operator.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "(", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp punctuation.section.parens.begin.bracket.round.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "sizeof", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp keyword.operator.functionlike.cuda-cpp keyword.operator.sizeof.cuda-cpp", + "r": { + "dark_plus": "keyword.operator.sizeof: #569CD6", + "light_plus": "keyword.operator.sizeof: #0000FF", + "dark_vs": "keyword.operator.sizeof: #569CD6", + "light_vs": "keyword.operator.sizeof: #0000FF", + "hc_black": "keyword.operator.sizeof: #569CD6" + } + }, + { + "c": "(", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp punctuation.section.arguments.begin.bracket.round.operator.sizeof.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "input", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp meta.arguments.operator.sizeof meta.bracket.square.access variable.other.object", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": "[", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp meta.arguments.operator.sizeof meta.bracket.square.access punctuation.definition.begin.bracket.square", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "0", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp meta.arguments.operator.sizeof meta.bracket.square.access constant.numeric.decimal.cuda-cpp", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #098658", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #098658", + "hc_black": "constant.numeric: #B5CEA8" + } + }, + { + "c": "]", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp meta.arguments.operator.sizeof meta.bracket.square.access punctuation.definition.end.bracket.square", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ".", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp meta.arguments.operator.sizeof punctuation.separator.dot-access.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "structArr", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp meta.arguments.operator.sizeof variable.other.property.cuda-cpp", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": ")", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp punctuation.section.arguments.end.bracket.round.operator.sizeof.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "-", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp keyword.operator.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "1", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp constant.numeric.decimal.cuda-cpp", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #098658", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #098658", + "hc_black": "constant.numeric: #B5CEA8" + } + }, + { + "c": ")", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp punctuation.section.parens.end.bracket.round.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "/", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp keyword.operator.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "2", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp constant.numeric.decimal.cuda-cpp", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #098658", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #098658", + "hc_black": "constant.numeric: #B5CEA8" + } + }, + { + "c": ";", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.terminator.statement.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "for", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp keyword.control.for.cuda-cpp", + "r": { + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "(", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp punctuation.section.parens.begin.bracket.round.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "unsigned", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp storage.type.primitive.cuda-cpp storage.type.built-in.primitive.cuda-cpp", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6" + } + }, + { + "c": " i ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "=", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp keyword.operator.assignment.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "0", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp constant.numeric.decimal.cuda-cpp", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #098658", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #098658", + "hc_black": "constant.numeric: #B5CEA8" + } + }, + { + "c": ";", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp punctuation.terminator.statement.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " i ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "<", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp keyword.operator.comparison.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " dataLength", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ";", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp punctuation.terminator.statement.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " i", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "++", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp keyword.operator.increment.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": ")", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.parens.cuda-cpp punctuation.section.parens.end.bracket.round.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "{", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp punctuation.section.block.begin.bracket.curly.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "if", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp keyword.control.if.cuda-cpp", + "r": { + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "(", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp punctuation.section.parens.begin.bracket.round.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "results", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp meta.bracket.square.access variable.other.object", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": "[", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp meta.bracket.square.access punctuation.definition.begin.bracket.square", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "i", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp meta.bracket.square.access", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "]", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp meta.bracket.square.access punctuation.definition.end.bracket.square", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "!=", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp keyword.operator.comparison.cuda-cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " expectedResult", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ")", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.parens.cuda-cpp punctuation.section.parens.end.bracket.round.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "{", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp punctuation.section.block.begin.bracket.curly.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "fprintf", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp entity.name.function.call.cuda-cpp", + "r": { + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.function: #DCDCAA" + } + }, + { + "c": "(", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp punctuation.section.arguments.begin.bracket.round.function.call.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "stderr", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ",", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp punctuation.separator.delimiter.comma.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "\"", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp string.quoted.double.cuda-cpp punctuation.definition.string.begin.cuda-cpp", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "results[", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp string.quoted.double.cuda-cpp", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "%u", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp string.quoted.double.cuda-cpp constant.other.placeholder", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "] (", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp string.quoted.double.cuda-cpp", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "%u", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp string.quoted.double.cuda-cpp constant.other.placeholder", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": ") != ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp string.quoted.double.cuda-cpp", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "%u", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp string.quoted.double.cuda-cpp constant.other.placeholder", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "\\n", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp string.quoted.double.cuda-cpp constant.character.escape.cuda-cpp", + "r": { + "dark_plus": "constant.character.escape: #D7BA7D", + "light_plus": "constant.character.escape: #EE0000", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "constant.character: #569CD6" + } + }, + { + "c": "\"", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp string.quoted.double.cuda-cpp punctuation.definition.string.end.cuda-cpp", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": ",", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp punctuation.separator.delimiter.comma.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " i", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ",", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp punctuation.separator.delimiter.comma.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "results", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp meta.bracket.square.access variable.other.object", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": "[", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp meta.bracket.square.access punctuation.definition.begin.bracket.square", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "i", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp meta.bracket.square.access", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "]", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp meta.bracket.square.access punctuation.definition.end.bracket.square", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ",", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp punctuation.separator.delimiter.comma.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " expectedResult", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ")", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp punctuation.section.arguments.end.bracket.round.function.call.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ";", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp punctuation.terminator.statement.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "exit", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp entity.name.function.call.cuda-cpp", + "r": { + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.function: #DCDCAA" + } + }, + { + "c": "(", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp punctuation.section.arguments.begin.bracket.round.function.call.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "1", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp constant.numeric.decimal.cuda-cpp", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #098658", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #098658", + "hc_black": "constant.numeric: #B5CEA8" + } + }, + { + "c": ")", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp punctuation.section.arguments.end.bracket.round.function.call.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ";", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp punctuation.terminator.statement.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "}", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp punctuation.section.block.end.bracket.curly.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "}", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp punctuation.section.block.end.bracket.curly.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "assertSucceeded", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp entity.name.function.call.cuda-cpp", + "r": { + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.function: #DCDCAA" + } + }, + { + "c": "(", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.section.arguments.begin.bracket.round.function.call.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "cudaFree", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp entity.name.function.call.cuda-cpp", + "r": { + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.function: #DCDCAA" + } + }, + { + "c": "(", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.section.arguments.begin.bracket.round.function.call.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "input", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "))", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.section.arguments.end.bracket.round.function.call.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ";", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.terminator.statement.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "assertSucceeded", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp entity.name.function.call.cuda-cpp", + "r": { + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.function: #DCDCAA" + } + }, + { + "c": "(", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.section.arguments.begin.bracket.round.function.call.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "cudaFree", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp entity.name.function.call.cuda-cpp", + "r": { + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.function: #DCDCAA" + } + }, + { + "c": "(", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.section.arguments.begin.bracket.round.function.call.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "results", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "))", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.section.arguments.end.bracket.round.function.call.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ";", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.terminator.statement.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "fprintf", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp entity.name.function.call.cuda-cpp", + "r": { + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.function: #DCDCAA" + } + }, + { + "c": "(", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.section.arguments.begin.bracket.round.function.call.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "stderr", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ",", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.separator.delimiter.comma.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "\"", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp string.quoted.double.cuda-cpp punctuation.definition.string.begin.cuda-cpp", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "Success", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp string.quoted.double.cuda-cpp", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "\\n", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp string.quoted.double.cuda-cpp constant.character.escape.cuda-cpp", + "r": { + "dark_plus": "constant.character.escape: #D7BA7D", + "light_plus": "constant.character.escape: #EE0000", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "constant.character: #569CD6" + } + }, + { + "c": "\"", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp string.quoted.double.cuda-cpp punctuation.definition.string.end.cuda-cpp", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": ")", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.section.arguments.end.bracket.round.function.call.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ";", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.terminator.statement.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "exit", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp entity.name.function.call.cuda-cpp", + "r": { + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.function: #DCDCAA" + } + }, + { + "c": "(", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.section.arguments.begin.bracket.round.function.call.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "0", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp constant.numeric.decimal.cuda-cpp", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #098658", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #098658", + "hc_black": "constant.numeric: #B5CEA8" + } + }, + { + "c": ")", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.section.arguments.end.bracket.round.function.call.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ";", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.terminator.statement.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "}", + "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp punctuation.section.block.end.bracket.curly.function.definition.cuda-cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + } +] \ No newline at end of file diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test_ps1.json b/extensions/vscode-colorize-tests/test/colorize-results/test_ps1.json index d12f004f..4f5c535e 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test_ps1.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test_ps1.json @@ -56,7 +56,7 @@ }, { "c": "(", - "t": "source.powershell punctuation.section.group.begin.powershell", + "t": "source.powershell meta.group.simple.subexpression.powershell punctuation.section.group.begin.powershell", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -67,7 +67,7 @@ }, { "c": ")", - "t": "source.powershell punctuation.section.group.end.powershell", + "t": "source.powershell meta.group.simple.subexpression.powershell punctuation.section.group.end.powershell", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -254,7 +254,7 @@ }, { "c": "(", - "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell punctuation.section.group.begin.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.group.simple.subexpression.powershell punctuation.section.group.begin.powershell", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -265,7 +265,7 @@ }, { "c": ")", - "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell punctuation.section.group.end.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.group.simple.subexpression.powershell punctuation.section.group.end.powershell", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -474,7 +474,7 @@ }, { "c": "(", - "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell punctuation.section.group.begin.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.group.simple.subexpression.powershell punctuation.section.group.begin.powershell", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -485,7 +485,7 @@ }, { "c": " ", - "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell interpolated.simple.source.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.group.simple.subexpression.powershell", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -496,7 +496,7 @@ }, { "c": "[", - "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell interpolated.simple.source.powershell punctuation.section.bracket.begin.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.group.simple.subexpression.powershell punctuation.section.bracket.begin.powershell", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -507,7 +507,7 @@ }, { "c": "Security.Principal.WindowsBuiltInRole", - "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell interpolated.simple.source.powershell storage.type.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.group.simple.subexpression.powershell storage.type.powershell", "r": { "dark_plus": "storage.type: #569CD6", "light_plus": "storage.type: #0000FF", @@ -518,7 +518,7 @@ }, { "c": "]", - "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell interpolated.simple.source.powershell punctuation.section.bracket.end.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.group.simple.subexpression.powershell punctuation.section.bracket.end.powershell", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -529,7 +529,7 @@ }, { "c": "::Administrator ", - "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell interpolated.simple.source.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.group.simple.subexpression.powershell", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -540,7 +540,7 @@ }, { "c": ")", - "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell punctuation.section.group.end.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.group.simple.subexpression.powershell punctuation.section.group.end.powershell", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -804,7 +804,7 @@ }, { "c": "(", - "t": "source.powershell punctuation.section.group.begin.powershell", + "t": "source.powershell meta.group.simple.subexpression.powershell punctuation.section.group.begin.powershell", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -815,7 +815,7 @@ }, { "c": ")", - "t": "source.powershell punctuation.section.group.end.powershell", + "t": "source.powershell meta.group.simple.subexpression.powershell punctuation.section.group.end.powershell", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -870,7 +870,7 @@ }, { "c": "(", - "t": "source.powershell meta.scriptblock.powershell punctuation.section.group.begin.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.group.simple.subexpression.powershell punctuation.section.group.begin.powershell", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -881,7 +881,7 @@ }, { "c": " ", - "t": "source.powershell meta.scriptblock.powershell interpolated.simple.source.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.group.simple.subexpression.powershell", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -892,7 +892,7 @@ }, { "c": "[", - "t": "source.powershell meta.scriptblock.powershell interpolated.simple.source.powershell meta.attribute.powershell punctuation.section.bracket.begin.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.group.simple.subexpression.powershell meta.attribute.powershell punctuation.section.bracket.begin.powershell", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -903,7 +903,7 @@ }, { "c": "Parameter", - "t": "source.powershell meta.scriptblock.powershell interpolated.simple.source.powershell meta.attribute.powershell support.function.attribute.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.group.simple.subexpression.powershell meta.attribute.powershell support.function.attribute.powershell", "r": { "dark_plus": "support.function: #DCDCAA", "light_plus": "support.function: #795E26", @@ -914,7 +914,7 @@ }, { "c": "(", - "t": "source.powershell meta.scriptblock.powershell interpolated.simple.source.powershell meta.attribute.powershell punctuation.section.group.begin.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.group.simple.subexpression.powershell meta.attribute.powershell punctuation.section.group.begin.powershell", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -925,7 +925,7 @@ }, { "c": "Mandatory", - "t": "source.powershell meta.scriptblock.powershell interpolated.simple.source.powershell meta.attribute.powershell variable.parameter.attribute.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.group.simple.subexpression.powershell meta.attribute.powershell variable.parameter.attribute.powershell", "r": { "dark_plus": "variable: #9CDCFE", "light_plus": "variable: #001080", @@ -936,7 +936,7 @@ }, { "c": "=", - "t": "source.powershell meta.scriptblock.powershell interpolated.simple.source.powershell meta.attribute.powershell keyword.operator.assignment.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.group.simple.subexpression.powershell meta.attribute.powershell keyword.operator.assignment.powershell", "r": { "dark_plus": "keyword.operator: #D4D4D4", "light_plus": "keyword.operator: #000000", @@ -947,7 +947,7 @@ }, { "c": "1", - "t": "source.powershell meta.scriptblock.powershell interpolated.simple.source.powershell meta.attribute.powershell constant.numeric.integer.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.group.simple.subexpression.powershell meta.attribute.powershell constant.numeric.integer.powershell", "r": { "dark_plus": "constant.numeric: #B5CEA8", "light_plus": "constant.numeric: #098658", @@ -958,7 +958,7 @@ }, { "c": ")", - "t": "source.powershell meta.scriptblock.powershell interpolated.simple.source.powershell meta.attribute.powershell punctuation.section.group.end.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.group.simple.subexpression.powershell meta.attribute.powershell punctuation.section.group.end.powershell", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -969,7 +969,7 @@ }, { "c": "]", - "t": "source.powershell meta.scriptblock.powershell interpolated.simple.source.powershell meta.attribute.powershell punctuation.section.bracket.end.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.group.simple.subexpression.powershell meta.attribute.powershell punctuation.section.bracket.end.powershell", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -980,7 +980,7 @@ }, { "c": "[", - "t": "source.powershell meta.scriptblock.powershell interpolated.simple.source.powershell punctuation.section.bracket.begin.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.group.simple.subexpression.powershell punctuation.section.bracket.begin.powershell", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -991,7 +991,7 @@ }, { "c": "string", - "t": "source.powershell meta.scriptblock.powershell interpolated.simple.source.powershell storage.type.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.group.simple.subexpression.powershell storage.type.powershell", "r": { "dark_plus": "storage.type: #569CD6", "light_plus": "storage.type: #0000FF", @@ -1002,7 +1002,7 @@ }, { "c": "]", - "t": "source.powershell meta.scriptblock.powershell interpolated.simple.source.powershell punctuation.section.bracket.end.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.group.simple.subexpression.powershell punctuation.section.bracket.end.powershell", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1013,7 +1013,7 @@ }, { "c": "$", - "t": "source.powershell meta.scriptblock.powershell interpolated.simple.source.powershell variable.other.readwrite.powershell punctuation.definition.variable.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.group.simple.subexpression.powershell variable.other.readwrite.powershell punctuation.definition.variable.powershell", "r": { "dark_plus": "variable: #9CDCFE", "light_plus": "variable: #001080", @@ -1024,7 +1024,7 @@ }, { "c": "Command", - "t": "source.powershell meta.scriptblock.powershell interpolated.simple.source.powershell variable.other.readwrite.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.group.simple.subexpression.powershell variable.other.readwrite.powershell", "r": { "dark_plus": "variable: #9CDCFE", "light_plus": "variable: #001080", @@ -1035,7 +1035,7 @@ }, { "c": " ", - "t": "source.powershell meta.scriptblock.powershell interpolated.simple.source.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.group.simple.subexpression.powershell", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1046,7 +1046,7 @@ }, { "c": ")", - "t": "source.powershell meta.scriptblock.powershell punctuation.section.group.end.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.group.simple.subexpression.powershell punctuation.section.group.end.powershell", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1079,7 +1079,7 @@ }, { "c": "(", - "t": "source.powershell meta.scriptblock.powershell punctuation.section.group.begin.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.group.simple.subexpression.powershell punctuation.section.group.begin.powershell", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1090,7 +1090,7 @@ }, { "c": "$", - "t": "source.powershell meta.scriptblock.powershell interpolated.simple.source.powershell support.variable.automatic.powershell punctuation.definition.variable.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.group.simple.subexpression.powershell support.variable.automatic.powershell punctuation.definition.variable.powershell", "r": { "dark_plus": "support.variable: #9CDCFE", "light_plus": "support.variable: #001080", @@ -1101,7 +1101,7 @@ }, { "c": "_", - "t": "source.powershell meta.scriptblock.powershell interpolated.simple.source.powershell support.variable.automatic.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.group.simple.subexpression.powershell support.variable.automatic.powershell", "r": { "dark_plus": "support.variable: #9CDCFE", "light_plus": "support.variable: #001080", @@ -1112,7 +1112,7 @@ }, { "c": " ", - "t": "source.powershell meta.scriptblock.powershell interpolated.simple.source.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.group.simple.subexpression.powershell", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1123,7 +1123,7 @@ }, { "c": "in", - "t": "source.powershell meta.scriptblock.powershell interpolated.simple.source.powershell keyword.control.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.group.simple.subexpression.powershell keyword.control.powershell", "r": { "dark_plus": "keyword.control: #C586C0", "light_plus": "keyword.control: #AF00DB", @@ -1134,7 +1134,7 @@ }, { "c": " cmd ", - "t": "source.powershell meta.scriptblock.powershell interpolated.simple.source.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.group.simple.subexpression.powershell", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1145,7 +1145,7 @@ }, { "c": "/", - "t": "source.powershell meta.scriptblock.powershell interpolated.simple.source.powershell keyword.operator.assignment.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.group.simple.subexpression.powershell keyword.operator.assignment.powershell", "r": { "dark_plus": "keyword.operator: #D4D4D4", "light_plus": "keyword.operator: #000000", @@ -1156,7 +1156,7 @@ }, { "c": "c ", - "t": "source.powershell meta.scriptblock.powershell interpolated.simple.source.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.group.simple.subexpression.powershell", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1167,7 +1167,7 @@ }, { "c": "\"", - "t": "source.powershell meta.scriptblock.powershell interpolated.simple.source.powershell string.quoted.double.powershell punctuation.definition.string.begin.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.group.simple.subexpression.powershell string.quoted.double.powershell punctuation.definition.string.begin.powershell", "r": { "dark_plus": "string: #CE9178", "light_plus": "string: #A31515", @@ -1178,7 +1178,7 @@ }, { "c": "$", - "t": "source.powershell meta.scriptblock.powershell interpolated.simple.source.powershell string.quoted.double.powershell variable.other.readwrite.powershell punctuation.definition.variable.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.group.simple.subexpression.powershell string.quoted.double.powershell variable.other.readwrite.powershell punctuation.definition.variable.powershell", "r": { "dark_plus": "variable: #9CDCFE", "light_plus": "variable: #001080", @@ -1189,7 +1189,7 @@ }, { "c": "Command", - "t": "source.powershell meta.scriptblock.powershell interpolated.simple.source.powershell string.quoted.double.powershell variable.other.readwrite.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.group.simple.subexpression.powershell string.quoted.double.powershell variable.other.readwrite.powershell", "r": { "dark_plus": "variable: #9CDCFE", "light_plus": "variable: #001080", @@ -1200,7 +1200,7 @@ }, { "c": " 2>&1 & set", - "t": "source.powershell meta.scriptblock.powershell interpolated.simple.source.powershell string.quoted.double.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.group.simple.subexpression.powershell string.quoted.double.powershell", "r": { "dark_plus": "string: #CE9178", "light_plus": "string: #A31515", @@ -1211,7 +1211,7 @@ }, { "c": "\"", - "t": "source.powershell meta.scriptblock.powershell interpolated.simple.source.powershell string.quoted.double.powershell punctuation.definition.string.end.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.group.simple.subexpression.powershell string.quoted.double.powershell punctuation.definition.string.end.powershell", "r": { "dark_plus": "string: #CE9178", "light_plus": "string: #A31515", @@ -1222,7 +1222,7 @@ }, { "c": ")", - "t": "source.powershell meta.scriptblock.powershell punctuation.section.group.end.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.group.simple.subexpression.powershell punctuation.section.group.end.powershell", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1288,7 +1288,7 @@ }, { "c": "(", - "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell punctuation.section.group.begin.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.group.simple.subexpression.powershell punctuation.section.group.begin.powershell", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1299,7 +1299,7 @@ }, { "c": "$", - "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell interpolated.simple.source.powershell support.variable.automatic.powershell punctuation.definition.variable.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.group.simple.subexpression.powershell support.variable.automatic.powershell punctuation.definition.variable.powershell", "r": { "dark_plus": "support.variable: #9CDCFE", "light_plus": "support.variable: #001080", @@ -1310,7 +1310,7 @@ }, { "c": "_", - "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell interpolated.simple.source.powershell support.variable.automatic.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.group.simple.subexpression.powershell support.variable.automatic.powershell", "r": { "dark_plus": "support.variable: #9CDCFE", "light_plus": "support.variable: #001080", @@ -1321,7 +1321,7 @@ }, { "c": " ", - "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell interpolated.simple.source.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.group.simple.subexpression.powershell", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1332,7 +1332,7 @@ }, { "c": "-match", - "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell interpolated.simple.source.powershell keyword.operator.comparison.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.group.simple.subexpression.powershell keyword.operator.comparison.powershell", "r": { "dark_plus": "keyword.operator: #D4D4D4", "light_plus": "keyword.operator: #000000", @@ -1343,7 +1343,7 @@ }, { "c": " ", - "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell interpolated.simple.source.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.group.simple.subexpression.powershell", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1354,7 +1354,7 @@ }, { "c": "'", - "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell interpolated.simple.source.powershell string.quoted.single.powershell punctuation.definition.string.begin.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.group.simple.subexpression.powershell string.quoted.single.powershell punctuation.definition.string.begin.powershell", "r": { "dark_plus": "string: #CE9178", "light_plus": "string: #A31515", @@ -1365,7 +1365,7 @@ }, { "c": "^([^=]+)=(.*)", - "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell interpolated.simple.source.powershell string.quoted.single.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.group.simple.subexpression.powershell string.quoted.single.powershell", "r": { "dark_plus": "string: #CE9178", "light_plus": "string: #A31515", @@ -1376,7 +1376,7 @@ }, { "c": "'", - "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell interpolated.simple.source.powershell string.quoted.single.powershell punctuation.definition.string.end.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.group.simple.subexpression.powershell string.quoted.single.powershell punctuation.definition.string.end.powershell", "r": { "dark_plus": "string: #CE9178", "light_plus": "string: #A31515", @@ -1387,7 +1387,7 @@ }, { "c": ")", - "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell punctuation.section.group.end.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.group.simple.subexpression.powershell punctuation.section.group.end.powershell", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1475,7 +1475,7 @@ }, { "c": "(", - "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.scriptblock.powershell punctuation.section.group.begin.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.group.simple.subexpression.powershell punctuation.section.group.begin.powershell", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1486,7 +1486,7 @@ }, { "c": "$", - "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.scriptblock.powershell interpolated.simple.source.powershell support.variable.automatic.powershell punctuation.definition.variable.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.group.simple.subexpression.powershell support.variable.automatic.powershell punctuation.definition.variable.powershell", "r": { "dark_plus": "support.variable: #9CDCFE", "light_plus": "support.variable: #001080", @@ -1497,7 +1497,7 @@ }, { "c": "matches", - "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.scriptblock.powershell interpolated.simple.source.powershell support.variable.automatic.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.group.simple.subexpression.powershell support.variable.automatic.powershell", "r": { "dark_plus": "support.variable: #9CDCFE", "light_plus": "support.variable: #001080", @@ -1508,7 +1508,7 @@ }, { "c": "[", - "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.scriptblock.powershell interpolated.simple.source.powershell punctuation.section.bracket.begin.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.group.simple.subexpression.powershell punctuation.section.bracket.begin.powershell", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1519,7 +1519,7 @@ }, { "c": "1", - "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.scriptblock.powershell interpolated.simple.source.powershell constant.numeric.integer.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.group.simple.subexpression.powershell constant.numeric.integer.powershell", "r": { "dark_plus": "constant.numeric: #B5CEA8", "light_plus": "constant.numeric: #098658", @@ -1530,7 +1530,7 @@ }, { "c": "]", - "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.scriptblock.powershell interpolated.simple.source.powershell punctuation.section.bracket.end.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.group.simple.subexpression.powershell punctuation.section.bracket.end.powershell", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1541,7 +1541,7 @@ }, { "c": ",", - "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.scriptblock.powershell interpolated.simple.source.powershell keyword.operator.other.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.group.simple.subexpression.powershell keyword.operator.other.powershell", "r": { "dark_plus": "keyword.operator: #D4D4D4", "light_plus": "keyword.operator: #000000", @@ -1552,7 +1552,7 @@ }, { "c": " ", - "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.scriptblock.powershell interpolated.simple.source.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.group.simple.subexpression.powershell", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1563,7 +1563,7 @@ }, { "c": "$", - "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.scriptblock.powershell interpolated.simple.source.powershell support.variable.automatic.powershell punctuation.definition.variable.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.group.simple.subexpression.powershell support.variable.automatic.powershell punctuation.definition.variable.powershell", "r": { "dark_plus": "support.variable: #9CDCFE", "light_plus": "support.variable: #001080", @@ -1574,7 +1574,7 @@ }, { "c": "matches", - "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.scriptblock.powershell interpolated.simple.source.powershell support.variable.automatic.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.group.simple.subexpression.powershell support.variable.automatic.powershell", "r": { "dark_plus": "support.variable: #9CDCFE", "light_plus": "support.variable: #001080", @@ -1585,7 +1585,7 @@ }, { "c": "[", - "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.scriptblock.powershell interpolated.simple.source.powershell punctuation.section.bracket.begin.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.group.simple.subexpression.powershell punctuation.section.bracket.begin.powershell", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1596,7 +1596,7 @@ }, { "c": "2", - "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.scriptblock.powershell interpolated.simple.source.powershell constant.numeric.integer.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.group.simple.subexpression.powershell constant.numeric.integer.powershell", "r": { "dark_plus": "constant.numeric: #B5CEA8", "light_plus": "constant.numeric: #098658", @@ -1607,7 +1607,7 @@ }, { "c": "]", - "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.scriptblock.powershell interpolated.simple.source.powershell punctuation.section.bracket.end.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.group.simple.subexpression.powershell punctuation.section.bracket.end.powershell", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1618,7 +1618,7 @@ }, { "c": ")", - "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.scriptblock.powershell punctuation.section.group.end.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.group.simple.subexpression.powershell punctuation.section.group.end.powershell", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1816,7 +1816,7 @@ }, { "c": "(", - "t": "source.powershell punctuation.section.group.begin.powershell", + "t": "source.powershell meta.group.simple.subexpression.powershell punctuation.section.group.begin.powershell", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1827,7 +1827,7 @@ }, { "c": "!", - "t": "source.powershell interpolated.simple.source.powershell keyword.operator.unary.powershell", + "t": "source.powershell meta.group.simple.subexpression.powershell keyword.operator.unary.powershell", "r": { "dark_plus": "keyword.operator: #D4D4D4", "light_plus": "keyword.operator: #000000", @@ -1838,7 +1838,7 @@ }, { "c": "(", - "t": "source.powershell interpolated.simple.source.powershell punctuation.section.group.begin.powershell", + "t": "source.powershell meta.group.simple.subexpression.powershell meta.group.simple.subexpression.powershell punctuation.section.group.begin.powershell", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1849,7 +1849,7 @@ }, { "c": "Test-IsAdmin", - "t": "source.powershell interpolated.simple.source.powershell interpolated.simple.source.powershell support.function.powershell", + "t": "source.powershell meta.group.simple.subexpression.powershell meta.group.simple.subexpression.powershell support.function.powershell", "r": { "dark_plus": "support.function: #DCDCAA", "light_plus": "support.function: #795E26", @@ -1860,7 +1860,7 @@ }, { "c": ")", - "t": "source.powershell interpolated.simple.source.powershell punctuation.section.group.end.powershell", + "t": "source.powershell meta.group.simple.subexpression.powershell meta.group.simple.subexpression.powershell punctuation.section.group.end.powershell", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1871,7 +1871,7 @@ }, { "c": ")", - "t": "source.powershell punctuation.section.group.end.powershell", + "t": "source.powershell meta.group.simple.subexpression.powershell punctuation.section.group.end.powershell", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -2234,7 +2234,7 @@ }, { "c": "(", - "t": "source.powershell punctuation.section.group.begin.powershell", + "t": "source.powershell meta.group.simple.subexpression.powershell punctuation.section.group.begin.powershell", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -2245,7 +2245,7 @@ }, { "c": "Test-Path", - "t": "source.powershell interpolated.simple.source.powershell support.function.powershell", + "t": "source.powershell meta.group.simple.subexpression.powershell support.function.powershell", "r": { "dark_plus": "support.function: #DCDCAA", "light_plus": "support.function: #795E26", @@ -2256,7 +2256,7 @@ }, { "c": " ", - "t": "source.powershell interpolated.simple.source.powershell", + "t": "source.powershell meta.group.simple.subexpression.powershell", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -2267,7 +2267,7 @@ }, { "c": "-", - "t": "source.powershell interpolated.simple.source.powershell keyword.operator.assignment.powershell", + "t": "source.powershell meta.group.simple.subexpression.powershell keyword.operator.assignment.powershell", "r": { "dark_plus": "keyword.operator: #D4D4D4", "light_plus": "keyword.operator: #000000", @@ -2278,7 +2278,7 @@ }, { "c": "Path ", - "t": "source.powershell interpolated.simple.source.powershell", + "t": "source.powershell meta.group.simple.subexpression.powershell", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -2289,7 +2289,7 @@ }, { "c": "\"", - "t": "source.powershell interpolated.simple.source.powershell string.quoted.double.powershell punctuation.definition.string.begin.powershell", + "t": "source.powershell meta.group.simple.subexpression.powershell string.quoted.double.powershell punctuation.definition.string.begin.powershell", "r": { "dark_plus": "string: #CE9178", "light_plus": "string: #A31515", @@ -2300,7 +2300,7 @@ }, { "c": "$", - "t": "source.powershell interpolated.simple.source.powershell string.quoted.double.powershell variable.other.readwrite.powershell punctuation.definition.variable.powershell", + "t": "source.powershell meta.group.simple.subexpression.powershell string.quoted.double.powershell variable.other.readwrite.powershell punctuation.definition.variable.powershell", "r": { "dark_plus": "variable: #9CDCFE", "light_plus": "variable: #001080", @@ -2311,7 +2311,7 @@ }, { "c": "env:", - "t": "source.powershell interpolated.simple.source.powershell string.quoted.double.powershell variable.other.readwrite.powershell support.variable.drive.powershell", + "t": "source.powershell meta.group.simple.subexpression.powershell string.quoted.double.powershell variable.other.readwrite.powershell support.variable.drive.powershell", "r": { "dark_plus": "support.variable: #9CDCFE", "light_plus": "support.variable: #001080", @@ -2322,7 +2322,7 @@ }, { "c": "ADXSDKProgramFiles", - "t": "source.powershell interpolated.simple.source.powershell string.quoted.double.powershell variable.other.readwrite.powershell", + "t": "source.powershell meta.group.simple.subexpression.powershell string.quoted.double.powershell variable.other.readwrite.powershell", "r": { "dark_plus": "variable: #9CDCFE", "light_plus": "variable: #001080", @@ -2333,7 +2333,7 @@ }, { "c": "\\Microsoft Visual Studio 12.0", - "t": "source.powershell interpolated.simple.source.powershell string.quoted.double.powershell", + "t": "source.powershell meta.group.simple.subexpression.powershell string.quoted.double.powershell", "r": { "dark_plus": "string: #CE9178", "light_plus": "string: #A31515", @@ -2344,7 +2344,7 @@ }, { "c": "\"", - "t": "source.powershell interpolated.simple.source.powershell string.quoted.double.powershell punctuation.definition.string.end.powershell", + "t": "source.powershell meta.group.simple.subexpression.powershell string.quoted.double.powershell punctuation.definition.string.end.powershell", "r": { "dark_plus": "string: #CE9178", "light_plus": "string: #A31515", @@ -2355,7 +2355,7 @@ }, { "c": ")", - "t": "source.powershell punctuation.section.group.end.powershell", + "t": "source.powershell meta.group.simple.subexpression.powershell punctuation.section.group.end.powershell", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -2892,4 +2892,4 @@ "hc_black": "default: #FFFFFF" } } -] +] \ No newline at end of file diff --git a/extensions/vscode-colorize-tests/tsconfig.json b/extensions/vscode-colorize-tests/tsconfig.json index 296ddb38..070854d6 100644 --- a/extensions/vscode-colorize-tests/tsconfig.json +++ b/extensions/vscode-colorize-tests/tsconfig.json @@ -1,9 +1,9 @@ { - "extends": "../shared.tsconfig.json", + "extends": "../tsconfig.base.json", "compilerOptions": { "outDir": "./out" }, "include": [ "src/**/*" ] -} \ No newline at end of file +} diff --git a/extensions/vscode-custom-editor-tests/media/icon.png b/extensions/vscode-custom-editor-tests/media/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..f785ef7031624d04d821a12d5f1534ecccfef491 GIT binary patch literal 2210 zcmY*bc{tSD8$aI}!x-BnOVKh?jjlCH3uR;sS}es?-O7+kNe0PS=WFSfa0#VCqq?E8 zrYwaSx|S@zQ9@eGSh8fYMPrzm?;Y-6zw^A$`F!5xInU>P-t(O2#5+6MDdDtm001Qi zd-7obASs0a7A*zef}^fdc;S4|)mDlC=76vk5Zbo1hqZ#RHW1Mc!lmxAMJz}DdxhBCV00mfSD~OY7m5z6hd3z%kkzuSRqHWL>=NFrX zeXiWg2oCDIhM&@n5V1A>r3@QE*~O`wpQYHZ*J}Q%w#BCqzIm$NkZlms5rs^i=?Kf zQznejSKBf)$LVPn3bl4wdFYOe_zsC8lCqe^NhwSOMbx{p-9Pie@ooN3QC;3%5@ZZ9 zdk%g7(OqqH8XQ9)@3n6*v6X=;fazL~`oO|+RyE%hVcOiW3M7FRcGbZR3}~uwSY91{ z?}ak(#70$w+#mNFw+CyRVI@V(^k>&&FNP|&dhYJ5T9j~JR&We!(;B1AI_5>~#X*Ub z!-`03xI@Wq==#-n@+7iF4@t2wDU)Q`TbB`fJ-lttGkrp7Nq}Y|@%Tw7zHwB;p8O0A zPf_GRz{s#Hdx3uBGv5BW3hX++tY$49M;!0j%@=xC zHuj3t{hJnj72u>Vs`RLL;<~>CJsO1l2GK)KCuSrqC~smguKGK@Jl`CYAI@V-rZdFb zWv{m>;tfh{gQ59Hq=W%XdA_Z|S3;IwAhM!grrbHyq3n28J|EdN zrlWNxXA@iRz;2A%!1=Waq}Zz;gEQQ+WIlOGvcID)@0u#eV*NsK4irk&Vz#ZndSgM~ zm^aiznkjzB-p60_(L+EyHM&g|j~3!M||zCrSL>*Ba#2~4SH#(Dgd>_)GFn?e)UDhTzJ zc{j`X~7EJ;}49U_HW-!ej40r_ZQVWFRDtMiTm!U@HsPW^_9YoSTA!?|Y zxls++HF6JvHTpmEfL_DVTUL->Hca;)No4u2!owG)~xc#Zz;a$j%uhXCE z9a037`EV&1DrS*lj(+fnjK-E0NE=_#r}4`jp-tg89kr^NaEc(+DB|Oy^lCiCcCH73 z@tn={p(M#@|G|rm{&Yr7$W?9nriuYSI-^0wE}M6QH7K6jyY8An<=_uGBO96#F`Fa! zk{0RcOxdc`35U_IwoO|fE&%3ea6Ae_kv7`#$D1u?`MCW(h8H_3Q9q&(`-xP#pu!^#JAeIA9eZ+^0vgL7p41q?8?Z1z{nYW zr)S{-GrziyB~y06T$)e1d=&9+WXP+j4JDK=%RHc@(J%6HZ%_riz|~06k&|p_g@W*z z5k49&PiGCI)?sN4B6ZC>JY#Yw!z+(2i?{CcSnTZAAkmaB8igVWL4{_dOUf5tO*}V6 zu#CLyF(*tdpUbrGRERkD=G$F~D=csicBiKbt7U{opgs>bcm&1GATS03IE(rUG|= z$_$zL3%4mmP`dQaP=itU1CLh1cT6YLLs7E2O%F6yC7xKRIQ644F8s|}Ic{Bgk`ed^ zNj!m`meD1G`?r!_Q!R~Fp9Jw+dMoAsDQh-22lsO&I|i|_nGfWkn43x|#G`Y>7&pd- z|AT=K%@R(~?NZf1w1GxZ;)DS((5y7G-ueaCRTk)+O7#|wOs`f?+oN`Fzj>}zvIY6E zqVlCbI}>ql4e=Uzbz=ETsniQ|JyzHWW8SblMIu^|v(Dn#^`|8@FwcegdQXnr-P^CS z(Fiu#sp5r>tCI<_9IYdEE~K;n-rN7^W)-Kix&e`0u{@1z&a+B2cv_r2K8gXO!erx!e z?Z=K9nngudTWX2w>-q*Xl2qQfByQQX5R|7j_zo}XZO=qxGT}IST0OaF>9gTr>qsuz HM~(Rx0c1bk literal 0 HcmV?d00001 diff --git a/extensions/vscode-custom-editor-tests/package.json b/extensions/vscode-custom-editor-tests/package.json index 8feb93ba..c718e324 100644 --- a/extensions/vscode-custom-editor-tests/package.json +++ b/extensions/vscode-custom-editor-tests/package.json @@ -13,6 +13,7 @@ "engines": { "vscode": "^1.48.0" }, + "icon": "media/icon.png", "scripts": { "compile": "node ./node_modules/vscode/bin/compile -watch -p ./", "vscode:prepublish": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:vscode-notebook-tests ./tsconfig.json" diff --git a/extensions/vscode-custom-editor-tests/src/customTextEditor.ts b/extensions/vscode-custom-editor-tests/src/customTextEditor.ts index cced14b9..c2b3754b 100644 --- a/extensions/vscode-custom-editor-tests/src/customTextEditor.ts +++ b/extensions/vscode-custom-editor-tests/src/customTextEditor.ts @@ -121,7 +121,7 @@ class AbcEditor extends Disposable { }); } - public dispose() { + public override dispose() { if (this.isDisposed) { return; } diff --git a/extensions/vscode-custom-editor-tests/src/test/customEditor.test.ts b/extensions/vscode-custom-editor-tests/src/test/customEditor.test.ts index d66ab665..b4725c22 100644 --- a/extensions/vscode-custom-editor-tests/src/test/customEditor.test.ts +++ b/extensions/vscode-custom-editor-tests/src/test/customEditor.test.ts @@ -264,7 +264,8 @@ suite('CustomEditor tests', () => { await vscode.commands.executeCommand(commands.open, testDocument, { preview: false }); const { content } = await listener.nextResponse(); assert.strictEqual(content, startingContent.toString()); - assert.ok(!vscode.window.activeTextEditor); + const activeEditor = vscode.window.activeTextEditor; + assert.ok(!activeEditor); } // Switch to non-default editor diff --git a/extensions/vscode-custom-editor-tests/src/test/index.ts b/extensions/vscode-custom-editor-tests/src/test/index.ts index e37bf5be..4a5238f7 100644 --- a/extensions/vscode-custom-editor-tests/src/test/index.ts +++ b/extensions/vscode-custom-editor-tests/src/test/index.ts @@ -10,8 +10,8 @@ const suite = 'Custom Editor Tests'; const options: any = { ui: 'tdd', - color: (!process.env.BUILD_ARTIFACTSTAGINGDIRECTORY && process.platform !== 'win32'), - timeout: 6000000 + color: true, + timeout: 60000 }; if (process.env.BUILD_ARTIFACTSTAGINGDIRECTORY) { diff --git a/extensions/vscode-custom-editor-tests/tsconfig.json b/extensions/vscode-custom-editor-tests/tsconfig.json index 296ddb38..070854d6 100644 --- a/extensions/vscode-custom-editor-tests/tsconfig.json +++ b/extensions/vscode-custom-editor-tests/tsconfig.json @@ -1,9 +1,9 @@ { - "extends": "../shared.tsconfig.json", + "extends": "../tsconfig.base.json", "compilerOptions": { "outDir": "./out" }, "include": [ "src/**/*" ] -} \ No newline at end of file +} diff --git a/extensions/vscode-notebook-tests/media/icon.png b/extensions/vscode-notebook-tests/media/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..f785ef7031624d04d821a12d5f1534ecccfef491 GIT binary patch literal 2210 zcmY*bc{tSD8$aI}!x-BnOVKh?jjlCH3uR;sS}es?-O7+kNe0PS=WFSfa0#VCqq?E8 zrYwaSx|S@zQ9@eGSh8fYMPrzm?;Y-6zw^A$`F!5xInU>P-t(O2#5+6MDdDtm001Qi zd-7obASs0a7A*zef}^fdc;S4|)mDlC=76vk5Zbo1hqZ#RHW1Mc!lmxAMJz}DdxhBCV00mfSD~OY7m5z6hd3z%kkzuSRqHWL>=NFrX zeXiWg2oCDIhM&@n5V1A>r3@QE*~O`wpQYHZ*J}Q%w#BCqzIm$NkZlms5rs^i=?Kf zQznejSKBf)$LVPn3bl4wdFYOe_zsC8lCqe^NhwSOMbx{p-9Pie@ooN3QC;3%5@ZZ9 zdk%g7(OqqH8XQ9)@3n6*v6X=;fazL~`oO|+RyE%hVcOiW3M7FRcGbZR3}~uwSY91{ z?}ak(#70$w+#mNFw+CyRVI@V(^k>&&FNP|&dhYJ5T9j~JR&We!(;B1AI_5>~#X*Ub z!-`03xI@Wq==#-n@+7iF4@t2wDU)Q`TbB`fJ-lttGkrp7Nq}Y|@%Tw7zHwB;p8O0A zPf_GRz{s#Hdx3uBGv5BW3hX++tY$49M;!0j%@=xC zHuj3t{hJnj72u>Vs`RLL;<~>CJsO1l2GK)KCuSrqC~smguKGK@Jl`CYAI@V-rZdFb zWv{m>;tfh{gQ59Hq=W%XdA_Z|S3;IwAhM!grrbHyq3n28J|EdN zrlWNxXA@iRz;2A%!1=Waq}Zz;gEQQ+WIlOGvcID)@0u#eV*NsK4irk&Vz#ZndSgM~ zm^aiznkjzB-p60_(L+EyHM&g|j~3!M||zCrSL>*Ba#2~4SH#(Dgd>_)GFn?e)UDhTzJ zc{j`X~7EJ;}49U_HW-!ej40r_ZQVWFRDtMiTm!U@HsPW^_9YoSTA!?|Y zxls++HF6JvHTpmEfL_DVTUL->Hca;)No4u2!owG)~xc#Zz;a$j%uhXCE z9a037`EV&1DrS*lj(+fnjK-E0NE=_#r}4`jp-tg89kr^NaEc(+DB|Oy^lCiCcCH73 z@tn={p(M#@|G|rm{&Yr7$W?9nriuYSI-^0wE}M6QH7K6jyY8An<=_uGBO96#F`Fa! zk{0RcOxdc`35U_IwoO|fE&%3ea6Ae_kv7`#$D1u?`MCW(h8H_3Q9q&(`-xP#pu!^#JAeIA9eZ+^0vgL7p41q?8?Z1z{nYW zr)S{-GrziyB~y06T$)e1d=&9+WXP+j4JDK=%RHc@(J%6HZ%_riz|~06k&|p_g@W*z z5k49&PiGCI)?sN4B6ZC>JY#Yw!z+(2i?{CcSnTZAAkmaB8igVWL4{_dOUf5tO*}V6 zu#CLyF(*tdpUbrGRERkD=G$F~D=csicBiKbt7U{opgs>bcm&1GATS03IE(rUG|= z$_$zL3%4mmP`dQaP=itU1CLh1cT6YLLs7E2O%F6yC7xKRIQ644F8s|}Ic{Bgk`ed^ zNj!m`meD1G`?r!_Q!R~Fp9Jw+dMoAsDQh-22lsO&I|i|_nGfWkn43x|#G`Y>7&pd- z|AT=K%@R(~?NZf1w1GxZ;)DS((5y7G-ueaCRTk)+O7#|wOs`f?+oN`Fzj>}zvIY6E zqVlCbI}>ql4e=Uzbz=ETsniQ|JyzHWW8SblMIu^|v(Dn#^`|8@FwcegdQXnr-P^CS z(Fiu#sp5r>tCI<_9IYdEE~K;n-rN7^W)-Kix&e`0u{@1z&a+B2cv_r2K8gXO!erx!e z?Z=K9nngudTWX2w>-q*Xl2qQfByQQX5R|7j_zo}XZO=qxGT}IST0OaF>9gTr>qsuz HM~(Rx0c1bk literal 0 HcmV?d00001 diff --git a/extensions/vscode-notebook-tests/package.json b/extensions/vscode-notebook-tests/package.json index 78164843..f1bdd988 100644 --- a/extensions/vscode-notebook-tests/package.json +++ b/extensions/vscode-notebook-tests/package.json @@ -13,6 +13,7 @@ "engines": { "vscode": "^1.25.0" }, + "icon": "media/icon.png", "scripts": { "compile": "node ./node_modules/vscode/bin/compile -watch -p ./", "vscode:prepublish": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:vscode-notebook-tests ./tsconfig.json" diff --git a/extensions/vscode-notebook-tests/src/extension.ts b/extensions/vscode-notebook-tests/src/extension.ts index 3338813d..ff0b4981 100644 --- a/extensions/vscode-notebook-tests/src/extension.ts +++ b/extensions/vscode-notebook-tests/src/extension.ts @@ -44,9 +44,6 @@ export function activate(context: vscode.ExtensionContext): any { return dto; }, - resolveNotebook: async (_document: vscode.NotebookDocument) => { - return; - }, saveNotebook: async (_document: vscode.NotebookDocument, _cancellation: vscode.CancellationToken) => { return; }, @@ -61,17 +58,15 @@ export function activate(context: vscode.ExtensionContext): any { } })); - const kernel: vscode.NotebookKernel = { - id: 'notebookSmokeTest', - label: 'notebookSmokeTest', - isPreferred: true, - executeCellsRequest: async (document: vscode.NotebookDocument, ranges: vscode.NotebookCellRange[]) => { - const idx = ranges[0].start; - const task = vscode.notebook.createNotebookCellExecutionTask(document.uri, idx, 'notebookSmokeTest'); - if (!task) { - return; - } + const controller = vscode.notebook.createNotebookController( + 'notebookSmokeTest', + 'notebookSmokeTest', + 'notebookSmokeTest' + ); + controller.executeHandler = (cells) => { + for (const cell of cells) { + const task = controller.createNotebookCellExecutionTask(cell); task.start(); task.replaceOutput([new vscode.NotebookCellOutput([ new vscode.NotebookCellOutputItem('text/html', ['test output'], undefined) @@ -80,11 +75,7 @@ export function activate(context: vscode.ExtensionContext): any { } }; - context.subscriptions.push(vscode.notebook.registerNotebookKernelProvider({ filenamePattern: '*.smoke-nb' }, { - provideKernels: async () => { - return [kernel]; - } - })); + context.subscriptions.push(controller); context.subscriptions.push(vscode.commands.registerCommand('vscode-notebook-tests.debugAction', async (cell: vscode.NotebookCell) => { if (cell) { diff --git a/extensions/vscode-notebook-tests/src/index.ts b/extensions/vscode-notebook-tests/src/index.ts index c6aec584..2f8fb129 100644 --- a/extensions/vscode-notebook-tests/src/index.ts +++ b/extensions/vscode-notebook-tests/src/index.ts @@ -8,7 +8,7 @@ const testRunner = require('../../../test/integration/electron/testrunner'); const options: any = { ui: 'tdd', - color: (!process.env.BUILD_ARTIFACTSTAGINGDIRECTORY && process.platform !== 'win32'), + color: true, timeout: 60000 }; diff --git a/extensions/vscode-notebook-tests/tsconfig.json b/extensions/vscode-notebook-tests/tsconfig.json index 296ddb38..070854d6 100644 --- a/extensions/vscode-notebook-tests/tsconfig.json +++ b/extensions/vscode-notebook-tests/tsconfig.json @@ -1,9 +1,9 @@ { - "extends": "../shared.tsconfig.json", + "extends": "../tsconfig.base.json", "compilerOptions": { "outDir": "./out" }, "include": [ "src/**/*" ] -} \ No newline at end of file +} diff --git a/extensions/vscode-test-resolver/media/icon.png b/extensions/vscode-test-resolver/media/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..18b2ca94e5158fe811ba2b5841534cccd9352525 GIT binary patch literal 2210 zcmY+GdoR$B)5rhgofM;BRY&4=1i^i{_(D7t^N7#y}o;|y`JZv=LX5kMO7K23;=+t zo2%nt0D#CO1W<&@5LSB3M}}&WhmVttKwLWjb^&o60KNUEj%x$1wEawJEdba8&}CXy z>;S;GKLzcV=gP0j<#ymo2Ox`dS^Q;_P53#a?N=)Ezm>I9N=LXIvy@UH_+mn|BEe_$ zbxr<*WB2erE;3(C;4^xqQYntz4L+X~OfD012ZZyhq{5;6j#*@USJcx_0j0w7K2dtp zH1Fe5dee+-hp}uIciGyyv4_2m074{%CjyB=94@Mhi7&r-lf)%)^V6fK`2iIIJf))T z-7XyfP`KjeXzxQKkN5khUB@BhC^#Pk3%MzE?_{Gx=lY7JX~u%p~Q;?O>(DfT z(%oKdo#{8tSJt@f#wD0Hje%3{+UPjC$}+UILt@9Z1+2}RZQc(C#deesE<5xa5@=F0 z1{$3@pX&U=gvEM40-TLQY2l;a*aAk2s#-XnQ(gS6JFBYVp*{*^!4HOMhffl~n1r}^ zg#*VY0iS|b03Hq9_(xScq&Pf*fK2#uv!B9F*}DR3?lZ8;f4?uAe^>Jv#Mod-v9xpn z3OHSafZl6`lB!GcJ0lL*>+o#Jw=Zles&Pf1jtJ{*()U}@svTaDk`t5^#%>y}+SR>Y z=e-iS#GI@9U-ciNT%Xj2dHqR*`~qLO-BkYhH>lcMQ?v}0S;8t7 zW*zf)geC9n+?mtsm;IW;Y!}?2GgoqseZ5Oqx-?Bmgg03*%^b zBw+^_c2eoNP>Hy^k?Klx%p{I-!ZmomLjs8xSVB$kQ>`J$DK^0mr!8PLk-eJ$LPgLM zXsmI?**ccxpcZNLV?sCMn(!g0n3ov)Lyf_PS{H`^jo@T|{Q(2d`uIg`SM&kZ(qYfmmu7!~yBYh{ATBAc&*(~YH%Kt-ssZI7DBUx&gDJ=($`sWFHuW|m zU5^y=hs9j8lR-s$v#i>9>eBibGT65AGw##Hd-bvysU9?#H3cQ`LUlu;N1L1!o^^7x^Q z9;mgzEIuI0k{1o(_-qr)i8-GLCKR*FzjYe}P1NK3WTVc&5#7qG5Y2n$L*Rl9GRk2p z#R@0c0&m}8{x`VLfJEv->zE35MXGxVN8lO=OPs{(FbI#@IC5zhNIxgh1HEVbzq%)# z8X)uZD{UZR9r*vw!>LXdsJB&mph2W_A)dxSD_g69QSbBWYY%Y;@J&dRsu_rc3zaVf6!;G@t?eM0NI>b-WcU0>#4YAe1mIx%v zMX%lUMt0-F$y37N6IY>8U3@_e)>raz2<7atd3QwFxK#+F5UTa?u-Aw-EInEH0N7)Z5G=)cX8llKL4%yvZ zpppq0B^XoE_B~m$ZKNI|rtYiGjC2u~DJ5NdR{c;_b`vX{7dFlIsKf%`GhF?&W{J;E zk2wvb^0r(N(=>6mdIx#2qrtvRY&^5b{V?wr5#|*Jt^anszAmW~etza@Y)^>?juUA^ z(Du$InryCp{4C%%LCZ=Jb8g<(rJ<~$w{;o9w}Wmg%aOzM{OJbzhuqr;H8zXD^9}L` zbUdn6v0i*5<%h1h_vTE9mRK@q6mJ5KZg+j3m_StWNR?-uFAT~6>Pa>txbtE)OrZfn z+Kdwt<*Be+7qMTZ@uLc(MYavCsi8@)*F}iY^ZyzV6U+}%58Cuke{L%0# zMpCG+GNyDZmI=o^BCkONYlS1QL-gG=fPQ)bSpJK~Af`+y{68Y!iloPueQ$_h^07Sh zF3pOQtnHuh&pWuxGdgaF$5L``oaXn$*G^T0^?mo3x!$Mq#;hh#;`p?uW;DkcbQ5-e)kEw6|U$9?bBIU8$*24aCJx7#N zjM;nE>J`I65vtaibDZDvU%(ShblYmakMhgup?3H36a(wJSiT@W$2N5^` literal 0 HcmV?d00001 diff --git a/extensions/vscode-test-resolver/package.json b/extensions/vscode-test-resolver/package.json index c2ddf436..2256c047 100644 --- a/extensions/vscode-test-resolver/package.json +++ b/extensions/vscode-test-resolver/package.json @@ -9,6 +9,7 @@ "engines": { "vscode": "^1.25.0" }, + "icon": "media/icon.png", "extensionKind": [ "ui" ], @@ -43,17 +44,17 @@ ], "commands": [ { - "title": "New Window", + "title": "New TestResolver Window", "category": "Remote-TestResolver", "command": "vscode-testresolver.newWindow" }, { - "title": "Show Log", + "title": "Show TestResolver Log", "category": "Remote-TestResolver", "command": "vscode-testresolver.showLog" }, { - "title": "Kill Server and Trigger Handled Error", + "title": "Kill Remote Server and Trigger Handled Error", "category": "Remote-TestResolver", "command": "vscode-testresolver.killServerAndTriggerHandledError" }, @@ -79,31 +80,31 @@ "when": "remoteName == test" } ], - "statusBar/windowIndicator": [ + "statusBar/remoteIndicator": [ { "command": "vscode-testresolver.newWindow", "when": "!remoteName", - "group": "9_local_testresolver@2" + "group": "remote_90_test_1_local@2" }, { "command": "vscode-testresolver.showLog", "when": "remoteName == test", - "group": "1_remote_testresolver_open@3" + "group": "remote_90_test_1_open@3" }, { "command": "vscode-testresolver.newWindow", "when": "remoteName == test", - "group": "1_remote_testresolver_open@1" + "group": "remote_90_test_1_open@1" }, { "command": "vscode-testresolver.openTunnel", "when": "remoteName == test", - "group": "1_remote_testresolver_open@4" + "group": "remote_90_test_2_more@4" }, { "command": "vscode-testresolver.startRemoteServer", "when": "remoteName == test", - "group": "1_remote_testresolver_open@5" + "group": "remote_90_test_2_more@5" } ] }, diff --git a/extensions/vscode-test-resolver/src/extension.ts b/extensions/vscode-test-resolver/src/extension.ts index 99d3619e..c3389571 100644 --- a/extensions/vscode-test-resolver/src/extension.ts +++ b/extensions/vscode-test-resolver/src/extension.ts @@ -203,7 +203,8 @@ export function activate(context: vscode.ExtensionContext) { proxyServer.listen(0, () => { const port = (proxyServer.address()).port; outputChannel.appendLine(`Going through proxy at port ${port}`); - res(new vscode.ResolvedAuthority('127.0.0.1', port)); + const r: vscode.ResolverResult = new vscode.ResolvedAuthority('127.0.0.1', port); + res(r); }); context.subscriptions.push({ dispose: () => { diff --git a/extensions/vscode-test-resolver/tsconfig.json b/extensions/vscode-test-resolver/tsconfig.json index 296ddb38..070854d6 100644 --- a/extensions/vscode-test-resolver/tsconfig.json +++ b/extensions/vscode-test-resolver/tsconfig.json @@ -1,9 +1,9 @@ { - "extends": "../shared.tsconfig.json", + "extends": "../tsconfig.base.json", "compilerOptions": { "outDir": "./out" }, "include": [ "src/**/*" ] -} \ No newline at end of file +} diff --git a/extensions/yarn.lock b/extensions/yarn.lock index b5fa13d8..067a5ebb 100644 --- a/extensions/yarn.lock +++ b/extensions/yarn.lock @@ -14,15 +14,20 @@ cson-parser@^1.3.3: dependencies: coffee-script "^1.10.0" +esbuild@^0.11.12: + version "0.11.12" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.11.12.tgz#8cbe15bcb44212624c3e77c896a835f74dc71c3c" + integrity sha512-c8cso/1RwVj+fbDvLtUgSG4ZJQ0y9Zdrl6Ot/GAjyy4pdMCHaFnDMts5gqFnWRPLajWtEnI+3hlET4R9fVoZng== + fast-plist@0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/fast-plist/-/fast-plist-0.1.2.tgz#a45aff345196006d406ca6cdcd05f69051ef35b8" integrity sha1-pFr/NFGWAG1AbKbNzQX2kFHvNbg= -typescript@4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.2.3.tgz#39062d8019912d43726298f09493d598048c1ce3" - integrity sha512-qOcYwxaByStAWrBf4x0fibwZvMRG+r4cQoTjbPtUlrWjBHbmCAww1i448U0GJ+3cNNEtebDteo/cHOR3xJ4wEw== +typescript@4.2.4: + version "4.2.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.2.4.tgz#8610b59747de028fda898a8aef0e103f156d0961" + integrity sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg== vscode-grammar-updater@^1.0.3: version "1.0.3" diff --git a/package.json b/package.json index 596b07d5..1430bbb7 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", - "version": "1.55.2", - "distro": "baceee6d8ed0e4d63d908ba975758ddca2397906", + "version": "1.56.0", + "distro": "3d76109d9437bda93a3f337625de2833149ca724", "author": { "name": "Microsoft Corporation" }, @@ -28,6 +28,8 @@ "watch-extension-media": "node --max_old_space_size=4095 ./node_modules/gulp/bin/gulp.js watch-extension-media", "watch-extensionsd": "deemon yarn watch-extensions", "kill-watch-extensionsd": "deemon --kill yarn watch-extensions", + "watch-extension-mediad": "deemon yarn watch-extension-media", + "kill-watch-extension-mediad": "deemon --kill yarn watch-extension-media", "mocha": "mocha test/unit/node/all.js --delay", "precommit": "node build/hygiene.js", "gulp": "node --max_old_space_size=8192 ./node_modules/gulp/bin/gulp.js", @@ -42,13 +44,11 @@ "monaco-compile-check": "tsc -p src/tsconfig.monaco.json --noEmit", "tsec-compile-check": "node node_modules/tsec/bin/tsec -p src/tsconfig.tsec.json", "valid-layers-check": "node build/lib/layersChecker.js", - "strict-function-types-watch": "tsc --watch -p src/tsconfig.json --noEmit --strictFunctionTypes", "update-distro": "node build/npm/update-distro.js", "web": "node resources/web/code-web.js", "compile-web": "node --max_old_space_size=4095 ./node_modules/gulp/bin/gulp.js compile-web", "watch-web": "node --max_old_space_size=4095 ./node_modules/gulp/bin/gulp.js watch-web", "eslint": "node build/eslint", - "electron-rebuild": "electron-rebuild --arch=arm64 --force --version=11.3.0", "playwright-install": "node build/azure-pipelines/common/installPlaywright.js", "compile-build": "node --max_old_space_size=4095 ./node_modules/gulp/bin/gulp.js compile-build", "compile-extensions-build": "node --max_old_space_size=4095 ./node_modules/gulp/bin/gulp.js compile-extensions-build", @@ -66,28 +66,28 @@ "http-proxy-agent": "^2.1.0", "https-proxy-agent": "^2.2.3", "iconv-lite-umd": "0.6.8", - "jschardet": "2.2.1", + "jschardet": "2.3.0", "keytar": "7.2.0", "minimist": "^1.2.5", - "native-is-elevated": "0.4.1", + "native-is-elevated": "0.4.3", "native-keymap": "2.2.1", "native-watchdog": "1.3.0", - "node-pty": "0.10.0-beta19", - "nsfw": "^2.1.1", + "node-pty": "0.10.1", + "nsfw": "2.1.2", "spdlog": "^0.11.1", - "sudo-prompt": "9.1.1", + "sudo-prompt": "9.2.1", "tas-client-umd": "0.1.4", "v8-inspect-profiler": "^0.0.20", "vscode-oniguruma": "1.3.1", - "vscode-proxy-agent": "^0.8.2", + "vscode-proxy-agent": "^0.11.0", "vscode-regexpp": "^3.1.0", - "vscode-ripgrep": "^1.11.1", - "vscode-sqlite3": "4.0.10", + "vscode-ripgrep": "^1.11.3", + "vscode-sqlite3": "4.0.11", "vscode-textmate": "5.2.0", - "xterm": "4.12.0-beta.7", - "xterm-addon-search": "0.8.0", - "xterm-addon-unicode11": "0.3.0-beta.3", - "xterm-addon-webgl": "0.10.0", + "xterm": "4.12.0-beta.26", + "xterm-addon-search": "0.9.0-beta.2", + "xterm-addon-unicode11": "0.3.0-beta.5", + "xterm-addon-webgl": "0.11.0-beta.8", "yauzl": "^2.9.2", "yazl": "^2.4.3" }, @@ -105,11 +105,12 @@ "@types/keytar": "^4.4.0", "@types/minimist": "^1.2.1", "@types/mocha": "^8.2.0", - "@types/node": "^12.19.9", + "@types/node": "^14.14.37", "@types/sinon": "^1.16.36", "@types/trusted-types": "^1.0.6", "@types/vscode-windows-registry": "^1.0.0", "@types/webpack": "^4.41.25", + "@types/wicg-file-system-access": "^2020.9.1", "@types/windows-foreground-love": "^0.3.0", "@types/windows-mutex": "^0.4.0", "@types/windows-process-tree": "^0.2.0", @@ -124,11 +125,10 @@ "copy-webpack-plugin": "^6.0.3", "cson-parser": "^1.3.3", "css-loader": "^3.2.0", - "cssnano": "^4.1.10", + "cssnano": "^4.1.11", "debounce": "^1.0.0", "deemon": "^1.4.0", - "electron": "11.3.0", - "electron-rebuild": "2.0.3", + "electron": "12.0.4", "eslint": "6.8.0", "eslint-plugin-jsdoc": "^19.1.0", "event-stream": "3.3.4", @@ -192,14 +192,14 @@ "style-loader": "^1.0.0", "ts-loader": "^6.2.1", "tsec": "0.1.4", - "typescript": "^4.3.0-dev.20210305", + "typescript": "^4.3.0-dev.20210426", "typescript-formatter": "7.1.0", "underscore": "^1.8.2", "vinyl": "^2.0.0", "vinyl-fs": "^3.0.0", - "vscode-debugprotocol": "1.46.0", + "vscode-debugprotocol": "1.47.0", "vscode-nls-dev": "^3.3.1", - "vscode-telemetry-extractor": "^1.6.0", + "vscode-telemetry-extractor": "^1.7.0", "webpack": "^4.43.0", "webpack-cli": "^3.3.12", "webpack-stream": "^5.2.1", @@ -214,11 +214,10 @@ "url": "https://github.com/microsoft/vscode/issues" }, "optionalDependencies": { - "vscode-windows-ca-certs": "^0.3.0", "vscode-windows-registry": "1.0.3", "windows-foreground-love": "0.2.0", "windows-mutex": "0.3.0", - "windows-process-tree": "0.2.4" + "windows-process-tree": "0.3.0" }, "resolutions": { "elliptic": "^6.5.3", diff --git a/product.json b/product.json index f3f0ba07..8443fe70 100644 --- a/product.json +++ b/product.json @@ -33,7 +33,7 @@ "builtInExtensions": [ { "name": "ms-vscode.node-debug", - "version": "1.44.19", + "version": "1.44.27", "repo": "https://github.com/microsoft/vscode-node-debug", "metadata": { "id": "b6ded8fb-a0a0-4c1c-acbd-ab2a3bc995a6", @@ -48,7 +48,7 @@ }, { "name": "ms-vscode.node-debug2", - "version": "1.42.5", + "version": "1.42.6", "repo": "https://github.com/microsoft/vscode-node-debug2", "metadata": { "id": "36d19e17-7569-4841-a001-947eb18602b2", @@ -63,7 +63,7 @@ }, { "name": "ms-vscode.references-view", - "version": "0.0.77", + "version": "0.0.80", "repo": "https://github.com/microsoft/vscode-references-view", "metadata": { "id": "dc489f46-520d-4556-ae85-1f9eab3c412d", @@ -78,7 +78,7 @@ }, { "name": "ms-vscode.js-debug-companion", - "version": "1.0.9", + "version": "1.0.13", "repo": "https://github.com/microsoft/vscode-js-debug-companion", "metadata": { "id": "99cb0b7f-7354-4278-b8da-6cc79972169d", @@ -93,7 +93,7 @@ }, { "name": "ms-vscode.js-debug", - "version": "1.55.2", + "version": "1.56.2", "repo": "https://github.com/microsoft/vscode-js-debug", "metadata": { "id": "25629058-ddac-4e17-abba-74678e126c5d", @@ -108,7 +108,7 @@ }, { "name": "ms-vscode.vscode-js-profile-table", - "version": "0.0.11", + "version": "0.0.18", "repo": "https://github.com/microsoft/vscode-js-profile-visualizer", "metadata": { "id": "7e52b41b-71ad-457b-ab7e-0620f1fc4feb", @@ -120,12 +120,27 @@ }, "publisherDisplayName": "Microsoft" } + }, + { + "name": "ms-vscode.remotehub", + "version": "0.5.3", + "repo": "https://github.com/microsoft/vscode-remotehub", + "metadata": { + "id": "ed0ffe1d-36f0-49a0-bafe-da68355eb33a", + "publisherId": { + "publisherId": "5f5636e7-69ed-4afe-b5d6-8d231fb3d3ee", + "publisherName": "ms-vscode", + "displayName": "Microsoft", + "flags": "verified" + }, + "publisherDisplayName": "Microsoft" + } } ], "webBuiltInExtensions": [ { "name": "ms-vscode.remotehub", - "version": "0.1.2", + "version": "0.5.3", "repo": "https://github.com/microsoft/vscode-remotehub", "metadata": { "id": "ed0ffe1d-36f0-49a0-bafe-da68355eb33a", diff --git a/remote/.yarnrc b/remote/.yarnrc index cd436416..bce4202a 100644 --- a/remote/.yarnrc +++ b/remote/.yarnrc @@ -1,3 +1,3 @@ disturl "http://nodejs.org/dist" -target "12.18.3" +target "14.16.0" runtime "node" diff --git a/remote/package.json b/remote/package.json index 7f58dd57..ff56643c 100644 --- a/remote/package.json +++ b/remote/package.json @@ -10,27 +10,26 @@ "http-proxy-agent": "^2.1.0", "https-proxy-agent": "^2.2.3", "iconv-lite-umd": "0.6.8", - "jschardet": "2.2.1", + "jschardet": "2.3.0", "minimist": "^1.2.5", "native-watchdog": "1.3.0", - "node-pty": "0.10.0-beta19", - "nsfw": "^2.1.1", + "node-pty": "0.10.1", + "nsfw": "2.1.2", "spdlog": "^0.11.1", "tas-client-umd": "0.1.4", "vscode-oniguruma": "1.3.1", - "vscode-proxy-agent": "^0.8.2", + "vscode-proxy-agent": "^0.11.0", "vscode-regexpp": "^3.1.0", - "vscode-ripgrep": "^1.11.1", + "vscode-ripgrep": "^1.11.3", "vscode-textmate": "5.2.0", - "xterm": "4.12.0-beta.7", - "xterm-addon-search": "0.8.0", - "xterm-addon-unicode11": "0.3.0-beta.3", - "xterm-addon-webgl": "0.10.0", + "xterm": "4.12.0-beta.26", + "xterm-addon-search": "0.9.0-beta.2", + "xterm-addon-unicode11": "0.3.0-beta.5", + "xterm-addon-webgl": "0.11.0-beta.8", "yauzl": "^2.9.2", "yazl": "^2.4.3" }, "optionalDependencies": { - "vscode-windows-ca-certs": "0.3.0", "vscode-windows-registry": "1.0.2", "windows-process-tree": "0.2.4" } diff --git a/remote/web/package.json b/remote/web/package.json index 46d76c50..317ccc0d 100644 --- a/remote/web/package.json +++ b/remote/web/package.json @@ -4,13 +4,13 @@ "private": true, "dependencies": { "iconv-lite-umd": "0.6.8", - "jschardet": "2.2.1", + "jschardet": "2.3.0", "tas-client-umd": "0.1.4", "vscode-oniguruma": "1.3.1", "vscode-textmate": "5.2.0", - "xterm": "4.12.0-beta.7", - "xterm-addon-search": "0.8.0", - "xterm-addon-unicode11": "0.3.0-beta.3", - "xterm-addon-webgl": "0.10.0" + "xterm": "4.12.0-beta.26", + "xterm-addon-search": "0.9.0-beta.2", + "xterm-addon-unicode11": "0.3.0-beta.5", + "xterm-addon-webgl": "0.11.0-beta.8" } } diff --git a/remote/web/yarn.lock b/remote/web/yarn.lock index 184ef090..6f88d682 100644 --- a/remote/web/yarn.lock +++ b/remote/web/yarn.lock @@ -7,10 +7,10 @@ iconv-lite-umd@0.6.8: resolved "https://registry.yarnpkg.com/iconv-lite-umd/-/iconv-lite-umd-0.6.8.tgz#5ad310ec126b260621471a2d586f7f37b9958ec0" integrity sha512-zvXJ5gSwMC9JD3wDzH8CoZGc1pbiJn12Tqjk8BXYCnYz3hYL5GRjHW8LEykjXhV9WgNGI4rgpgHcbIiBfrRq6A== -jschardet@2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/jschardet/-/jschardet-2.2.1.tgz#03b0264669a90c7a5c436a68c5a7d4e4cb0c9823" - integrity sha512-Ks2JNuUJoc7PGaZ7bVFtSEvOcr0rBq6Q1J5/7+zKWLT+g+4zziL63O0jg7y2jxhzIa1LVsHUbPXrbaWmz9iwDw== +jschardet@2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/jschardet/-/jschardet-2.3.0.tgz#06e2636e16c8ada36feebbdc08aa34e6a9b3ff75" + integrity sha512-6I6xT7XN/7sBB7q8ObzKbmv5vN+blzLcboDE1BNEsEfmRXJValMxO6OIRT69ylPBRemS3rw6US+CMCar0OBc9g== tas-client-umd@0.1.4: version "0.1.4" @@ -27,22 +27,22 @@ vscode-textmate@5.2.0: resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-5.2.0.tgz#01f01760a391e8222fe4f33fbccbd1ad71aed74e" integrity sha512-Uw5ooOQxRASHgu6C7GVvUxisKXfSgW4oFlO+aa+PAkgmH89O3CXxEEzNRNtHSqtXFTl0nAC1uYj0GMSH27uwtQ== -xterm-addon-search@0.8.0: - version "0.8.0" - resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.8.0.tgz#e33eab918df7eac7e7baf95dd2b3d14133754881" - integrity sha512-MPJGPVPpHRUw9cLIuqQbrVepmENMOybVUSxIALz5h1ryyQBrVqVujq2hL5aroX5/dZJoHx9lGHQTVLQ07SKgKA== +xterm-addon-search@0.9.0-beta.2: + version "0.9.0-beta.2" + resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.9.0-beta.2.tgz#46de7c7d5f1d0ae546b84552c08182916d83025d" + integrity sha512-Ljg+O8HcGx1z2RjpV+nX070zpSjmefU09SFPBVWAHjGT983y6b5yoqG7AVVZdirsJ0zGiccAZL9Kila1CQN6nQ== -xterm-addon-unicode11@0.3.0-beta.3: - version "0.3.0-beta.3" - resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.3.0-beta.3.tgz#70af2dfb67809258edb62c19e2861f7ce5ccf5cd" - integrity sha512-vaYopnOjn19wCLDCyIWPWLwKR7CvLPxB5YZ3CAxt9qL05o3symxIJJJC0DuCa4GaGKVjNc7EmjRCs5bsJ2O1tw== +xterm-addon-unicode11@0.3.0-beta.5: + version "0.3.0-beta.5" + resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.3.0-beta.5.tgz#7e490799d530d3b301125c7a4e92317c161761c4" + integrity sha512-SgDDL3PoMH1G48JO6T45whKAex4NPxi80UzUVitnrqyd8dFQP+oF6cxqUutULgm9HSGk62qy3mrZvIMGO5VXog== -xterm-addon-webgl@0.10.0: - version "0.10.0" - resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.10.0.tgz#e99366fdc4cbd46b798a5e2fc114ecc19f9fd4b7" - integrity sha512-MJzyWie5yw+PH6p//fXlXzmsULLtpBo992EWEKl2uv5M5Zj9etTwfuutCUK7o98mr6itDl+vS/CYIMP68jCf8w== +xterm-addon-webgl@0.11.0-beta.8: + version "0.11.0-beta.8" + resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.11.0-beta.8.tgz#8cb4925d67c31beb8144275daf46358f42eff9fe" + integrity sha512-udRmQ/jgH8cL8VQOZweytkToIROevVeiA7WY0tIe878Wt2zKY+AYHZV8js3c1W9wHDu5G90BhmzTidJ5UwZK3Q== -xterm@4.12.0-beta.7: - version "4.12.0-beta.7" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.12.0-beta.7.tgz#8ee0a6e180dc57ea63a59682f2ccaf41e2cefd1f" - integrity sha512-qUKJg/aOVy2pOORiwuxd2Qp0MHJyQ/gcK7OMyjMEFLdS5L+KBbUkXtpOO9LWbW+B1DvS4b1k1MzvB4GF+qXXtg== +xterm@4.12.0-beta.26: + version "4.12.0-beta.26" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.12.0-beta.26.tgz#57c75b732808795398a66bc1a3e06d09eaff2ada" + integrity sha512-yZB1kMBXQu2G0G1ch7TUi6f893iTZC+tmfjw/PQNZTmN46b4oX1l7rplc3sFcdrICHtmQ0Q5n1u0d6WUAdq1Kw== diff --git a/remote/yarn.lock b/remote/yarn.lock index ec7e3769..2a0d2ccc 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -2,6 +2,11 @@ # yarn lockfile v1 +"@tootallnate/once@1", "@tootallnate/once@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" + integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== + agent-base@4: version "4.2.0" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.0.tgz#9838b5c3392b962bad031e6a4c5e1024abec45ce" @@ -14,6 +19,13 @@ agent-base@5: resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-5.1.1.tgz#e8fb3f242959db44d63be665db7a8e739537a32c" integrity sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g== +agent-base@6, agent-base@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + dependencies: + debug "4" + agent-base@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee" @@ -21,13 +33,6 @@ agent-base@^4.3.0: dependencies: es6-promisify "^5.0.0" -agent-base@~4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9" - integrity sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg== - dependencies: - es6-promisify "^5.0.0" - anymatch@~3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.1.tgz#c55ecf02185e2469259399310c173ce31233b142" @@ -89,6 +94,16 @@ cookie@^0.4.0: resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== +core-util-is@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= + +data-uri-to-buffer@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz#594b8973938c5bc2c33046535785341abc4f3636" + integrity sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og== + debug@3.1.0, debug@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" @@ -103,6 +118,13 @@ debug@4: dependencies: ms "^2.1.1" +debug@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" + integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== + dependencies: + ms "2.1.2" + diagnostic-channel-publishers@0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/diagnostic-channel-publishers/-/diagnostic-channel-publishers-0.2.1.tgz#8e2d607a8b6d79fe880b548bc58cc6beb288c4f3" @@ -139,6 +161,11 @@ file-uri-to-path@1.0.0: resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== +file-uri-to-path@2: + version "2.0.0" + resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-2.0.0.tgz#7b415aeba227d575851e0a5b0c640d7656403fba" + integrity sha512-hjPFI8oE/2iQPVe4gbrJ73Pp+Xfub2+WI2LlXDbsaJBwT5wuMh35WNWVYYTpnz895shtwfyutMFLFywpQAFdLg== + fill-range@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" @@ -146,11 +173,40 @@ fill-range@^7.0.1: dependencies: to-regex-range "^5.0.1" +fs-extra@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" + integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^4.0.0" + universalify "^0.1.0" + fsevents@~2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.1.tgz#b209ab14c61012636c8863507edf7fb68cc54e9f" integrity sha512-YR47Eg4hChJGAB1O3yEAOkGO+rlzutoICGqGo9EZ4lKWokzZRSyIW1QmTzqjtw8MJdj9srP869CuWw/hyzSiBw== +ftp@^0.3.10: + version "0.3.10" + resolved "https://registry.yarnpkg.com/ftp/-/ftp-0.3.10.tgz#9197d861ad8142f3e63d5a83bfe4c59f7330885d" + integrity sha1-kZfYYa2BQvPmPVqDv+TFn3MwiF0= + dependencies: + readable-stream "1.1.x" + xregexp "2.0.0" + +get-uri@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/get-uri/-/get-uri-3.0.2.tgz#f0ef1356faabc70e1f9404fa3b66b2ba9bfc725c" + integrity sha512-+5s0SJbGoyiJTZZ2JTpFPLMPSch72KEqGOTvQsBqg0RBWvwhWUSYZFAtz3TPW0GXJuLBJPts1E241iHg+VRfhg== + dependencies: + "@tootallnate/once" "1" + data-uri-to-buffer "3" + debug "4" + file-uri-to-path "2" + fs-extra "^8.1.0" + ftp "^0.3.10" + glob-parent@~5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.0.tgz#5f4c1d1e748d30cd73ad2944b3577a81b081e8c2" @@ -163,6 +219,11 @@ graceful-fs@4.2.3: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ== +graceful-fs@^4.1.6, graceful-fs@^4.2.0: + version "4.2.6" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee" + integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ== + http-proxy-agent@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz#e4821beef5b2142a2026bd73926fe537631c5405" @@ -171,6 +232,15 @@ http-proxy-agent@^2.1.0: agent-base "4" debug "3.1.0" +http-proxy-agent@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a" + integrity sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg== + dependencies: + "@tootallnate/once" "1" + agent-base "6" + debug "4" + https-proxy-agent@^2.2.3: version "2.2.4" resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz#4ee7a737abd92678a293d9b34a1af4d0d08c787b" @@ -187,11 +257,24 @@ https-proxy-agent@^4.0.0: agent-base "5" debug "4" +https-proxy-agent@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2" + integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA== + dependencies: + agent-base "6" + debug "4" + iconv-lite-umd@0.6.8: version "0.6.8" resolved "https://registry.yarnpkg.com/iconv-lite-umd/-/iconv-lite-umd-0.6.8.tgz#5ad310ec126b260621471a2d586f7f37b9958ec0" integrity sha512-zvXJ5gSwMC9JD3wDzH8CoZGc1pbiJn12Tqjk8BXYCnYz3hYL5GRjHW8LEykjXhV9WgNGI4rgpgHcbIiBfrRq6A== +inherits@~2.0.1: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + ip@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" @@ -221,15 +304,22 @@ is-number@^7.0.0: resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== -jschardet@2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/jschardet/-/jschardet-2.2.1.tgz#03b0264669a90c7a5c436a68c5a7d4e4cb0c9823" - integrity sha512-Ks2JNuUJoc7PGaZ7bVFtSEvOcr0rBq6Q1J5/7+zKWLT+g+4zziL63O0jg7y2jxhzIa1LVsHUbPXrbaWmz9iwDw== +isarray@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" + integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= -minimist@0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" - integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= +jschardet@2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/jschardet/-/jschardet-2.3.0.tgz#06e2636e16c8ada36feebbdc08aa34e6a9b3ff75" + integrity sha512-6I6xT7XN/7sBB7q8ObzKbmv5vN+blzLcboDE1BNEsEfmRXJValMxO6OIRT69ylPBRemS3rw6US+CMCar0OBc9g== + +jsonfile@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" + integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss= + optionalDependencies: + graceful-fs "^4.1.6" minimist@^1.2.5: version "1.2.5" @@ -237,18 +327,18 @@ minimist@^1.2.5: integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== mkdirp@^0.5.1: - version "0.5.1" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" - integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= + version "0.5.5" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" + integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== dependencies: - minimist "0.0.8" + minimist "^1.2.5" ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= -ms@^2.1.1: +ms@2.1.2, ms@^2.1.1: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== @@ -268,20 +358,15 @@ native-watchdog@1.3.0: resolved "https://registry.yarnpkg.com/native-watchdog/-/native-watchdog-1.3.0.tgz#88cee94c9dc766b85c8506eda14c8bd8c9618e27" integrity sha512-WOjGRNGkYZ5MXsntcvCYrKtSYMaewlbCFplbcUVo9bE80LPVt8TAVFHYWB8+a6fWCGYheq21+Wtt6CJrUaCJhw== -node-addon-api@*: +node-addon-api@*, node-addon-api@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.1.0.tgz#98b21931557466c6729e51cb77cd39c965f42239" integrity sha512-flmrDNB06LIl5lywUz7YlNGZH/5p0M7W28k8hzd9Lshtdh1wshD2Y+U4h9LD6KObOy1f+fEVdgprPrEymjM5uw== -node-addon-api@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.0.2.tgz#04bc7b83fd845ba785bb6eae25bc857e1ef75681" - integrity sha512-+D4s2HCnxPd5PjjI0STKwncjXTUKKqm74MDMz9OPXavjsGmjkvwgLtA5yoxJUdmpj52+2u+RrXgPipahKczMKg== - -node-pty@0.10.0-beta19: - version "0.10.0-beta19" - resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-0.10.0-beta19.tgz#b7cbfba53f7b2a816efe8c9302dd083cc5874458" - integrity sha512-4UIOGMvpofUbe+ZniBUtY8zc/psMURSzbMonQgIhK7JlMQsUwcbkDIrKzStVLJX0FkeZpUNlsVtK7qqzHvrUZA== +node-pty@0.10.1: + version "0.10.1" + resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-0.10.1.tgz#cd05d03a2710315ec40221232ec04186f6ac2c6d" + integrity sha512-JTdtUS0Im/yRsWJSx7yiW9rtpfmxqxolrtnyKwPLI+6XqTAPW/O2MjS8FYL4I5TsMbH2lVgDb2VMjp+9LoQGNg== dependencies: nan "^2.14.0" @@ -290,10 +375,10 @@ normalize-path@^3.0.0, normalize-path@~3.0.0: resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== -nsfw@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/nsfw/-/nsfw-2.1.1.tgz#3cc8cc9ebf567c0121364cd42bb26167242b4be0" - integrity sha512-DvkPpsIkw/DogPxI4vQugtrSc0AtuCoxiprLxTLVZrZuEe8H++0DHpV/081q0LOtUc+96yW3FBl0pEKiyMOfJg== +nsfw@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/nsfw/-/nsfw-2.1.2.tgz#4fa841e7f7122b60b2e1f61187d1b57ad3403428" + integrity sha512-zGPdt32aJ5b1laK9rvgXQmXGAagrx3VkcMt0JePtu6wBfzC1o4xLCM3kq7FxZxUnxyxYhODyBYzpt3H16FhaGA== dependencies: node-addon-api "*" @@ -317,6 +402,16 @@ proxy-from-env@^1.1.0: resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== +readable-stream@1.1.x: + version "1.1.14" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" + integrity sha1-fPTFTvZI44EwhMY23SB54WbAgdk= + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.1" + isarray "0.0.1" + string_decoder "~0.10.x" + readdirp@~3.5.0: version "3.5.0" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.5.0.tgz#9ba74c019b15d365278d2e91bb8c48d7b4d42c9e" @@ -329,26 +424,27 @@ semver@^5.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg== -smart-buffer@4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.0.2.tgz#5207858c3815cc69110703c6b94e46c15634395d" - integrity sha512-JDhEpTKzXusOqXZ0BUIdH+CjFdO/CR3tLlf5CN34IypI+xMmXW1uB16OOY8z3cICbJlDAVJzNbwBhNO0wt9OAw== +smart-buffer@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.1.0.tgz#91605c25d91652f4661ea69ccf45f1b331ca21ba" + integrity sha512-iVICrxOzCynf/SNaBQCw34eM9jROU/s5rzIhpOvzhzuYHfJR/DhZfDkXiZSgKXfgv26HT3Yni3AV/DGw0cGnnw== -socks-proxy-agent@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-4.0.2.tgz#3c8991f3145b2799e70e11bd5fbc8b1963116386" - integrity sha512-NT6syHhI9LmuEMSK6Kd2V7gNv5KFZoLE7V5udWmn0de+3Mkj3UMA/AJPLyeNUVmElCurSHtUdM3ETpR3z770Wg== +socks-proxy-agent@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-5.0.0.tgz#7c0f364e7b1cf4a7a437e71253bed72e9004be60" + integrity sha512-lEpa1zsWCChxiynk+lCycKuC502RxDWLKJZoIhnxrWNjLSDGYRFflHA1/228VkRcnv9TIb8w98derGbpKxJRgA== dependencies: - agent-base "~4.2.1" - socks "~2.3.2" + agent-base "6" + debug "4" + socks "^2.3.3" -socks@~2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/socks/-/socks-2.3.2.tgz#ade388e9e6d87fdb11649c15746c578922a5883e" - integrity sha512-pCpjxQgOByDHLlNqlnh/mNSAxIUkyBBuwwhTcV+enZGbDaClPvHdvm6uvOwZfFJkam7cGhBNbb4JxiP8UZkRvQ== +socks@^2.3.3: + version "2.6.1" + resolved "https://registry.yarnpkg.com/socks/-/socks-2.6.1.tgz#989e6534a07cf337deb1b1c94aaa44296520d30e" + integrity sha512-kLQ9N5ucj8uIcxrDwjm0Jsqk06xdpBjGNQtpXy4Q8/QY2k+fY7nZH8CARy+hkbG+SGAovmzzuauCpBlb8FrnBA== dependencies: ip "^1.1.5" - smart-buffer "4.0.2" + smart-buffer "^4.1.0" spdlog@^0.11.1: version "0.11.1" @@ -359,6 +455,11 @@ spdlog@^0.11.1: mkdirp "^0.5.1" nan "^2.14.0" +string_decoder@~0.10.x: + version "0.10.31" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" + integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ= + tas-client-umd@0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/tas-client-umd/-/tas-client-umd-0.1.4.tgz#49db4130dd63a8342fabf77185a740fc6a7bea80" @@ -371,30 +472,40 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" +universalify@^0.1.0: + version "0.1.2" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" + integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== + vscode-oniguruma@1.3.1: version "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.8.2: - version "0.8.2" - resolved "https://registry.yarnpkg.com/vscode-proxy-agent/-/vscode-proxy-agent-0.8.2.tgz#5125e7f1efedd84e0114abe9f38cef3f7b33eb50" - integrity sha512-pRNhgAqrgMB4k1rZTHthCLVH+CtJ3TFlKKhFlt4IMxDRZnMEAbPiAGthvEt/Ku6qS4Vuca8b2PqT+rJlbmnznQ== +vscode-proxy-agent@^0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/vscode-proxy-agent/-/vscode-proxy-agent-0.11.0.tgz#9dc8d2bb9d448f1e33bb1caef97a741289660f2f" + integrity sha512-Y5mHjDGq/OKOvKG0IwCYfj25cvQ2cLEil8ce8n55IZHRAP9RF3e1sKU4ZUNDB8X2NIpKwyltrWpK9tFFE/kc3g== dependencies: - debug "^3.1.0" - http-proxy-agent "^2.1.0" - https-proxy-agent "^2.2.3" - socks-proxy-agent "^4.0.1" + "@tootallnate/once" "^1.1.2" + agent-base "^6.0.2" + debug "^4.3.1" + get-uri "^3.0.2" + http-proxy-agent "^4.0.1" + https-proxy-agent "^5.0.0" + socks-proxy-agent "^5.0.0" + optionalDependencies: + vscode-windows-ca-certs "^0.3.0" vscode-regexpp@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/vscode-regexpp/-/vscode-regexpp-3.1.0.tgz#42d059b6fffe99bd42939c0d013f632f0cad823f" integrity sha512-pqtN65VC1jRLawfluX4Y80MMG0DHJydWhe5ZwMHewZD6sys4LbU6lHwFAHxeuaVE6Y6+xZOtAw+9hvq7/0ejkg== -vscode-ripgrep@^1.11.1: - version "1.11.1" - resolved "https://registry.yarnpkg.com/vscode-ripgrep/-/vscode-ripgrep-1.11.1.tgz#9fa3c0a96c2939d5a2389f71218bd1bb6eaa8679" - integrity sha512-oHJfpqeXuTQhOO+szqIObYOddwQ9o+lzd4PQLlTQN+sQ7ex8D1qqFip207O2iJyFc5oWE8Bekf4YHTibdbW66w== +vscode-ripgrep@^1.11.3: + version "1.11.3" + resolved "https://registry.yarnpkg.com/vscode-ripgrep/-/vscode-ripgrep-1.11.3.tgz#a997f4f4535dfeb9d775f04053c1247454d7a37a" + integrity sha512-fdD+BciXiEO1iWTrV/S3sAthlK/tHRBjHF+aJIZDxUMD/q9wpNq+YPFEiLCrW+8epahfR19241DeVHHgX/I4Ww== dependencies: https-proxy-agent "^4.0.0" proxy-from-env "^1.1.0" @@ -404,7 +515,7 @@ vscode-textmate@5.2.0: resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-5.2.0.tgz#01f01760a391e8222fe4f33fbccbd1ad71aed74e" integrity sha512-Uw5ooOQxRASHgu6C7GVvUxisKXfSgW4oFlO+aa+PAkgmH89O3CXxEEzNRNtHSqtXFTl0nAC1uYj0GMSH27uwtQ== -vscode-windows-ca-certs@0.3.0: +vscode-windows-ca-certs@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/vscode-windows-ca-certs/-/vscode-windows-ca-certs-0.3.0.tgz#324e1f8ba842bbf048a39e7c0ee8fe655e9adfcc" integrity sha512-CYrpCEKmAFQJoZNReOrelNL+VKyebOVRCqL9evrBlVcpWQDliliJgU5RggGS8FPGtQ3jAKLQt9frF0qlxYYPKA== @@ -423,25 +534,30 @@ windows-process-tree@0.2.4: dependencies: nan "^2.13.2" -xterm-addon-search@0.8.0: - version "0.8.0" - resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.8.0.tgz#e33eab918df7eac7e7baf95dd2b3d14133754881" - integrity sha512-MPJGPVPpHRUw9cLIuqQbrVepmENMOybVUSxIALz5h1ryyQBrVqVujq2hL5aroX5/dZJoHx9lGHQTVLQ07SKgKA== +xregexp@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-2.0.0.tgz#52a63e56ca0b84a7f3a5f3d61872f126ad7a5943" + integrity sha1-UqY+VsoLhKfzpfPWGHLxJq16WUM= -xterm-addon-unicode11@0.3.0-beta.3: - version "0.3.0-beta.3" - resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.3.0-beta.3.tgz#70af2dfb67809258edb62c19e2861f7ce5ccf5cd" - integrity sha512-vaYopnOjn19wCLDCyIWPWLwKR7CvLPxB5YZ3CAxt9qL05o3symxIJJJC0DuCa4GaGKVjNc7EmjRCs5bsJ2O1tw== +xterm-addon-search@0.9.0-beta.2: + version "0.9.0-beta.2" + resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.9.0-beta.2.tgz#46de7c7d5f1d0ae546b84552c08182916d83025d" + integrity sha512-Ljg+O8HcGx1z2RjpV+nX070zpSjmefU09SFPBVWAHjGT983y6b5yoqG7AVVZdirsJ0zGiccAZL9Kila1CQN6nQ== -xterm-addon-webgl@0.10.0: - version "0.10.0" - resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.10.0.tgz#e99366fdc4cbd46b798a5e2fc114ecc19f9fd4b7" - integrity sha512-MJzyWie5yw+PH6p//fXlXzmsULLtpBo992EWEKl2uv5M5Zj9etTwfuutCUK7o98mr6itDl+vS/CYIMP68jCf8w== +xterm-addon-unicode11@0.3.0-beta.5: + version "0.3.0-beta.5" + resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.3.0-beta.5.tgz#7e490799d530d3b301125c7a4e92317c161761c4" + integrity sha512-SgDDL3PoMH1G48JO6T45whKAex4NPxi80UzUVitnrqyd8dFQP+oF6cxqUutULgm9HSGk62qy3mrZvIMGO5VXog== -xterm@4.12.0-beta.7: - version "4.12.0-beta.7" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.12.0-beta.7.tgz#8ee0a6e180dc57ea63a59682f2ccaf41e2cefd1f" - integrity sha512-qUKJg/aOVy2pOORiwuxd2Qp0MHJyQ/gcK7OMyjMEFLdS5L+KBbUkXtpOO9LWbW+B1DvS4b1k1MzvB4GF+qXXtg== +xterm-addon-webgl@0.11.0-beta.8: + version "0.11.0-beta.8" + resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.11.0-beta.8.tgz#8cb4925d67c31beb8144275daf46358f42eff9fe" + integrity sha512-udRmQ/jgH8cL8VQOZweytkToIROevVeiA7WY0tIe878Wt2zKY+AYHZV8js3c1W9wHDu5G90BhmzTidJ5UwZK3Q== + +xterm@4.12.0-beta.26: + version "4.12.0-beta.26" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.12.0-beta.26.tgz#57c75b732808795398a66bc1a3e06d09eaff2ada" + integrity sha512-yZB1kMBXQu2G0G1ch7TUi6f893iTZC+tmfjw/PQNZTmN46b4oX1l7rplc3sFcdrICHtmQ0Q5n1u0d6WUAdq1Kw== yauzl@^2.9.2: version "2.10.0" diff --git a/resources/linux/debian/control.template b/resources/linux/debian/control.template index 5a6d7be6..ca6c51a6 100644 --- a/resources/linux/debian/control.template +++ b/resources/linux/debian/control.template @@ -11,4 +11,7 @@ Provides: visual-studio-@@NAME@@ Conflicts: visual-studio-@@NAME@@ Replaces: visual-studio-@@NAME@@ Description: Code editing. Redefined. - Visual Studio Code is a new choice of tool that combines the simplicity of a code editor with what developers need for the core edit-build-debug cycle. See https://code.visualstudio.com/docs/setup/linux for installation instructions and FAQ. + Visual Studio Code is a new choice of tool that combines the simplicity of a + code editor with what developers need for the core edit-build-debug cycle. + See https://code.visualstudio.com/docs/setup/linux for installation + instructions and FAQ. diff --git a/resources/web/code-web.js b/resources/web/code-web.js index 2690d2fc..f9c00450 100644 --- a/resources/web/code-web.js +++ b/resources/web/code-web.js @@ -261,6 +261,9 @@ const requestHandler = (req, res) => { } else if (pathname === '/fetch-callback') { // callback fetch support return handleFetchCallback(req, res, parsedUrl); + } else if (pathname === '/builtin') { + // builtin extnesions JSON + return handleBuiltInExtensions(req, res, parsedUrl); } return serveError(req, res, 404, 'Not found.'); @@ -303,6 +306,17 @@ function addCORSReplyHeader(req) { return (ALLOWED_CORS_ORIGINS.indexOf(req.headers['origin']) >= 0); } +/** + * @param {import('http').IncomingMessage} req + * @param {import('http').ServerResponse} res + * @param {import('url').UrlWithParsedQuery} parsedUrl + */ +async function handleBuiltInExtensions(req, res, parsedUrl) { + const { extensions } = await builtInExtensionsPromise; + res.writeHead(200, { 'Content-Type': 'application/json' }); + return res.end(JSON.stringify(extensions)); +} + /** * @param {import('http').IncomingMessage} req * @param {import('http').ServerResponse} res diff --git a/scripts/test-integration.bat b/scripts/test-integration.bat index 1220b529..0ce9c1ba 100644 --- a/scripts/test-integration.bat +++ b/scripts/test-integration.bat @@ -29,7 +29,8 @@ if "%INTEGRATION_TEST_ELECTRON_PATH%"=="" ( compile-extension:css-language-features-server^ compile-extension:html-language-features-server^ compile-extension:json-language-features-server^ - compile-extension:git + compile-extension:git^ + compile-extension-media :: Configuration for more verbose output set VSCODE_CLI=1 diff --git a/scripts/test-integration.sh b/scripts/test-integration.sh index 0eeefd80..c6c116c2 100755 --- a/scripts/test-integration.sh +++ b/scripts/test-integration.sh @@ -36,7 +36,8 @@ else compile-extension:css-language-features-server \ compile-extension:html-language-features-server \ compile-extension:json-language-features-server \ - compile-extension:git + compile-extension:git \ + compile-extension-media # Configuration for more verbose output export VSCODE_CLI=1 @@ -83,7 +84,7 @@ after_suite "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_EXTRA_ARGS $ROOT/extensions/typescript-language-features/test-workspace --extensionDevelopmentPath=$ROOT/extensions/typescript-language-features --extensionTestsPath=$ROOT/extensions/typescript-language-features/out/test/unit $ALL_PLATFORMS_API_TESTS_EXTRA_ARGS after_suite -"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_EXTRA_ARGS $ROOT/extensions/emmet/out/test/test-fixtures --extensionDevelopmentPath=$ROOT/extensions/emmet --extensionTestsPath=$ROOT/extensions/emmet/out/test $ALL_PLATFORMS_API_TESTS_EXTRA_ARGS +"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_EXTRA_ARGS $ROOT/extensions/emmet/test-workspace --extensionDevelopmentPath=$ROOT/extensions/emmet --extensionTestsPath=$ROOT/extensions/emmet/out/test $ALL_PLATFORMS_API_TESTS_EXTRA_ARGS after_suite "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_EXTRA_ARGS $(mktemp -d 2>/dev/null) --enable-proposed-api=vscode.git --extensionDevelopmentPath=$ROOT/extensions/git --extensionTestsPath=$ROOT/extensions/git/out/test $ALL_PLATFORMS_API_TESTS_EXTRA_ARGS diff --git a/src/bootstrap-node.js b/src/bootstrap-node.js index 35ead94b..b9cc3f67 100644 --- a/src/bootstrap-node.js +++ b/src/bootstrap-node.js @@ -98,7 +98,7 @@ exports.removeGlobalNodeModuleLookupPaths = function () { /** * Helper to enable portable mode. * - * @param {Partial} product + * @param {Partial} product * @returns {{ portableDataPath: string; isPortable: boolean; }} */ exports.configurePortable = function (product) { diff --git a/src/bootstrap-window.js b/src/bootstrap-window.js index c6213db7..ec669360 100644 --- a/src/bootstrap-window.js +++ b/src/bootstrap-window.js @@ -22,45 +22,65 @@ } }(this, function () { const bootstrapLib = bootstrap(); - const preloadGlobals = globals(); - const webFrame = preloadGlobals.webFrame; + const preloadGlobals = sandboxGlobals(); const safeProcess = preloadGlobals.process; - const configuration = parseWindowConfiguration(); - 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 - const whenEnvResolved = safeProcess.resolveEnv(configuration.userEnv); + const useCustomProtocol = safeProcess.sandboxed || typeof safeProcess.env['VSCODE_BROWSER_CODE_LOADING'] === 'string'; /** + * @typedef {import('./vs/base/parts/sandbox/common/sandboxTypes').ISandboxConfiguration} ISandboxConfiguration + * * @param {string[]} modulePaths - * @param {(result: unknown, configuration: object) => Promise | undefined} resultCallback - * @param {{ forceEnableDeveloperKeybindings?: boolean, disallowReloadKeybinding?: boolean, removeDeveloperKeybindingsAfterLoad?: boolean, canModifyDOM?: (config: object) => void, beforeLoaderConfig?: (config: object, loaderConfig: object) => void, beforeRequire?: () => void }=} options + * @param {(result: unknown, configuration: ISandboxConfiguration) => Promise | undefined} resultCallback + * @param {{ + * configureDeveloperSettings?: (config: ISandboxConfiguration) => { + * forceDisableShowDevtoolsOnError?: boolean, + * forceEnableDeveloperKeybindings?: boolean, + * disallowReloadKeybinding?: boolean, + * removeDeveloperKeybindingsAfterLoad?: boolean + * }, + * canModifyDOM?: (config: ISandboxConfiguration) => void, + * beforeLoaderConfig?: (loaderConfig: object) => void, + * beforeRequire?: () => void + * }} [options] */ - function load(modulePaths, resultCallback, options) { + async function load(modulePaths, resultCallback, options) { - // Apply zoom level early to avoid glitches - const zoomLevel = configuration.zoomLevel; - if (typeof zoomLevel === 'number' && zoomLevel !== 0) { - webFrame.setZoomLevel(zoomLevel); - } - - // Error handler - safeProcess.on('uncaughtException', function (error) { - onUnexpectedError(error, enableDeveloperTools); + // Error handler (TODO@sandbox non-sandboxed only) + let showDevtoolsOnError = !!safeProcess.env['VSCODE_DEV']; + safeProcess.on('uncaughtException', function (/** @type {string | Error} */ error) { + onUnexpectedError(error, showDevtoolsOnError); }); - // Developer tools - const enableDeveloperTools = (safeProcess.env['VSCODE_DEV'] || !!configuration.extensionDevelopmentPath) && !configuration.extensionTestsPath; - let developerToolsUnbind; - if (enableDeveloperTools || (options && options.forceEnableDeveloperKeybindings)) { - developerToolsUnbind = registerDeveloperKeybindings(options && options.disallowReloadKeybinding); + // Await window configuration from preload + performance.mark('code/willWaitForWindowConfig'); + /** @type {ISandboxConfiguration} */ + const configuration = await preloadGlobals.context.resolveConfiguration(); + performance.mark('code/didWaitForWindowConfig'); + + // Developer settings + const { + forceDisableShowDevtoolsOnError, + forceEnableDeveloperKeybindings, + disallowReloadKeybinding, + removeDeveloperKeybindingsAfterLoad + } = typeof options?.configureDeveloperSettings === 'function' ? options.configureDeveloperSettings(configuration) : { + forceDisableShowDevtoolsOnError: false, + forceEnableDeveloperKeybindings: false, + disallowReloadKeybinding: false, + removeDeveloperKeybindingsAfterLoad: false + }; + showDevtoolsOnError = safeProcess.env['VSCODE_DEV'] && !forceDisableShowDevtoolsOnError; + const enableDeveloperKeybindings = safeProcess.env['VSCODE_DEV'] || forceEnableDeveloperKeybindings; + let developerDeveloperKeybindingsDisposable; + if (enableDeveloperKeybindings) { + developerDeveloperKeybindingsDisposable = registerDeveloperKeybindings(disallowReloadKeybinding); } // Enable ASAR support globalThis.MonacoBootstrap.enableASARSupport(configuration.appRoot); - if (options && typeof options.canModifyDOM === 'function') { + // Signal DOM modifications are now OK + if (typeof options?.canModifyDOM === 'function') { options.canModifyDOM(configuration); } @@ -76,24 +96,22 @@ window.document.documentElement.setAttribute('lang', locale); - // do not advertise AMD to avoid confusing UMD modules loaded with nodejs + // Do not advertise AMD to avoid confusing UMD modules loaded with nodejs if (!useCustomProtocol) { window['define'] = undefined; } - // replace the patched electron fs with the original node fs for all AMD code (TODO@sandbox non-sandboxed only) + // Replace the patched electron fs with the original node fs for all AMD code (TODO@sandbox non-sandboxed only) if (!safeProcess.sandboxed) { require.define('fs', [], function () { return require.__$__nodeRequire('original-fs'); }); } window['MonacoEnvironment'] = {}; - const baseUrl = useCustomProtocol ? - `${bootstrapLib.fileUriFromPath(configuration.appRoot, { isWindows: safeProcess.platform === 'win32', scheme: 'vscode-file', fallbackAuthority: 'vscode-app' })}/out` : - `${bootstrapLib.fileUriFromPath(configuration.appRoot, { isWindows: safeProcess.platform === 'win32' })}/out`; - const loaderConfig = { - baseUrl, + baseUrl: useCustomProtocol ? + `${bootstrapLib.fileUriFromPath(configuration.appRoot, { isWindows: safeProcess.platform === 'win32', scheme: 'vscode-file', fallbackAuthority: 'vscode-app' })}/out` : + `${bootstrapLib.fileUriFromPath(configuration.appRoot, { isWindows: safeProcess.platform === 'win32' })}/out`, 'vs/nls': nlsConfig, preferScriptTags: useCustomProtocol }; @@ -129,7 +147,7 @@ loaderConfig.amdModulesPattern = /^vs\//; } - // cached data config + // Cached data config if (configuration.nodeCachedDataDir) { loaderConfig.nodeCachedData = { path: configuration.nodeCachedDataDir, @@ -137,29 +155,34 @@ }; } - if (options && typeof options.beforeLoaderConfig === 'function') { - options.beforeLoaderConfig(configuration, loaderConfig); + // Signal before require.config() + if (typeof options?.beforeLoaderConfig === 'function') { + options.beforeLoaderConfig(loaderConfig); } + // Configure loader require.config(loaderConfig); + // Handle pseudo NLS if (nlsConfig.pseudo) { require(['vs/nls'], function (nlsPlugin) { nlsPlugin.setPseudoTranslation(nlsConfig.pseudo); }); } - if (options && typeof options.beforeRequire === 'function') { + // Signal before require() + if (typeof options?.beforeRequire === 'function') { options.beforeRequire(); } + // Actually require the main module as specified require(modulePaths, async result => { try { // Wait for process environment being fully resolved performance.mark('code/willWaitForShellEnv'); if (!safeProcess.env['VSCODE_SKIP_PROCESS_ENV_PATCHING'] /* TODO@bpasero for https://github.com/microsoft/vscode/issues/108804 */) { - await whenEnvResolved; + await safeProcess.shellEnv(); } performance.mark('code/didWaitForShellEnv'); @@ -168,39 +191,16 @@ if (callbackResult instanceof Promise) { await callbackResult; - if (developerToolsUnbind && options && options.removeDeveloperKeybindingsAfterLoad) { - developerToolsUnbind(); + if (developerDeveloperKeybindingsDisposable && removeDeveloperKeybindingsAfterLoad) { + developerDeveloperKeybindingsDisposable(); } } } catch (error) { - onUnexpectedError(error, enableDeveloperTools); + onUnexpectedError(error, enableDeveloperKeybindings); } }, onUnexpectedError); } - /** - * Parses the contents of the window condiguration that - * is passed into the URL from the `electron-main` side. - * - * @returns {{ - * zoomLevel?: number, - * extensionDevelopmentPath?: string[], - * extensionTestsPath?: string, - * userEnv?: { [key: string]: string | undefined }, - * appRoot: string, - * nodeCachedDataDir?: string - * }} - */ - function parseWindowConfiguration() { - const rawConfiguration = (window.location.search || '').split(/[?&]/) - .filter(function (param) { return !!param; }) - .map(function (param) { return param.split('='); }) - .filter(function (param) { return param.length === 2; }) - .reduce(function (r, param) { r[param[0]] = decodeURIComponent(param[1]); return r; }, {}); - - return JSON.parse(rawConfiguration['config'] || '{}') || {}; - } - /** * @param {boolean | undefined} disallowReloadKeybinding * @returns {() => void} @@ -249,10 +249,10 @@ /** * @param {string | Error} error - * @param {boolean} [enableDeveloperTools] + * @param {boolean} [showDevtoolsOnError] */ - function onUnexpectedError(error, enableDeveloperTools) { - if (enableDeveloperTools) { + function onUnexpectedError(error, showDevtoolsOnError) { + if (showDevtoolsOnError) { const ipcRenderer = preloadGlobals.ipcRenderer; ipcRenderer.send('vscode:openDevTools'); } @@ -275,13 +275,12 @@ /** * @return {typeof import('./vs/base/parts/sandbox/electron-sandbox/globals')} */ - function globals() { + function sandboxGlobals() { // @ts-ignore (defined in globals.js) return window.vscode; } return { - load, - globals + load }; })); diff --git a/src/bootstrap.js b/src/bootstrap.js index 02c2ad09..d75e7633 100644 --- a/src/bootstrap.js +++ b/src/bootstrap.js @@ -173,7 +173,7 @@ /** * @returns {typeof import('./vs/base/parts/sandbox/electron-sandbox/globals') | undefined} */ - function safeGlobals() { + function safeSandboxGlobals() { const globals = (typeof self === 'object' ? self : typeof global === 'object' ? global : {}); return globals.vscode; @@ -183,13 +183,13 @@ * @returns {import('./vs/base/parts/sandbox/electron-sandbox/globals').ISandboxNodeProcess | NodeJS.Process} */ function safeProcess() { - if (typeof process !== 'undefined') { - return process; // Native environment (non-sandboxed) + const sandboxGlobals = safeSandboxGlobals(); + if (sandboxGlobals) { + return sandboxGlobals.process; // Native environment (sandboxed) } - const globals = safeGlobals(); - if (globals) { - return globals.process; // Native environment (sandboxed) + if (typeof process !== 'undefined') { + return process; // Native environment (non-sandboxed) } return undefined; @@ -199,9 +199,9 @@ * @returns {import('./vs/base/parts/sandbox/electron-sandbox/electronTypes').IpcRenderer | undefined} */ function safeIpcRenderer() { - const globals = safeGlobals(); - if (globals) { - return globals.ipcRenderer; + const sandboxGlobals = safeSandboxGlobals(); + if (sandboxGlobals) { + return sandboxGlobals.ipcRenderer; } return undefined; diff --git a/src/cli.js b/src/cli.js index 5624cb93..9e70249e 100644 --- a/src/cli.js +++ b/src/cli.js @@ -19,5 +19,8 @@ bootstrapNode.configurePortable(product); // Enable ASAR support bootstrap.enableASARSupport(undefined); +// Signal processes that we got launched as CLI +process.env['VSCODE_CLI'] = '1'; + // Load CLI through AMD loader require('./bootstrap-amd').load('vs/code/node/cli'); diff --git a/src/main.js b/src/main.js index 8a27a5a5..1e3b9896 100644 --- a/src/main.js +++ b/src/main.js @@ -6,17 +6,22 @@ //@ts-check 'use strict'; +/** + * @typedef {import('./vs/base/common/product').IProductConfiguration} IProductConfiguration + * @typedef {import('./vs/base/node/languagePacks').NLSConfiguration} NLSConfiguration + * @typedef {import('./vs/platform/environment/common/argv').NativeParsedArgs} NativeParsedArgs + */ + const perf = require('./vs/base/common/performance'); perf.mark('code/didStartMain'); const path = require('path'); const fs = require('fs'); const os = require('os'); -const { getNLSConfiguration } = require('./vs/base/node/languagePacks'); const bootstrap = require('./bootstrap'); const bootstrapNode = require('./bootstrap-node'); const { getUserDataPath } = require('./vs/platform/environment/node/userDataPath'); -/** @type {Partial} */ +/** @type {Partial} */ const product = require('../product.json'); const { app, protocol, crashReporter } = require('electron'); @@ -39,7 +44,9 @@ app.setPath('userData', userDataPath); const argvConfig = configureCommandlineSwitchesSync(args); // Configure crash reporter +perf.mark('code/willStartCrashReporter'); configureCrashReporter(); +perf.mark('code/didStartCrashReporter'); // Set logs path before app 'ready' event if running portable // to ensure that no 'logs' folder is created on disk at a @@ -53,10 +60,7 @@ if (portable && portable.isPortable) { protocol.registerSchemesAsPrivileged([ { scheme: 'vscode-webview', - privileges: { standard: true, secure: true, supportFetchAPI: true, corsEnabled: true } - }, { - scheme: 'vscode-webview-resource', - privileges: { secure: true, standard: true, supportFetchAPI: true, corsEnabled: true } + privileges: { standard: true, secure: true, supportFetchAPI: true, corsEnabled: true, allowServiceWorkers: true, } }, { scheme: 'vscode-file', @@ -74,13 +78,14 @@ const nodeCachedDataDir = getNodeCachedDir(); * Support user defined locale: load it early before app('ready') * to have more things running in parallel. * - * @type {Promise | undefined} + * @type {Promise | undefined} */ let nlsConfigurationPromise = undefined; const metaDataFile = path.join(__dirname, 'nls.metadata.json'); const locale = getUserDefinedLocale(argvConfig); if (locale) { + const { getNLSConfiguration } = require('./vs/base/node/languagePacks'); nlsConfigurationPromise = getNLSConfiguration(product.commit, userDataPath, metaDataFile, locale); } @@ -104,7 +109,7 @@ app.once('ready', function () { * Main startup routine * * @param {string | undefined} cachedDataDir - * @param {import('./vs/base/node/languagePacks').NLSConfiguration} nlsConfig + * @param {NLSConfiguration} nlsConfig */ function startup(cachedDataDir, nlsConfig) { nlsConfig._languagePackSupport = true; @@ -132,7 +137,7 @@ async function onReady() { } /** - * @param {import('./vs/platform/environment/common/argv').NativeParsedArgs} cliArgs + * @param {NativeParsedArgs} cliArgs */ function configureCommandlineSwitchesSync(cliArgs) { const SUPPORTED_ELECTRON_SWITCHES = [ @@ -159,16 +164,18 @@ function configureCommandlineSwitchesSync(cliArgs) { 'enable-proposed-api', // 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`) + // (all traces of `enable-browser-code-loading` and `VSCODE_BROWSER_CODE_LOADING`) 'enable-browser-code-loading', // Log level to use. Default is 'info'. Allowed values are 'critical', 'error', 'warn', 'info', 'debug', 'trace', 'off'. - 'log-level', + 'log-level' ]; // Read argv config const argvConfig = readArgvConfigSync(); + let browserCodeLoadingStrategy = undefined; + Object.keys(argvConfig).forEach(argvKey => { const argvValue = argvConfig[argvKey]; @@ -204,8 +211,10 @@ function configureCommandlineSwitchesSync(cliArgs) { break; case 'enable-browser-code-loading': - if (typeof argvValue === 'string') { - process.env['ENABLE_VSCODE_BROWSER_CODE_LOADING'] = argvValue; + if (argvValue === false) { + browserCodeLoadingStrategy = undefined; + } else if (typeof argvValue === 'string') { + browserCodeLoadingStrategy = argvValue; } break; @@ -224,9 +233,9 @@ function configureCommandlineSwitchesSync(cliArgs) { app.commandLine.appendSwitch('js-flags', jsFlags); } - // Support __sandbox flag - if (cliArgs.__sandbox) { - process.env['ENABLE_VSCODE_BROWSER_CODE_LOADING'] = 'bypassHeatCheck'; + // Configure vscode-file:// code loading environment + if (cliArgs.__sandbox || browserCodeLoadingStrategy) { + process.env['VSCODE_BROWSER_CODE_LOADING'] = browserCodeLoadingStrategy || 'bypassHeatCheck'; } return argvConfig; @@ -411,7 +420,7 @@ function configureCrashReporter() { } /** - * @param {import('./vs/platform/environment/common/argv').NativeParsedArgs} cliArgs + * @param {NativeParsedArgs} cliArgs * @returns {string | null} */ function getJSFlags(cliArgs) { @@ -431,7 +440,7 @@ function getJSFlags(cliArgs) { } /** - * @returns {import('./vs/platform/environment/common/argv').NativeParsedArgs} + * @returns {NativeParsedArgs} */ function parseCLIArgs() { const minimist = require('minimist'); @@ -549,7 +558,7 @@ function mkdirp(dir) { /** * Resolve the NLS configuration * - * @return {Promise} + * @return {Promise} */ async function resolveNlsConfiguration() { @@ -569,6 +578,7 @@ async function resolveNlsConfiguration() { // See above the comment about the loader and case sensitiviness appLocale = appLocale.toLowerCase(); + const { getNLSConfiguration } = require('./vs/base/node/languagePacks'); nlsConfiguration = await getNLSConfiguration(product.commit, userDataPath, metaDataFile, appLocale); if (!nlsConfiguration) { nlsConfiguration = { locale: appLocale, availableLanguages: {} }; diff --git a/src/tsconfig.base.json b/src/tsconfig.base.json index 6decaae0..40ad020a 100644 --- a/src/tsconfig.base.json +++ b/src/tsconfig.base.json @@ -4,6 +4,7 @@ "moduleResolution": "node", "experimentalDecorators": true, "noImplicitReturns": true, + "noImplicitOverride": true, "noUnusedLocals": true, "allowUnreachableCode": false, "strict": true, diff --git a/src/tsconfig.json b/src/tsconfig.json index 2f1c7462..e6e78721 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -12,7 +12,8 @@ "semver", "sinon", "winreg", - "trusted-types" + "trusted-types", + "wicg-file-system-access" ], "plugins": [ { diff --git a/src/tsconfig.monaco.json b/src/tsconfig.monaco.json index a36bbe6f..601aca6f 100644 --- a/src/tsconfig.monaco.json +++ b/src/tsconfig.monaco.json @@ -2,9 +2,7 @@ "extends": "./tsconfig.base.json", "compilerOptions": { "noEmit": true, - "types": [ - "trusted-types" - ], + "types": ["trusted-types"], "paths": {}, "module": "amd", "moduleResolution": "classic", @@ -27,6 +25,7 @@ "vs/platform/*/browser/*" ], "exclude": [ - "node_modules/*" + "node_modules/*", + "vs/platform/files/browser/htmlFileSystemProvider.ts" ] } diff --git a/src/vs/base/browser/contextmenu.ts b/src/vs/base/browser/contextmenu.ts index 49667d70..9d798339 100644 --- a/src/vs/base/browser/contextmenu.ts +++ b/src/vs/base/browser/contextmenu.ts @@ -20,7 +20,7 @@ export interface IContextMenuDelegate { getActions(): readonly IAction[]; getCheckedActionsRepresentation?(action: IAction): 'radio' | 'checkbox'; getActionViewItem?(action: IAction): IActionViewItem | undefined; - getActionsContext?(event?: IContextMenuEvent): any; + getActionsContext?(event?: IContextMenuEvent): unknown; getKeyBinding?(action: IAction): ResolvedKeybinding | undefined; getMenuClassName?(): string; onHide?(didCancel: boolean): void; diff --git a/src/vs/base/browser/dnd.ts b/src/vs/base/browser/dnd.ts index 642b0827..c9d07ccc 100644 --- a/src/vs/base/browser/dnd.ts +++ b/src/vs/base/browser/dnd.ts @@ -42,7 +42,7 @@ export class DelayedDragHandler extends Disposable { } } - dispose(): void { + override dispose(): void { super.dispose(); this.clearDragTimeout(); @@ -89,7 +89,7 @@ export function applyDragImage(event: DragEvent, label: string | null, clazz: st export interface IDragAndDropData { update(dataTransfer: DataTransfer): void; - getData(): any; + getData(): unknown; } export class DragAndDropData implements IDragAndDropData { diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index 0bdba6f8..022fe7c0 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -1193,18 +1193,21 @@ export function computeScreenAwareSize(cssPx: number): number { * to change the location of the current page. * See https://mathiasbynens.github.io/rel-noopener/ */ -export function windowOpenNoOpener(url: string): void { +export function windowOpenNoOpener(url: string): boolean { if (browser.isElectron || browser.isEdgeLegacyWebView) { // In VSCode, window.open() always returns null... // The same is true for a WebView (see https://github.com/microsoft/monaco-editor/issues/628) // Also call directly window.open in sandboxed Electron (see https://github.com/microsoft/monaco-editor/issues/2220) window.open(url); + return true; } else { let newTab = window.open(); if (newTab) { (newTab as any).opener = null; newTab.location.href = url; + return true; } + return false; } } @@ -1409,37 +1412,8 @@ export function multibyteAwareBtoa(str: string): string { */ export namespace WebFileSystemAccess { - // https://wicg.github.io/file-system-access/#dom-window-showdirectorypicker - export interface FileSystemAccess { - showDirectoryPicker: () => Promise; - } - - // https://wicg.github.io/file-system-access/#api-filesystemdirectoryhandle - export interface FileSystemDirectoryHandle { - readonly kind: 'directory', - readonly name: string, - - getFileHandle: (name: string, options?: { create?: boolean }) => Promise; - getDirectoryHandle: (name: string, options?: { create?: boolean }) => Promise; - } - - // https://wicg.github.io/file-system-access/#api-filesystemfilehandle - export interface FileSystemFileHandle { - readonly kind: 'file', - readonly name: string, - - createWritable: (options?: { keepExistingData?: boolean }) => Promise; - } - - // https://wicg.github.io/file-system-access/#api-filesystemwritablefilestream - export interface FileSystemWritableFileStream { - write: (buffer: Uint8Array) => Promise; - close: () => Promise; - } - - export function supported(obj: any & Window): obj is FileSystemAccess { - const candidate = obj as FileSystemAccess | undefined; - if (typeof candidate?.showDirectoryPicker === 'function') { + export function supported(obj: any & Window): boolean { + if (typeof obj?.showDirectoryPicker === 'function') { return true; } @@ -1589,7 +1563,7 @@ export class ModifierKeyEmitter extends Emitter { return ModifierKeyEmitter.instance; } - dispose() { + override dispose() { super.dispose(); this._subscriptions.dispose(); } diff --git a/src/vs/base/browser/event.ts b/src/vs/base/browser/event.ts index 02a8488d..9ae46f15 100644 --- a/src/vs/base/browser/event.ts +++ b/src/vs/base/browser/event.ts @@ -9,7 +9,7 @@ export type EventHandler = HTMLElement | HTMLDocument | Window; export interface IDomEvent { (element: EventHandler, type: K, useCapture?: boolean): BaseEvent; - (element: EventHandler, type: string, useCapture?: boolean): BaseEvent; + (element: EventHandler, type: string, useCapture?: boolean): BaseEvent; } export const domEvent: IDomEvent = (element: EventHandler, type: string, useCapture?: boolean) => { diff --git a/src/vs/base/browser/formattedTextRenderer.ts b/src/vs/base/browser/formattedTextRenderer.ts index 0e9b0c95..9ab2dfe7 100644 --- a/src/vs/base/browser/formattedTextRenderer.ts +++ b/src/vs/base/browser/formattedTextRenderer.ts @@ -16,6 +16,7 @@ export interface FormattedTextRenderOptions { readonly className?: string; readonly inline?: boolean; readonly actionHandler?: IContentActionHandler; + readonly renderCodeSegements?: boolean; } export function renderText(text: string, options: FormattedTextRenderOptions = {}): HTMLElement { @@ -26,7 +27,7 @@ export function renderText(text: string, options: FormattedTextRenderOptions = { export function renderFormattedText(formattedText: string, options: FormattedTextRenderOptions = {}): HTMLElement { const element = createElement(options); - _renderFormattedText(element, parseFormattedText(formattedText), options.actionHandler); + _renderFormattedText(element, parseFormattedText(formattedText, !!options.renderCodeSegements), options.actionHandler, options.renderCodeSegements); return element; } @@ -75,6 +76,7 @@ const enum FormatType { Italics, Action, ActionClose, + Code, NewLine } @@ -85,7 +87,7 @@ interface IFormatParseTree { children?: IFormatParseTree[]; } -function _renderFormattedText(element: Node, treeNode: IFormatParseTree, actionHandler?: IContentActionHandler) { +function _renderFormattedText(element: Node, treeNode: IFormatParseTree, actionHandler?: IContentActionHandler, renderCodeSegements?: boolean) { let child: Node | undefined; if (treeNode.type === FormatType.Text) { @@ -94,6 +96,8 @@ function _renderFormattedText(element: Node, treeNode: IFormatParseTree, actionH child = document.createElement('b'); } else if (treeNode.type === FormatType.Italics) { child = document.createElement('i'); + } else if (treeNode.type === FormatType.Code && renderCodeSegements) { + child = document.createElement('code'); } else if (treeNode.type === FormatType.Action && actionHandler) { const a = document.createElement('a'); a.href = '#'; @@ -114,12 +118,12 @@ function _renderFormattedText(element: Node, treeNode: IFormatParseTree, actionH if (child && Array.isArray(treeNode.children)) { treeNode.children.forEach((nodeChild) => { - _renderFormattedText(child!, nodeChild, actionHandler); + _renderFormattedText(child!, nodeChild, actionHandler, renderCodeSegements); }); } } -function parseFormattedText(content: string): IFormatParseTree { +function parseFormattedText(content: string, parseCodeSegments: boolean): IFormatParseTree { const root: IFormatParseTree = { type: FormatType.Root, @@ -134,19 +138,19 @@ function parseFormattedText(content: string): IFormatParseTree { while (!stream.eos()) { let next = stream.next(); - const isEscapedFormatType = (next === '\\' && formatTagType(stream.peek()) !== FormatType.Invalid); + const isEscapedFormatType = (next === '\\' && formatTagType(stream.peek(), parseCodeSegments) !== FormatType.Invalid); if (isEscapedFormatType) { next = stream.next(); // unread the backslash if it escapes a format tag type } - if (!isEscapedFormatType && isFormatTag(next) && next === stream.peek()) { + if (!isEscapedFormatType && isFormatTag(next, parseCodeSegments) && next === stream.peek()) { stream.advance(); if (current.type === FormatType.Text) { current = stack.pop()!; } - const type = formatTagType(next); + const type = formatTagType(next, parseCodeSegments); if (current.type === type || (current.type === FormatType.Action && type === FormatType.ActionClose)) { current = stack.pop()!; } else { @@ -200,11 +204,11 @@ function parseFormattedText(content: string): IFormatParseTree { return root; } -function isFormatTag(char: string): boolean { - return formatTagType(char) !== FormatType.Invalid; +function isFormatTag(char: string, supportCodeSegments: boolean): boolean { + return formatTagType(char, supportCodeSegments) !== FormatType.Invalid; } -function formatTagType(char: string): FormatType { +function formatTagType(char: string, supportCodeSegments: boolean): FormatType { switch (char) { case '*': return FormatType.Bold; @@ -214,6 +218,8 @@ function formatTagType(char: string): FormatType { return FormatType.Action; case ']': return FormatType.ActionClose; + case '`': + return supportCodeSegments ? FormatType.Code : FormatType.Invalid; default: return FormatType.Invalid; } diff --git a/src/vs/base/browser/touch.ts b/src/vs/base/browser/touch.ts index 7166c4ec..8e9c0857 100644 --- a/src/vs/base/browser/touch.ts +++ b/src/vs/base/browser/touch.ts @@ -136,7 +136,7 @@ export class Gesture extends Disposable { return 'ontouchstart' in window || navigator.maxTouchPoints > 0 || (window as Window).navigator.msMaxTouchPoints > 0; } - public dispose(): void { + public override dispose(): void { if (this.handle) { this.handle.dispose(); this.handle = null; diff --git a/src/vs/base/browser/ui/actionbar/actionViewItems.ts b/src/vs/base/browser/ui/actionbar/actionViewItems.ts index 26523c55..67419aca 100644 --- a/src/vs/base/browser/ui/actionbar/actionViewItems.ts +++ b/src/vs/base/browser/ui/actionbar/actionViewItems.ts @@ -27,12 +27,12 @@ export class BaseActionViewItem extends Disposable implements IActionViewItem { element: HTMLElement | undefined; - _context: any; + _context: unknown; _action: IAction; private _actionRunner: IActionRunner | undefined; - constructor(context: any, action: IAction, protected options: IBaseActionViewItemOptions = {}) { + constructor(context: unknown, action: IAction, protected options: IBaseActionViewItemOptions = {}) { super(); this._context = context || this; @@ -174,6 +174,10 @@ export class BaseActionViewItem extends Disposable implements IActionViewItem { } } + isFocused(): boolean { + return !!this.element?.classList.contains('focused'); + } + blur(): void { if (this.element) { this.element.blur(); @@ -212,7 +216,7 @@ export class BaseActionViewItem extends Disposable implements IActionViewItem { // implement in subclass } - dispose(): void { + override dispose(): void { if (this.element) { this.element.remove(); this.element = undefined; @@ -231,7 +235,7 @@ export interface IActionViewItemOptions extends IBaseActionViewItemOptions { export class ActionViewItem extends BaseActionViewItem { protected label: HTMLElement | undefined; - protected options: IActionViewItemOptions; + protected override options: IActionViewItemOptions; private cssClass?: string; @@ -244,7 +248,7 @@ export class ActionViewItem extends BaseActionViewItem { this.cssClass = ''; } - render(container: HTMLElement): void { + override render(container: HTMLElement): void { super.render(container); if (this.element) { @@ -276,32 +280,36 @@ export class ActionViewItem extends BaseActionViewItem { // Only set the tabIndex on the element once it is about to get focused // That way this element wont be a tab stop when it is not needed #106441 - focus(): void { + override focus(): void { if (this.label) { this.label.tabIndex = 0; this.label.focus(); } } - blur(): void { + override isFocused(): boolean { + return !!this.label && this.label?.tabIndex === 0; + } + + override blur(): void { if (this.label) { this.label.tabIndex = -1; } } - setFocusable(focusable: boolean): void { + override setFocusable(focusable: boolean): void { if (this.label) { this.label.tabIndex = focusable ? 0 : -1; } } - updateLabel(): void { + override updateLabel(): void { if (this.options.label && this.label) { this.label.textContent = this.getAction().label; } } - updateTooltip(): void { + override updateTooltip(): void { let title: string | null = null; if (this.getAction().tooltip) { @@ -320,7 +328,7 @@ export class ActionViewItem extends BaseActionViewItem { } } - updateClass(): void { + override updateClass(): void { if (this.cssClass && this.label) { this.label.classList.remove(...this.cssClass.split(' ')); } @@ -343,7 +351,7 @@ export class ActionViewItem extends BaseActionViewItem { } } - updateEnabled(): void { + override updateEnabled(): void { if (this.getAction().enabled) { if (this.label) { this.label.removeAttribute('aria-disabled'); @@ -365,7 +373,7 @@ export class ActionViewItem extends BaseActionViewItem { } } - updateChecked(): void { + override updateChecked(): void { if (this.label) { if (this.getAction().checked) { this.label.classList.add('checked'); @@ -407,23 +415,23 @@ export class SelectActionViewItem extends BaseActionViewItem { return option; } - setFocusable(focusable: boolean): void { + override setFocusable(focusable: boolean): void { this.selectBox.setFocusable(focusable); } - focus(): void { + override focus(): void { if (this.selectBox) { this.selectBox.focus(); } } - blur(): void { + override blur(): void { if (this.selectBox) { this.selectBox.blur(); } } - render(container: HTMLElement): void { + override render(container: HTMLElement): void { this.selectBox.render(container); } } diff --git a/src/vs/base/browser/ui/actionbar/actionbar.css b/src/vs/base/browser/ui/actionbar/actionbar.css index 30c24917..7020bdc6 100644 --- a/src/vs/base/browser/ui/actionbar/actionbar.css +++ b/src/vs/base/browser/ui/actionbar/actionbar.css @@ -4,16 +4,17 @@ *--------------------------------------------------------------------------------------------*/ .monaco-action-bar { - text-align: right; white-space: nowrap; + height: 100%; } .monaco-action-bar .actions-container { display: flex; margin: 0 auto; padding: 0; + height: 100%; width: 100%; - justify-content: flex-end; + align-items: center; } .monaco-action-bar.vertical .actions-container { @@ -21,9 +22,10 @@ } .monaco-action-bar .action-item { + display: block; + align-items: center; + justify-content: center; cursor: pointer; - display: inline-block; - transition: transform 50ms ease; position: relative; /* DO NOT REMOVE - this is the key to preventing the ghosting icon bug in Chrome 42 */ } @@ -31,23 +33,22 @@ cursor: default; } -.monaco-action-bar.animated .action-item.active { - transform: scale(1.272019649, 1.272019649); /* 1.272019649 = √φ */ -} - .monaco-action-bar .action-item .icon, .monaco-action-bar .action-item .codicon { - display: inline-block; + display: block; } .monaco-action-bar .action-item .codicon { display: flex; align-items: center; + width: 16px; + height: 16px; } .monaco-action-bar .action-label { font-size: 11px; - margin-right: 4px; + padding: 3px; + border-radius: 5px; } .monaco-action-bar .action-item.disabled .action-label, @@ -74,10 +75,6 @@ margin-right: .8em; } -.monaco-action-bar.animated.vertical .action-item.active { - transform: translate(5px, 0); -} - .secondary-actions .monaco-action-bar .action-label { margin-left: 6px; } diff --git a/src/vs/base/browser/ui/actionbar/actionbar.ts b/src/vs/base/browser/ui/actionbar/actionbar.ts index 2a51544c..d5486aef 100644 --- a/src/vs/base/browser/ui/actionbar/actionbar.ts +++ b/src/vs/base/browser/ui/actionbar/actionbar.ts @@ -15,7 +15,7 @@ import { IActionViewItemOptions, ActionViewItem, BaseActionViewItem } from 'vs/b export interface IActionViewItem extends IDisposable { actionRunner: IActionRunner; - setActionContext(context: any): void; + setActionContext(context: unknown): void; render(element: HTMLElement): void; isEnabled(): boolean; focus(fromRight?: boolean): void; // TODO@isidorn what is this? @@ -38,7 +38,7 @@ export interface ActionTrigger { export interface IActionBarOptions { readonly orientation?: ActionsOrientation; - readonly context?: any; + readonly context?: unknown; readonly actionViewItemProvider?: IActionViewItemProvider; readonly actionRunner?: IActionRunner; readonly ariaLabel?: string; @@ -264,11 +264,11 @@ export class ActionBar extends Disposable implements IActionRunner { } } - get context(): any { + get context(): unknown { return this._context; } - set context(context: any) { + set context(context: unknown) { this._context = context; this.viewItems.forEach(i => i.setActionContext(context)); } @@ -525,11 +525,11 @@ export class ActionBar extends Disposable implements IActionRunner { } } - run(action: IAction, context?: unknown): Promise { - return this._actionRunner.run(action, context); + async run(action: IAction, context?: unknown): Promise { + await this._actionRunner.run(action, context); } - dispose(): void { + override dispose(): void { dispose(this.viewItems); this.viewItems = []; diff --git a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.css b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.css index 50ad242a..3cd01666 100644 --- a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.css +++ b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.css @@ -24,6 +24,9 @@ height: 100%; outline: none; } +.monaco-breadcrumbs.disabled .monaco-breadcrumb-item { + cursor: default; +} .monaco-breadcrumbs .monaco-breadcrumb-item .codicon-breadcrumb-separator { color: inherit; diff --git a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts index 6f55172c..8aa46b57 100644 --- a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts +++ b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts @@ -56,6 +56,7 @@ export class BreadcrumbsWidget { private readonly _nodes = new Array(); private readonly _freeNodes = new Array(); + private _enabled: boolean = true; private _focusedItemIdx: number = -1; private _selectedItemIdx: number = -1; @@ -155,13 +156,18 @@ export class BreadcrumbsWidget { content += `.monaco-breadcrumbs .monaco-breadcrumb-item.focused.selected { color: ${style.breadcrumbsFocusAndSelectionForeground}}\n`; } if (style.breadcrumbsHoverForeground) { - content += `.monaco-breadcrumbs .monaco-breadcrumb-item:hover:not(.focused):not(.selected) { color: ${style.breadcrumbsHoverForeground}}\n`; + content += `.monaco-breadcrumbs:not(.disabled ) .monaco-breadcrumb-item:hover:not(.focused):not(.selected) { color: ${style.breadcrumbsHoverForeground}}\n`; } if (this._styleElement.innerText !== content) { this._styleElement.innerText = content; } } + setEnabled(value: boolean) { + this._enabled = value; + this._domNode.classList.toggle('disabled', !this._enabled); + } + domFocus(): void { let idx = this._focusedItemIdx >= 0 ? this._focusedItemIdx : this._items.length - 1; if (idx >= 0 && idx < this._items.length) { @@ -326,6 +332,9 @@ export class BreadcrumbsWidget { } private _onClick(event: IMouseEvent): void { + if (!this._enabled) { + return; + } for (let el: HTMLElement | null = event.target; el; el = el.parentElement) { let idx = this._nodes.indexOf(el as HTMLDivElement); if (idx >= 0) { diff --git a/src/vs/base/browser/ui/button/button.css b/src/vs/base/browser/ui/button/button.css index e4411a2e..fb4ebbf0 100644 --- a/src/vs/base/browser/ui/button/button.css +++ b/src/vs/base/browser/ui/button/button.css @@ -22,8 +22,9 @@ text-decoration: none !important; } +.monaco-button.disabled:focus, .monaco-button.disabled { - opacity: 0.4; + opacity: 0.4 !important; cursor: default; } @@ -39,3 +40,15 @@ .monaco-button-dropdown > .monaco-dropdown-button { margin-left: 1px; } + +.monaco-description-button { + flex-direction: column; +} + +.monaco-description-button .monaco-button-label { + font-weight: 500; +} + +.monaco-description-button .monaco-button-description { + font-style: italic; +} diff --git a/src/vs/base/browser/ui/button/button.ts b/src/vs/base/browser/ui/button/button.ts index 473eb2a2..e39f90d3 100644 --- a/src/vs/base/browser/ui/button/button.ts +++ b/src/vs/base/browser/ui/button/button.ts @@ -50,6 +50,10 @@ export interface IButton extends IDisposable { hasFocus(): boolean; } +export interface IButtonWithDescription extends IButton { + description: string; +} + export class Button extends Disposable implements IButton { private _element: HTMLElement; @@ -303,6 +307,207 @@ export class ButtonWithDropdown extends Disposable implements IButton { } } +export class ButtonWithDescription extends Disposable implements IButtonWithDescription { + + private _element: HTMLElement; + private _labelElement: HTMLElement; + private _descriptionElement: HTMLElement; + private options: IButtonOptions; + + private buttonBackground: Color | undefined; + private buttonHoverBackground: Color | undefined; + private buttonForeground: Color | undefined; + private buttonSecondaryBackground: Color | undefined; + private buttonSecondaryHoverBackground: Color | undefined; + private buttonSecondaryForeground: Color | undefined; + private buttonBorder: Color | undefined; + + private _onDidClick = this._register(new Emitter()); + get onDidClick(): BaseEvent { return this._onDidClick.event; } + + private focusTracker: IFocusTracker; + + constructor(container: HTMLElement, options?: IButtonOptions) { + super(); + + this.options = options || Object.create(null); + mixin(this.options, defaultOptions, false); + + this.buttonForeground = this.options.buttonForeground; + this.buttonBackground = this.options.buttonBackground; + this.buttonHoverBackground = this.options.buttonHoverBackground; + + this.buttonSecondaryForeground = this.options.buttonSecondaryForeground; + this.buttonSecondaryBackground = this.options.buttonSecondaryBackground; + this.buttonSecondaryHoverBackground = this.options.buttonSecondaryHoverBackground; + + this.buttonBorder = this.options.buttonBorder; + + this._element = document.createElement('a'); + this._element.classList.add('monaco-button'); + this._element.classList.add('monaco-description-button'); + this._element.tabIndex = 0; + this._element.setAttribute('role', 'button'); + + this._labelElement = document.createElement('div'); + this._labelElement.classList.add('monaco-button-label'); + this._labelElement.tabIndex = -1; + this._element.appendChild(this._labelElement); + + this._descriptionElement = document.createElement('div'); + this._descriptionElement.classList.add('monaco-button-description'); + this._descriptionElement.tabIndex = -1; + this._element.appendChild(this._descriptionElement); + + container.appendChild(this._element); + + this._register(Gesture.addTarget(this._element)); + + [EventType.CLICK, TouchEventType.Tap].forEach(eventType => { + this._register(addDisposableListener(this._element, eventType, e => { + if (!this.enabled) { + EventHelper.stop(e); + return; + } + + this._onDidClick.fire(e); + })); + }); + + this._register(addDisposableListener(this._element, EventType.KEY_DOWN, e => { + const event = new StandardKeyboardEvent(e); + let eventHandled = false; + if (this.enabled && (event.equals(KeyCode.Enter) || event.equals(KeyCode.Space))) { + this._onDidClick.fire(e); + eventHandled = true; + } else if (event.equals(KeyCode.Escape)) { + this._element.blur(); + eventHandled = true; + } + + if (eventHandled) { + EventHelper.stop(event, true); + } + })); + + this._register(addDisposableListener(this._element, EventType.MOUSE_OVER, e => { + if (!this._element.classList.contains('disabled')) { + this.setHoverBackground(); + } + })); + + this._register(addDisposableListener(this._element, EventType.MOUSE_OUT, e => { + this.applyStyles(); // restore standard styles + })); + + // Also set hover background when button is focused for feedback + this.focusTracker = this._register(trackFocus(this._element)); + this._register(this.focusTracker.onDidFocus(() => this.setHoverBackground())); + this._register(this.focusTracker.onDidBlur(() => this.applyStyles())); // restore standard styles + + this.applyStyles(); + } + + private setHoverBackground(): void { + let hoverBackground; + if (this.options.secondary) { + hoverBackground = this.buttonSecondaryHoverBackground ? this.buttonSecondaryHoverBackground.toString() : null; + } else { + hoverBackground = this.buttonHoverBackground ? this.buttonHoverBackground.toString() : null; + } + if (hoverBackground) { + this._element.style.backgroundColor = hoverBackground; + } + } + + style(styles: IButtonStyles): void { + this.buttonForeground = styles.buttonForeground; + this.buttonBackground = styles.buttonBackground; + this.buttonHoverBackground = styles.buttonHoverBackground; + this.buttonSecondaryForeground = styles.buttonSecondaryForeground; + this.buttonSecondaryBackground = styles.buttonSecondaryBackground; + this.buttonSecondaryHoverBackground = styles.buttonSecondaryHoverBackground; + this.buttonBorder = styles.buttonBorder; + + this.applyStyles(); + } + + private applyStyles(): void { + if (this._element) { + let background, foreground; + if (this.options.secondary) { + foreground = this.buttonSecondaryForeground ? this.buttonSecondaryForeground.toString() : ''; + background = this.buttonSecondaryBackground ? this.buttonSecondaryBackground.toString() : ''; + } else { + foreground = this.buttonForeground ? this.buttonForeground.toString() : ''; + background = this.buttonBackground ? this.buttonBackground.toString() : ''; + } + + const border = this.buttonBorder ? this.buttonBorder.toString() : ''; + + this._element.style.color = foreground; + this._element.style.backgroundColor = background; + + this._element.style.borderWidth = border ? '1px' : ''; + this._element.style.borderStyle = border ? 'solid' : ''; + this._element.style.borderColor = border; + } + } + + get element(): HTMLElement { + return this._element; + } + + set label(value: string) { + this._element.classList.add('monaco-text-button'); + if (this.options.supportIcons) { + reset(this._labelElement, ...renderLabelWithIcons(value)); + } else { + this._labelElement.textContent = value; + } + if (typeof this.options.title === 'string') { + this._element.title = this.options.title; + } else if (this.options.title) { + this._element.title = value; + } + } + + set description(value: string) { + if (this.options.supportIcons) { + reset(this._descriptionElement, ...renderLabelWithIcons(value)); + } else { + this._descriptionElement.textContent = value; + } + } + + set icon(icon: CSSIcon) { + this._element.classList.add(...CSSIcon.asClassNameArray(icon)); + } + + set enabled(value: boolean) { + if (value) { + this._element.classList.remove('disabled'); + this._element.setAttribute('aria-disabled', String(false)); + this._element.tabIndex = 0; + } else { + this._element.classList.add('disabled'); + this._element.setAttribute('aria-disabled', String(true)); + } + } + + get enabled() { + return !this._element.classList.contains('disabled'); + } + + focus(): void { + this._element.focus(); + } + + hasFocus(): boolean { + return this._element === document.activeElement; + } +} + export class ButtonBar extends Disposable { private _buttons: IButton[] = []; @@ -321,6 +526,12 @@ export class ButtonBar extends Disposable { return button; } + addButtonWithDescription(options?: IButtonOptions): IButtonWithDescription { + const button = this._register(new ButtonWithDescription(this.container, options)); + this.pushButton(button); + return button; + } + addButtonWithDropdown(options: IButtonWithDropdownOptions): IButton { const button = this._register(new ButtonWithDropdown(this.container, options)); this.pushButton(button); diff --git a/src/vs/base/browser/ui/checkbox/checkbox.ts b/src/vs/base/browser/ui/checkbox/checkbox.ts index d9319424..5a67a234 100644 --- a/src/vs/base/browser/ui/checkbox/checkbox.ts +++ b/src/vs/base/browser/ui/checkbox/checkbox.ts @@ -44,7 +44,7 @@ export class CheckboxActionViewItem extends BaseActionViewItem { protected checkbox: Checkbox | undefined; protected readonly disposables = new DisposableStore(); - render(container: HTMLElement): void { + override render(container: HTMLElement): void { this.element = container; this.disposables.clear(); @@ -59,7 +59,7 @@ export class CheckboxActionViewItem extends BaseActionViewItem { this.element.appendChild(this.checkbox.domNode); } - updateEnabled(): void { + override updateEnabled(): void { if (this.checkbox) { if (this.isEnabled()) { this.checkbox.enable(); @@ -69,33 +69,33 @@ export class CheckboxActionViewItem extends BaseActionViewItem { } } - updateChecked(): void { + override updateChecked(): void { if (this.checkbox) { this.checkbox.checked = this._action.checked; } } - focus(): void { + override focus(): void { if (this.checkbox) { this.checkbox.domNode.tabIndex = 0; this.checkbox.focus(); } } - blur(): void { + override blur(): void { if (this.checkbox) { this.checkbox.domNode.tabIndex = -1; this.checkbox.domNode.blur(); } } - setFocusable(focusable: boolean): void { + override setFocusable(focusable: boolean): void { if (this.checkbox) { this.checkbox.domNode.tabIndex = focusable ? 0 : -1; } } - dispose(): void { + override dispose(): void { this.disposables.dispose(); super.dispose(); } diff --git a/src/vs/base/browser/ui/codicons/codicon/codicon-modifiers.css b/src/vs/base/browser/ui/codicons/codicon/codicon-modifiers.css index 4b5b5141..9666216f 100644 --- a/src/vs/base/browser/ui/codicons/codicon/codicon-modifiers.css +++ b/src/vs/base/browser/ui/codicons/codicon/codicon-modifiers.css @@ -13,7 +13,10 @@ } } -.codicon-sync.codicon-modifier-spin, .codicon-loading.codicon-modifier-spin, .codicon-gear.codicon-modifier-spin { +.codicon-sync.codicon-modifier-spin, +.codicon-loading.codicon-modifier-spin, +.codicon-gear.codicon-modifier-spin, +.codicon-notebook-state-executing.codicon-modifier-spin { /* Use steps to throttle FPS to reduce CPU usage */ animation: codicon-spin 1.5s steps(30) infinite; } diff --git a/src/vs/base/browser/ui/codicons/codicon/codicon.ttf b/src/vs/base/browser/ui/codicons/codicon/codicon.ttf index 66ab56e0456106da681a192ef2196c52b892308e..016e9a1e000af380e5512c4bdb85aec40db82477 100644 GIT binary patch delta 10735 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 delta 5961 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 diff --git a/src/vs/base/browser/ui/contextview/contextview.ts b/src/vs/base/browser/ui/contextview/contextview.ts index ef695b6a..30efdd96 100644 --- a/src/vs/base/browser/ui/contextview/contextview.ts +++ b/src/vs/base/browser/ui/contextview/contextview.ts @@ -45,7 +45,7 @@ export interface IDelegate { anchorAxisAlignment?: AnchorAxisAlignment; // default: vertical canRelayout?: boolean; // default: true onDOMEvent?(e: Event, activeElement: HTMLElement): void; - onHide?(data?: any): void; + onHide?(data?: unknown): void; } export interface IContextViewProvider { @@ -324,7 +324,7 @@ export class ContextView extends Disposable { this.view.style.width = 'initial'; } - hide(data?: any): void { + hide(data?: unknown): void { const delegate = this.delegate; this.delegate = null; @@ -351,7 +351,7 @@ export class ContextView extends Disposable { } } - dispose(): void { + override dispose(): void { this.hide(); super.dispose(); diff --git a/src/vs/base/browser/ui/dialog/dialog.css b/src/vs/base/browser/ui/dialog/dialog.css index 7c5ffc1c..067a6d54 100644 --- a/src/vs/base/browser/ui/dialog/dialog.css +++ b/src/vs/base/browser/ui/dialog/dialog.css @@ -10,7 +10,7 @@ width: 100%; left:0; top:0; - z-index: 2000; + z-index: 2600; display: flex; justify-content: center; align-items: center; @@ -34,17 +34,12 @@ /** Dialog: Title Actions Row */ .monaco-dialog-box .dialog-toolbar-row { + height: 22px; padding-bottom: 4px; } -.monaco-dialog-box .action-label { - height: 16px; - min-width: 16px; - background-size: 16px; - background-position: 50%; - background-repeat: no-repeat; - margin: 0px; - margin-left: 4px; +.monaco-dialog-box .dialog-toolbar-row .actions-container { + justify-content: flex-end; } /** Dialog: Message Row */ @@ -144,6 +139,10 @@ overflow: hidden; } +.monaco-dialog-box > .dialog-buttons-row > .dialog-buttons.centered { + justify-content: center; +} + .monaco-dialog-box > .dialog-buttons-row > .dialog-buttons > .monaco-button { width: fit-content; width: -moz-fit-content; diff --git a/src/vs/base/browser/ui/dialog/dialog.ts b/src/vs/base/browser/ui/dialog/dialog.ts index a1646c4c..2c5d315a 100644 --- a/src/vs/base/browser/ui/dialog/dialog.ts +++ b/src/vs/base/browser/ui/dialog/dialog.ts @@ -11,7 +11,7 @@ import { domEvent } from 'vs/base/browser/event'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { Color } from 'vs/base/common/color'; -import { ButtonBar, IButtonStyles } from 'vs/base/browser/ui/button/button'; +import { ButtonBar, ButtonWithDescription, IButtonStyles } from 'vs/base/browser/ui/button/button'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { Action } from 'vs/base/common/actions'; import { mnemonicButtonLabel } from 'vs/base/common/labels'; @@ -34,6 +34,10 @@ export interface IDialogOptions { readonly type?: 'none' | 'info' | 'error' | 'question' | 'warning' | 'pending'; readonly inputs?: IDialogInputOptions[]; readonly keyEventProcessor?: (event: StandardKeyboardEvent) => void; + readonly renderBody?: (container: HTMLElement) => void; + readonly icon?: Codicon; + readonly buttonDetails?: string[]; + readonly disableCloseAction?: boolean; } export interface IDialogResult { @@ -53,6 +57,8 @@ export interface IDialogStyles extends IButtonStyles, ISimpleCheckboxStyles { readonly inputBackground?: Color; readonly inputForeground?: Color; readonly inputBorder?: Color; + readonly textLinkForeground?: Color; + } interface ButtonMapEntry { @@ -71,6 +77,7 @@ export class Dialog extends Disposable { private modalElement: HTMLElement | undefined; private readonly buttonsContainer: HTMLElement; private readonly messageDetailElement: HTMLElement; + private readonly messageContainer: HTMLElement; private readonly iconElement: HTMLElement; private readonly checkbox: SimpleCheckbox | undefined; private readonly toolbarContainer: HTMLElement; @@ -83,7 +90,7 @@ export class Dialog extends Disposable { constructor(private container: HTMLElement, private message: string, buttons: string[], private options: IDialogOptions) { super(); - this.modalElement = this.container.appendChild($(`.monaco-dialog-modal-block${options.type === 'pending' ? '.dimmed' : ''}`)); + this.modalElement = this.container.appendChild($(`.monaco-dialog-modal-block.dimmed`)); this.shadowElement = this.modalElement.appendChild($('.dialog-shadow')); this.element = this.shadowElement.appendChild($('.monaco-dialog-box')); this.element.setAttribute('role', 'dialog'); @@ -95,20 +102,29 @@ export class Dialog extends Disposable { const messageRowElement = this.element.appendChild($('.dialog-message-row')); this.iconElement = messageRowElement.appendChild($('.dialog-icon')); - const messageContainer = messageRowElement.appendChild($('.dialog-message-container')); + this.messageContainer = messageRowElement.appendChild($('.dialog-message-container')); - if (this.options.detail) { - const messageElement = messageContainer.appendChild($('.dialog-message')); + if (this.options.detail || this.options.renderBody) { + const messageElement = this.messageContainer.appendChild($('.dialog-message')); const messageTextElement = messageElement.appendChild($('.dialog-message-text')); messageTextElement.innerText = this.message; } - this.messageDetailElement = messageContainer.appendChild($('.dialog-message-detail')); - this.messageDetailElement.innerText = this.options.detail ? this.options.detail : message; + this.messageDetailElement = this.messageContainer.appendChild($('.dialog-message-detail')); + if (this.options.detail || !this.options.renderBody) { + this.messageDetailElement.innerText = this.options.detail ? this.options.detail : message; + } else { + this.messageDetailElement.style.display = 'none'; + } + + if (this.options.renderBody) { + const customBody = this.messageContainer.appendChild($('.dialog-message-body')); + this.options.renderBody(customBody); + } if (this.options.inputs) { this.inputs = this.options.inputs.map(input => { - const inputRowElement = messageContainer.appendChild($('.dialog-message-input')); + const inputRowElement = this.messageContainer.appendChild($('.dialog-message-input')); const inputBox = this._register(new InputBox(inputRowElement, undefined, { placeholder: input.placeholder, @@ -126,7 +142,7 @@ export class Dialog extends Disposable { } if (this.options.checkboxLabel) { - const checkboxRowElement = messageContainer.appendChild($('.dialog-checkbox-row')); + const checkboxRowElement = this.messageContainer.appendChild($('.dialog-checkbox-row')); const checkbox = this.checkbox = this._register(new SimpleCheckbox(this.options.checkboxLabel, !!this.options.checkboxChecked)); @@ -175,12 +191,16 @@ export class Dialog extends Disposable { const buttonBar = this.buttonBar = this._register(new ButtonBar(this.buttonsContainer)); const buttonMap = this.rearrangeButtons(this.buttons, this.options.cancelId); + this.buttonsContainer.classList.toggle('centered'); // Handle button clicks buttonMap.forEach((entry, index) => { - const button = this._register(buttonBar.addButton({ title: true })); + const primary = buttonMap[index].index === 0; + const button = this.options.buttonDetails ? this._register(buttonBar.addButtonWithDescription({ title: true, secondary: !primary })) : this._register(buttonBar.addButton({ title: true, secondary: !primary })); button.label = mnemonicButtonLabel(buttonMap[index].label, true); - + if (button instanceof ButtonWithDescription) { + button.description = this.options.buttonDetails![buttonMap[index].index]; + } this._register(button.onDidClick(e => { if (e) { EventHelper.stop(e); @@ -287,7 +307,7 @@ export class Dialog extends Disposable { EventHelper.stop(e, true); const evt = new StandardKeyboardEvent(e); - if (evt.equals(KeyCode.Escape)) { + if (!this.options.disableCloseAction && evt.equals(KeyCode.Escape)) { resolve({ button: this.options.cancelId || 0, checkboxChecked: this.checkbox ? this.checkbox.checked : undefined @@ -313,34 +333,41 @@ export class Dialog extends Disposable { this.iconElement.classList.remove(...dialogErrorIcon.classNamesArray, ...dialogWarningIcon.classNamesArray, ...dialogInfoIcon.classNamesArray, ...Codicon.loading.classNamesArray, spinModifierClassName); - switch (this.options.type) { - case 'error': - this.iconElement.classList.add(...dialogErrorIcon.classNamesArray); - break; - case 'warning': - this.iconElement.classList.add(...dialogWarningIcon.classNamesArray); - break; - case 'pending': - this.iconElement.classList.add(...Codicon.loading.classNamesArray, spinModifierClassName); - break; - case 'none': - case 'info': - case 'question': - default: - this.iconElement.classList.add(...dialogInfoIcon.classNamesArray); - break; + if (this.options.icon) { + this.iconElement.classList.add(...this.options.icon.classNamesArray); + } else { + switch (this.options.type) { + case 'error': + this.iconElement.classList.add(...dialogErrorIcon.classNamesArray); + break; + case 'warning': + this.iconElement.classList.add(...dialogWarningIcon.classNamesArray); + break; + case 'pending': + this.iconElement.classList.add(...Codicon.loading.classNamesArray, spinModifierClassName); + break; + case 'none': + case 'info': + case 'question': + default: + this.iconElement.classList.add(...dialogInfoIcon.classNamesArray); + break; + } } - const actionBar = this._register(new ActionBar(this.toolbarContainer, {})); - const action = this._register(new Action('dialog.close', nls.localize('dialogClose', "Close Dialog"), dialogCloseIcon.classNames, true, async () => { - resolve({ - button: this.options.cancelId || 0, - checkboxChecked: this.checkbox ? this.checkbox.checked : undefined - }); - })); + if (!this.options.disableCloseAction) { + const actionBar = this._register(new ActionBar(this.toolbarContainer, {})); - actionBar.push(action, { icon: true, label: false, }); + const action = this._register(new Action('dialog.close', nls.localize('dialogClose', "Close Dialog"), dialogCloseIcon.classNames, true, async () => { + resolve({ + button: this.options.cancelId || 0, + checkboxChecked: this.checkbox ? this.checkbox.checked : undefined + }); + })); + + actionBar.push(action, { icon: true, label: false, }); + } this.applyStyles(); @@ -369,6 +396,7 @@ export class Dialog extends Disposable { const bgColor = style.dialogBackground; const shadowColor = style.dialogShadow ? `0 0px 8px ${style.dialogShadow}` : ''; const border = style.dialogBorder ? `1px solid ${style.dialogBorder}` : ''; + const linkFgColor = style.textLinkForeground; this.shadowElement.style.boxShadow = shadowColor; @@ -389,6 +417,12 @@ export class Dialog extends Disposable { this.messageDetailElement.style.color = messageDetailColor.makeOpaque(bgColor).toString(); } + if (linkFgColor) { + for (const el of this.messageContainer.getElementsByTagName('a')) { + el.style.color = linkFgColor.toString(); + } + } + let color; switch (this.options.type) { case 'error': @@ -417,7 +451,7 @@ export class Dialog extends Disposable { this.applyStyles(); } - dispose(): void { + override dispose(): void { super.dispose(); if (this.modalElement) { diff --git a/src/vs/base/browser/ui/dropdown/dropdown.css b/src/vs/base/browser/ui/dropdown/dropdown.css index 77060ee2..b363b557 100644 --- a/src/vs/base/browser/ui/dropdown/dropdown.css +++ b/src/vs/base/browser/ui/dropdown/dropdown.css @@ -11,8 +11,29 @@ .monaco-dropdown > .dropdown-label { cursor: pointer; height: 100%; + display: flex; + align-items: center; + justify-content: center; } .monaco-dropdown > .dropdown-label > .action-label.disabled { cursor: default; } + +.monaco-dropdown-with-primary { + display: flex !important; + flex-direction: row; + border-radius: 5px; +} + +.monaco-dropdown-with-primary > .action-container > .action-label { + margin-right: 0; +} + +.monaco-dropdown-with-primary > .dropdown-action-container > .monaco-dropdown > .dropdown-label .codicon[class*='codicon-'] { + font-size: 12px; + padding-left: 0px; + padding-right: 0px; + line-height: 16px; + margin-left: -4px; +} diff --git a/src/vs/base/browser/ui/dropdown/dropdown.ts b/src/vs/base/browser/ui/dropdown/dropdown.ts index ea6611ab..059b85ec 100644 --- a/src/vs/base/browser/ui/dropdown/dropdown.ts +++ b/src/vs/base/browser/ui/dropdown/dropdown.ts @@ -125,7 +125,7 @@ export class BaseDropdown extends ActionRunner { this.hide(); } - dispose(): void { + override dispose(): void { super.dispose(); this.hide(); @@ -159,7 +159,7 @@ export class Dropdown extends BaseDropdown { this.contextViewProvider = options.contextViewProvider; } - show(): void { + override show(): void { super.show(); this.element.classList.add('active'); @@ -187,7 +187,7 @@ export class Dropdown extends BaseDropdown { this.element.classList.remove('active'); } - hide(): void { + override hide(): void { super.hide(); if (this.contextViewProvider) { @@ -250,7 +250,7 @@ export class DropdownMenu extends BaseDropdown { this._actions = actions; } - show(): void { + override show(): void { super.show(); this.element.classList.add('active'); @@ -269,7 +269,7 @@ export class DropdownMenu extends BaseDropdown { }); } - hide(): void { + override hide(): void { super.hide(); } diff --git a/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts b/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts index 67e2e9bb..20a54b79 100644 --- a/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts +++ b/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts @@ -7,14 +7,15 @@ import 'vs/css!./dropdown'; import { Action, IAction, IActionRunner } from 'vs/base/common/actions'; import { IDisposable } from 'vs/base/common/lifecycle'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; -import { ResolvedKeybinding } from 'vs/base/common/keyCodes'; -import { append, $ } from 'vs/base/browser/dom'; +import { KeyCode, ResolvedKeybinding } from 'vs/base/common/keyCodes'; +import { append, $, addDisposableListener, EventType } from 'vs/base/browser/dom'; import { Emitter } from 'vs/base/common/event'; import { ActionViewItem, BaseActionViewItem, IActionViewItemOptions, IBaseActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { IActionProvider, DropdownMenu, IDropdownMenuOptions, ILabelRenderer } from 'vs/base/browser/ui/dropdown/dropdown'; import { IContextMenuProvider } from 'vs/base/browser/contextmenu'; import { Codicon } from 'vs/base/common/codicons'; import { IActionViewItemProvider } from 'vs/base/browser/ui/actionbar/actionbar'; +import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; export interface IKeybindingProvider { (action: IAction): ResolvedKeybinding | undefined; @@ -42,23 +43,26 @@ export class DropdownMenuActionViewItem extends BaseActionViewItem { private _onDidChangeVisibility = this._register(new Emitter()); readonly onDidChangeVisibility = this._onDidChangeVisibility.event; + protected override readonly options: IDropdownMenuActionViewItemOptions; + constructor( action: IAction, menuActionsOrProvider: readonly IAction[] | IActionProvider, contextMenuProvider: IContextMenuProvider, - protected options: IDropdownMenuActionViewItemOptions = {} + options: IDropdownMenuActionViewItemOptions = Object.create(null) ) { super(null, action, options); this.menuActionsOrProvider = menuActionsOrProvider; this.contextMenuProvider = contextMenuProvider; + this.options = options; if (this.options.actionRunner) { this.actionRunner = this.options.actionRunner; } } - render(container: HTMLElement): void { + override render(container: HTMLElement): void { this.actionItem = container; const labelRenderer: ILabelRenderer = (el: HTMLElement): IDisposable | null => { @@ -123,7 +127,7 @@ export class DropdownMenuActionViewItem extends BaseActionViewItem { this.updateEnabled(); } - setActionContext(newContext: unknown): void { + override setActionContext(newContext: unknown): void { super.setActionContext(newContext); if (this.dropdownMenu) { @@ -141,7 +145,7 @@ export class DropdownMenuActionViewItem extends BaseActionViewItem { } } - protected updateEnabled(): void { + protected override updateEnabled(): void { const disabled = !this.getAction().enabled; this.actionItem?.classList.toggle('disabled', disabled); this.element?.classList.toggle('disabled', disabled); @@ -166,7 +170,7 @@ export class ActionWithDropdownActionViewItem extends ActionViewItem { super(context, action, options); } - render(container: HTMLElement): void { + override render(container: HTMLElement): void { super.render(container); if (this.element) { this.element.classList.add('action-dropdown-item'); @@ -181,6 +185,36 @@ export class ActionWithDropdownActionViewItem extends ActionViewItem { }; this.dropdownMenuActionViewItem = new DropdownMenuActionViewItem(this._register(new Action('dropdownAction', undefined)), menuActionsProvider, this.contextMenuProvider, { classNames: ['dropdown', ...Codicon.dropDownButton.classNamesArray, ...(this.options).menuActionClassNames || []] }); this.dropdownMenuActionViewItem.render(this.element); + + this._register(addDisposableListener(this.element, EventType.KEY_DOWN, e => { + const event = new StandardKeyboardEvent(e); + let handled: boolean = false; + if (this.dropdownMenuActionViewItem?.isFocused() && event.equals(KeyCode.LeftArrow)) { + handled = true; + this.dropdownMenuActionViewItem?.blur(); + this.focus(); + } else if (this.isFocused() && event.equals(KeyCode.RightArrow)) { + handled = true; + this.blur(); + this.dropdownMenuActionViewItem?.focus(); + } + if (handled) { + event.preventDefault(); + event.stopPropagation(); + } + })); } } + + override blur(): void { + super.blur(); + this.dropdownMenuActionViewItem?.blur(); + } + + override setFocusable(focusable: boolean): void { + super.setFocusable(focusable); + this.dropdownMenuActionViewItem?.setFocusable(focusable); + } } + + diff --git a/src/vs/base/browser/ui/dropdown/dropdownWithPrimaryActionViewItem.ts b/src/vs/base/browser/ui/dropdown/dropdownWithPrimaryActionViewItem.ts new file mode 100644 index 00000000..eef52a5a --- /dev/null +++ b/src/vs/base/browser/ui/dropdown/dropdownWithPrimaryActionViewItem.ts @@ -0,0 +1,106 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IContextMenuProvider } from 'vs/base/browser/contextmenu'; +import { ActionViewItem, BaseActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; +import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdownActionViewItem'; +import { IAction } from 'vs/base/common/actions'; +import * as DOM from 'vs/base/browser/dom'; +import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; +import { KeyCode } from 'vs/base/common/keyCodes'; +import { dispose, IDisposable } from 'vs/base/common/lifecycle'; + +export class DropdownWithPrimaryActionViewItem extends BaseActionViewItem { + private _primaryAction: ActionViewItem; + private _dropdown: DropdownMenuActionViewItem; + private _container: HTMLElement | null = null; + private toDispose: IDisposable[]; + + constructor( + primaryAction: IAction, + dropdownAction: IAction, + dropdownMenuActions: IAction[], + _className: string, + private readonly _contextMenuProvider: IContextMenuProvider, + dropdownIcon?: string + ) { + super(null, primaryAction); + this._primaryAction = new ActionViewItem(undefined, primaryAction, { + icon: true, + label: false + }); + this._dropdown = new DropdownMenuActionViewItem(dropdownAction, dropdownMenuActions, this._contextMenuProvider, { + menuAsChild: true + }); + this.toDispose = []; + } + + override render(container: HTMLElement): void { + this._container = container; + super.render(this._container); + this._container.classList.add('monaco-dropdown-with-primary'); + const primaryContainer = DOM.$('.action-container'); + this._primaryAction.render(DOM.append(this._container, primaryContainer)); + const dropdownContainer = DOM.$('.dropdown-action-container'); + this._dropdown.render(DOM.append(this._container, dropdownContainer)); + + this.toDispose.push(DOM.addDisposableListener(primaryContainer, DOM.EventType.KEY_DOWN, (e: KeyboardEvent) => { + const event = new StandardKeyboardEvent(e); + if (event.equals(KeyCode.RightArrow)) { + this._primaryAction.element!.tabIndex = -1; + this._dropdown.focus(); + event.stopPropagation(); + } + })); + this.toDispose.push(DOM.addDisposableListener(dropdownContainer, DOM.EventType.KEY_DOWN, (e: KeyboardEvent) => { + const event = new StandardKeyboardEvent(e); + if (event.equals(KeyCode.LeftArrow)) { + this._primaryAction.element!.tabIndex = 0; + this._dropdown.setFocusable(false); + this._primaryAction.element?.focus(); + event.stopPropagation(); + } + })); + } + + override focus(fromRight?: boolean): void { + if (fromRight) { + this._dropdown.focus(); + } else { + this._primaryAction.element!.tabIndex = 0; + this._primaryAction.element!.focus(); + } + } + + override blur(): void { + this._primaryAction.element!.tabIndex = -1; + this._dropdown.blur(); + this._container!.blur(); + } + + override setFocusable(focusable: boolean): void { + if (focusable) { + this._primaryAction.element!.tabIndex = 0; + } else { + this._primaryAction.element!.tabIndex = -1; + this._dropdown.setFocusable(false); + } + } + + override dispose(): void { + this.toDispose = dispose(this.toDispose); + } + + update(dropdownAction: IAction, dropdownMenuActions: IAction[], dropdownIcon?: string): void { + this._dropdown?.dispose(); + this._dropdown = new DropdownMenuActionViewItem(dropdownAction, dropdownMenuActions, this._contextMenuProvider, { + menuAsChild: true, + classNames: ['codicon', dropdownIcon || 'codicon-chevron-down'] + }); + if (this.element) { + this._dropdown.render(this.element); + } + } +} diff --git a/src/vs/base/browser/ui/findinput/replaceInput.ts b/src/vs/base/browser/ui/findinput/replaceInput.ts index 1d6200fd..13ed1ade 100644 --- a/src/vs/base/browser/ui/findinput/replaceInput.ts +++ b/src/vs/base/browser/ui/findinput/replaceInput.ts @@ -382,7 +382,7 @@ export class ReplaceInput extends Widget { this.domNode.style.width = newWidth + 'px'; } - public dispose(): void { + public override dispose(): void { super.dispose(); } } diff --git a/src/vs/base/browser/ui/grid/grid.ts b/src/vs/base/browser/ui/grid/grid.ts index 22d8a75e..38bc8c53 100644 --- a/src/vs/base/browser/ui/grid/grid.ts +++ b/src/vs/base/browser/ui/grid/grid.ts @@ -521,51 +521,6 @@ export class SerializableGrid extends Grid { return { type: 'branch', data: node.children.map(c => SerializableGrid.serializeNode(c, orthogonal(orientation))), size }; } - private static deserializeNode(json: ISerializedNode, orientation: Orientation, box: Box, deserializer: IViewDeserializer): GridNode { - if (!json || typeof json !== 'object') { - throw new Error('Invalid JSON'); - } - - if (json.type === 'branch') { - if (!Array.isArray(json.data)) { - throw new Error('Invalid JSON: \'data\' property of branch must be an array.'); - } - - const children: GridNode[] = []; - let offset = 0; - - for (const child of json.data) { - if (typeof child.size !== 'number') { - throw new Error('Invalid JSON: \'size\' property of node must be a number.'); - } - - const childSize = child.type === 'leaf' && child.visible === false ? 0 : child.size; - const childBox: Box = orientation === Orientation.HORIZONTAL - ? { top: box.top, left: box.left + offset, width: childSize, height: box.height } - : { top: box.top + offset, left: box.left, width: box.width, height: childSize }; - - children.push(SerializableGrid.deserializeNode(child, orthogonal(orientation), childBox, deserializer)); - offset += childSize; - } - - return { children, box }; - - } else if (json.type === 'leaf') { - const view: T = deserializer.fromJSON(json.data); - return { view, box, cachedVisibleSize: json.visible === false ? json.size : undefined }; - } - - throw new Error('Invalid JSON: \'type\' property must be either \'branch\' or \'leaf\'.'); - } - - private static getFirstLeaf(node: GridNode): GridLeafNode { - if (!isGridBranchNode(node)) { - return node; - } - - return SerializableGrid.getFirstLeaf(node.children[0]); - } - static deserialize(json: ISerializedGrid, deserializer: IViewDeserializer, options: IGridOptions = {}): SerializableGrid { if (typeof json.orientation !== 'number') { throw new Error('Invalid JSON: \'orientation\' property must be a number.'); @@ -596,7 +551,7 @@ export class SerializableGrid extends Grid { }; } - layout(width: number, height: number): void { + override layout(width: number, height: number): void { super.layout(width, height); if (this.initialLayoutContext) { diff --git a/src/vs/base/browser/ui/hover/hover.css b/src/vs/base/browser/ui/hover/hover.css index 5b8bf0af..f22533ee 100644 --- a/src/vs/base/browser/ui/hover/hover.css +++ b/src/vs/base/browser/ui/hover/hover.css @@ -30,8 +30,7 @@ } .monaco-hover .markdown-hover > .hover-contents:not(.code-hover-contents) hr { - /* This is a strange rule but it avoids https://github.com/microsoft/vscode/issues/96795, just 100vw on its own caused the actual hover width to increase */ - min-width: calc(100% + 100vw); + min-width: 100%; } .monaco-hover p, diff --git a/src/vs/base/browser/ui/hover/hoverWidget.ts b/src/vs/base/browser/ui/hover/hoverWidget.ts index 28b240f0..34a02c21 100644 --- a/src/vs/base/browser/ui/hover/hoverWidget.ts +++ b/src/vs/base/browser/ui/hover/hoverWidget.ts @@ -10,6 +10,10 @@ import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableEle const $ = dom.$; +export const enum HoverPosition { + LEFT, RIGHT, BELOW, ABOVE +} + export class HoverWidget extends Disposable { public readonly containerDomNode: HTMLElement; diff --git a/src/vs/base/browser/ui/iconLabel/iconHoverDelegate.ts b/src/vs/base/browser/ui/iconLabel/iconHoverDelegate.ts index 1f4453fa..33e7c72f 100644 --- a/src/vs/base/browser/ui/iconLabel/iconHoverDelegate.ts +++ b/src/vs/base/browser/ui/iconLabel/iconHoverDelegate.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { AnchorPosition } from 'vs/base/browser/ui/contextview/contextview'; +import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget'; import { IMarkdownString } from 'vs/base/common/htmlContent'; import { IDisposable } from 'vs/base/common/lifecycle'; @@ -15,7 +15,7 @@ export interface IHoverDelegateTarget extends IDisposable { export interface IHoverDelegateOptions { text: IMarkdownString | string; target: IHoverDelegateTarget | HTMLElement; - anchorPosition?: AnchorPosition; + hoverPosition?: HoverPosition; } export interface IHoverDelegate { diff --git a/src/vs/base/browser/ui/iconLabel/iconLabel.ts b/src/vs/base/browser/ui/iconLabel/iconLabel.ts index b131ad7f..ba672788 100644 --- a/src/vs/base/browser/ui/iconLabel/iconLabel.ts +++ b/src/vs/base/browser/ui/iconLabel/iconLabel.ts @@ -11,12 +11,12 @@ import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { Range } from 'vs/base/common/range'; import { equals } from 'vs/base/common/objects'; import { IHoverDelegate, IHoverDelegateOptions, IHoverDelegateTarget } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; -import { AnchorPosition } from 'vs/base/browser/ui/contextview/contextview'; import { IMarkdownString } from 'vs/base/common/htmlContent'; import { isFunction, isString } from 'vs/base/common/types'; import { domEvent } from 'vs/base/browser/event'; import { localize } from 'vs/nls'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; +import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget'; export interface IIconLabelCreationOptions { supportHighlights?: boolean; @@ -222,12 +222,12 @@ export class IconLabel extends Disposable { let isHovering = false; let tokenSource: CancellationTokenSource; let hoverDisposable: IDisposable | undefined; - function mouseOver(this: HTMLElement, e: MouseEvent): any { + function mouseOver(this: HTMLElement, e: MouseEvent): void { if (isHovering) { return; } tokenSource = new CancellationTokenSource(); - function mouseLeaveOrDown(this: HTMLElement, e: MouseEvent): any { + function mouseLeaveOrDown(this: HTMLElement, e: MouseEvent): void { const isMouseDown = e.type === dom.EventType.MOUSE_DOWN; if (isMouseDown) { hoverDisposable?.dispose(); @@ -245,7 +245,7 @@ export class IconLabel extends Disposable { const mouseDownDisposable = domEvent(htmlElement, dom.EventType.MOUSE_DOWN, true)(mouseLeaveOrDown.bind(htmlElement)); isHovering = true; - function mouseMove(this: HTMLElement, e: MouseEvent): any { + function mouseMove(this: HTMLElement, e: MouseEvent): void { mouseX = e.x; } const mouseMoveDisposable = domEvent(htmlElement, dom.EventType.MOUSE_MOVE, true)(mouseMove.bind(htmlElement)); @@ -260,7 +260,7 @@ export class IconLabel extends Disposable { hoverOptions = { text: localize('iconLabel.loading', "Loading..."), target, - anchorPosition: AnchorPosition.BELOW + hoverPosition: HoverPosition.BELOW }; hoverDisposable = IconLabel.adjustXAndShowCustomHover(hoverOptions, mouseX, hoverDelegate, isHovering); @@ -269,7 +269,7 @@ export class IconLabel extends Disposable { hoverOptions = { text: resolvedTooltip, target, - anchorPosition: AnchorPosition.BELOW + hoverPosition: HoverPosition.BELOW }; // awaiting the tooltip could take a while. Make sure we're still hovering. hoverDisposable = IconLabel.adjustXAndShowCustomHover(hoverOptions, mouseX, hoverDelegate, isHovering); diff --git a/src/vs/base/browser/ui/inputbox/inputBox.ts b/src/vs/base/browser/ui/inputbox/inputBox.ts index c22ee416..62638456 100644 --- a/src/vs/base/browser/ui/inputbox/inputBox.ts +++ b/src/vs/base/browser/ui/inputbox/inputBox.ts @@ -610,7 +610,7 @@ export class InputBox extends Widget { } } - public dispose(): void { + public override dispose(): void { this._hideMessage(); this.message = null; diff --git a/src/vs/base/browser/ui/keybindingLabel/keybindingLabel.css b/src/vs/base/browser/ui/keybindingLabel/keybindingLabel.css index e33663d3..7f203773 100644 --- a/src/vs/base/browser/ui/keybindingLabel/keybindingLabel.css +++ b/src/vs/base/browser/ui/keybindingLabel/keybindingLabel.css @@ -11,13 +11,10 @@ .monaco-keybinding > .monaco-keybinding-key { display: inline-block; - border: solid 1px rgba(204, 204, 204, 0.4); - border-bottom-color: rgba(187, 187, 187, 0.4); + border-style: solid; + border-width: 1px; border-radius: 3px; - box-shadow: inset 0 -1px 0 rgba(187, 187, 187, 0.4); - background-color: rgba(221, 221, 221, 0.4); vertical-align: middle; - color: #555; font-size: 11px; padding: 3px 5px; margin: 0 2px; @@ -31,19 +28,10 @@ margin-right: 0; } -.hc-black .monaco-keybinding > .monaco-keybinding-key, -.vs-dark .monaco-keybinding > .monaco-keybinding-key { - background-color: rgba(128, 128, 128, 0.17); - color: #ccc; - border: solid 1px rgba(51, 51, 51, 0.6); - border-bottom-color: rgba(68, 68, 68, 0.6); - box-shadow: inset 0 -1px 0 rgba(68, 68, 68, 0.6); -} - .monaco-keybinding > .monaco-keybinding-key-separator { display: inline-block; } .monaco-keybinding > .monaco-keybinding-key-chord-separator { width: 6px; -} \ No newline at end of file +} diff --git a/src/vs/base/browser/ui/keybindingLabel/keybindingLabel.ts b/src/vs/base/browser/ui/keybindingLabel/keybindingLabel.ts index 13abc51d..998ca121 100644 --- a/src/vs/base/browser/ui/keybindingLabel/keybindingLabel.ts +++ b/src/vs/base/browser/ui/keybindingLabel/keybindingLabel.ts @@ -10,6 +10,8 @@ import { ResolvedKeybinding, ResolvedKeybindingPart } from 'vs/base/common/keyCo import { UILabelProvider } from 'vs/base/common/keybindingLabels'; import * as dom from 'vs/base/browser/dom'; import { localize } from 'vs/nls'; +import { IThemable } from 'vs/base/common/styler'; +import { Color } from 'vs/base/common/color'; const $ = dom.$; @@ -26,18 +28,44 @@ export interface Matches { chordPart: PartMatches; } -export interface KeybindingLabelOptions { - renderUnboundKeybindings: boolean; +export interface KeybindingLabelOptions extends IKeybindingLabelStyles { + renderUnboundKeybindings?: boolean; } -export class KeybindingLabel { +export interface IKeybindingLabelStyles { + keybindingLabelBackground?: Color; + keybindingLabelForeground?: Color; + keybindingLabelBorder?: Color; + keybindingLabelBottomBorder?: Color; + keybindingLabelShadow?: Color; +} + +export class KeybindingLabel implements IThemable { private domNode: HTMLElement; + private options: KeybindingLabelOptions; + + private readonly keyElements = new Set(); + private keybinding: ResolvedKeybinding | undefined; private matches: Matches | undefined; private didEverRender: boolean; - constructor(container: HTMLElement, private os: OperatingSystem, private options?: KeybindingLabelOptions) { + private labelBackground: Color | undefined; + private labelForeground: Color | undefined; + private labelBorder: Color | undefined; + private labelBottomBorder: Color | undefined; + private labelShadow: Color | undefined; + + constructor(container: HTMLElement, private os: OperatingSystem, options?: KeybindingLabelOptions) { + this.options = options || Object.create(null); + + this.labelBackground = this.options.keybindingLabelBackground; + this.labelForeground = this.options.keybindingLabelForeground; + this.labelBorder = this.options.keybindingLabelBorder; + this.labelBottomBorder = this.options.keybindingLabelBottomBorder; + this.labelShadow = this.options.keybindingLabelShadow; + this.domNode = dom.append(container, $('.monaco-keybinding')); this.didEverRender = false; container.appendChild(this.domNode); @@ -58,7 +86,7 @@ export class KeybindingLabel { } private render() { - dom.clearNode(this.domNode); + this.clear(); if (this.keybinding) { let [firstPart, chordPart] = this.keybinding.getParts(); @@ -74,9 +102,16 @@ export class KeybindingLabel { this.renderUnbound(this.domNode); } + this.applyStyles(); + this.didEverRender = true; } + private clear(): void { + dom.clearNode(this.domNode); + this.keyElements.clear(); + } + private renderPart(parent: HTMLElement, part: ResolvedKeybindingPart, match: PartMatches | null) { const modifierLabels = UILabelProvider.modifierLabels[this.os]; if (part.ctrlKey) { @@ -98,14 +133,54 @@ export class KeybindingLabel { } private renderKey(parent: HTMLElement, label: string, highlight: boolean, separator: string): void { - dom.append(parent, $('span.monaco-keybinding-key' + (highlight ? '.highlight' : ''), undefined, label)); + dom.append(parent, this.createKeyElement(label, highlight ? '.highlight' : '')); if (separator) { dom.append(parent, $('span.monaco-keybinding-key-separator', undefined, separator)); } } private renderUnbound(parent: HTMLElement): void { - dom.append(parent, $('span.monaco-keybinding-key', undefined, localize('unbound', "Unbound"))); + dom.append(parent, this.createKeyElement(localize('unbound', "Unbound"))); + } + + private createKeyElement(label: string, extraClass = ''): HTMLElement { + const keyElement = $('span.monaco-keybinding-key' + extraClass, undefined, label); + this.keyElements.add(keyElement); + + return keyElement; + } + + style(styles: IKeybindingLabelStyles): void { + this.labelBackground = styles.keybindingLabelBackground; + this.labelForeground = styles.keybindingLabelForeground; + this.labelBorder = styles.keybindingLabelBorder; + this.labelBottomBorder = styles.keybindingLabelBottomBorder; + this.labelShadow = styles.keybindingLabelShadow; + + this.applyStyles(); + } + + private applyStyles() { + if (this.element) { + for (const keyElement of this.keyElements) { + if (this.labelBackground) { + keyElement.style.backgroundColor = this.labelBackground?.toString(); + } + if (this.labelBorder) { + keyElement.style.borderColor = this.labelBorder.toString(); + } + if (this.labelBottomBorder) { + keyElement.style.borderBottomColor = this.labelBottomBorder.toString(); + } + if (this.labelShadow) { + keyElement.style.boxShadow = `inset 0 -1px 0 ${this.labelShadow}`; + } + } + + if (this.labelForeground) { + this.element.style.color = this.labelForeground.toString(); + } + } } private static areSame(a: Matches | undefined, b: Matches | undefined): boolean { diff --git a/src/vs/base/browser/ui/list/listView.ts b/src/vs/base/browser/ui/list/listView.ts index 840d53cb..6a2e9867 100644 --- a/src/vs/base/browser/ui/list/listView.ts +++ b/src/vs/base/browser/ui/list/listView.ts @@ -339,7 +339,7 @@ export class ListView implements ISpliceable, IDisposable { container.appendChild(this.domNode); this.scrollableElement.onScroll(this.onScroll, this, this.disposables); - domEvent(this.rowsContainer, TouchEventType.Change)(this.onTouchChange, this, this.disposables); + domEvent(this.rowsContainer, TouchEventType.Change)(e => this.onTouchChange(e as GestureEvent), this, this.disposables); // Prevent the monaco-scrollable-element from scrolling // https://github.com/microsoft/vscode/issues/44181 @@ -362,6 +362,7 @@ export class ListView implements ISpliceable, IDisposable { updateOptions(options: IListViewOptionsUpdate) { if (options.additionalScrollHeight !== undefined) { this.additionalScrollHeight = options.additionalScrollHeight; + this.scrollableElement.setScrollDimensions({ scrollHeight: this.scrollHeight }); } if (options.smoothScrolling !== undefined) { @@ -407,6 +408,7 @@ export class ListView implements ISpliceable, IDisposable { this.items[index].size = size; this.render(lastRenderRange, Math.max(0, this.lastRenderTop + heightDiff), this.lastRenderHeight, undefined, undefined, true); + this.setScrollTop(this.lastRenderTop); this.eventuallyUpdateScrollDimensions(); @@ -679,12 +681,12 @@ export class ListView implements ISpliceable, IDisposable { if (this.supportDynamicHeights) { this._rerender(this.scrollTop, this.renderHeight); } + } - if (this.horizontalScrolling) { - this.scrollableElement.setScrollDimensions({ - width: typeof width === 'number' ? width : getContentWidth(this.domNode) - }); - } + if (this.horizontalScrolling) { + this.scrollableElement.setScrollDimensions({ + width: typeof width === 'number' ? width : getContentWidth(this.domNode) + }); } } @@ -898,7 +900,7 @@ export class ListView implements ISpliceable, IDisposable { @memoize get onMouseOut(): Event> { return Event.map(domEvent(this.domNode, 'mouseout'), e => this.toMouseEvent(e)); } @memoize get onContextMenu(): Event> { return Event.map(domEvent(this.domNode, 'contextmenu'), e => this.toMouseEvent(e)); } @memoize get onTouchStart(): Event> { return Event.map(domEvent(this.domNode, 'touchstart'), e => this.toTouchEvent(e)); } - @memoize get onTap(): Event> { return Event.map(domEvent(this.rowsContainer, TouchEventType.Tap), e => this.toGestureEvent(e)); } + @memoize get onTap(): Event> { return Event.map(domEvent(this.rowsContainer, TouchEventType.Tap), e => this.toGestureEvent(e as GestureEvent)); } private toMouseEvent(browserEvent: MouseEvent): IListMouseEvent { const index = this.getItemIndexFromEventTarget(browserEvent.target || null); diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index 41cfef5c..e66c6bbc 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -188,7 +188,7 @@ class SelectionTrait extends Trait { super('selected'); } - renderIndex(index: number, container: HTMLElement): void { + override renderIndex(index: number, container: HTMLElement): void { super.renderIndex(index, container); if (this.setAriaSelected) { @@ -1635,13 +1635,13 @@ export class List implements ISpliceable, IThemable, IDisposable { this.view.setScrollTop(m * clamp(relativeTop, 0, 1) + elementTop); } else { const viewItemBottom = elementTop + elementHeight; - const wrapperBottom = scrollTop + this.view.renderHeight; + const scrollBottom = scrollTop + this.view.renderHeight; - if (elementTop < scrollTop && viewItemBottom >= wrapperBottom) { + if (elementTop < scrollTop && viewItemBottom >= scrollBottom) { // The element is already overflowing the viewport, no-op - } else if (elementTop < scrollTop) { + } else if (elementTop < scrollTop || (viewItemBottom >= scrollBottom && elementHeight >= this.view.renderHeight)) { this.view.setScrollTop(elementTop); - } else if (viewItemBottom >= wrapperBottom) { + } else if (viewItemBottom >= scrollBottom) { this.view.setScrollTop(viewItemBottom - this.view.renderHeight); } } diff --git a/src/vs/base/browser/ui/menu/menu.ts b/src/vs/base/browser/ui/menu/menu.ts index 21ef5090..4bf8f108 100644 --- a/src/vs/base/browser/ui/menu/menu.ts +++ b/src/vs/base/browser/ui/menu/menu.ts @@ -37,7 +37,7 @@ export enum Direction { } export interface IMenuOptions { - context?: any; + context?: unknown; actionViewItemProvider?: IActionViewItemProvider; actionRunner?: IActionRunner; getKeyBinding?: (action: IAction) => ResolvedKeybinding | undefined; @@ -264,7 +264,7 @@ export class Menu extends ActionBar { } } - getContainer(): HTMLElement { + override getContainer(): HTMLElement { return this.scrollableElement.getDomNode(); } @@ -309,7 +309,7 @@ export class Menu extends ActionBar { } } - protected updateFocus(fromRight?: boolean): void { + protected override updateFocus(fromRight?: boolean): void { super.updateFocus(fromRight, true); if (typeof this.focusedItem !== 'undefined') { @@ -385,7 +385,7 @@ class BaseMenuActionViewItem extends BaseActionViewItem { public container: HTMLElement | undefined; - protected options: IMenuItemOptions; + protected override options: IMenuItemOptions; protected item: HTMLElement | undefined; private runOnceToEnableMouseUp: RunOnceScheduler; @@ -465,7 +465,7 @@ class BaseMenuActionViewItem extends BaseActionViewItem { this._register(this.runOnceToEnableMouseUp); } - render(container: HTMLElement): void { + override render(container: HTMLElement): void { super.render(container); if (!this.element) { @@ -504,12 +504,12 @@ class BaseMenuActionViewItem extends BaseActionViewItem { this.updateChecked(); } - blur(): void { + override blur(): void { super.blur(); this.applyStyle(); } - focus(): void { + override focus(): void { super.focus(); if (this.item) { @@ -526,7 +526,7 @@ class BaseMenuActionViewItem extends BaseActionViewItem { } } - updateLabel(): void { + override updateLabel(): void { if (!this.label) { return; } @@ -579,7 +579,7 @@ class BaseMenuActionViewItem extends BaseActionViewItem { } } - updateTooltip(): void { + override updateTooltip(): void { let title: string | null = null; if (this.getAction().tooltip) { @@ -598,7 +598,7 @@ class BaseMenuActionViewItem extends BaseActionViewItem { } } - updateClass(): void { + override updateClass(): void { if (this.cssClass && this.item) { this.item.classList.remove(...this.cssClass.split(' ')); } @@ -614,7 +614,7 @@ class BaseMenuActionViewItem extends BaseActionViewItem { } } - updateEnabled(): void { + override updateEnabled(): void { if (this.getAction().enabled) { if (this.element) { this.element.classList.remove('disabled'); @@ -639,7 +639,7 @@ class BaseMenuActionViewItem extends BaseActionViewItem { } } - updateChecked(): void { + override updateChecked(): void { if (!this.item) { return; } @@ -724,7 +724,7 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem { }, 750); } - render(container: HTMLElement): void { + override render(container: HTMLElement): void { super.render(container); if (!this.element) { @@ -783,7 +783,7 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem { })); } - updateEnabled(): void { + override updateEnabled(): void { // override on submenu entry // native menus do not observe enablement on sumbenus // we mimic that behavior @@ -794,7 +794,7 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem { this.createSubmenu(selectFirst); } - onClick(e: EventLike): void { + override onClick(e: EventLike): void { // stop clicking from trying to run an action EventHelper.stop(e, true); @@ -925,7 +925,7 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem { } } - protected applyStyle(): void { + protected override applyStyle(): void { super.applyStyle(); if (!this.menuStyle) { @@ -944,7 +944,7 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem { } } - dispose(): void { + override dispose(): void { super.dispose(); this.hideScheduler.dispose(); diff --git a/src/vs/base/browser/ui/menu/menubar.ts b/src/vs/base/browser/ui/menu/menubar.ts index 129c5663..1667f808 100644 --- a/src/vs/base/browser/ui/menu/menubar.ts +++ b/src/vs/base/browser/ui/menu/menubar.ts @@ -422,7 +422,7 @@ export class MenuBar extends Disposable { } } - dispose(): void { + override dispose(): void { super.dispose(); this.menuCache.forEach(menuBarMenu => { diff --git a/src/vs/base/browser/ui/sash/sash.ts b/src/vs/base/browser/ui/sash/sash.ts index 34322ee6..c0845989 100644 --- a/src/vs/base/browser/ui/sash/sash.ts +++ b/src/vs/base/browser/ui/sash/sash.ts @@ -211,7 +211,7 @@ export class Sash extends Disposable { this._register(domEvent(this.el, 'mouseleave')(() => Sash.onMouseLeave(this))); this._register(Gesture.addTarget(this.el)); - this._register(domEvent(this.el, EventType.Start)(this.onTouchStart, this)); + this._register(domEvent(this.el, EventType.Start)(e => this.onTouchStart(e as GestureEvent), this)); if (typeof options.size === 'number') { this.size = options.size; @@ -430,6 +430,10 @@ export class Sash extends Disposable { } } + clearSashHoverState(): void { + Sash.onMouseLeave(this); + } + layout(): void { if (this.orientation === Orientation.VERTICAL) { const verticalProvider = (this.layoutProvider); @@ -484,7 +488,7 @@ export class Sash extends Disposable { return undefined; } - dispose(): void { + override dispose(): void { super.dispose(); this.el.remove(); } diff --git a/src/vs/base/browser/ui/scrollbar/scrollableElement.ts b/src/vs/base/browser/ui/scrollbar/scrollableElement.ts index fe7b7edc..bb4476f1 100644 --- a/src/vs/base/browser/ui/scrollbar/scrollableElement.ts +++ b/src/vs/base/browser/ui/scrollbar/scrollableElement.ts @@ -214,7 +214,7 @@ export abstract class AbstractScrollableElement extends Widget { this._domNode.appendChild(this._topShadowDomNode.domNode); this._topLeftShadowDomNode = createFastDomNode(document.createElement('div')); - this._topLeftShadowDomNode.setClassName('shadow top-left-corner'); + this._topLeftShadowDomNode.setClassName('shadow'); this._domNode.appendChild(this._topLeftShadowDomNode.domNode); } else { this._leftShadowDomNode = null; @@ -239,7 +239,7 @@ export abstract class AbstractScrollableElement extends Widget { this._revealOnScroll = true; } - public dispose(): void { + public override dispose(): void { this._mouseWheelToDispose = dispose(this._mouseWheelToDispose); super.dispose(); } @@ -484,9 +484,12 @@ export abstract class AbstractScrollableElement extends Widget { const enableTop = scrollState.scrollTop > 0; const enableLeft = scrollState.scrollLeft > 0; - this._leftShadowDomNode!.setClassName('shadow' + (enableLeft ? ' left' : '')); - this._topShadowDomNode!.setClassName('shadow' + (enableTop ? ' top' : '')); - this._topLeftShadowDomNode!.setClassName('shadow top-left-corner' + (enableTop ? ' top' : '') + (enableLeft ? ' left' : '')); + const leftClassName = (enableLeft ? ' left' : ''); + const topClassName = (enableTop ? ' top' : ''); + const topLeftClassName = (enableLeft || enableTop ? ' top-left-corner' : ''); + this._leftShadowDomNode!.setClassName(`shadow${leftClassName}`); + this._topShadowDomNode!.setClassName(`shadow${topClassName}`); + this._topLeftShadowDomNode!.setClassName(`shadow${topLeftClassName}${topClassName}${leftClassName}`); } } diff --git a/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts b/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts index 19e22c09..9f60bfb4 100644 --- a/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts +++ b/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts @@ -1046,7 +1046,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi } } - public dispose(): void { + public override dispose(): void { this.hideSelectDropDown(false); super.dispose(); } diff --git a/src/vs/base/browser/ui/splitview/paneview.css b/src/vs/base/browser/ui/splitview/paneview.css index a2ab9466..7dc22bed 100644 --- a/src/vs/base/browser/ui/splitview/paneview.css +++ b/src/vs/base/browser/ui/splitview/paneview.css @@ -52,6 +52,14 @@ margin-left: auto; } +.monaco-pane-view .pane > .pane-header > .actions .action-item { + margin-right: 4px; +} + +.monaco-pane-view .pane > .pane-header > .actions .action-label { + padding: 2px; +} + /* TODO: actions should be part of the pane, but they aren't yet */ .monaco-pane-view .pane:hover > .pane-header.expanded > .actions, .monaco-pane-view .pane:focus-within > .pane-header.expanded > .actions, @@ -60,22 +68,6 @@ display: initial; } -/* TODO: actions should be part of the pane, but they aren't yet */ -.monaco-pane-view .pane > .pane-header > .actions .action-label.icon, -.monaco-pane-view .pane > .pane-header > .actions .action-label.codicon { - width: 28px; - height: 22px; - background-size: 16px; - background-position: center center; - background-repeat: no-repeat; - margin-right: 0; - display: flex; - align-items: center; - justify-content: center; - color: inherit; - outline-offset: -2px; -} - .monaco-pane-view .pane > .pane-header .monaco-action-bar .action-item.select-container { cursor: default; } diff --git a/src/vs/base/browser/ui/splitview/paneview.ts b/src/vs/base/browser/ui/splitview/paneview.ts index 15bdc774..06e8b064 100644 --- a/src/vs/base/browser/ui/splitview/paneview.ts +++ b/src/vs/base/browser/ui/splitview/paneview.ts @@ -568,7 +568,7 @@ export class PaneView extends Disposable { }, 200); } - dispose(): void { + override dispose(): void { super.dispose(); this.paneItems.forEach(i => i.disposable.dispose()); diff --git a/src/vs/base/browser/ui/splitview/splitview.ts b/src/vs/base/browser/ui/splitview/splitview.ts index 40082a22..8434bc1c 100644 --- a/src/vs/base/browser/ui/splitview/splitview.ts +++ b/src/vs/base/browser/ui/splitview/splitview.ts @@ -1022,7 +1022,7 @@ export class SplitView extends Disposable { return undefined; } - dispose(): void { + override dispose(): void { super.dispose(); this.viewItems.forEach(i => i.dispose()); diff --git a/src/vs/base/browser/ui/toolbar/toolbar.css b/src/vs/base/browser/ui/toolbar/toolbar.css index 9a8c84a7..8182fbd9 100644 --- a/src/vs/base/browser/ui/toolbar/toolbar.css +++ b/src/vs/base/browser/ui/toolbar/toolbar.css @@ -3,6 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +.monaco-toolbar { + height: 100%; +} + .monaco-toolbar .toolbar-toggle-more { display: inline-block; padding: 0; diff --git a/src/vs/base/browser/ui/toolbar/toolbar.ts b/src/vs/base/browser/ui/toolbar/toolbar.ts index 8d1c5b6b..0abbb426 100644 --- a/src/vs/base/browser/ui/toolbar/toolbar.ts +++ b/src/vs/base/browser/ui/toolbar/toolbar.ts @@ -181,7 +181,7 @@ export class ToolBar extends Disposable { this.actionBar.clear(); } - dispose(): void { + override dispose(): void { this.clear(); super.dispose(); } @@ -202,7 +202,7 @@ class ToggleMenuAction extends Action { this.toggleDropdownMenu = toggleDropdownMenu; } - async run(): Promise { + override async run(): Promise { this.toggleDropdownMenu(); } diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index 2b466e54..efc69fa4 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -29,11 +29,11 @@ import { treeItemExpandedIcon, treeFilterOnTypeOnIcon, treeFilterOnTypeOffIcon, class TreeElementsDragAndDropData extends ElementsDragAndDropData { - set context(context: TContext | undefined) { + override set context(context: TContext | undefined) { this.data.context = context; } - get context(): TContext | undefined { + override get context(): TContext | undefined { return this.data.context; } @@ -1091,7 +1091,7 @@ class TreeNodeListMouseController extends MouseController< super(list); } - protected onViewPointer(e: IListMouseEvent>): void { + protected override onViewPointer(e: IListMouseEvent>): void { if (isInputElement(e.browserEvent.target as HTMLElement) || isMonacoEditor(e.browserEvent.target as HTMLElement)) { return; } @@ -1141,7 +1141,7 @@ class TreeNodeListMouseController extends MouseController< super.onViewPointer(e); } - protected onDoubleClick(e: IListMouseEvent>): void { + protected override onDoubleClick(e: IListMouseEvent>): void { const onTwistie = (e.browserEvent.target as HTMLElement).classList.contains('monaco-tl-twistie'); if (onTwistie || !this.tree.expandOnDoubleClick) { @@ -1175,11 +1175,11 @@ class TreeNodeList extends List> super(user, container, virtualDelegate, renderers, options); } - protected createMouseController(options: ITreeNodeListOptions): MouseController> { + protected override createMouseController(options: ITreeNodeListOptions): MouseController> { return new TreeNodeListMouseController(this, options.tree); } - splice(start: number, deleteCount: number, elements: ITreeNode[] = []): void { + override splice(start: number, deleteCount: number, elements: ITreeNode[] = []): void { super.splice(start, deleteCount, elements); if (elements.length === 0) { @@ -1217,7 +1217,7 @@ class TreeNodeList extends List> } } - setFocus(indexes: number[], browserEvent?: UIEvent, fromAPI = false): void { + override setFocus(indexes: number[], browserEvent?: UIEvent, fromAPI = false): void { super.setFocus(indexes, browserEvent); if (!fromAPI) { @@ -1225,7 +1225,7 @@ class TreeNodeList extends List> } } - setSelection(indexes: number[], browserEvent?: UIEvent, fromAPI = false): void { + override setSelection(indexes: number[], browserEvent?: UIEvent, fromAPI = false): void { super.setSelection(indexes, browserEvent); if (!fromAPI) { @@ -1233,7 +1233,7 @@ class TreeNodeList extends List> } } - setAnchor(index: number | undefined, fromAPI = false): void { + override setAnchor(index: number | undefined, fromAPI = false): void { super.setAnchor(index); if (!fromAPI) { diff --git a/src/vs/base/browser/ui/tree/asyncDataTree.ts b/src/vs/base/browser/ui/tree/asyncDataTree.ts index 731137df..4740d2c7 100644 --- a/src/vs/base/browser/ui/tree/asyncDataTree.ts +++ b/src/vs/base/browser/ui/tree/asyncDataTree.ts @@ -157,11 +157,11 @@ function asTreeContextMenuEvent(e: ITreeContextMenuEvent extends ElementsDragAndDropData { - set context(context: TContext | undefined) { + override set context(context: TContext | undefined) { this.data.context = context; } - get context(): TContext | undefined { + override get context(): TContext | undefined { return this.data.context; } @@ -1131,7 +1131,7 @@ export interface ICompressibleAsyncDataTreeOptionsUpdate extends IAsyncDataTreeO export class CompressibleAsyncDataTree extends AsyncDataTree { - protected readonly tree!: CompressibleObjectTree, TFilterData>; + protected override readonly tree!: CompressibleObjectTree, TFilterData>; protected readonly compressibleNodeMapper: CompressibleAsyncDataTreeNodeMapper = new WeakMapper(node => new CompressibleAsyncDataTreeNodeWrapper(node)); private filter?: ITreeFilter; @@ -1148,7 +1148,7 @@ export class CompressibleAsyncDataTree extends As this.filter = options.filter; } - protected createTree( + protected override createTree( user: string, container: HTMLElement, delegate: IListVirtualDelegate, @@ -1162,18 +1162,18 @@ export class CompressibleAsyncDataTree extends As return new CompressibleObjectTree(user, container, objectTreeDelegate, objectTreeRenderers, objectTreeOptions); } - protected asTreeElement(node: IAsyncDataTreeNode, viewStateContext?: IAsyncDataTreeViewStateContext): ICompressedTreeElement> { + protected override asTreeElement(node: IAsyncDataTreeNode, viewStateContext?: IAsyncDataTreeViewStateContext): ICompressedTreeElement> { return { incompressible: this.compressionDelegate.isIncompressible(node.element as T), ...super.asTreeElement(node, viewStateContext) }; } - updateOptions(options: ICompressibleAsyncDataTreeOptionsUpdate = {}): void { + override updateOptions(options: ICompressibleAsyncDataTreeOptionsUpdate = {}): void { this.tree.updateOptions(options); } - getViewState(): IAsyncDataTreeViewState { + override getViewState(): IAsyncDataTreeViewState { if (!this.identityProvider) { throw new TreeError(this.user, 'Can\'t get tree view state without an identity provider'); } @@ -1201,7 +1201,7 @@ export class CompressibleAsyncDataTree extends As return { focus, selection, expanded, scrollTop: this.scrollTop }; } - protected render(node: IAsyncDataTreeNode, viewStateContext?: IAsyncDataTreeViewStateContext): void { + protected override render(node: IAsyncDataTreeNode, viewStateContext?: IAsyncDataTreeViewStateContext): void { if (!this.identityProvider) { return super.render(node, viewStateContext); } @@ -1277,7 +1277,7 @@ export class CompressibleAsyncDataTree extends As // For compressed async data trees, `TreeVisibility.Recurse` doesn't currently work // and we have to filter everything beforehand // Related to #85193 and #85835 - protected processChildren(children: Iterable): Iterable { + protected override processChildren(children: Iterable): Iterable { if (this.filter) { children = Iterable.filter(children, e => { const result = this.filter!.filter(e, TreeVisibility.Visible); diff --git a/src/vs/base/browser/ui/tree/dataTree.ts b/src/vs/base/browser/ui/tree/dataTree.ts index b548e2e3..3adca02b 100644 --- a/src/vs/base/browser/ui/tree/dataTree.ts +++ b/src/vs/base/browser/ui/tree/dataTree.ts @@ -23,7 +23,7 @@ export interface IDataTreeViewState { export class DataTree extends AbstractTree { - protected model!: ObjectTreeModel; + protected override model!: ObjectTreeModel; private input: TInput | undefined; private identityProvider: IIdentityProvider | undefined; diff --git a/src/vs/base/browser/ui/tree/indexTree.ts b/src/vs/base/browser/ui/tree/indexTree.ts index 5272fa19..07b91eec 100644 --- a/src/vs/base/browser/ui/tree/indexTree.ts +++ b/src/vs/base/browser/ui/tree/indexTree.ts @@ -14,7 +14,7 @@ export interface IIndexTreeOptions extends IAbstractTreeO export class IndexTree extends AbstractTree { - protected model!: IndexTreeModel; + protected override model!: IndexTreeModel; constructor( user: string, diff --git a/src/vs/base/browser/ui/tree/objectTree.ts b/src/vs/base/browser/ui/tree/objectTree.ts index 21e65570..7649792a 100644 --- a/src/vs/base/browser/ui/tree/objectTree.ts +++ b/src/vs/base/browser/ui/tree/objectTree.ts @@ -38,9 +38,9 @@ export interface IObjectTreeSetChildrenOptions { export class ObjectTree, TFilterData = void> extends AbstractTree { - protected model!: IObjectTreeModel; + protected override model!: IObjectTreeModel; - get onDidChangeCollapseState(): Event> { return this.model.onDidChangeCollapseState; } + override get onDidChangeCollapseState(): Event> { return this.model.onDidChangeCollapseState; } constructor( user: string, @@ -194,7 +194,7 @@ export interface ICompressibleObjectTreeOptionsUpdate extends IAbstractTreeOptio export class CompressibleObjectTree, TFilterData = void> extends ObjectTree implements ICompressedTreeNodeProvider { - protected model!: CompressibleObjectTreeModel; + protected override model!: CompressibleObjectTreeModel; constructor( user: string, @@ -208,15 +208,15 @@ export class CompressibleObjectTree, TFilterData = vo super(user, container, delegate, compressibleRenderers, asObjectTreeOptions(compressedTreeNodeProvider, options)); } - setChildren(element: T | null, children: Iterable> = Iterable.empty(), options?: IObjectTreeSetChildrenOptions): void { + override setChildren(element: T | null, children: Iterable> = Iterable.empty(), options?: IObjectTreeSetChildrenOptions): void { this.model.setChildren(element, children, options); } - protected createModel(user: string, view: IList>, options: ICompressibleObjectTreeOptions): ITreeModel { + protected override createModel(user: string, view: IList>, options: ICompressibleObjectTreeOptions): ITreeModel { return new CompressibleObjectTreeModel(user, view, options); } - updateOptions(optionsUpdate: ICompressibleObjectTreeOptionsUpdate = {}): void { + override updateOptions(optionsUpdate: ICompressibleObjectTreeOptionsUpdate = {}): void { super.updateOptions(optionsUpdate); if (typeof optionsUpdate.compressionEnabled !== 'undefined') { diff --git a/src/vs/base/browser/ui/tree/treeDefaults.ts b/src/vs/base/browser/ui/tree/treeDefaults.ts index 834ab449..36e10cf4 100644 --- a/src/vs/base/browser/ui/tree/treeDefaults.ts +++ b/src/vs/base/browser/ui/tree/treeDefaults.ts @@ -13,11 +13,9 @@ export class CollapseAllAction extends Action { super('vs.tree.collapse', nls.localize('collapse all', "Collapse All"), 'collapse-all', enabled); } - async run(): Promise { + override async run(): Promise { this.viewer.collapseAll(); this.viewer.setSelection([]); this.viewer.setFocus([]); - this.viewer.domFocus(); - this.viewer.focusFirst(); } } diff --git a/src/vs/base/common/actions.ts b/src/vs/base/common/actions.ts index 466be3c5..171853e2 100644 --- a/src/vs/base/common/actions.ts +++ b/src/vs/base/common/actions.ts @@ -10,7 +10,7 @@ import { Event, Emitter } from 'vs/base/common/event'; export interface ITelemetryData { readonly from?: string; readonly target?: string; - [key: string]: any; + [key: string]: unknown; } export type WorkbenchActionExecutedClassification = { @@ -30,13 +30,14 @@ export interface IAction extends IDisposable { class: string | undefined; enabled: boolean; checked: boolean; - run(event?: any): Promise; + run(event?: unknown): Promise; } export interface IActionRunner extends IDisposable { - run(action: IAction, context?: any): Promise; readonly onDidRun: Event; readonly onBeforeRun: Event; + + run(action: IAction, context?: unknown): Promise; } export interface IActionChangeEvent { @@ -58,9 +59,9 @@ export class Action extends Disposable implements IAction { protected _cssClass: string | undefined; protected _enabled: boolean = true; protected _checked: boolean = false; - protected readonly _actionCallback?: (event?: any) => Promise; + protected readonly _actionCallback?: (event?: unknown) => Promise; - constructor(id: string, label: string = '', cssClass: string = '', enabled: boolean = true, actionCallback?: (event?: any) => Promise) { + constructor(id: string, label: string = '', cssClass: string = '', enabled: boolean = true, actionCallback?: (event?: unknown) => Promise) { super(); this._id = id; this._label = label; @@ -148,19 +149,16 @@ export class Action extends Disposable implements IAction { } } - run(event?: any, _data?: ITelemetryData): Promise { + async run(event?: unknown, data?: ITelemetryData): Promise { if (this._actionCallback) { - return this._actionCallback(event); + await this._actionCallback(event); } - - return Promise.resolve(true); } } export interface IRunEvent { readonly action: IAction; - readonly result?: any; - readonly error?: any; + readonly error?: Error; } export class ActionRunner extends Disposable implements IActionRunner { @@ -171,24 +169,25 @@ export class ActionRunner extends Disposable implements IActionRunner { private _onDidRun = this._register(new Emitter()); readonly onDidRun = this._onDidRun.event; - async run(action: IAction, context?: any): Promise { + async run(action: IAction, context?: unknown): Promise { if (!action.enabled) { - return Promise.resolve(null); + return; } - this._onBeforeRun.fire({ action: action }); + this._onBeforeRun.fire({ action }); + let error: Error | undefined = undefined; try { - const result = await this.runAction(action, context); - this._onDidRun.fire({ action: action, result: result }); - } catch (error) { - this._onDidRun.fire({ action: action, error: error }); + await this.runAction(action, context); + } catch (e) { + error = e; } + + this._onDidRun.fire({ action, error }); } - protected runAction(action: IAction, context?: any): Promise { - const res = context ? action.run(context) : action.run(); - return Promise.resolve(res); + protected async runAction(action: IAction, context?: unknown): Promise { + await action.run(context); } } @@ -198,6 +197,7 @@ export class Separator extends Action { constructor(label?: string) { super(Separator.ID, label, label ? 'separator text' : 'separator'); + this.checked = false; this.enabled = false; } @@ -213,6 +213,7 @@ export class SubmenuAction implements IAction { readonly checked: boolean = false; private readonly _actions: readonly IAction[]; + get actions(): readonly IAction[] { return this._actions; } constructor(id: string, label: string, actions: readonly IAction[], cssClass?: string) { this.id = id; @@ -227,15 +228,13 @@ export class SubmenuAction implements IAction { // to bridge into the rendering world. } - get actions(): readonly IAction[] { - return this._actions; - } - - async run(): Promise { } + async run(): Promise { } } export class EmptySubmenuAction extends Action { + static readonly ID = 'vs.actions.empty'; + constructor() { super(EmptySubmenuAction.ID, nls.localize('submenu.empty', '(empty)'), undefined, false); } diff --git a/src/vs/base/common/async.ts b/src/vs/base/common/async.ts index 95107d4d..7090d5ad 100644 --- a/src/vs/base/common/async.ts +++ b/src/vs/base/common/async.ts @@ -381,7 +381,7 @@ export class AutoOpenBarrier extends Barrier { this._timeout = setTimeout(() => this.open(), autoOpenTimeMs); } - open(): void { + override open(): void { clearTimeout(this._timeout); super.open(); } @@ -772,7 +772,7 @@ export class RunOnceWorker extends RunOnceScheduler { } } - protected doRun(): void { + protected override doRun(): void { const units = this.units; this.units = []; @@ -781,7 +781,7 @@ export class RunOnceWorker extends RunOnceScheduler { } } - dispose(): void { + override dispose(): void { this.units = []; super.dispose(); @@ -942,7 +942,7 @@ export class TaskSequentializer { } setPending(taskId: number, promise: Promise, onCancel?: () => void,): Promise { - this._pending = { taskId: taskId, cancel: () => onCancel?.(), promise }; + this._pending = { taskId, cancel: () => onCancel?.(), promise }; promise.then(() => this.donePending(taskId), () => this.donePending(taskId)); @@ -1179,7 +1179,7 @@ export namespace Promises { * Interface of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled */ interface PromiseWithAllSettled { - allSettled(promises: Promise[]): Promise | IRejectedPromise>>; + allSettled(promises: Promise[]): Promise | IRejectedPromise)[]>; } /** @@ -1188,7 +1188,7 @@ export namespace Promises { * in the order of the original passed in promises array. * See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled */ - export async function allSettled(promises: Promise[]): Promise | IRejectedPromise>> { + export async function allSettled(promises: Promise[]): Promise | IRejectedPromise)[]> { if (typeof (Promise as unknown as PromiseWithAllSettled).allSettled === 'function') { return allSettledNative(promises); // in some environments we can benefit from native implementation } @@ -1196,11 +1196,11 @@ export namespace Promises { return allSettledShim(promises); } - async function allSettledNative(promises: Promise[]): Promise | IRejectedPromise>> { + async function allSettledNative(promises: Promise[]): Promise | IRejectedPromise)[]> { return (Promise as unknown as PromiseWithAllSettled).allSettled(promises); } - async function allSettledShim(promises: Promise[]): Promise | IRejectedPromise>> { + async function allSettledShim(promises: Promise[]): Promise | IRejectedPromise)[]> { return Promise.all(promises.map(promise => (promise.then(value => { const fulfilled: IResolvedPromise = { status: 'fulfilled', value }; diff --git a/src/vs/base/common/buffer.ts b/src/vs/base/common/buffer.ts index 9df28d9b..c25d74a7 100644 --- a/src/vs/base/common/buffer.ts +++ b/src/vs/base/common/buffer.ts @@ -235,3 +235,11 @@ export function streamToBufferReadableStream(stream: streams.ReadableStreamEvent export function newWriteableBufferStream(options?: streams.WriteableStreamOptions): streams.WriteableStream { return streams.newWriteableStream(chunks => VSBuffer.concat(chunks), options); } + +export function prefixedBufferReadable(prefix: VSBuffer, readable: VSBufferReadable): VSBufferReadable { + return streams.prefixedReadable(prefix, readable, chunks => VSBuffer.concat(chunks)); +} + +export function prefixedBufferStream(prefix: VSBuffer, stream: VSBufferReadableStream): VSBufferReadableStream { + return streams.prefixedStream(prefix, stream, chunks => VSBuffer.concat(chunks)); +} diff --git a/src/vs/base/common/codicons.ts b/src/vs/base/common/codicons.ts index 1c000ae9..fc38ba8e 100644 --- a/src/vs/base/common/codicons.ts +++ b/src/vs/base/common/codicons.ts @@ -49,6 +49,16 @@ export function registerCodicon(id: string, def: Codicon): Codicon { return new Codicon(id, def); } +// Selects all codicon names encapsulated in the `$()` syntax and wraps the +// results with spaces so that screen readers can read the text better. +export function getCodiconAriaLabel(text: string | undefined) { + if (!text) { + return ''; + } + + return text.replace(/\$\((.*?)\)/g, (_match, codiconName) => ` ${codiconName} `).trim(); +} + export class Codicon implements CSSIcon { constructor(public readonly id: string, public readonly definition: Codicon | IconDefinition, public description?: string) { _registry.add(this); @@ -131,7 +141,6 @@ export namespace Codicon { export const tagAdd = new Codicon('tag-add', { fontCharacter: '\\ea66' }); export const tagRemove = new Codicon('tag-remove', { fontCharacter: '\\ea66' }); export const person = new Codicon('person', { fontCharacter: '\\ea67' }); - export const personAdd = new Codicon('person-add', { fontCharacter: '\\ea67' }); export const personFollow = new Codicon('person-follow', { fontCharacter: '\\ea67' }); export const personOutline = new Codicon('person-outline', { fontCharacter: '\\ea67' }); export const personFilled = new Codicon('person-filled', { fontCharacter: '\\ea67' }); @@ -550,7 +559,20 @@ export namespace Codicon { export const debugRerun = new Codicon('debug-rerun', { fontCharacter: '\\ebc0' }); export const workspaceTrusted = new Codicon('workspace-trusted', { fontCharacter: '\\ebc1' }); export const workspaceUntrusted = new Codicon('workspace-untrusted', { fontCharacter: '\\ebc2' }); - export const workspaceUnknown = new Codicon('workspace-unknown', { fontCharacter: '\\ebc3' }); + export const workspaceUnspecified = new Codicon('workspace-unspecified', { fontCharacter: '\\ebc3' }); + export const terminalCmd = new Codicon('terminal-cmd', { fontCharacter: '\\ebc4' }); + export const terminalDebian = new Codicon('terminal-debian', { fontCharacter: '\\ebc5' }); + export const terminalLinux = new Codicon('terminal-linux', { fontCharacter: '\\ebc6' }); + export const terminalPowershell = new Codicon('terminal-powershell', { fontCharacter: '\\ebc7' }); + export const terminalTmux = new Codicon('terminal-tmux', { fontCharacter: '\\ebc8' }); + export const terminalUbuntu = new Codicon('terminal-ubuntu', { fontCharacter: '\\ebc9' }); + export const terminalBash = new Codicon('terminal-bash', { fontCharacter: '\\ebca' }); + export const arrowSwap = new Codicon('arrow-swap', { fontCharacter: '\\ebcb' }); + export const copy = new Codicon('copy', { fontCharacter: '\\ebcc' }); + export const personAdd = new Codicon('person-add', { fontCharacter: '\\ebcd' }); + export const filterFilled = new Codicon('filter-filled', { fontCharacter: '\\ebce' }); + export const wand = new Codicon('wand', { fontCharacter: '\\ebcf' }); + export const debugLineByLine = new Codicon('debug-line-by-line', { fontCharacter: '\\ebd0' }); export const dropDownButton = new Codicon('drop-down-button', Codicon.chevronDown.definition); } diff --git a/src/vs/base/common/collections.ts b/src/vs/base/common/collections.ts index f97dc23f..cc037ecf 100644 --- a/src/vs/base/common/collections.ts +++ b/src/vs/base/common/collections.ts @@ -66,6 +66,24 @@ export function groupBy(data: T[], groupFn: (element: T) => string): IStringD return result; } +/** + * Groups the collection into a dictionary based on the provided + * group function. + */ +export function groupByNumber(data: T[], groupFn: (element: T) => number): Map { + const result = new Map(); + for (const element of data) { + const key = groupFn(element); + let target = result.get(key); + if (!target) { + target = []; + result.set(key, target); + } + target.push(element); + } + return result; +} + export function fromMap(original: Map): IStringDictionary { const result: IStringDictionary = Object.create(null); if (original) { diff --git a/src/vs/base/common/errors.ts b/src/vs/base/common/errors.ts index 6043f469..0784a0c6 100644 --- a/src/vs/base/common/errors.ts +++ b/src/vs/base/common/errors.ts @@ -229,11 +229,11 @@ export class ExpectedError extends Error { } export interface IErrorOptions { - actions?: ReadonlyArray; + actions?: readonly IAction[]; } export interface IErrorWithActions { - actions?: ReadonlyArray; + actions?: readonly IAction[]; } export function isErrorWithActions(obj: unknown): obj is IErrorWithActions { diff --git a/src/vs/base/common/event.ts b/src/vs/base/common/event.ts index add858d2..8b4efda9 100644 --- a/src/vs/base/common/event.ts +++ b/src/vs/base/common/event.ts @@ -674,7 +674,7 @@ export class PauseableEmitter extends Emitter { } } - fire(event: T): void { + override fire(event: T): void { if (this._listeners) { if (this._isPaused !== 0) { this._eventQueue.push(event); diff --git a/src/vs/base/common/keyCodes.ts b/src/vs/base/common/keyCodes.ts index 1406c453..8d02df82 100644 --- a/src/vs/base/common/keyCodes.ts +++ b/src/vs/base/common/keyCodes.ts @@ -12,6 +12,8 @@ import { illegalArgument } from 'vs/base/common/errors'; * But these are "more general", as they should work across browsers & OS`s. */ export const enum KeyCode { + DependsOnKbLayout = -1, + /** * Placed first to cover the 0 value of the enum. */ diff --git a/src/vs/base/common/map.ts b/src/vs/base/common/map.ts index 0e08876e..36d543ee 100644 --- a/src/vs/base/common/map.ts +++ b/src/vs/base/common/map.ts @@ -887,7 +887,7 @@ export class LinkedMap implements Map { this._tail = undefined; } else if (item === this._head) { - // This can only happend if size === 1 which is handle + // This can only happen if size === 1 which is handled // by the case above. if (!item.next) { throw new Error('Invalid list'); @@ -896,7 +896,7 @@ export class LinkedMap implements Map { this._head = item.next; } else if (item === this._tail) { - // This can only happend if size === 1 which is handle + // This can only happen if size === 1 which is handled // by the case above. if (!item.previous) { throw new Error('Invalid list'); @@ -1028,7 +1028,7 @@ export class LRUCache extends LinkedMap { this.checkTrim(); } - get(key: K, touch: Touch = Touch.AsNew): V | undefined { + override get(key: K, touch: Touch = Touch.AsNew): V | undefined { return super.get(key, touch); } @@ -1036,7 +1036,7 @@ export class LRUCache extends LinkedMap { return super.get(key, Touch.None); } - set(key: K, value: V): this { + override set(key: K, value: V): this { super.set(key, value, Touch.AsNew); this.checkTrim(); return this; @@ -1048,3 +1048,47 @@ export class LRUCache extends LinkedMap { } } } + +/** + * Wraps the map in type that only implements readonly properties. Useful + * in the extension host to prevent the consumer from making any mutations. + */ +export class ReadonlyMapView implements ReadonlyMap{ + readonly #source: ReadonlyMap; + + public get size() { + return this.#source.size; + } + + constructor(source: ReadonlyMap) { + this.#source = source; + } + + forEach(callbackfn: (value: V, key: K, map: ReadonlyMap) => void, thisArg?: any): void { + this.#source.forEach(callbackfn, thisArg); + } + + get(key: K): V | undefined { + return this.#source.get(key); + } + + has(key: K): boolean { + return this.#source.has(key); + } + + entries(): IterableIterator<[K, V]> { + return this.#source.entries(); + } + + keys(): IterableIterator { + return this.#source.keys(); + } + + values(): IterableIterator { + return this.#source.values(); + } + + [Symbol.iterator](): IterableIterator<[K, V]> { + return this.#source.entries(); + } +} diff --git a/src/vs/base/common/network.ts b/src/vs/base/common/network.ts index 402d5149..0e6e28a6 100644 --- a/src/vs/base/common/network.ts +++ b/src/vs/base/common/network.ts @@ -66,6 +66,8 @@ export namespace Schemas { export const vscodeWorkspaceTrust = 'vscode-workspace-trust'; + export const vscodeTerminal = 'vscode-terminal'; + export const webviewPanel = 'webview-panel'; /** @@ -73,11 +75,6 @@ export namespace Schemas { */ export const vscodeWebview = 'vscode-webview'; - /** - * Scheme used for loading resources inside of webviews. - */ - export const vscodeWebviewResource = 'vscode-webview-resource'; - /** * Scheme used for extension pages */ @@ -88,6 +85,11 @@ export namespace Schemas { * files with our custom protocol handler (desktop only). */ export const vscodeFileResource = 'vscode-file'; + + /** + * Scheme used for temporary resources + */ + export const tmp = 'tmp'; } class RemoteAuthoritiesImpl { @@ -161,7 +163,7 @@ class FileAccessImpl { } // Only convert the URI if we are in a native context and it has `file:` scheme - // and we have explicitly enabled the conversion (sandbox, or ENABLE_VSCODE_BROWSER_CODE_LOADING) + // and we have explicitly enabled the conversion (sandbox, or VSCODE_BROWSER_CODE_LOADING) if (platform.isNative && (__forceCodeFileUri || platform.isPreferringBrowserCodeLoad) && uri.scheme === Schemas.file) { return uri.with({ scheme: Schemas.vscodeFileResource, diff --git a/src/vs/base/common/platform.ts b/src/vs/base/common/platform.ts index 78aecad6..5bbdbe95 100644 --- a/src/vs/base/common/platform.ts +++ b/src/vs/base/common/platform.ts @@ -24,7 +24,7 @@ interface NLSConfig { } export interface IProcessEnvironment { - [key: string]: string; + [key: string]: string | undefined; } /** @@ -53,12 +53,12 @@ declare const self: unknown; 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') { +if (typeof globals.vscode !== 'undefined') { // Native environment (sandboxed) nodeProcess = globals.vscode.process; +} else if (typeof process !== 'undefined') { + // Native environment (non-sandboxed) + nodeProcess = process; } const isElectronRenderer = typeof nodeProcess?.versions?.electron === 'string' && nodeProcess.type === 'renderer'; @@ -71,7 +71,7 @@ export const browserCodeLoadingCacheStrategy: 'none' | 'code' | 'bypassHeatCheck } // Otherwise, only enabled conditionally - const env = nodeProcess?.env['ENABLE_VSCODE_BROWSER_CODE_LOADING']; + const env = nodeProcess?.env['VSCODE_BROWSER_CODE_LOADING']; if (typeof env === 'string') { if (env === 'none' || env === 'code' || env === 'bypassHeatCheck' || env === 'bypassHeatCheckAndEagerCompile') { return env; diff --git a/src/vs/base/common/process.ts b/src/vs/base/common/process.ts index 0cc42d38..2df6dd5d 100644 --- a/src/vs/base/common/process.ts +++ b/src/vs/base/common/process.ts @@ -6,20 +6,10 @@ import { isWindows, isMacintosh, setImmediate, globals, INodeProcess } from 'vs/base/common/platform'; let safeProcess: INodeProcess & { nextTick: (callback: (...args: any[]) => void) => void; }; - -// Native node.js environment declare const process: INodeProcess; -if (typeof process !== 'undefined') { - 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') { +if (typeof globals.vscode !== 'undefined') { const sandboxProcess: INodeProcess = globals.vscode.process; safeProcess = { get platform() { return sandboxProcess.platform; }, @@ -29,6 +19,16 @@ else if (typeof globals.vscode !== 'undefined') { }; } +// Native node.js environment +else if (typeof process !== 'undefined') { + 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); } + }; +} + // Web environment else { safeProcess = { diff --git a/src/vs/base/common/product.ts b/src/vs/base/common/product.ts new file mode 100644 index 00000000..129b8dec --- /dev/null +++ b/src/vs/base/common/product.ts @@ -0,0 +1,168 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IStringDictionary } from 'vs/base/common/collections'; + +export interface IBuiltInExtension { + readonly name: string; + readonly version: string; + readonly repo: string; + readonly metadata: any; +} + +export type ConfigurationSyncStore = { + url: string, + insidersUrl: string, + stableUrl: string, + canSwitch: boolean, + authenticationProviders: IStringDictionary<{ scopes: string[] }> +}; + +export type ExtensionUntrustedWorkspaceSupport = { + readonly default?: boolean | 'limited', + readonly override?: boolean | 'limited' +}; + +export interface IProductConfiguration { + readonly version: string; + readonly date?: string; + readonly quality?: string; + readonly commit?: string; + + readonly nameShort: string; + readonly nameLong: string; + + readonly win32AppUserModelId?: string; + readonly win32MutexName?: string; + readonly applicationName: string; + + readonly urlProtocol: string; + readonly dataFolderName: string; // location for extensions (e.g. ~/.vscode-insiders) + + readonly builtInExtensions?: IBuiltInExtension[]; + + readonly downloadUrl?: string; + readonly updateUrl?: string; + readonly webEndpointUrl?: string; + readonly target?: string; + + readonly settingsSearchBuildId?: number; + readonly settingsSearchUrl?: string; + + readonly tasConfig?: { + endpoint: string; + telemetryEventName: string; + featuresTelemetryPropertyName: string; + assignmentContextTelemetryPropertyName: string; + }; + + readonly experimentsUrl?: string; + + readonly extensionsGallery?: { + readonly serviceUrl: string; + readonly itemUrl: string; + readonly controlUrl: string; + readonly recommendationsUrl: string; + }; + + readonly extensionTips?: { [id: string]: string; }; + readonly extensionImportantTips?: IStringDictionary; + readonly configBasedExtensionTips?: { [id: string]: IConfigBasedExtensionTip; }; + readonly exeBasedExtensionTips?: { [id: string]: IExeBasedExtensionTip; }; + readonly remoteExtensionTips?: { [remoteName: string]: IRemoteExtensionTip; }; + readonly extensionKeywords?: { [extension: string]: readonly string[]; }; + readonly keymapExtensionTips?: readonly string[]; + readonly trustedExtensionUrlPublicKeys?: { [id: string]: string[]; }; + + readonly crashReporter?: { + readonly companyName: string; + readonly productName: string; + }; + + readonly enableTelemetry?: boolean; + readonly aiConfig?: { + readonly asimovKey: string; + }; + + readonly sendASmile?: { + readonly reportIssueUrl: string, + readonly requestFeatureUrl: string + }; + + readonly documentationUrl?: string; + readonly releaseNotesUrl?: string; + readonly keyboardShortcutsUrlMac?: string; + readonly keyboardShortcutsUrlLinux?: string; + readonly keyboardShortcutsUrlWin?: string; + readonly introductoryVideosUrl?: string; + readonly tipsAndTricksUrl?: string; + readonly newsletterSignupUrl?: string; + readonly twitterUrl?: string; + readonly requestFeatureUrl?: string; + readonly reportIssueUrl?: string; + readonly reportMarketplaceIssueUrl?: string; + readonly licenseUrl?: string; + readonly privacyStatementUrl?: string; + readonly telemetryOptOutUrl?: string; + + readonly npsSurveyUrl?: string; + readonly cesSurveyUrl?: string; + readonly surveys?: readonly ISurveyData[]; + + readonly checksums?: { [path: string]: string; }; + readonly checksumFailMoreInfoUrl?: string; + + readonly appCenter?: IAppCenterConfiguration; + + readonly portable?: string; + + readonly extensionKind?: { readonly [extensionId: string]: ('ui' | 'workspace' | 'web')[]; }; + readonly extensionSyncedKeys?: { readonly [extensionId: string]: string[]; }; + readonly extensionAllowedProposedApi?: readonly string[]; + readonly extensionUntrustedWorkspaceSupport?: { readonly [extensionId: string]: ExtensionUntrustedWorkspaceSupport }; + readonly extensionVirtualWorkspacesSupport?: { readonly [extensionId: string]: { default?: boolean, override?: boolean } }; + + readonly msftInternalDomains?: string[]; + readonly linkProtectionTrustedDomains?: readonly string[]; + + readonly 'configurationSync.store'?: ConfigurationSyncStore; + + readonly darwinUniversalAssetId?: string; +} + +export type ImportantExtensionTip = { name: string; languages?: string[]; pattern?: string; isExtensionPack?: boolean }; + +export interface IAppCenterConfiguration { + readonly 'win32-ia32': string; + readonly 'win32-x64': string; + readonly 'linux-x64': string; + readonly 'darwin': string; +} + +export interface IConfigBasedExtensionTip { + configPath: string; + configName: string; + recommendations: IStringDictionary<{ name: string, remotes?: string[], important?: boolean, isExtensionPack?: boolean }>; +} + +export interface IExeBasedExtensionTip { + friendlyName: string; + windowsPath?: string; + important?: boolean; + recommendations: IStringDictionary<{ name: string, important?: boolean, isExtensionPack?: boolean }>; +} + +export interface IRemoteExtensionTip { + friendlyName: string; + extensionId: string; +} + +export interface ISurveyData { + surveyId: string; + surveyUrl: string; + languageId: string; + editCount: number; + userProbability: number; +} diff --git a/src/vs/base/common/scanCode.ts b/src/vs/base/common/scanCode.ts index a021562b..b0547e05 100644 --- a/src/vs/base/common/scanCode.ts +++ b/src/vs/base/common/scanCode.ts @@ -9,6 +9,7 @@ import { KeyCode } from 'vs/base/common/keyCodes'; * keyboardEvent.code */ export const enum ScanCode { + DependsOnKbLayout = -1, None, Hyper, @@ -468,11 +469,11 @@ export class ScanCodeBinding { (function () { for (let i = 0; i <= ScanCode.MAX_VALUE; i++) { - IMMUTABLE_CODE_TO_KEY_CODE[i] = -1; + IMMUTABLE_CODE_TO_KEY_CODE[i] = KeyCode.DependsOnKbLayout; } for (let i = 0; i <= KeyCode.MAX_VALUE; i++) { - IMMUTABLE_KEY_CODE_TO_CODE[i] = -1; + IMMUTABLE_KEY_CODE_TO_CODE[i] = ScanCode.DependsOnKbLayout; } function define(code: ScanCode, keyCode: KeyCode): void { diff --git a/src/vs/base/common/scrollable.ts b/src/vs/base/common/scrollable.ts index 73a86228..cb2f7860 100644 --- a/src/vs/base/common/scrollable.ts +++ b/src/vs/base/common/scrollable.ts @@ -223,7 +223,7 @@ export class Scrollable extends Disposable { this._smoothScrolling = null; } - public dispose(): void { + public override dispose(): void { if (this._smoothScrolling) { this._smoothScrolling.dispose(); this._smoothScrolling = null; diff --git a/src/vs/base/common/severity.ts b/src/vs/base/common/severity.ts index 0937e4dd..83e753d8 100644 --- a/src/vs/base/common/severity.ts +++ b/src/vs/base/common/severity.ts @@ -18,6 +18,7 @@ namespace Severity { const _warning = 'warning'; const _warn = 'warn'; const _info = 'info'; + const _ignore = 'ignore'; /** * Parses 'error', 'warning', 'warn', 'info' in call casings @@ -41,6 +42,15 @@ namespace Severity { } return Severity.Ignore; } + + export function toString(severity: Severity): string { + switch (severity) { + case Severity.Error: return _error; + case Severity.Warning: return _warning; + case Severity.Info: return _info; + default: return _ignore; + } + } } export default Severity; diff --git a/src/vs/base/common/stream.ts b/src/vs/base/common/stream.ts index c6d9276f..67df6415 100644 --- a/src/vs/base/common/stream.ts +++ b/src/vs/base/common/stream.ts @@ -142,15 +142,21 @@ export interface ReadableBufferedStream { } export function isReadableStream(obj: unknown): obj is ReadableStream { - const candidate = obj as ReadableStream; + const candidate = obj as ReadableStream | undefined; + if (!candidate) { + return false; + } - return candidate && [candidate.on, candidate.pause, candidate.resume, candidate.destroy].every(fn => typeof fn === 'function'); + return [candidate.on, candidate.pause, candidate.resume, candidate.destroy].every(fn => typeof fn === 'function'); } export function isReadableBufferedStream(obj: unknown): obj is ReadableBufferedStream { - const candidate = obj as ReadableBufferedStream; + const candidate = obj as ReadableBufferedStream | undefined; + if (!candidate) { + return false; + } - return candidate && isReadableStream(candidate.stream) && Array.isArray(candidate.buffer) && typeof candidate.ended === 'boolean'; + return isReadableStream(candidate.stream) && Array.isArray(candidate.buffer) && typeof candidate.ended === 'boolean'; } export interface IReducer { @@ -625,6 +631,16 @@ export function toStream(t: T, reducer: IReducer): ReadableStream { return stream; } +/** + * Helper to create an empty stream + */ +export function emptyStream(): ReadableStream { + const stream = newWriteableStream(() => { throw new Error('not supported'); }); + stream.end(); + + return stream; +} + /** * Helper to convert a T into a Readable. */ @@ -658,3 +674,71 @@ export function transform(stream: ReadableStreamEvents(prefix: T, readable: Readable, reducer: IReducer): Readable { + let prefixHandled = false; + + return { + read: () => { + const chunk = readable.read(); + + // Handle prefix only once + if (!prefixHandled) { + prefixHandled = true; + + // If we have also a read-result, make + // sure to reduce it to a single result + if (chunk !== null) { + return reducer([prefix, chunk]); + } + + // Otherwise, just return prefix directly + return prefix; + } + + return chunk; + } + }; +} + +/** + * Helper to take an existing stream that will + * have a prefix injected to the beginning. + */ +export function prefixedStream(prefix: T, stream: ReadableStream, reducer: IReducer): ReadableStream { + let prefixHandled = false; + + const target = newWriteableStream(reducer); + + listenStream(stream, { + onData: data => { + + // Handle prefix only once + if (!prefixHandled) { + prefixHandled = true; + + return target.write(reducer([prefix, data])); + } + + return target.write(data); + }, + onError: error => target.error(error), + onEnd: () => { + + // Handle prefix only once + if (!prefixHandled) { + prefixHandled = true; + + target.write(prefix); + } + + target.end(); + } + }); + + return target; +} diff --git a/src/vs/base/common/types.ts b/src/vs/base/common/types.ts index 983d30c3..3e431196 100644 --- a/src/vs/base/common/types.ts +++ b/src/vs/base/common/types.ts @@ -240,19 +240,19 @@ export function withUndefinedAsNull(x: T | undefined): T | null { return typeof x === 'undefined' ? null : x; } +type AddFirstParameterToFunction = T extends (...args: any[]) => TargetFunctionsReturnType ? + // Function: add param to function + (firstArg: FirstParameter, ...args: Parameters) => ReturnType : + + // Else: just leave as is + T; + /** * Allows to add a first parameter to functions of a type. */ export type AddFirstParameterToFunctions = { - - // For every property - [K in keyof Target]: - - // Function: add param to function - Target[K] extends (...args: any[]) => TargetFunctionsReturnType ? (firstArg: FirstParameter, ...args: Parameters) => ReturnType : - - // Else: just leave as is - Target[K] + // For every property + [K in keyof Target]: AddFirstParameterToFunction; }; /** @@ -286,3 +286,7 @@ export function NotImplementedProxy(name: string): { new(): T } { } }; } + +export function assertNever(value: never) { + throw new Error('Unreachable'); +} diff --git a/src/vs/base/common/uri.ts b/src/vs/base/common/uri.ts index 55a39c0f..c2b0b02e 100644 --- a/src/vs/base/common/uri.ts +++ b/src/vs/base/common/uri.ts @@ -418,14 +418,14 @@ class Uri extends URI { _formatted: string | null = null; _fsPath: string | null = null; - get fsPath(): string { + override get fsPath(): string { if (!this._fsPath) { this._fsPath = uriToFsPath(this, false); } return this._fsPath; } - toString(skipEncoding: boolean = false): string { + override toString(skipEncoding: boolean = false): string { if (!skipEncoding) { if (!this._formatted) { this._formatted = _asFormatted(this, false); @@ -437,7 +437,7 @@ class Uri extends URI { } } - toJSON(): UriComponents { + override toJSON(): UriComponents { const res = { $mid: 1 }; diff --git a/src/vs/base/node/decoder.ts b/src/vs/base/node/decoder.ts index 767cf6d8..36a3de51 100644 --- a/src/vs/base/node/decoder.ts +++ b/src/vs/base/node/decoder.ts @@ -18,7 +18,7 @@ export class LineDecoder { private stringDecoder: sd.StringDecoder; private remaining: string | null; - constructor(encoding: string = 'utf8') { + constructor(encoding: BufferEncoding = 'utf8') { this.stringDecoder = new sd.StringDecoder(encoding); this.remaining = null; } diff --git a/src/vs/base/node/id.ts b/src/vs/base/node/id.ts index 2799ffc7..3ff83c8d 100644 --- a/src/vs/base/node/id.ts +++ b/src/vs/base/node/id.ts @@ -56,8 +56,9 @@ export const virtualMachineHint: { value(): number } = new class { const interfaces = networkInterfaces(); for (let name in interfaces) { - if (Object.prototype.hasOwnProperty.call(interfaces, name)) { - for (const { mac, internal } of interfaces[name]) { + const networkInterface = interfaces[name]; + if (networkInterface) { + for (const { mac, internal } of networkInterface) { if (!internal) { interfaceCount += 1; if (this._isVirtualMachineMacAdress(mac.toUpperCase())) { diff --git a/src/vs/base/node/macAddress.ts b/src/vs/base/node/macAddress.ts index 35fec9fc..acfdf239 100644 --- a/src/vs/base/node/macAddress.ts +++ b/src/vs/base/node/macAddress.ts @@ -34,10 +34,13 @@ function doGetMac(): Promise { return new Promise((resolve, reject) => { try { const ifaces = networkInterfaces(); - for (const [, infos] of Object.entries(ifaces)) { - for (const info of infos) { - if (validateMacAddress(info.mac)) { - return resolve(info.mac); + for (let name in ifaces) { + const networkInterface = ifaces[name]; + if (networkInterface) { + for (const { mac } of networkInterface) { + if (validateMacAddress(mac)) { + return resolve(mac); + } } } } diff --git a/src/vs/base/node/processes.ts b/src/vs/base/node/processes.ts index 0273af23..363dc229 100644 --- a/src/vs/base/node/processes.ts +++ b/src/vs/base/node/processes.ts @@ -379,7 +379,7 @@ export class LineProcess extends AbstractProcess { this.stderrLineDecoder = stderrLineDecoder; } - protected handleClose(data: any, cc: ValueCallback, pp: ProgressCallback, ee: ErrorCallback): void { + protected override handleClose(data: any, cc: ValueCallback, pp: ProgressCallback, ee: ErrorCallback): void { const stdoutLine = this.stdoutLineDecoder ? this.stdoutLineDecoder.end() : null; if (stdoutLine) { pp({ line: stdoutLine, source: Source.stdout }); diff --git a/src/vs/base/node/shell.ts b/src/vs/base/node/shell.ts index ae28f291..3df529ec 100644 --- a/src/vs/base/node/shell.ts +++ b/src/vs/base/node/shell.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as os from 'os'; +import { release, userInfo } from 'os'; import * as platform from 'vs/base/common/platform'; import { getFirstAvailablePowerShellInstallation } from 'vs/base/node/powershell'; import * as processes from 'vs/base/node/processes'; @@ -11,10 +11,10 @@ import * as processes from 'vs/base/node/processes'; /** * Gets the detected default shell for the _system_, not to be confused with VS Code's _default_ * shell that the terminal uses by default. - * @param p The platform to detect the shell of. + * @param os The platform to detect the shell of. */ -export async function getSystemShell(p: platform.Platform, env: platform.IProcessEnvironment): Promise { - if (p === platform.Platform.Windows) { +export async function getSystemShell(os: platform.OperatingSystem, env: platform.IProcessEnvironment): Promise { + if (os === platform.OperatingSystem.Windows) { if (platform.isWindows) { return getSystemShellWindows(); } @@ -22,11 +22,11 @@ export async function getSystemShell(p: platform.Platform, env: platform.IProces return processes.getWindowsShell(env); } - return getSystemShellUnixLike(p, env); + return getSystemShellUnixLike(os, env); } -export function getSystemShellSync(p: platform.Platform, env: platform.IProcessEnvironment): string { - if (p === platform.Platform.Windows) { +export function getSystemShellSync(os: platform.OperatingSystem, env: platform.IProcessEnvironment): string { + if (os === platform.OperatingSystem.Windows) { if (platform.isWindows) { return getSystemShellWindowsSync(env); } @@ -34,13 +34,13 @@ export function getSystemShellSync(p: platform.Platform, env: platform.IProcessE return processes.getWindowsShell(env); } - return getSystemShellUnixLike(p, env); + return getSystemShellUnixLike(os, env); } let _TERMINAL_DEFAULT_SHELL_UNIX_LIKE: string | null = null; -function getSystemShellUnixLike(p: platform.Platform, env: platform.IProcessEnvironment): string { +function getSystemShellUnixLike(os: platform.OperatingSystem, env: platform.IProcessEnvironment): string { // Only use $SHELL for the current OS - if (platform.isLinux && p === platform.Platform.Mac || platform.isMacintosh && p === platform.Platform.Linux) { + if (platform.isLinux && os === platform.OperatingSystem.Macintosh || platform.isMacintosh && os === platform.OperatingSystem.Linux) { return '/bin/bash'; } @@ -55,7 +55,7 @@ function getSystemShellUnixLike(p: platform.Platform, env: platform.IProcessEnvi try { // It's possible for $SHELL to be unset, this API reads /etc/passwd. See https://github.com/github/codespaces/issues/1639 // Node docs: "Throws a SystemError if a user has no username or homedir." - unixLikeTerminal = os.userInfo().shell; + unixLikeTerminal = userInfo().shell; } catch (err) { } } @@ -86,7 +86,7 @@ function getSystemShellWindowsSync(env: platform.IProcessEnvironment): string { return _TERMINAL_DEFAULT_SHELL_WINDOWS; } - const isAtLeastWindows10 = platform.isWindows && parseFloat(os.release()) >= 10; + const isAtLeastWindows10 = platform.isWindows && parseFloat(release()) >= 10; const is32ProcessOn64Windows = env.hasOwnProperty('PROCESSOR_ARCHITEW6432'); const powerShellPath = `${env['windir']}\\${is32ProcessOn64Windows ? 'Sysnative' : 'System32'}\\WindowsPowerShell\\v1.0\\powershell.exe`; return isAtLeastWindows10 ? powerShellPath : processes.getWindowsShell(env); diff --git a/src/vs/base/parts/ipc/common/ipc.mp.ts b/src/vs/base/parts/ipc/common/ipc.mp.ts index 01c36e63..1022eb17 100644 --- a/src/vs/base/parts/ipc/common/ipc.mp.ts +++ b/src/vs/base/parts/ipc/common/ipc.mp.ts @@ -72,7 +72,7 @@ export class Client extends IPCClient implements IDisposable { this.protocol = protocol; } - dispose(): void { + override dispose(): void { this.protocol.disconnect(); } } diff --git a/src/vs/base/parts/ipc/common/ipc.net.ts b/src/vs/base/parts/ipc/common/ipc.net.ts index 777d4379..aad4fb4e 100644 --- a/src/vs/base/parts/ipc/common/ipc.net.ts +++ b/src/vs/base/parts/ipc/common/ipc.net.ts @@ -252,7 +252,7 @@ class ProtocolReader extends Disposable { return this._incomingData.read(this._incomingData.byteLength); } - public dispose(): void { + public override dispose(): void { this._isDisposed = true; super.dispose(); } @@ -412,7 +412,7 @@ export class Client extends IPCClient { super(protocol, id, ipcLogger); } - dispose(): void { + override dispose(): void { super.dispose(); const socket = this.protocol.getSocket(); this.protocol.sendDisconnect(); diff --git a/src/vs/base/parts/ipc/electron-sandbox/ipc.electron.ts b/src/vs/base/parts/ipc/electron-sandbox/ipc.electron.ts index f2eaa813..94ece5a4 100644 --- a/src/vs/base/parts/ipc/electron-sandbox/ipc.electron.ts +++ b/src/vs/base/parts/ipc/electron-sandbox/ipc.electron.ts @@ -32,7 +32,7 @@ export class Client extends IPCClient implements IDisposable { this.protocol = protocol; } - dispose(): void { + override dispose(): void { this.protocol.disconnect(); } } diff --git a/src/vs/base/parts/ipc/node/ipc.net.ts b/src/vs/base/parts/ipc/node/ipc.net.ts index 09024a5f..aa2512c4 100644 --- a/src/vs/base/parts/ipc/node/ipc.net.ts +++ b/src/vs/base/parts/ipc/node/ipc.net.ts @@ -266,7 +266,7 @@ export class WebSocketNodeSocket extends Disposable implements ISocket { this._register(this.socket.onClose(() => this._onClose.fire())); } - public dispose(): void { + public override dispose(): void { if (this._zlibDeflateFlushWaitingCount > 0) { // Wait for any outstanding writes to finish before disposing this._register(this._onDidZlibFlush.event(() => { @@ -581,7 +581,7 @@ export class Server extends IPCServer { this.server = server; } - dispose(): void { + override dispose(): void { super.dispose(); if (this.server) { this.server.close(); diff --git a/src/vs/base/parts/ipc/test/common/ipc.test.ts b/src/vs/base/parts/ipc/test/common/ipc.test.ts index 6e3dd39a..38ce85a7 100644 --- a/src/vs/base/parts/ipc/test/common/ipc.test.ts +++ b/src/vs/base/parts/ipc/test/common/ipc.test.ts @@ -66,7 +66,7 @@ class TestIPCClient extends IPCClient { super(protocol, id); } - dispose(): void { + override dispose(): void { this._onDidDisconnect.fire(); super.dispose(); } @@ -253,7 +253,7 @@ suite('Base IPC', function () { test('call success', async function () { const r = await ipcService.marco(); - return assert.equal(r, 'polo'); + return assert.strictEqual(r, 'polo'); }); test('call error', async function () { @@ -261,7 +261,7 @@ suite('Base IPC', function () { await ipcService.error('nice error'); return assert.fail('should not reach here'); } catch (err) { - return assert.equal(err.message, 'nice error'); + return assert.strictEqual(err.message, 'nice error'); } }); @@ -304,20 +304,20 @@ suite('Base IPC', function () { ipcService.onPong(msg => messages.push(msg)); await timeout(0); - assert.deepEqual(messages, []); + assert.deepStrictEqual(messages, []); service.ping('hello'); await timeout(0); - assert.deepEqual(messages, ['hello']); + assert.deepStrictEqual(messages, ['hello']); service.ping('world'); await timeout(0); - assert.deepEqual(messages, ['hello', 'world']); + assert.deepStrictEqual(messages, ['hello', 'world']); }); test('buffers in arrays', async function () { const r = await ipcService.buffersLength([VSBuffer.alloc(2), VSBuffer.alloc(3)]); - return assert.equal(r, 5); + return assert.strictEqual(r, 5); }); }); @@ -345,7 +345,7 @@ suite('Base IPC', function () { test('call success', async function () { const r = await ipcService.marco(); - return assert.equal(r, 'polo'); + return assert.strictEqual(r, 'polo'); }); test('call error', async function () { @@ -353,7 +353,7 @@ suite('Base IPC', function () { await ipcService.error('nice error'); return assert.fail('should not reach here'); } catch (err) { - return assert.equal(err.message, 'nice error'); + return assert.strictEqual(err.message, 'nice error'); } }); @@ -363,15 +363,15 @@ suite('Base IPC', function () { ipcService.onPong(msg => messages.push(msg)); await timeout(0); - assert.deepEqual(messages, []); + assert.deepStrictEqual(messages, []); service.ping('hello'); await timeout(0); - assert.deepEqual(messages, ['hello']); + assert.deepStrictEqual(messages, ['hello']); service.ping('world'); await timeout(0); - assert.deepEqual(messages, ['hello', 'world']); + assert.deepStrictEqual(messages, ['hello', 'world']); }); test('marshalling uri', async function () { @@ -383,7 +383,7 @@ suite('Base IPC', function () { test('buffers in arrays', async function () { const r = await ipcService.buffersLength([VSBuffer.alloc(2), VSBuffer.alloc(3)]); - return assert.equal(r, 5); + return assert.strictEqual(r, 5); }); }); @@ -411,7 +411,7 @@ suite('Base IPC', function () { test('call extra context', async function () { const r = await ipcService.context(); - return assert.equal(r, 'Super Context'); + return assert.strictEqual(r, 'Super Context'); }); }); @@ -461,7 +461,7 @@ suite('Base IPC', function () { clientService1.ping('hello 1'); await timeout(1); - assert.deepEqual(pings, ['hello 1']); + assert.deepStrictEqual(pings, ['hello 1']); const client2 = server.createConnection('client2'); const clientService2 = new TestService(); @@ -472,19 +472,19 @@ suite('Base IPC', function () { clientService2.ping('hello 2'); await timeout(1); - assert.deepEqual(pings, ['hello 1', 'hello 2']); + assert.deepStrictEqual(pings, ['hello 1', 'hello 2']); client1.dispose(); clientService1.ping('hello 1'); await timeout(1); - assert.deepEqual(pings, ['hello 1', 'hello 2']); + assert.deepStrictEqual(pings, ['hello 1', 'hello 2']); await timeout(1); clientService2.ping('hello again 2'); await timeout(1); - assert.deepEqual(pings, ['hello 1', 'hello 2', 'hello again 2']); + assert.deepStrictEqual(pings, ['hello 1', 'hello 2', 'hello again 2']); client2.dispose(); server.dispose(); 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 726b4689..4b0bbaa1 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 @@ -22,8 +22,8 @@ suite('IPC, Child Process', () => { const service = new TestServiceClient(channel); const result = service.pong('ping').then(r => { - assert.equal(r.incoming, 'ping'); - assert.equal(r.outgoing, 'pong'); + assert.strictEqual(r.incoming, 'ping'); + assert.strictEqual(r.outgoing, 'pong'); }); return result.finally(() => client.dispose()); @@ -37,7 +37,7 @@ suite('IPC, Child Process', () => { const event = new Promise((c, e) => { service.onMarco(({ answer }) => { try { - assert.equal(answer, 'polo'); + assert.strictEqual(answer, 'polo'); c(undefined); } catch (err) { e(err); @@ -60,17 +60,17 @@ suite('IPC, Child Process', () => { const disposable = service.onMarco(() => count++); const result = service.marco().then(async answer => { - assert.equal(answer, 'polo'); - assert.equal(count, 1); + assert.strictEqual(answer, 'polo'); + assert.strictEqual(count, 1); const answer_1 = await service.marco(); - assert.equal(answer_1, 'polo'); - assert.equal(count, 2); + assert.strictEqual(answer_1, 'polo'); + assert.strictEqual(count, 2); disposable.dispose(); const answer_2 = await service.marco(); - assert.equal(answer_2, 'polo'); - assert.equal(count, 2); + assert.strictEqual(answer_2, 'polo'); + assert.strictEqual(count, 2); }); return result.finally(() => client.dispose()); diff --git a/src/vs/base/parts/quickinput/browser/media/quickInput.css b/src/vs/base/parts/quickinput/browser/media/quickInput.css index db7cdb89..297f763a 100644 --- a/src/vs/base/parts/quickinput/browser/media/quickInput.css +++ b/src/vs/base/parts/quickinput/browser/media/quickInput.css @@ -14,6 +14,7 @@ .quick-input-titlebar { display: flex; + align-items: center; } .quick-input-left-action-bar { @@ -22,10 +23,6 @@ flex: 1; } -.quick-input-left-action-bar.monaco-action-bar .actions-container { - justify-content: flex-start; -} - .quick-input-title { padding: 3px 0px; text-align: center; @@ -37,12 +34,14 @@ flex: 1; } +.quick-input-right-action-bar > .actions-container { + justify-content: flex-end; +} + .quick-input-titlebar .monaco-action-bar .action-label.codicon { - margin: 0; - width: 19px; - height: 100%; background-position: center; background-repeat: no-repeat; + padding: 2px; } .quick-input-description { @@ -260,10 +259,8 @@ } .quick-input-list .quick-input-list-entry-action-bar .action-label.codicon { - margin: 0; - height: 100%; - padding: 0 2px; - vertical-align: middle; + margin-right: 4px; + padding: 2px; } .quick-input-list .quick-input-list-entry-action-bar { @@ -274,10 +271,6 @@ margin-right: 4px; /* separate from scrollbar */ } -.quick-input-list .quick-input-list-entry-action-bar .action-label.codicon { - margin-right: 4px; /* separate actions */ -} - .quick-input-list .quick-input-list-entry .quick-input-list-entry-action-bar .action-label.always-visible, .quick-input-list .quick-input-list-entry:hover .quick-input-list-entry-action-bar .action-label, .quick-input-list .monaco-list-row.focused .quick-input-list-entry-action-bar .action-label { diff --git a/src/vs/base/parts/quickinput/browser/quickInput.ts b/src/vs/base/parts/quickinput/browser/quickInput.ts index f84ec147..7aed5e20 100644 --- a/src/vs/base/parts/quickinput/browser/quickInput.ts +++ b/src/vs/base/parts/quickinput/browser/quickInput.ts @@ -32,6 +32,7 @@ import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { escape } from 'vs/base/common/strings'; import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels'; import { isString } from 'vs/base/common/types'; +import { IKeybindingLabelStyles } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel'; export interface IQuickInputOptions { idPrefix: string; @@ -57,6 +58,7 @@ export interface IQuickInputStyles { countBadge: ICountBadgetyles; button: IButtonStyles; progressBar: IProgressBarStyles; + keybindingLabel: IKeybindingLabelStyles; list: IListStyles & { pickerGroupBorder?: Color; pickerGroupForeground?: Color; }; } @@ -410,7 +412,7 @@ class QuickInput extends Disposable implements IQuickInput { readonly onDispose = this.onDisposeEmitter.event; - dispose(): void { + override dispose(): void { this.hide(); this.onDisposeEmitter.fire(); @@ -694,7 +696,7 @@ class QuickPick extends QuickInput implements IQuickPi } } - show() { + override show() { if (!this.visible) { this.visibleDisposables.add( this.ui.inputBox.onDidChange(value => { @@ -884,20 +886,11 @@ class QuickPick extends QuickInput implements IQuickPi }); } - protected update() { + protected override update() { if (!this.visible) { return; } - let hideInput = false; - let inputShownJustForScreenReader = false; - if (!!this._hideInput && this._items.length > 0) { - if (this.ui.isScreenReaderOptimized()) { - // Always show input if screen reader attached https://github.com/microsoft/vscode/issues/94360 - inputShownJustForScreenReader = true; - } else { - hideInput = true; - } - } + const hideInput = !!this._hideInput && this._items.length > 0; this.ui.container.classList.toggle('hidden-input', hideInput && !this.description); const visibilities: Visibilities = { title: !!this.title || !!this.step || !!this.buttons.length, @@ -925,13 +918,9 @@ class QuickPick extends QuickInput implements IQuickPi if (this.ui.inputBox.placeholder !== (this.placeholder || '')) { this.ui.inputBox.placeholder = (this.placeholder || ''); } - if (inputShownJustForScreenReader) { - this.ui.inputBox.ariaLabel = ''; - } else { - const ariaLabel = this.ariaLabel || this.placeholder || QuickPick.DEFAULT_ARIA_LABEL; - if (this.ui.inputBox.ariaLabel !== ariaLabel) { - this.ui.inputBox.ariaLabel = ariaLabel; - } + const ariaLabel = this.ariaLabel || this.placeholder || QuickPick.DEFAULT_ARIA_LABEL; + if (this.ui.inputBox.ariaLabel !== ariaLabel) { + this.ui.inputBox.ariaLabel = ariaLabel; } this.ui.list.matchOnDescription = this.matchOnDescription; this.ui.list.matchOnDetail = this.matchOnDetail; @@ -1063,7 +1052,7 @@ class InputBox extends QuickInput implements IInputBox { readonly onDidAccept = this.onDidAcceptEmitter.event; - show() { + override show() { if (!this.visible) { this.visibleDisposables.add( this.ui.inputBox.onDidChange(value => { @@ -1079,7 +1068,7 @@ class InputBox extends QuickInput implements IInputBox { super.show(); } - protected update() { + protected override update() { if (!this.visible) { return; } @@ -1388,8 +1377,12 @@ export class QuickInputController extends Disposable { const index = input.items.indexOf(event.item); if (index !== -1) { const items = input.items.slice(); - items.splice(index, 1); + const removed = items.splice(index, 1); + const activeItems = input.activeItems.filter((ai) => ai !== removed[0]); input.items = items; + if (activeItems) { + input.activeItems = activeItems; + } } } })), @@ -1729,6 +1722,34 @@ export class QuickInputController extends Disposable { if (this.styles.list.pickerGroupForeground) { content.push(`.quick-input-list .quick-input-list-separator { color: ${this.styles.list.pickerGroupForeground}; }`); } + + if ( + this.styles.keybindingLabel.keybindingLabelBackground || + this.styles.keybindingLabel.keybindingLabelBorder || + this.styles.keybindingLabel.keybindingLabelBottomBorder || + this.styles.keybindingLabel.keybindingLabelShadow || + this.styles.keybindingLabel.keybindingLabelForeground + ) { + content.push('.quick-input-list .monaco-keybinding > .monaco-keybinding-key {'); + if (this.styles.keybindingLabel.keybindingLabelBackground) { + content.push(`background-color: ${this.styles.keybindingLabel.keybindingLabelBackground};`); + } + if (this.styles.keybindingLabel.keybindingLabelBorder) { + // Order matters here. `border-color` must come before `border-bottom-color`. + content.push(`border-color: ${this.styles.keybindingLabel.keybindingLabelBorder};`); + } + if (this.styles.keybindingLabel.keybindingLabelBottomBorder) { + content.push(`border-bottom-color: ${this.styles.keybindingLabel.keybindingLabelBottomBorder};`); + } + if (this.styles.keybindingLabel.keybindingLabelShadow) { + content.push(`box-shadow: inset 0 -1px 0 ${this.styles.keybindingLabel.keybindingLabelShadow};`); + } + if (this.styles.keybindingLabel.keybindingLabelForeground) { + content.push(`color: ${this.styles.keybindingLabel.keybindingLabelForeground};`); + } + content.push('}'); + } + const newStyles = content.join('\n'); if (newStyles !== this.ui.styleSheet.textContent) { this.ui.styleSheet.textContent = newStyles; diff --git a/src/vs/base/parts/quickinput/browser/quickInputList.ts b/src/vs/base/parts/quickinput/browser/quickInputList.ts index 7126af12..706d46f1 100644 --- a/src/vs/base/parts/quickinput/browser/quickInputList.ts +++ b/src/vs/base/parts/quickinput/browser/quickInputList.ts @@ -27,6 +27,7 @@ import { IQuickInputOptions } from 'vs/base/parts/quickinput/browser/quickInput' import { IListOptions, List, IListStyles, IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { KeybindingLabel } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel'; import { localize } from 'vs/nls'; +import { getCodiconAriaLabel } from 'vs/base/common/codicons'; const $ = dom.$; @@ -427,7 +428,7 @@ export class QuickInputList { const saneDescription = item.description && item.description.replace(/\r?\n/g, ' '); const saneDetail = item.detail && item.detail.replace(/\r?\n/g, ' '); const saneAriaLabel = item.ariaLabel || [saneLabel, saneDescription, saneDetail] - .map(s => s && parseLabelWithIcons(s).text) + .map(s => getCodiconAriaLabel(s)) .filter(s => !!s) .join(', '); @@ -603,6 +604,7 @@ export class QuickInputList { // Filter by value (since we support icons in labels, use $(..) aware fuzzy matching) else { + let currentSeparator: IQuickPickSeparator | undefined; this.elements.forEach(element => { const labelHighlights = this.matchOnLabel ? withNullAsUndefined(matchesFuzzyIconAware(query, parseLabelWithIcons(element.saneLabel))) : undefined; const descriptionHighlights = this.matchOnDescription ? withNullAsUndefined(matchesFuzzyIconAware(query, parseLabelWithIcons(element.saneDescription || ''))) : undefined; @@ -621,6 +623,16 @@ export class QuickInputList { element.hidden = !element.item.alwaysShow; } element.separator = undefined; + + // we can show the separator unless the list gets sorted by match + if (!this.sortByLabel) { + const previous = element.index && this.inputElements[element.index - 1]; + currentSeparator = previous && previous.type === 'separator' ? previous : currentSeparator; + if (currentSeparator && !element.hidden) { + element.separator = currentSeparator; + currentSeparator = undefined; + } + } }); } diff --git a/src/vs/base/parts/quickinput/common/quickInput.ts b/src/vs/base/parts/quickinput/common/quickInput.ts index 477f4169..56aed158 100644 --- a/src/vs/base/parts/quickinput/common/quickInput.ts +++ b/src/vs/base/parts/quickinput/common/quickInput.ts @@ -363,7 +363,7 @@ export interface IQuickPickItemButtonContext extends I export type QuickPickInput = T | IQuickPickSeparator; -//region Fuzzy Scorer Support +//#region Fuzzy Scorer Support export type IQuickPickItemWithResource = IQuickPickItem & { resource?: URI }; diff --git a/src/vs/base/parts/sandbox/common/sandboxTypes.ts b/src/vs/base/parts/sandbox/common/sandboxTypes.ts new file mode 100644 index 00000000..0fcb57f9 --- /dev/null +++ b/src/vs/base/parts/sandbox/common/sandboxTypes.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 { IProcessEnvironment } from 'vs/base/common/platform'; +import { IProductConfiguration } from 'vs/base/common/product'; + + +// ####################################################################### +// ### ### +// ### Types we need in a common layer for reuse ### +// ### ### +// ####################################################################### + + +/** + * The common properties required for any sandboxed + * renderer to function. + */ +export interface ISandboxConfiguration { + + /** + * Identifier of the sandboxed renderer. + */ + windowId: number; + + /** + * Absolute installation path. + */ + appRoot: string; + + /** + * Per window process environment. + */ + userEnv: IProcessEnvironment; + + /** + * Product configuration. + */ + product: IProductConfiguration; + + /** + * Configured zoom level. + */ + zoomLevel?: number; + + /** + * @deprecated to be removed soon + */ + nodeCachedDataDir?: string; +} diff --git a/src/vs/base/parts/sandbox/electron-browser/preload.js b/src/vs/base/parts/sandbox/electron-browser/preload.js index b68c5aac..bae46cc3 100644 --- a/src/vs/base/parts/sandbox/electron-browser/preload.js +++ b/src/vs/base/parts/sandbox/electron-browser/preload.js @@ -9,6 +9,122 @@ const { ipcRenderer, webFrame, crashReporter, contextBridge } = require('electron'); + //#region Utilities + + /** + * @param {string} channel + * @returns {true | never} + */ + function validateIPC(channel) { + if (!channel || !channel.startsWith('vscode:')) { + throw new Error(`Unsupported event IPC channel '${channel}'`); + } + + return true; + } + + /** + * @param {string} type + * @returns {type is 'uncaughtException'} + */ + function validateProcessEventType(type) { + if (type !== 'uncaughtException') { + throw new Error(`Unsupported process event '${type}'`); + } + + return true; + } + + /** + * @param {string} key the name of the process argument to parse + * @returns {string | undefined} + */ + function parseArgv(key) { + for (const arg of process.argv) { + if (arg.indexOf(`--${key}=`) === 0) { + return arg.split('=')[1]; + } + } + + return undefined; + } + + //#endregion + + //#region Resolve Configuration + + /** + * @typedef {import('../common/sandboxTypes').ISandboxConfiguration} ISandboxConfiguration + */ + + /** @type {ISandboxConfiguration | undefined} */ + let configuration = undefined; + + /** @type {Promise} */ + const resolveConfiguration = (async () => { + const windowConfigIpcChannel = parseArgv('vscode-window-config'); + if (!windowConfigIpcChannel) { + throw new Error('Preload: did not find expected vscode-window-config in renderer process arguments list.'); + } + + try { + if (validateIPC(windowConfigIpcChannel)) { + + // Resolve configuration from electron-main + configuration = await ipcRenderer.invoke(windowConfigIpcChannel); + + // Apply `userEnv` directly + Object.assign(process.env, configuration.userEnv); + + // Apply zoom level early before even building the + // window DOM elements to avoid UI flicker. We always + // have to set the zoom level from within the window + // because Chrome has it's own way of remembering zoom + // settings per origin (if vscode-file:// is used) and + // we want to ensure that the user configuration wins. + webFrame.setZoomLevel(configuration.zoomLevel ?? 0); + + return configuration; + } + } catch (error) { + throw new Error(`Preload: unable to fetch vscode-window-config: ${error}`); + } + })(); + + //#endregion + + //#region Resolve Shell Environment + + /** + * If VSCode is not run from a terminal, we should resolve additional + * shell specific environment from the OS shell to ensure we are seeing + * all development related environment variables. We do this from the + * main process because it may involve spawning a shell. + * + * @type {Promise} + */ + const resolveShellEnv = (async () => { + + // Resolve `userEnv` from configuration and + // `shellEnv` from the main side + const [userEnv, shellEnv] = await Promise.all([ + (async () => (await resolveConfiguration).userEnv)(), + ipcRenderer.invoke('vscode:fetchShellEnv') + ]); + + if (!process.env['VSCODE_SKIP_PROCESS_ENV_PATCHING'] /* TODO@bpasero for https://github.com/microsoft/vscode/issues/108804 */) { + // Assign all keys of the shell environment to our process environment + // But make sure that the user environment wins in the end over shell environment + Object.assign(process.env, shellEnv, userEnv); + } + + return { ...process.env, ...shellEnv, ...userEnv }; + })(); + + //#endregion + + //#region Globals Definition + // ####################################################################### // ### ### // ### !!! DO NOT USE GET/SET PROPERTIES ANYWHERE HERE !!! ### @@ -17,14 +133,21 @@ // ### ### // ####################################################################### + /** + * @type {import('../electron-sandbox/globals')} + */ const globals = { /** * A minimal set of methods exposed from Electron's `ipcRenderer` * to support communication to main process. * - * @type {import('../electron-sandbox/electronTypes').IpcRenderer} + * @typedef {import('../electron-sandbox/electronTypes').IpcRenderer} IpcRenderer + * @typedef {import('electron').IpcRendererEvent} IpcRendererEvent + * + * @type {IpcRenderer} */ + ipcRenderer: { /** @@ -50,8 +173,8 @@ /** * @param {string} channel - * @param {(event: import('electron').IpcRendererEvent, ...args: any[]) => void} listener - * @returns {import('../electron-sandbox/electronTypes').IpcRenderer} + * @param {(event: IpcRendererEvent, ...args: any[]) => void} listener + * @returns {IpcRenderer} */ on(channel, listener) { if (validateIPC(channel)) { @@ -63,8 +186,8 @@ /** * @param {string} channel - * @param {(event: import('electron').IpcRendererEvent, ...args: any[]) => void} listener - * @returns {import('../electron-sandbox/electronTypes').IpcRenderer} + * @param {(event: IpcRendererEvent, ...args: any[]) => void} listener + * @returns {IpcRenderer} */ once(channel, listener) { if (validateIPC(channel)) { @@ -76,8 +199,8 @@ /** * @param {string} channel - * @param {(event: import('electron').IpcRendererEvent, ...args: any[]) => void} listener - * @returns {import('../electron-sandbox/electronTypes').IpcRenderer} + * @param {(event: IpcRendererEvent, ...args: any[]) => void} listener + * @returns {IpcRenderer} */ removeListener(channel, listener) { if (validateIPC(channel)) { @@ -100,7 +223,7 @@ */ connect(channelRequest, channelResponse, requestNonce) { if (validateIPC(channelRequest) && validateIPC(channelResponse)) { - const responseListener = (/** @type {import('electron').IpcRendererEvent} */ e, /** @type {string} */ responseNonce) => { + const responseListener = (/** @type {IpcRendererEvent} */ e, /** @type {string} */ responseNonce) => { // validate that the nonce from the response is the same // as when requested. and if so, use `postMessage` to // send the `MessagePort` safely over, even when context @@ -157,7 +280,9 @@ * 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} + * @typedef {import('../electron-sandbox/globals').ISandboxNodeProcess} ISandboxNodeProcess + * + * @type {ISandboxNodeProcess} */ process: { get platform() { return process.platform; }, @@ -178,16 +303,8 @@ /** * @returns {Promise} */ - getShellEnv() { - return shellEnv; - }, - - /** - * @param {{[key: string]: string}} userEnv - * @returns {Promise} - */ - resolveEnv(userEnv) { - return resolveEnv(userEnv); + shellEnv() { + return resolveShellEnv; }, /** @@ -200,7 +317,7 @@ /** * @param {string} type * @param {Function} callback - * @returns {import('../electron-sandbox/globals').ISandboxNodeProcess} + * @returns {ISandboxNodeProcess} */ on(type, callback) { if (validateProcessEventType(type)) { @@ -210,6 +327,37 @@ return this; } } + }, + + /** + * Some information about the context we are running in. + * + * @type {import('../electron-sandbox/globals').ISandboxContext} + */ + context: { + + /** + * A configuration object made accessible from the main side + * to configure the sandbox browser window. + * + * Note: intentionally not using a getter here because the + * actual value will be set after `resolveConfiguration` + * has finished. + * + * @returns {ISandboxConfiguration | undefined} + */ + configuration() { + return configuration; + }, + + /** + * Allows to await the resolution of the configuration object. + * + * @returns {Promise} + */ + async resolveConfiguration() { + return resolveConfiguration; + } } }; @@ -231,69 +379,4 @@ // @ts-ignore window.vscode = globals; } - - //#region Utilities - - /** - * @param {string} channel - * @returns {true | never} - */ - function validateIPC(channel) { - if (!channel || !channel.startsWith('vscode:')) { - throw new Error(`Unsupported event IPC channel '${channel}'`); - } - - return true; - } - - /** - * @param {string} type - * @returns {type is 'uncaughtException'} - */ - function validateProcessEventType(type) { - if (type !== 'uncaughtException') { - throw new Error(`Unsupported process event '${type}'`); - } - - return true; - } - - /** @type {Promise | undefined} */ - let shellEnv = undefined; - - /** - * If VSCode is not run from a terminal, we should resolve additional - * shell specific environment from the OS shell to ensure we are seeing - * all development related environment variables. We do this from the - * main process because it may involve spawning a shell. - * - * @param {{[key: string]: string}} userEnv - * @returns {Promise} - */ - async function resolveEnv(userEnv) { - if (!shellEnv) { - - // Apply `userEnv` directly - Object.assign(process.env, userEnv); - - // Resolve `shellEnv` from the main side - shellEnv = new Promise(function (resolve) { - ipcRenderer.once('vscode:acceptShellEnv', function (event, shellEnvResult) { - if (!process.env['VSCODE_SKIP_PROCESS_ENV_PATCHING'] /* TODO@bpasero for https://github.com/microsoft/vscode/issues/108804 */) { - // Assign all keys of the shell environment to our process environment - // But make sure that the user environment wins in the end over shell environment - Object.assign(process.env, shellEnvResult, userEnv); - } - - resolve({ ...process.env, ...shellEnvResult, ...userEnv }); - }); - - ipcRenderer.send('vscode:fetchShellEnv'); - }); - } - - await shellEnv; - } - - //#endregion }()); diff --git a/src/vs/base/parts/sandbox/electron-sandbox/globals.ts b/src/vs/base/parts/sandbox/electron-sandbox/globals.ts index 7fc10080..4103c867 100644 --- a/src/vs/base/parts/sandbox/electron-sandbox/globals.ts +++ b/src/vs/base/parts/sandbox/electron-sandbox/globals.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { globals, INodeProcess, IProcessEnvironment } from 'vs/base/common/platform'; +import { ISandboxConfiguration } from 'vs/base/parts/sandbox/common/sandboxTypes'; import { ProcessMemoryInfo, CrashReporter, IpcRenderer, WebFrame } from 'vs/base/parts/sandbox/electron-sandbox/electronTypes'; /** @@ -74,8 +75,8 @@ export interface ISandboxNodeProcess extends INodeProcess { getProcessMemoryInfo: () => Promise; /** - * A custom method we add to `process`: Resolve the true process environment to use and - * apply it to `process.env`. + * Returns a process environment that includes all shell environment variables even if + * the application was not started from a shell / terminal / console. * * There are different layers of environment that will apply: * - `process.env`: this is the actual environment of the process before this method @@ -86,17 +87,8 @@ export interface ISandboxNodeProcess extends INodeProcess { * from a terminal and changed certain variables * * The order of overwrites is `process.env` < `shellEnv` < `userEnv`. - * - * It is critical that every process awaits this method early on startup to get the right - * set of environment in `process.env`. */ - resolveEnv(userEnv: IProcessEnvironment): Promise; - - /** - * Returns a process environment that includes any shell environment even if the application - * was not started from a shell / terminal / console. - */ - getShellEnv(): Promise; + shellEnv(): Promise; } export interface IpcMessagePort { @@ -114,8 +106,24 @@ export interface IpcMessagePort { connect(channelRequest: string, channelResponse: string, requestNonce: string): void; } +export interface ISandboxContext { + + /** + * A configuration object made accessible from the main side + * to configure the sandbox browser window. Will be `undefined` + * for as long as `resolveConfiguration` is not awaited. + */ + configuration(): ISandboxConfiguration | undefined; + + /** + * Allows to await the resolution of the configuration object. + */ + resolveConfiguration(): Promise; +} + export const ipcRenderer: IpcRenderer = globals.vscode.ipcRenderer; 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 45463115..2c59fd10 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 @@ -4,13 +4,17 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { ipcRenderer, crashReporter, webFrame, process } from 'vs/base/parts/sandbox/electron-sandbox/globals'; +import { ipcRenderer, crashReporter, webFrame, context, process } from 'vs/base/parts/sandbox/electron-sandbox/globals'; suite('Sandbox', () => { - test('globals', () => { + test('globals', async () => { assert.ok(typeof ipcRenderer.send === 'function'); assert.ok(typeof crashReporter.addExtraParameter === 'function'); assert.ok(typeof webFrame.setZoomLevel === 'function'); assert.ok(typeof process.platform === 'string'); + + const config = await context.resolveConfiguration(); + assert.ok(config); + assert.ok(context.configuration()); }); }); diff --git a/src/vs/base/parts/storage/common/storage.ts b/src/vs/base/parts/storage/common/storage.ts index a2c28e17..ff52a449 100644 --- a/src/vs/base/parts/storage/common/storage.ts +++ b/src/vs/base/parts/storage/common/storage.ts @@ -320,7 +320,7 @@ export class Storage extends Disposable implements IStorage { return new Promise(resolve => this.whenFlushedCallbacks.push(resolve)); } - dispose(): void { + override dispose(): void { this.flushDelayer.cancel(); // workaround https://github.com/microsoft/vscode/issues/116777 this.flushDelayer.dispose(); diff --git a/src/vs/base/parts/storage/test/node/storage.test.ts b/src/vs/base/parts/storage/test/node/storage.test.ts index 990e5319..fd963590 100644 --- a/src/vs/base/parts/storage/test/node/storage.test.ts +++ b/src/vs/base/parts/storage/test/node/storage.test.ts @@ -111,7 +111,7 @@ flakySuite('Storage Library', function () { class TestSQLiteStorageDatabase extends SQLiteStorageDatabase { private readonly _onDidChangeItemsExternal = new Emitter(); - get onDidChangeItemsExternal(): Event { return this._onDidChangeItemsExternal.event; } + override get onDidChangeItemsExternal(): Event { return this._onDidChangeItemsExternal.event; } fireDidChangeItemsExternal(event: IStorageItemsChangeEvent): void { this._onDidChangeItemsExternal.fire(event); diff --git a/src/vs/base/test/browser/comparers.test.ts b/src/vs/base/test/browser/comparers.test.ts index 5f85f7be..ef5856b7 100644 --- a/src/vs/base/test/browser/comparers.test.ts +++ b/src/vs/base/test/browser/comparers.test.ts @@ -59,8 +59,8 @@ suite('Comparers', () => { // name-only comparisons assert(compareFileNames('a', 'A') !== compareLocale('a', 'A'), 'the same letter does not sort by locale'); assert(compareFileNames('â', 'Â') !== compareLocale('â', 'Â'), 'the same accented letter does not sort by locale'); - assert.notDeepEqual(['artichoke', 'Artichoke', 'art', 'Art'].sort(compareFileNames), ['artichoke', 'Artichoke', 'art', 'Art'].sort(compareLocale), 'words with the same root and different cases do not sort in locale order'); - assert.notDeepEqual(['email', 'Email', 'émail', 'Émail'].sort(compareFileNames), ['email', 'Email', 'émail', 'Émail'].sort(compareLocale), 'the same base characters with different case or accents do not sort in locale order'); + assert.notDeepStrictEqual(['artichoke', 'Artichoke', 'art', 'Art'].sort(compareFileNames), ['artichoke', 'Artichoke', 'art', 'Art'].sort(compareLocale), 'words with the same root and different cases do not sort in locale order'); + assert.notDeepStrictEqual(['email', 'Email', 'émail', 'Émail'].sort(compareFileNames), ['email', 'Email', 'émail', 'Émail'].sort(compareLocale), 'the same base characters with different case or accents do not sort in locale order'); // numeric comparisons assert(compareFileNames('abc02.txt', 'abc002.txt') > 0, 'filenames with equivalent numbers and leading zeros sort in unicode order'); diff --git a/src/vs/base/test/browser/formattedTextRenderer.test.ts b/src/vs/base/test/browser/formattedTextRenderer.test.ts index 0be97478..067d7933 100644 --- a/src/vs/base/test/browser/formattedTextRenderer.test.ts +++ b/src/vs/base/test/browser/formattedTextRenderer.test.ts @@ -44,8 +44,14 @@ suite('FormattedTextRenderer', () => { result = renderFormattedText('__italics__'); assert.strictEqual(result.innerHTML, 'italics'); - result = renderFormattedText('this string has **bold** and __italics__'); - assert.strictEqual(result.innerHTML, 'this string has bold and italics'); + result = renderFormattedText('``code``'); + assert.strictEqual(result.innerHTML, '``code``'); + + result = renderFormattedText('``code``', { renderCodeSegements: true }); + assert.strictEqual(result.innerHTML, 'code'); + + result = renderFormattedText('this string has **bold**, __italics__, and ``code``!!', { renderCodeSegements: true }); + assert.strictEqual(result.innerHTML, 'this string has bold, italics, and code!!'); }); test('no formatting', () => { @@ -96,6 +102,26 @@ suite('FormattedTextRenderer', () => { assert.strictEqual(callbackCalled, true); }); + test('fancier action', () => { + let callbackCalled = false; + let result: HTMLElement = renderFormattedText('``__**[[action]]**__``', { + renderCodeSegements: true, + actionHandler: { + callback(content) { + assert.strictEqual(content, '0'); + callbackCalled = true; + }, + disposeables: store + } + }); + assert.strictEqual(result.innerHTML, 'action'); + + let event: MouseEvent = document.createEvent('MouseEvent'); + event.initEvent('click', true, true); + result.firstChild!.firstChild!.firstChild!.firstChild!.dispatchEvent(event); + assert.strictEqual(callbackCalled, true); + }); + test('escaped formatting', () => { let result: HTMLElement = renderFormattedText('\\*\\*bold\\*\\*'); assert.strictEqual(result.children.length, 0); diff --git a/src/vs/base/test/browser/markdownRenderer.test.ts b/src/vs/base/test/browser/markdownRenderer.test.ts index f4bfa18d..e1618f18 100644 --- a/src/vs/base/test/browser/markdownRenderer.test.ts +++ b/src/vs/base/test/browser/markdownRenderer.test.ts @@ -111,7 +111,7 @@ suite('MarkdownRenderer', () => { const data = <{ script: string, documentUri: URI }>parse(decodeURIComponent(uri.query)); assert.ok(data); - assert.equal(data.script, 'echo'); + assert.strictEqual(data.script, 'echo'); assert.ok(data.documentUri.toString().startsWith('file:///c%3A/')); }); diff --git a/src/vs/base/test/browser/ui/grid/grid.test.ts b/src/vs/base/test/browser/ui/grid/grid.test.ts index 7956239d..6ae60546 100644 --- a/src/vs/base/test/browser/ui/grid/grid.test.ts +++ b/src/vs/base/test/browser/ui/grid/grid.test.ts @@ -40,35 +40,35 @@ suite('Grid', function () { }); test('getRelativeLocation', () => { - assert.deepEqual(getRelativeLocation(Orientation.VERTICAL, [0], Direction.Up), [0]); - assert.deepEqual(getRelativeLocation(Orientation.VERTICAL, [0], Direction.Down), [1]); - assert.deepEqual(getRelativeLocation(Orientation.VERTICAL, [0], Direction.Left), [0, 0]); - assert.deepEqual(getRelativeLocation(Orientation.VERTICAL, [0], Direction.Right), [0, 1]); + assert.deepStrictEqual(getRelativeLocation(Orientation.VERTICAL, [0], Direction.Up), [0]); + assert.deepStrictEqual(getRelativeLocation(Orientation.VERTICAL, [0], Direction.Down), [1]); + assert.deepStrictEqual(getRelativeLocation(Orientation.VERTICAL, [0], Direction.Left), [0, 0]); + assert.deepStrictEqual(getRelativeLocation(Orientation.VERTICAL, [0], Direction.Right), [0, 1]); - assert.deepEqual(getRelativeLocation(Orientation.HORIZONTAL, [0], Direction.Up), [0, 0]); - assert.deepEqual(getRelativeLocation(Orientation.HORIZONTAL, [0], Direction.Down), [0, 1]); - assert.deepEqual(getRelativeLocation(Orientation.HORIZONTAL, [0], Direction.Left), [0]); - assert.deepEqual(getRelativeLocation(Orientation.HORIZONTAL, [0], Direction.Right), [1]); + assert.deepStrictEqual(getRelativeLocation(Orientation.HORIZONTAL, [0], Direction.Up), [0, 0]); + assert.deepStrictEqual(getRelativeLocation(Orientation.HORIZONTAL, [0], Direction.Down), [0, 1]); + assert.deepStrictEqual(getRelativeLocation(Orientation.HORIZONTAL, [0], Direction.Left), [0]); + assert.deepStrictEqual(getRelativeLocation(Orientation.HORIZONTAL, [0], Direction.Right), [1]); - assert.deepEqual(getRelativeLocation(Orientation.VERTICAL, [4], Direction.Up), [4]); - assert.deepEqual(getRelativeLocation(Orientation.VERTICAL, [4], Direction.Down), [5]); - assert.deepEqual(getRelativeLocation(Orientation.VERTICAL, [4], Direction.Left), [4, 0]); - assert.deepEqual(getRelativeLocation(Orientation.VERTICAL, [4], Direction.Right), [4, 1]); + assert.deepStrictEqual(getRelativeLocation(Orientation.VERTICAL, [4], Direction.Up), [4]); + assert.deepStrictEqual(getRelativeLocation(Orientation.VERTICAL, [4], Direction.Down), [5]); + assert.deepStrictEqual(getRelativeLocation(Orientation.VERTICAL, [4], Direction.Left), [4, 0]); + assert.deepStrictEqual(getRelativeLocation(Orientation.VERTICAL, [4], Direction.Right), [4, 1]); - assert.deepEqual(getRelativeLocation(Orientation.VERTICAL, [0, 0], Direction.Up), [0, 0, 0]); - assert.deepEqual(getRelativeLocation(Orientation.VERTICAL, [0, 0], Direction.Down), [0, 0, 1]); - assert.deepEqual(getRelativeLocation(Orientation.VERTICAL, [0, 0], Direction.Left), [0, 0]); - assert.deepEqual(getRelativeLocation(Orientation.VERTICAL, [0, 0], Direction.Right), [0, 1]); + assert.deepStrictEqual(getRelativeLocation(Orientation.VERTICAL, [0, 0], Direction.Up), [0, 0, 0]); + assert.deepStrictEqual(getRelativeLocation(Orientation.VERTICAL, [0, 0], Direction.Down), [0, 0, 1]); + assert.deepStrictEqual(getRelativeLocation(Orientation.VERTICAL, [0, 0], Direction.Left), [0, 0]); + assert.deepStrictEqual(getRelativeLocation(Orientation.VERTICAL, [0, 0], Direction.Right), [0, 1]); - assert.deepEqual(getRelativeLocation(Orientation.VERTICAL, [1, 2], Direction.Up), [1, 2, 0]); - assert.deepEqual(getRelativeLocation(Orientation.VERTICAL, [1, 2], Direction.Down), [1, 2, 1]); - assert.deepEqual(getRelativeLocation(Orientation.VERTICAL, [1, 2], Direction.Left), [1, 2]); - assert.deepEqual(getRelativeLocation(Orientation.VERTICAL, [1, 2], Direction.Right), [1, 3]); + assert.deepStrictEqual(getRelativeLocation(Orientation.VERTICAL, [1, 2], Direction.Up), [1, 2, 0]); + assert.deepStrictEqual(getRelativeLocation(Orientation.VERTICAL, [1, 2], Direction.Down), [1, 2, 1]); + assert.deepStrictEqual(getRelativeLocation(Orientation.VERTICAL, [1, 2], Direction.Left), [1, 2]); + assert.deepStrictEqual(getRelativeLocation(Orientation.VERTICAL, [1, 2], Direction.Right), [1, 3]); - assert.deepEqual(getRelativeLocation(Orientation.VERTICAL, [1, 2, 3], Direction.Up), [1, 2, 3]); - assert.deepEqual(getRelativeLocation(Orientation.VERTICAL, [1, 2, 3], Direction.Down), [1, 2, 4]); - assert.deepEqual(getRelativeLocation(Orientation.VERTICAL, [1, 2, 3], Direction.Left), [1, 2, 3, 0]); - assert.deepEqual(getRelativeLocation(Orientation.VERTICAL, [1, 2, 3], Direction.Right), [1, 2, 3, 1]); + assert.deepStrictEqual(getRelativeLocation(Orientation.VERTICAL, [1, 2, 3], Direction.Up), [1, 2, 3]); + assert.deepStrictEqual(getRelativeLocation(Orientation.VERTICAL, [1, 2, 3], Direction.Down), [1, 2, 4]); + assert.deepStrictEqual(getRelativeLocation(Orientation.VERTICAL, [1, 2, 3], Direction.Left), [1, 2, 3, 0]); + assert.deepStrictEqual(getRelativeLocation(Orientation.VERTICAL, [1, 2, 3], Direction.Right), [1, 2, 3, 1]); }); test('empty', () => { @@ -77,7 +77,7 @@ suite('Grid', function () { container.appendChild(gridview.element); gridview.layout(800, 600); - assert.deepEqual(view1.size, [800, 600]); + assert.deepStrictEqual(view1.size, [800, 600]); }); test('two views vertically', function () { @@ -85,12 +85,12 @@ suite('Grid', function () { const grid = new Grid(view1); container.appendChild(grid.element); grid.layout(800, 600); - assert.deepEqual(view1.size, [800, 600]); + assert.deepStrictEqual(view1.size, [800, 600]); const view2 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); grid.addView(view2, 200, view1, Direction.Up); - assert.deepEqual(view1.size, [800, 400]); - assert.deepEqual(view2.size, [800, 200]); + assert.deepStrictEqual(view1.size, [800, 400]); + assert.deepStrictEqual(view2.size, [800, 200]); }); test('two views horizontally', function () { @@ -99,12 +99,12 @@ suite('Grid', function () { container.appendChild(grid.element); grid.layout(800, 600); - assert.deepEqual(view1.size, [800, 600]); + assert.deepStrictEqual(view1.size, [800, 600]); const view2 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); grid.addView(view2, 300, view1, Direction.Right); - assert.deepEqual(view1.size, [500, 600]); - assert.deepEqual(view2.size, [300, 600]); + assert.deepStrictEqual(view1.size, [500, 600]); + assert.deepStrictEqual(view2.size, [300, 600]); }); test('simple layout', function () { @@ -113,33 +113,33 @@ suite('Grid', function () { container.appendChild(grid.element); grid.layout(800, 600); - assert.deepEqual(view1.size, [800, 600]); + assert.deepStrictEqual(view1.size, [800, 600]); const view2 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); grid.addView(view2, 200, view1, Direction.Up); - assert.deepEqual(view1.size, [800, 400]); - assert.deepEqual(view2.size, [800, 200]); + assert.deepStrictEqual(view1.size, [800, 400]); + assert.deepStrictEqual(view2.size, [800, 200]); const view3 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); grid.addView(view3, 200, view1, Direction.Right); - assert.deepEqual(view1.size, [600, 400]); - assert.deepEqual(view2.size, [800, 200]); - assert.deepEqual(view3.size, [200, 400]); + assert.deepStrictEqual(view1.size, [600, 400]); + assert.deepStrictEqual(view2.size, [800, 200]); + assert.deepStrictEqual(view3.size, [200, 400]); const view4 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); grid.addView(view4, 200, view2, Direction.Left); - assert.deepEqual(view1.size, [600, 400]); - assert.deepEqual(view2.size, [600, 200]); - assert.deepEqual(view3.size, [200, 400]); - assert.deepEqual(view4.size, [200, 200]); + assert.deepStrictEqual(view1.size, [600, 400]); + assert.deepStrictEqual(view2.size, [600, 200]); + assert.deepStrictEqual(view3.size, [200, 400]); + assert.deepStrictEqual(view4.size, [200, 200]); const view5 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); grid.addView(view5, 100, view1, Direction.Down); - assert.deepEqual(view1.size, [600, 300]); - assert.deepEqual(view2.size, [600, 200]); - assert.deepEqual(view3.size, [200, 400]); - assert.deepEqual(view4.size, [200, 200]); - assert.deepEqual(view5.size, [600, 100]); + assert.deepStrictEqual(view1.size, [600, 300]); + assert.deepStrictEqual(view2.size, [600, 200]); + assert.deepStrictEqual(view3.size, [200, 400]); + assert.deepStrictEqual(view4.size, [200, 200]); + assert.deepStrictEqual(view5.size, [600, 100]); }); test('another simple layout with automatic size distribution', function () { @@ -148,42 +148,42 @@ suite('Grid', function () { container.appendChild(grid.element); grid.layout(800, 600); - assert.deepEqual(view1.size, [800, 600]); + assert.deepStrictEqual(view1.size, [800, 600]); const view2 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); grid.addView(view2, Sizing.Distribute, view1, Direction.Left); - assert.deepEqual(view1.size, [400, 600]); - assert.deepEqual(view2.size, [400, 600]); + assert.deepStrictEqual(view1.size, [400, 600]); + assert.deepStrictEqual(view2.size, [400, 600]); const view3 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); grid.addView(view3, Sizing.Distribute, view1, Direction.Right); - assert.deepEqual(view1.size, [266, 600]); - assert.deepEqual(view2.size, [266, 600]); - assert.deepEqual(view3.size, [268, 600]); + assert.deepStrictEqual(view1.size, [266, 600]); + assert.deepStrictEqual(view2.size, [266, 600]); + assert.deepStrictEqual(view3.size, [268, 600]); const view4 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); grid.addView(view4, Sizing.Distribute, view2, Direction.Down); - assert.deepEqual(view1.size, [266, 600]); - assert.deepEqual(view2.size, [266, 300]); - assert.deepEqual(view3.size, [268, 600]); - assert.deepEqual(view4.size, [266, 300]); + assert.deepStrictEqual(view1.size, [266, 600]); + assert.deepStrictEqual(view2.size, [266, 300]); + assert.deepStrictEqual(view3.size, [268, 600]); + assert.deepStrictEqual(view4.size, [266, 300]); const view5 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); grid.addView(view5, Sizing.Distribute, view3, Direction.Up); - assert.deepEqual(view1.size, [266, 600]); - assert.deepEqual(view2.size, [266, 300]); - assert.deepEqual(view3.size, [268, 300]); - assert.deepEqual(view4.size, [266, 300]); - assert.deepEqual(view5.size, [268, 300]); + assert.deepStrictEqual(view1.size, [266, 600]); + assert.deepStrictEqual(view2.size, [266, 300]); + assert.deepStrictEqual(view3.size, [268, 300]); + assert.deepStrictEqual(view4.size, [266, 300]); + assert.deepStrictEqual(view5.size, [268, 300]); const view6 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); grid.addView(view6, Sizing.Distribute, view3, Direction.Down); - assert.deepEqual(view1.size, [266, 600]); - assert.deepEqual(view2.size, [266, 300]); - assert.deepEqual(view3.size, [268, 200]); - assert.deepEqual(view4.size, [266, 300]); - assert.deepEqual(view5.size, [268, 200]); - assert.deepEqual(view6.size, [268, 200]); + assert.deepStrictEqual(view1.size, [266, 600]); + assert.deepStrictEqual(view2.size, [266, 300]); + assert.deepStrictEqual(view3.size, [268, 200]); + assert.deepStrictEqual(view4.size, [266, 300]); + assert.deepStrictEqual(view5.size, [268, 200]); + assert.deepStrictEqual(view6.size, [268, 200]); }); test('another simple layout with split size distribution', function () { @@ -192,42 +192,42 @@ suite('Grid', function () { container.appendChild(grid.element); grid.layout(800, 600); - assert.deepEqual(view1.size, [800, 600]); + assert.deepStrictEqual(view1.size, [800, 600]); const view2 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); grid.addView(view2, Sizing.Split, view1, Direction.Left); - assert.deepEqual(view1.size, [400, 600]); - assert.deepEqual(view2.size, [400, 600]); + assert.deepStrictEqual(view1.size, [400, 600]); + assert.deepStrictEqual(view2.size, [400, 600]); const view3 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); grid.addView(view3, Sizing.Split, view1, Direction.Right); - assert.deepEqual(view1.size, [200, 600]); - assert.deepEqual(view2.size, [400, 600]); - assert.deepEqual(view3.size, [200, 600]); + assert.deepStrictEqual(view1.size, [200, 600]); + assert.deepStrictEqual(view2.size, [400, 600]); + assert.deepStrictEqual(view3.size, [200, 600]); const view4 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); grid.addView(view4, Sizing.Split, view2, Direction.Down); - assert.deepEqual(view1.size, [200, 600]); - assert.deepEqual(view2.size, [400, 300]); - assert.deepEqual(view3.size, [200, 600]); - assert.deepEqual(view4.size, [400, 300]); + assert.deepStrictEqual(view1.size, [200, 600]); + assert.deepStrictEqual(view2.size, [400, 300]); + assert.deepStrictEqual(view3.size, [200, 600]); + assert.deepStrictEqual(view4.size, [400, 300]); const view5 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); grid.addView(view5, Sizing.Split, view3, Direction.Up); - assert.deepEqual(view1.size, [200, 600]); - assert.deepEqual(view2.size, [400, 300]); - assert.deepEqual(view3.size, [200, 300]); - assert.deepEqual(view4.size, [400, 300]); - assert.deepEqual(view5.size, [200, 300]); + assert.deepStrictEqual(view1.size, [200, 600]); + assert.deepStrictEqual(view2.size, [400, 300]); + assert.deepStrictEqual(view3.size, [200, 300]); + assert.deepStrictEqual(view4.size, [400, 300]); + assert.deepStrictEqual(view5.size, [200, 300]); const view6 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); grid.addView(view6, Sizing.Split, view3, Direction.Down); - assert.deepEqual(view1.size, [200, 600]); - assert.deepEqual(view2.size, [400, 300]); - assert.deepEqual(view3.size, [200, 150]); - assert.deepEqual(view4.size, [400, 300]); - assert.deepEqual(view5.size, [200, 300]); - assert.deepEqual(view6.size, [200, 150]); + assert.deepStrictEqual(view1.size, [200, 600]); + assert.deepStrictEqual(view2.size, [400, 300]); + assert.deepStrictEqual(view3.size, [200, 150]); + assert.deepStrictEqual(view4.size, [400, 300]); + assert.deepStrictEqual(view5.size, [200, 300]); + assert.deepStrictEqual(view6.size, [200, 150]); }); test('3/2 layout with split', function () { @@ -236,33 +236,33 @@ suite('Grid', function () { container.appendChild(grid.element); grid.layout(800, 600); - assert.deepEqual(view1.size, [800, 600]); + assert.deepStrictEqual(view1.size, [800, 600]); const view2 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); grid.addView(view2, Sizing.Split, view1, Direction.Down); - assert.deepEqual(view1.size, [800, 300]); - assert.deepEqual(view2.size, [800, 300]); + assert.deepStrictEqual(view1.size, [800, 300]); + assert.deepStrictEqual(view2.size, [800, 300]); const view3 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); grid.addView(view3, Sizing.Split, view2, Direction.Right); - assert.deepEqual(view1.size, [800, 300]); - assert.deepEqual(view2.size, [400, 300]); - assert.deepEqual(view3.size, [400, 300]); + assert.deepStrictEqual(view1.size, [800, 300]); + assert.deepStrictEqual(view2.size, [400, 300]); + assert.deepStrictEqual(view3.size, [400, 300]); const view4 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); grid.addView(view4, Sizing.Split, view1, Direction.Right); - assert.deepEqual(view1.size, [400, 300]); - assert.deepEqual(view2.size, [400, 300]); - assert.deepEqual(view3.size, [400, 300]); - assert.deepEqual(view4.size, [400, 300]); + assert.deepStrictEqual(view1.size, [400, 300]); + assert.deepStrictEqual(view2.size, [400, 300]); + assert.deepStrictEqual(view3.size, [400, 300]); + assert.deepStrictEqual(view4.size, [400, 300]); const view5 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); grid.addView(view5, Sizing.Split, view1, Direction.Right); - assert.deepEqual(view1.size, [200, 300]); - assert.deepEqual(view2.size, [400, 300]); - assert.deepEqual(view3.size, [400, 300]); - assert.deepEqual(view4.size, [400, 300]); - assert.deepEqual(view5.size, [200, 300]); + assert.deepStrictEqual(view1.size, [200, 300]); + assert.deepStrictEqual(view2.size, [400, 300]); + assert.deepStrictEqual(view3.size, [400, 300]); + assert.deepStrictEqual(view4.size, [400, 300]); + assert.deepStrictEqual(view5.size, [200, 300]); }); test('sizing should be correct after branch demotion #50564', function () { @@ -280,15 +280,15 @@ suite('Grid', function () { const view4 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); grid.addView(view4, Sizing.Split, view2, Direction.Right); - assert.deepEqual(view1.size, [400, 600]); - assert.deepEqual(view2.size, [200, 300]); - assert.deepEqual(view3.size, [400, 300]); - assert.deepEqual(view4.size, [200, 300]); + assert.deepStrictEqual(view1.size, [400, 600]); + assert.deepStrictEqual(view2.size, [200, 300]); + assert.deepStrictEqual(view3.size, [400, 300]); + assert.deepStrictEqual(view4.size, [200, 300]); grid.removeView(view3); - assert.deepEqual(view1.size, [400, 600]); - assert.deepEqual(view2.size, [200, 600]); - assert.deepEqual(view4.size, [200, 600]); + assert.deepStrictEqual(view1.size, [400, 600]); + assert.deepStrictEqual(view2.size, [200, 600]); + assert.deepStrictEqual(view4.size, [200, 600]); }); test('sizing should be correct after branch demotion #50675', function () { @@ -306,15 +306,15 @@ suite('Grid', function () { const view4 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); grid.addView(view4, Sizing.Distribute, view3, Direction.Right); - assert.deepEqual(view1.size, [800, 200]); - assert.deepEqual(view2.size, [800, 200]); - assert.deepEqual(view3.size, [400, 200]); - assert.deepEqual(view4.size, [400, 200]); + assert.deepStrictEqual(view1.size, [800, 200]); + assert.deepStrictEqual(view2.size, [800, 200]); + assert.deepStrictEqual(view3.size, [400, 200]); + assert.deepStrictEqual(view4.size, [400, 200]); grid.removeView(view3, Sizing.Distribute); - assert.deepEqual(view1.size, [800, 200]); - assert.deepEqual(view2.size, [800, 200]); - assert.deepEqual(view4.size, [800, 200]); + assert.deepStrictEqual(view1.size, [800, 200]); + assert.deepStrictEqual(view2.size, [800, 200]); + assert.deepStrictEqual(view4.size, [800, 200]); }); test('getNeighborViews should work on single view layout', function () { @@ -324,15 +324,15 @@ suite('Grid', function () { grid.layout(800, 600); - assert.deepEqual(grid.getNeighborViews(view1, Direction.Up), []); - assert.deepEqual(grid.getNeighborViews(view1, Direction.Right), []); - assert.deepEqual(grid.getNeighborViews(view1, Direction.Down), []); - assert.deepEqual(grid.getNeighborViews(view1, Direction.Left), []); + assert.deepStrictEqual(grid.getNeighborViews(view1, Direction.Up), []); + assert.deepStrictEqual(grid.getNeighborViews(view1, Direction.Right), []); + assert.deepStrictEqual(grid.getNeighborViews(view1, Direction.Down), []); + assert.deepStrictEqual(grid.getNeighborViews(view1, Direction.Left), []); - assert.deepEqual(grid.getNeighborViews(view1, Direction.Up, true), [view1]); - assert.deepEqual(grid.getNeighborViews(view1, Direction.Right, true), [view1]); - assert.deepEqual(grid.getNeighborViews(view1, Direction.Down, true), [view1]); - assert.deepEqual(grid.getNeighborViews(view1, Direction.Left, true), [view1]); + assert.deepStrictEqual(grid.getNeighborViews(view1, Direction.Up, true), [view1]); + assert.deepStrictEqual(grid.getNeighborViews(view1, Direction.Right, true), [view1]); + assert.deepStrictEqual(grid.getNeighborViews(view1, Direction.Down, true), [view1]); + assert.deepStrictEqual(grid.getNeighborViews(view1, Direction.Left, true), [view1]); }); test('getNeighborViews should work on simple layout', function () { @@ -348,35 +348,35 @@ suite('Grid', function () { const view3 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); grid.addView(view3, Sizing.Distribute, view2, Direction.Down); - assert.deepEqual(grid.getNeighborViews(view1, Direction.Up), []); - assert.deepEqual(grid.getNeighborViews(view1, Direction.Right), []); - assert.deepEqual(grid.getNeighborViews(view1, Direction.Down), [view2]); - assert.deepEqual(grid.getNeighborViews(view1, Direction.Left), []); + assert.deepStrictEqual(grid.getNeighborViews(view1, Direction.Up), []); + assert.deepStrictEqual(grid.getNeighborViews(view1, Direction.Right), []); + assert.deepStrictEqual(grid.getNeighborViews(view1, Direction.Down), [view2]); + assert.deepStrictEqual(grid.getNeighborViews(view1, Direction.Left), []); - assert.deepEqual(grid.getNeighborViews(view1, Direction.Up, true), [view3]); - assert.deepEqual(grid.getNeighborViews(view1, Direction.Right, true), [view1]); - assert.deepEqual(grid.getNeighborViews(view1, Direction.Down, true), [view2]); - assert.deepEqual(grid.getNeighborViews(view1, Direction.Left, true), [view1]); + assert.deepStrictEqual(grid.getNeighborViews(view1, Direction.Up, true), [view3]); + assert.deepStrictEqual(grid.getNeighborViews(view1, Direction.Right, true), [view1]); + assert.deepStrictEqual(grid.getNeighborViews(view1, Direction.Down, true), [view2]); + assert.deepStrictEqual(grid.getNeighborViews(view1, Direction.Left, true), [view1]); - assert.deepEqual(grid.getNeighborViews(view2, Direction.Up), [view1]); - assert.deepEqual(grid.getNeighborViews(view2, Direction.Right), []); - assert.deepEqual(grid.getNeighborViews(view2, Direction.Down), [view3]); - assert.deepEqual(grid.getNeighborViews(view2, Direction.Left), []); + assert.deepStrictEqual(grid.getNeighborViews(view2, Direction.Up), [view1]); + assert.deepStrictEqual(grid.getNeighborViews(view2, Direction.Right), []); + assert.deepStrictEqual(grid.getNeighborViews(view2, Direction.Down), [view3]); + assert.deepStrictEqual(grid.getNeighborViews(view2, Direction.Left), []); - assert.deepEqual(grid.getNeighborViews(view2, Direction.Up, true), [view1]); - assert.deepEqual(grid.getNeighborViews(view2, Direction.Right, true), [view2]); - assert.deepEqual(grid.getNeighborViews(view2, Direction.Down, true), [view3]); - assert.deepEqual(grid.getNeighborViews(view2, Direction.Left, true), [view2]); + assert.deepStrictEqual(grid.getNeighborViews(view2, Direction.Up, true), [view1]); + assert.deepStrictEqual(grid.getNeighborViews(view2, Direction.Right, true), [view2]); + assert.deepStrictEqual(grid.getNeighborViews(view2, Direction.Down, true), [view3]); + assert.deepStrictEqual(grid.getNeighborViews(view2, Direction.Left, true), [view2]); - assert.deepEqual(grid.getNeighborViews(view3, Direction.Up), [view2]); - assert.deepEqual(grid.getNeighborViews(view3, Direction.Right), []); - assert.deepEqual(grid.getNeighborViews(view3, Direction.Down), []); - assert.deepEqual(grid.getNeighborViews(view3, Direction.Left), []); + assert.deepStrictEqual(grid.getNeighborViews(view3, Direction.Up), [view2]); + assert.deepStrictEqual(grid.getNeighborViews(view3, Direction.Right), []); + assert.deepStrictEqual(grid.getNeighborViews(view3, Direction.Down), []); + assert.deepStrictEqual(grid.getNeighborViews(view3, Direction.Left), []); - assert.deepEqual(grid.getNeighborViews(view3, Direction.Up, true), [view2]); - assert.deepEqual(grid.getNeighborViews(view3, Direction.Right, true), [view3]); - assert.deepEqual(grid.getNeighborViews(view3, Direction.Down, true), [view1]); - assert.deepEqual(grid.getNeighborViews(view3, Direction.Left, true), [view3]); + assert.deepStrictEqual(grid.getNeighborViews(view3, Direction.Up, true), [view2]); + assert.deepStrictEqual(grid.getNeighborViews(view3, Direction.Right, true), [view3]); + assert.deepStrictEqual(grid.getNeighborViews(view3, Direction.Down, true), [view1]); + assert.deepStrictEqual(grid.getNeighborViews(view3, Direction.Left, true), [view3]); }); test('getNeighborViews should work on a complex layout', function () { @@ -398,26 +398,26 @@ suite('Grid', function () { const view5 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); grid.addView(view5, Sizing.Distribute, view4, Direction.Down); - assert.deepEqual(grid.getNeighborViews(view1, Direction.Up), []); - assert.deepEqual(grid.getNeighborViews(view1, Direction.Right), []); - assert.deepEqual(grid.getNeighborViews(view1, Direction.Down), [view2, view4]); - assert.deepEqual(grid.getNeighborViews(view1, Direction.Left), []); - assert.deepEqual(grid.getNeighborViews(view2, Direction.Up), [view1]); - assert.deepEqual(grid.getNeighborViews(view2, Direction.Right), [view4, view5]); - assert.deepEqual(grid.getNeighborViews(view2, Direction.Down), [view3]); - assert.deepEqual(grid.getNeighborViews(view2, Direction.Left), []); - assert.deepEqual(grid.getNeighborViews(view4, Direction.Up), [view1]); - assert.deepEqual(grid.getNeighborViews(view4, Direction.Right), []); - assert.deepEqual(grid.getNeighborViews(view4, Direction.Down), [view5]); - assert.deepEqual(grid.getNeighborViews(view4, Direction.Left), [view2]); - assert.deepEqual(grid.getNeighborViews(view5, Direction.Up), [view4]); - assert.deepEqual(grid.getNeighborViews(view5, Direction.Right), []); - assert.deepEqual(grid.getNeighborViews(view5, Direction.Down), [view3]); - assert.deepEqual(grid.getNeighborViews(view5, Direction.Left), [view2]); - assert.deepEqual(grid.getNeighborViews(view3, Direction.Up), [view2, view5]); - assert.deepEqual(grid.getNeighborViews(view3, Direction.Right), []); - assert.deepEqual(grid.getNeighborViews(view3, Direction.Down), []); - assert.deepEqual(grid.getNeighborViews(view3, Direction.Left), []); + assert.deepStrictEqual(grid.getNeighborViews(view1, Direction.Up), []); + assert.deepStrictEqual(grid.getNeighborViews(view1, Direction.Right), []); + assert.deepStrictEqual(grid.getNeighborViews(view1, Direction.Down), [view2, view4]); + assert.deepStrictEqual(grid.getNeighborViews(view1, Direction.Left), []); + assert.deepStrictEqual(grid.getNeighborViews(view2, Direction.Up), [view1]); + assert.deepStrictEqual(grid.getNeighborViews(view2, Direction.Right), [view4, view5]); + assert.deepStrictEqual(grid.getNeighborViews(view2, Direction.Down), [view3]); + assert.deepStrictEqual(grid.getNeighborViews(view2, Direction.Left), []); + assert.deepStrictEqual(grid.getNeighborViews(view4, Direction.Up), [view1]); + assert.deepStrictEqual(grid.getNeighborViews(view4, Direction.Right), []); + assert.deepStrictEqual(grid.getNeighborViews(view4, Direction.Down), [view5]); + assert.deepStrictEqual(grid.getNeighborViews(view4, Direction.Left), [view2]); + assert.deepStrictEqual(grid.getNeighborViews(view5, Direction.Up), [view4]); + assert.deepStrictEqual(grid.getNeighborViews(view5, Direction.Right), []); + assert.deepStrictEqual(grid.getNeighborViews(view5, Direction.Down), [view3]); + assert.deepStrictEqual(grid.getNeighborViews(view5, Direction.Left), [view2]); + assert.deepStrictEqual(grid.getNeighborViews(view3, Direction.Up), [view2, view5]); + assert.deepStrictEqual(grid.getNeighborViews(view3, Direction.Right), []); + assert.deepStrictEqual(grid.getNeighborViews(view3, Direction.Down), []); + assert.deepStrictEqual(grid.getNeighborViews(view3, Direction.Left), []); }); test('getNeighborViews should work on another simple layout', function () { @@ -436,10 +436,10 @@ suite('Grid', function () { const view4 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); grid.addView(view4, Sizing.Distribute, view2, Direction.Right); - assert.deepEqual(grid.getNeighborViews(view4, Direction.Up), []); - assert.deepEqual(grid.getNeighborViews(view4, Direction.Right), []); - assert.deepEqual(grid.getNeighborViews(view4, Direction.Down), [view3]); - assert.deepEqual(grid.getNeighborViews(view4, Direction.Left), [view2]); + assert.deepStrictEqual(grid.getNeighborViews(view4, Direction.Up), []); + assert.deepStrictEqual(grid.getNeighborViews(view4, Direction.Right), []); + assert.deepStrictEqual(grid.getNeighborViews(view4, Direction.Down), [view3]); + assert.deepStrictEqual(grid.getNeighborViews(view4, Direction.Left), [view2]); }); test('getNeighborViews should only return immediate neighbors', function () { @@ -458,7 +458,7 @@ suite('Grid', function () { const view4 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); grid.addView(view4, Sizing.Distribute, view2, Direction.Right); - assert.deepEqual(grid.getNeighborViews(view1, Direction.Right), [view2, view3]); + assert.deepStrictEqual(grid.getNeighborViews(view1, Direction.Right), [view2, view3]); }); }); @@ -524,7 +524,7 @@ suite('SerializableGrid', function () { grid.layout(800, 600); const actual = grid.serialize(); - assert.deepEqual(actual, { + assert.deepStrictEqual(actual, { orientation: 0, width: 800, height: 600, @@ -562,7 +562,7 @@ suite('SerializableGrid', function () { const view5 = new TestSerializableView('view5', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE); grid.addView(view5, 100, view1, Direction.Down); - assert.deepEqual(grid.serialize(), { + assert.deepStrictEqual(grid.serialize(), { orientation: 0, width: 800, height: 600, @@ -611,7 +611,7 @@ suite('SerializableGrid', function () { const grid2 = SerializableGrid.deserialize(json, deserializer); grid2.layout(800, 600); - assert.deepEqual(nodesToNames(grid2.getViews()), ['view1']); + assert.deepStrictEqual(nodesToNames(grid2.getViews()), ['view1']); }); test('deserialize simple layout', function () { @@ -645,15 +645,15 @@ suite('SerializableGrid', function () { const view4Copy = deserializer.getView('view4'); const view5Copy = deserializer.getView('view5'); - assert.deepEqual(nodesToArrays(grid2.getViews()), [[view4Copy, view2Copy], [[view1Copy, view5Copy], view3Copy]]); + assert.deepStrictEqual(nodesToArrays(grid2.getViews()), [[view4Copy, view2Copy], [[view1Copy, view5Copy], view3Copy]]); grid2.layout(800, 600); - assert.deepEqual(view1Copy.size, [600, 300]); - assert.deepEqual(view2Copy.size, [600, 200]); - assert.deepEqual(view3Copy.size, [200, 400]); - assert.deepEqual(view4Copy.size, [200, 200]); - assert.deepEqual(view5Copy.size, [600, 100]); + assert.deepStrictEqual(view1Copy.size, [600, 300]); + assert.deepStrictEqual(view2Copy.size, [600, 200]); + assert.deepStrictEqual(view3Copy.size, [200, 400]); + assert.deepStrictEqual(view4Copy.size, [200, 200]); + assert.deepStrictEqual(view5Copy.size, [600, 100]); }); test('deserialize simple layout with scaling', function () { @@ -688,11 +688,11 @@ suite('SerializableGrid', function () { const view5Copy = deserializer.getView('view5'); grid2.layout(400, 800); // [/2, *4/3] - assert.deepEqual(view1Copy.size, [300, 400]); - assert.deepEqual(view2Copy.size, [300, 267]); - assert.deepEqual(view3Copy.size, [100, 533]); - assert.deepEqual(view4Copy.size, [100, 267]); - assert.deepEqual(view5Copy.size, [300, 133]); + assert.deepStrictEqual(view1Copy.size, [300, 400]); + assert.deepStrictEqual(view2Copy.size, [300, 267]); + assert.deepStrictEqual(view3Copy.size, [100, 533]); + assert.deepStrictEqual(view4Copy.size, [100, 267]); + assert.deepStrictEqual(view5Copy.size, [300, 133]); }); test('deserialize 4 view layout (ben issue #2)', function () { @@ -723,10 +723,10 @@ suite('SerializableGrid', function () { grid2.layout(800, 600); - assert.deepEqual(view1Copy.size, [800, 300]); - assert.deepEqual(view2Copy.size, [800, 150]); - assert.deepEqual(view3Copy.size, [400, 150]); - assert.deepEqual(view4Copy.size, [400, 150]); + assert.deepStrictEqual(view1Copy.size, [800, 300]); + assert.deepStrictEqual(view2Copy.size, [800, 150]); + assert.deepStrictEqual(view3Copy.size, [400, 150]); + assert.deepStrictEqual(view4Copy.size, [400, 150]); }); test('deserialize 2 view layout (ben issue #3)', function () { @@ -750,8 +750,8 @@ suite('SerializableGrid', function () { grid2.layout(800, 600); - assert.deepEqual(view1Copy.size, [400, 600]); - assert.deepEqual(view2Copy.size, [400, 600]); + assert.deepStrictEqual(view1Copy.size, [400, 600]); + assert.deepStrictEqual(view2Copy.size, [400, 600]); }); test('deserialize simple view layout #50609', function () { @@ -780,21 +780,21 @@ suite('SerializableGrid', function () { grid2.layout(800, 600); - assert.deepEqual(view2Copy.size, [800, 300]); - assert.deepEqual(view3Copy.size, [800, 300]); + assert.deepStrictEqual(view2Copy.size, [800, 300]); + assert.deepStrictEqual(view3Copy.size, [800, 300]); }); test('sanitizeGridNodeDescriptor', () => { const nodeDescriptor = { groups: [{ size: 0.2 }, { size: 0.2 }, { size: 0.6, groups: [{}, {}] }] }; const nodeDescriptorCopy = deepClone(nodeDescriptor); sanitizeGridNodeDescriptor(nodeDescriptorCopy, true); - assert.deepEqual(nodeDescriptorCopy, { groups: [{ size: 0.2 }, { size: 0.2 }, { size: 0.6, groups: [{ size: 0.5 }, { size: 0.5 }] }] }); + assert.deepStrictEqual(nodeDescriptorCopy, { groups: [{ size: 0.2 }, { size: 0.2 }, { size: 0.6, groups: [{ size: 0.5 }, { size: 0.5 }] }] }); }); test('createSerializedGrid', () => { const gridDescriptor = { orientation: Orientation.VERTICAL, groups: [{ size: 0.2 }, { size: 0.2 }, { size: 0.6, groups: [{}, {}] }] }; const serializedGrid = createSerializedGrid(gridDescriptor); - assert.deepEqual(serializedGrid, { + assert.deepStrictEqual(serializedGrid, { root: { type: 'branch', size: undefined, @@ -836,7 +836,7 @@ suite('SerializableGrid', function () { }; const grid = SerializableGrid.deserialize(serializedGrid, deserializer); - assert.equal(views.length, 3); + assert.strictEqual(views.length, 3); // should not throw grid.removeView(views[2]); @@ -860,40 +860,40 @@ suite('SerializableGrid', function () { const view5 = new TestSerializableView('view5', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE); grid.addView(view5, 100, view1, Direction.Down); - assert.deepEqual(view1.size, [600, 300]); - assert.deepEqual(view2.size, [600, 200]); - assert.deepEqual(view3.size, [200, 400]); - assert.deepEqual(view4.size, [200, 200]); - assert.deepEqual(view5.size, [600, 100]); + assert.deepStrictEqual(view1.size, [600, 300]); + assert.deepStrictEqual(view2.size, [600, 200]); + assert.deepStrictEqual(view3.size, [200, 400]); + assert.deepStrictEqual(view4.size, [200, 200]); + assert.deepStrictEqual(view5.size, [600, 100]); grid.setViewVisible(view5, false); - assert.deepEqual(view1.size, [600, 400]); - assert.deepEqual(view2.size, [600, 200]); - assert.deepEqual(view3.size, [200, 400]); - assert.deepEqual(view4.size, [200, 200]); - assert.deepEqual(view5.size, [600, 0]); + assert.deepStrictEqual(view1.size, [600, 400]); + assert.deepStrictEqual(view2.size, [600, 200]); + assert.deepStrictEqual(view3.size, [200, 400]); + assert.deepStrictEqual(view4.size, [200, 200]); + assert.deepStrictEqual(view5.size, [600, 0]); grid.setViewVisible(view5, true); - assert.deepEqual(view1.size, [600, 300]); - assert.deepEqual(view2.size, [600, 200]); - assert.deepEqual(view3.size, [200, 400]); - assert.deepEqual(view4.size, [200, 200]); - assert.deepEqual(view5.size, [600, 100]); + assert.deepStrictEqual(view1.size, [600, 300]); + assert.deepStrictEqual(view2.size, [600, 200]); + assert.deepStrictEqual(view3.size, [200, 400]); + assert.deepStrictEqual(view4.size, [200, 200]); + assert.deepStrictEqual(view5.size, [600, 100]); grid.setViewVisible(view5, false); - assert.deepEqual(view1.size, [600, 400]); - assert.deepEqual(view2.size, [600, 200]); - assert.deepEqual(view3.size, [200, 400]); - assert.deepEqual(view4.size, [200, 200]); - assert.deepEqual(view5.size, [600, 0]); + assert.deepStrictEqual(view1.size, [600, 400]); + assert.deepStrictEqual(view2.size, [600, 200]); + assert.deepStrictEqual(view3.size, [200, 400]); + assert.deepStrictEqual(view4.size, [200, 200]); + assert.deepStrictEqual(view5.size, [600, 0]); grid.setViewVisible(view5, false); const json = grid.serialize(); - assert.deepEqual(json, { + assert.deepStrictEqual(json, { orientation: 0, width: 800, height: 600, @@ -939,34 +939,34 @@ suite('SerializableGrid', function () { const view4Copy = deserializer.getView('view4'); const view5Copy = deserializer.getView('view5'); - assert.deepEqual(nodesToArrays(grid2.getViews()), [[view4Copy, view2Copy], [[view1Copy, view5Copy], view3Copy]]); + assert.deepStrictEqual(nodesToArrays(grid2.getViews()), [[view4Copy, view2Copy], [[view1Copy, view5Copy], view3Copy]]); grid2.layout(800, 600); - assert.deepEqual(view1Copy.size, [600, 400]); - assert.deepEqual(view2Copy.size, [600, 200]); - assert.deepEqual(view3Copy.size, [200, 400]); - assert.deepEqual(view4Copy.size, [200, 200]); - assert.deepEqual(view5Copy.size, [600, 0]); + assert.deepStrictEqual(view1Copy.size, [600, 400]); + assert.deepStrictEqual(view2Copy.size, [600, 200]); + assert.deepStrictEqual(view3Copy.size, [200, 400]); + assert.deepStrictEqual(view4Copy.size, [200, 200]); + assert.deepStrictEqual(view5Copy.size, [600, 0]); - assert.deepEqual(grid2.isViewVisible(view1Copy), true); - assert.deepEqual(grid2.isViewVisible(view2Copy), true); - assert.deepEqual(grid2.isViewVisible(view3Copy), true); - assert.deepEqual(grid2.isViewVisible(view4Copy), true); - assert.deepEqual(grid2.isViewVisible(view5Copy), false); + assert.deepStrictEqual(grid2.isViewVisible(view1Copy), true); + assert.deepStrictEqual(grid2.isViewVisible(view2Copy), true); + assert.deepStrictEqual(grid2.isViewVisible(view3Copy), true); + assert.deepStrictEqual(grid2.isViewVisible(view4Copy), true); + assert.deepStrictEqual(grid2.isViewVisible(view5Copy), false); grid2.setViewVisible(view5Copy, true); - assert.deepEqual(view1Copy.size, [600, 300]); - assert.deepEqual(view2Copy.size, [600, 200]); - assert.deepEqual(view3Copy.size, [200, 400]); - assert.deepEqual(view4Copy.size, [200, 200]); - assert.deepEqual(view5Copy.size, [600, 100]); + assert.deepStrictEqual(view1Copy.size, [600, 300]); + assert.deepStrictEqual(view2Copy.size, [600, 200]); + assert.deepStrictEqual(view3Copy.size, [200, 400]); + assert.deepStrictEqual(view4Copy.size, [200, 200]); + assert.deepStrictEqual(view5Copy.size, [600, 100]); - assert.deepEqual(grid2.isViewVisible(view1Copy), true); - assert.deepEqual(grid2.isViewVisible(view2Copy), true); - assert.deepEqual(grid2.isViewVisible(view3Copy), true); - assert.deepEqual(grid2.isViewVisible(view4Copy), true); - assert.deepEqual(grid2.isViewVisible(view5Copy), true); + assert.deepStrictEqual(grid2.isViewVisible(view1Copy), true); + assert.deepStrictEqual(grid2.isViewVisible(view2Copy), true); + assert.deepStrictEqual(grid2.isViewVisible(view3Copy), true); + assert.deepStrictEqual(grid2.isViewVisible(view4Copy), true); + assert.deepStrictEqual(grid2.isViewVisible(view5Copy), true); }); test('serialize should store visibility and previous size even for first leaf', function () { @@ -987,22 +987,22 @@ suite('SerializableGrid', function () { const view5 = new TestSerializableView('view5', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE); grid.addView(view5, 100, view1, Direction.Down); - assert.deepEqual(view1.size, [600, 300]); - assert.deepEqual(view2.size, [600, 200]); - assert.deepEqual(view3.size, [200, 400]); - assert.deepEqual(view4.size, [200, 200]); - assert.deepEqual(view5.size, [600, 100]); + assert.deepStrictEqual(view1.size, [600, 300]); + assert.deepStrictEqual(view2.size, [600, 200]); + assert.deepStrictEqual(view3.size, [200, 400]); + assert.deepStrictEqual(view4.size, [200, 200]); + assert.deepStrictEqual(view5.size, [600, 100]); grid.setViewVisible(view4, false); - assert.deepEqual(view1.size, [600, 300]); - assert.deepEqual(view2.size, [800, 200]); - assert.deepEqual(view3.size, [200, 400]); - assert.deepEqual(view4.size, [0, 200]); - assert.deepEqual(view5.size, [600, 100]); + assert.deepStrictEqual(view1.size, [600, 300]); + assert.deepStrictEqual(view2.size, [800, 200]); + assert.deepStrictEqual(view3.size, [200, 400]); + assert.deepStrictEqual(view4.size, [0, 200]); + assert.deepStrictEqual(view5.size, [600, 100]); const json = grid.serialize(); - assert.deepEqual(json, { + assert.deepStrictEqual(json, { orientation: 0, width: 800, height: 600, @@ -1048,33 +1048,33 @@ suite('SerializableGrid', function () { const view4Copy = deserializer.getView('view4'); const view5Copy = deserializer.getView('view5'); - assert.deepEqual(nodesToArrays(grid2.getViews()), [[view4Copy, view2Copy], [[view1Copy, view5Copy], view3Copy]]); + assert.deepStrictEqual(nodesToArrays(grid2.getViews()), [[view4Copy, view2Copy], [[view1Copy, view5Copy], view3Copy]]); grid2.layout(800, 600); - assert.deepEqual(view1Copy.size, [600, 300]); - assert.deepEqual(view2Copy.size, [800, 200]); - assert.deepEqual(view3Copy.size, [200, 400]); - assert.deepEqual(view4Copy.size, [0, 200]); - assert.deepEqual(view5Copy.size, [600, 100]); + assert.deepStrictEqual(view1Copy.size, [600, 300]); + assert.deepStrictEqual(view2Copy.size, [800, 200]); + assert.deepStrictEqual(view3Copy.size, [200, 400]); + assert.deepStrictEqual(view4Copy.size, [0, 200]); + assert.deepStrictEqual(view5Copy.size, [600, 100]); - assert.deepEqual(grid2.isViewVisible(view1Copy), true); - assert.deepEqual(grid2.isViewVisible(view2Copy), true); - assert.deepEqual(grid2.isViewVisible(view3Copy), true); - assert.deepEqual(grid2.isViewVisible(view4Copy), false); - assert.deepEqual(grid2.isViewVisible(view5Copy), true); + assert.deepStrictEqual(grid2.isViewVisible(view1Copy), true); + assert.deepStrictEqual(grid2.isViewVisible(view2Copy), true); + assert.deepStrictEqual(grid2.isViewVisible(view3Copy), true); + assert.deepStrictEqual(grid2.isViewVisible(view4Copy), false); + assert.deepStrictEqual(grid2.isViewVisible(view5Copy), true); grid2.setViewVisible(view4Copy, true); - assert.deepEqual(view1Copy.size, [600, 300]); - assert.deepEqual(view2Copy.size, [600, 200]); - assert.deepEqual(view3Copy.size, [200, 400]); - assert.deepEqual(view4Copy.size, [200, 200]); - assert.deepEqual(view5Copy.size, [600, 100]); + assert.deepStrictEqual(view1Copy.size, [600, 300]); + assert.deepStrictEqual(view2Copy.size, [600, 200]); + assert.deepStrictEqual(view3Copy.size, [200, 400]); + assert.deepStrictEqual(view4Copy.size, [200, 200]); + assert.deepStrictEqual(view5Copy.size, [600, 100]); - assert.deepEqual(grid2.isViewVisible(view1Copy), true); - assert.deepEqual(grid2.isViewVisible(view2Copy), true); - assert.deepEqual(grid2.isViewVisible(view3Copy), true); - assert.deepEqual(grid2.isViewVisible(view4Copy), true); - assert.deepEqual(grid2.isViewVisible(view5Copy), true); + assert.deepStrictEqual(grid2.isViewVisible(view1Copy), true); + assert.deepStrictEqual(grid2.isViewVisible(view2Copy), true); + assert.deepStrictEqual(grid2.isViewVisible(view3Copy), true); + assert.deepStrictEqual(grid2.isViewVisible(view4Copy), true); + assert.deepStrictEqual(grid2.isViewVisible(view5Copy), true); }); }); diff --git a/src/vs/base/test/browser/ui/grid/gridview.test.ts b/src/vs/base/test/browser/ui/grid/gridview.test.ts index 78cc44cc..ab86c490 100644 --- a/src/vs/base/test/browser/ui/grid/gridview.test.ts +++ b/src/vs/base/test/browser/ui/grid/gridview.test.ts @@ -22,7 +22,7 @@ suite('Gridview', function () { }); test('empty gridview is empty', function () { - assert.deepEqual(nodesToArrays(gridview.getView()), []); + assert.deepStrictEqual(nodesToArrays(gridview.getView()), []); gridview.dispose(); }); @@ -43,7 +43,7 @@ suite('Gridview', function () { gridview.addView(views[1], 200, [1]); gridview.addView(views[2], 200, [2]); - assert.deepEqual(nodesToArrays(gridview.getView()), views); + assert.deepStrictEqual(nodesToArrays(gridview.getView()), views); gridview.dispose(); }); @@ -62,7 +62,7 @@ suite('Gridview', function () { gridview.addView((views[1] as TestView[])[0] as IView, 200, [1]); gridview.addView((views[1] as TestView[])[1] as IView, 200, [1, 1]); - assert.deepEqual(nodesToArrays(gridview.getView()), views); + assert.deepStrictEqual(nodesToArrays(gridview.getView()), views); gridview.dispose(); }); @@ -71,35 +71,35 @@ suite('Gridview', function () { const view1 = new TestView(20, 20, 20, 20); gridview.addView(view1 as IView, 200, [0]); - assert.deepEqual(nodesToArrays(gridview.getView()), [view1]); + assert.deepStrictEqual(nodesToArrays(gridview.getView()), [view1]); const view2 = new TestView(20, 20, 20, 20); gridview.addView(view2 as IView, 200, [1]); - assert.deepEqual(nodesToArrays(gridview.getView()), [view1, view2]); + assert.deepStrictEqual(nodesToArrays(gridview.getView()), [view1, view2]); const view3 = new TestView(20, 20, 20, 20); gridview.addView(view3 as IView, 200, [1, 0]); - assert.deepEqual(nodesToArrays(gridview.getView()), [view1, [view3, view2]]); + assert.deepStrictEqual(nodesToArrays(gridview.getView()), [view1, [view3, view2]]); const view4 = new TestView(20, 20, 20, 20); gridview.addView(view4 as IView, 200, [1, 0, 0]); - assert.deepEqual(nodesToArrays(gridview.getView()), [view1, [[view4, view3], view2]]); + assert.deepStrictEqual(nodesToArrays(gridview.getView()), [view1, [[view4, view3], view2]]); const view5 = new TestView(20, 20, 20, 20); gridview.addView(view5 as IView, 200, [1, 0]); - assert.deepEqual(nodesToArrays(gridview.getView()), [view1, [view5, [view4, view3], view2]]); + assert.deepStrictEqual(nodesToArrays(gridview.getView()), [view1, [view5, [view4, view3], view2]]); const view6 = new TestView(20, 20, 20, 20); gridview.addView(view6 as IView, 200, [2]); - assert.deepEqual(nodesToArrays(gridview.getView()), [view1, [view5, [view4, view3], view2], view6]); + assert.deepStrictEqual(nodesToArrays(gridview.getView()), [view1, [view5, [view4, view3], view2], view6]); const view7 = new TestView(20, 20, 20, 20); gridview.addView(view7 as IView, 200, [1, 1]); - assert.deepEqual(nodesToArrays(gridview.getView()), [view1, [view5, view7, [view4, view3], view2], view6]); + assert.deepStrictEqual(nodesToArrays(gridview.getView()), [view1, [view5, view7, [view4, view3], view2], view6]); const view8 = new TestView(20, 20, 20, 20); gridview.addView(view8 as IView, 200, [1, 1, 0]); - assert.deepEqual(nodesToArrays(gridview.getView()), [view1, [view5, [view8, view7], [view4, view3], view2], view6]); + assert.deepStrictEqual(nodesToArrays(gridview.getView()), [view1, [view5, [view8, view7], [view4, view3], view2], view6]); gridview.dispose(); }); @@ -109,48 +109,48 @@ suite('Gridview', function () { const view1 = new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY); gridview.addView(view1, 200, [0]); - assert.deepEqual(view1.size, [800, 600]); - assert.deepEqual(gridview.getViewSize([0]), { width: 800, height: 600 }); + assert.deepStrictEqual(view1.size, [800, 600]); + assert.deepStrictEqual(gridview.getViewSize([0]), { width: 800, height: 600 }); const view2 = new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY); gridview.addView(view2, 200, [0]); - assert.deepEqual(view1.size, [800, 400]); - assert.deepEqual(gridview.getViewSize([1]), { width: 800, height: 400 }); - assert.deepEqual(view2.size, [800, 200]); - assert.deepEqual(gridview.getViewSize([0]), { width: 800, height: 200 }); + assert.deepStrictEqual(view1.size, [800, 400]); + assert.deepStrictEqual(gridview.getViewSize([1]), { width: 800, height: 400 }); + assert.deepStrictEqual(view2.size, [800, 200]); + assert.deepStrictEqual(gridview.getViewSize([0]), { width: 800, height: 200 }); const view3 = new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY); gridview.addView(view3, 200, [1, 1]); - assert.deepEqual(view1.size, [600, 400]); - assert.deepEqual(gridview.getViewSize([1, 0]), { width: 600, height: 400 }); - assert.deepEqual(view2.size, [800, 200]); - assert.deepEqual(gridview.getViewSize([0]), { width: 800, height: 200 }); - assert.deepEqual(view3.size, [200, 400]); - assert.deepEqual(gridview.getViewSize([1, 1]), { width: 200, height: 400 }); + assert.deepStrictEqual(view1.size, [600, 400]); + assert.deepStrictEqual(gridview.getViewSize([1, 0]), { width: 600, height: 400 }); + assert.deepStrictEqual(view2.size, [800, 200]); + assert.deepStrictEqual(gridview.getViewSize([0]), { width: 800, height: 200 }); + assert.deepStrictEqual(view3.size, [200, 400]); + assert.deepStrictEqual(gridview.getViewSize([1, 1]), { width: 200, height: 400 }); const view4 = new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY); gridview.addView(view4, 200, [0, 0]); - assert.deepEqual(view1.size, [600, 400]); - assert.deepEqual(gridview.getViewSize([1, 0]), { width: 600, height: 400 }); - assert.deepEqual(view2.size, [600, 200]); - assert.deepEqual(gridview.getViewSize([0, 1]), { width: 600, height: 200 }); - assert.deepEqual(view3.size, [200, 400]); - assert.deepEqual(gridview.getViewSize([1, 1]), { width: 200, height: 400 }); - assert.deepEqual(view4.size, [200, 200]); - assert.deepEqual(gridview.getViewSize([0, 0]), { width: 200, height: 200 }); + assert.deepStrictEqual(view1.size, [600, 400]); + assert.deepStrictEqual(gridview.getViewSize([1, 0]), { width: 600, height: 400 }); + assert.deepStrictEqual(view2.size, [600, 200]); + assert.deepStrictEqual(gridview.getViewSize([0, 1]), { width: 600, height: 200 }); + assert.deepStrictEqual(view3.size, [200, 400]); + assert.deepStrictEqual(gridview.getViewSize([1, 1]), { width: 200, height: 400 }); + assert.deepStrictEqual(view4.size, [200, 200]); + assert.deepStrictEqual(gridview.getViewSize([0, 0]), { width: 200, height: 200 }); const view5 = new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY); gridview.addView(view5, 100, [1, 0, 1]); - assert.deepEqual(view1.size, [600, 300]); - assert.deepEqual(gridview.getViewSize([1, 0, 0]), { width: 600, height: 300 }); - assert.deepEqual(view2.size, [600, 200]); - assert.deepEqual(gridview.getViewSize([0, 1]), { width: 600, height: 200 }); - assert.deepEqual(view3.size, [200, 400]); - assert.deepEqual(gridview.getViewSize([1, 1]), { width: 200, height: 400 }); - assert.deepEqual(view4.size, [200, 200]); - assert.deepEqual(gridview.getViewSize([0, 0]), { width: 200, height: 200 }); - assert.deepEqual(view5.size, [600, 100]); - assert.deepEqual(gridview.getViewSize([1, 0, 1]), { width: 600, height: 100 }); + assert.deepStrictEqual(view1.size, [600, 300]); + assert.deepStrictEqual(gridview.getViewSize([1, 0, 0]), { width: 600, height: 300 }); + assert.deepStrictEqual(view2.size, [600, 200]); + assert.deepStrictEqual(gridview.getViewSize([0, 1]), { width: 600, height: 200 }); + assert.deepStrictEqual(view3.size, [200, 400]); + assert.deepStrictEqual(gridview.getViewSize([1, 1]), { width: 200, height: 400 }); + assert.deepStrictEqual(view4.size, [200, 200]); + assert.deepStrictEqual(gridview.getViewSize([0, 0]), { width: 200, height: 200 }); + assert.deepStrictEqual(view5.size, [600, 100]); + assert.deepStrictEqual(gridview.getViewSize([1, 0, 1]), { width: 600, height: 100 }); }); test('simple layout with automatic size distribution', function () { @@ -158,34 +158,34 @@ suite('Gridview', function () { const view1 = new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY); gridview.addView(view1, Sizing.Distribute, [0]); - assert.deepEqual(view1.size, [800, 600]); - assert.deepEqual(gridview.getViewSize([0]), { width: 800, height: 600 }); + assert.deepStrictEqual(view1.size, [800, 600]); + assert.deepStrictEqual(gridview.getViewSize([0]), { width: 800, height: 600 }); const view2 = new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY); gridview.addView(view2, Sizing.Distribute, [0]); - assert.deepEqual(view1.size, [800, 300]); - assert.deepEqual(view2.size, [800, 300]); + assert.deepStrictEqual(view1.size, [800, 300]); + assert.deepStrictEqual(view2.size, [800, 300]); const view3 = new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY); gridview.addView(view3, Sizing.Distribute, [1, 1]); - assert.deepEqual(view1.size, [400, 300]); - assert.deepEqual(view2.size, [800, 300]); - assert.deepEqual(view3.size, [400, 300]); + assert.deepStrictEqual(view1.size, [400, 300]); + assert.deepStrictEqual(view2.size, [800, 300]); + assert.deepStrictEqual(view3.size, [400, 300]); const view4 = new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY); gridview.addView(view4, Sizing.Distribute, [0, 0]); - assert.deepEqual(view1.size, [400, 300]); - assert.deepEqual(view2.size, [400, 300]); - assert.deepEqual(view3.size, [400, 300]); - assert.deepEqual(view4.size, [400, 300]); + assert.deepStrictEqual(view1.size, [400, 300]); + assert.deepStrictEqual(view2.size, [400, 300]); + assert.deepStrictEqual(view3.size, [400, 300]); + assert.deepStrictEqual(view4.size, [400, 300]); const view5 = new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY); gridview.addView(view5, Sizing.Distribute, [1, 0, 1]); - assert.deepEqual(view1.size, [400, 150]); - assert.deepEqual(view2.size, [400, 300]); - assert.deepEqual(view3.size, [400, 300]); - assert.deepEqual(view4.size, [400, 300]); - assert.deepEqual(view5.size, [400, 150]); + assert.deepStrictEqual(view1.size, [400, 150]); + assert.deepStrictEqual(view2.size, [400, 300]); + assert.deepStrictEqual(view3.size, [400, 300]); + assert.deepStrictEqual(view4.size, [400, 300]); + assert.deepStrictEqual(view5.size, [400, 150]); }); test('addviews before layout call 1', function () { @@ -201,9 +201,9 @@ suite('Gridview', function () { gridview.layout(800, 600); - assert.deepEqual(view1.size, [400, 300]); - assert.deepEqual(view2.size, [800, 300]); - assert.deepEqual(view3.size, [400, 300]); + assert.deepStrictEqual(view1.size, [400, 300]); + assert.deepStrictEqual(view2.size, [800, 300]); + assert.deepStrictEqual(view3.size, [400, 300]); }); test('addviews before layout call 2', function () { @@ -218,8 +218,8 @@ suite('Gridview', function () { gridview.layout(800, 600); - assert.deepEqual(view1.size, [800, 300]); - assert.deepEqual(view2.size, [400, 300]); - assert.deepEqual(view3.size, [400, 300]); + assert.deepStrictEqual(view1.size, [800, 300]); + assert.deepStrictEqual(view2.size, [400, 300]); + assert.deepStrictEqual(view3.size, [400, 300]); }); }); diff --git a/src/vs/base/test/browser/ui/list/listView.test.ts b/src/vs/base/test/browser/ui/list/listView.test.ts index 99f9bac7..77383125 100644 --- a/src/vs/base/test/browser/ui/list/listView.test.ts +++ b/src/vs/base/test/browser/ui/list/listView.test.ts @@ -31,10 +31,10 @@ suite('ListView', function () { const listView = new ListView(element, delegate, [renderer]); listView.layout(200); - assert.equal(templatesCount, 0, 'no templates have been allocated'); + assert.strictEqual(templatesCount, 0, 'no templates have been allocated'); listView.splice(0, 0, range(100)); - assert.equal(templatesCount, 10, 'some templates have been allocated'); + assert.strictEqual(templatesCount, 10, 'some templates have been allocated'); listView.dispose(); - assert.equal(templatesCount, 0, 'all templates have been disposed'); + assert.strictEqual(templatesCount, 0, 'all templates have been disposed'); }); -}); \ No newline at end of file +}); diff --git a/src/vs/base/test/browser/ui/list/rangeMap.test.ts b/src/vs/base/test/browser/ui/list/rangeMap.test.ts index c92612c1..32f7f402 100644 --- a/src/vs/base/test/browser/ui/list/rangeMap.test.ts +++ b/src/vs/base/test/browser/ui/list/rangeMap.test.ts @@ -15,24 +15,24 @@ suite('RangeMap', () => { }); test('intersection', () => { - assert.deepEqual(Range.intersect({ start: 0, end: 0 }, { start: 0, end: 0 }), { start: 0, end: 0 }); - assert.deepEqual(Range.intersect({ start: 0, end: 0 }, { start: 5, end: 5 }), { start: 0, end: 0 }); - assert.deepEqual(Range.intersect({ start: 0, end: 1 }, { start: 5, end: 6 }), { start: 0, end: 0 }); - assert.deepEqual(Range.intersect({ start: 5, end: 6 }, { start: 0, end: 1 }), { start: 0, end: 0 }); - assert.deepEqual(Range.intersect({ start: 0, end: 5 }, { start: 2, end: 2 }), { start: 0, end: 0 }); - assert.deepEqual(Range.intersect({ start: 0, end: 1 }, { start: 0, end: 1 }), { start: 0, end: 1 }); - assert.deepEqual(Range.intersect({ start: 0, end: 10 }, { start: 0, end: 5 }), { start: 0, end: 5 }); - assert.deepEqual(Range.intersect({ start: 0, end: 5 }, { start: 0, end: 10 }), { start: 0, end: 5 }); - assert.deepEqual(Range.intersect({ start: 0, end: 10 }, { start: 5, end: 10 }), { start: 5, end: 10 }); - assert.deepEqual(Range.intersect({ start: 5, end: 10 }, { start: 0, end: 10 }), { start: 5, end: 10 }); - assert.deepEqual(Range.intersect({ start: 0, end: 10 }, { start: 2, end: 8 }), { start: 2, end: 8 }); - assert.deepEqual(Range.intersect({ start: 2, end: 8 }, { start: 0, end: 10 }), { start: 2, end: 8 }); - assert.deepEqual(Range.intersect({ start: 0, end: 10 }, { start: 5, end: 15 }), { start: 5, end: 10 }); - assert.deepEqual(Range.intersect({ start: 5, end: 15 }, { start: 0, end: 10 }), { start: 5, end: 10 }); + assert.deepStrictEqual(Range.intersect({ start: 0, end: 0 }, { start: 0, end: 0 }), { start: 0, end: 0 }); + assert.deepStrictEqual(Range.intersect({ start: 0, end: 0 }, { start: 5, end: 5 }), { start: 0, end: 0 }); + assert.deepStrictEqual(Range.intersect({ start: 0, end: 1 }, { start: 5, end: 6 }), { start: 0, end: 0 }); + assert.deepStrictEqual(Range.intersect({ start: 5, end: 6 }, { start: 0, end: 1 }), { start: 0, end: 0 }); + assert.deepStrictEqual(Range.intersect({ start: 0, end: 5 }, { start: 2, end: 2 }), { start: 0, end: 0 }); + assert.deepStrictEqual(Range.intersect({ start: 0, end: 1 }, { start: 0, end: 1 }), { start: 0, end: 1 }); + assert.deepStrictEqual(Range.intersect({ start: 0, end: 10 }, { start: 0, end: 5 }), { start: 0, end: 5 }); + assert.deepStrictEqual(Range.intersect({ start: 0, end: 5 }, { start: 0, end: 10 }), { start: 0, end: 5 }); + assert.deepStrictEqual(Range.intersect({ start: 0, end: 10 }, { start: 5, end: 10 }), { start: 5, end: 10 }); + assert.deepStrictEqual(Range.intersect({ start: 5, end: 10 }, { start: 0, end: 10 }), { start: 5, end: 10 }); + assert.deepStrictEqual(Range.intersect({ start: 0, end: 10 }, { start: 2, end: 8 }), { start: 2, end: 8 }); + assert.deepStrictEqual(Range.intersect({ start: 2, end: 8 }, { start: 0, end: 10 }), { start: 2, end: 8 }); + assert.deepStrictEqual(Range.intersect({ start: 0, end: 10 }, { start: 5, end: 15 }), { start: 5, end: 10 }); + assert.deepStrictEqual(Range.intersect({ start: 5, end: 15 }, { start: 0, end: 10 }), { start: 5, end: 10 }); }); test('multiIntersect', () => { - assert.deepEqual( + assert.deepStrictEqual( groupIntersect( { start: 0, end: 0 }, [{ range: { start: 0, end: 10 }, size: 1 }] @@ -40,7 +40,7 @@ suite('RangeMap', () => { [] ); - assert.deepEqual( + assert.deepStrictEqual( groupIntersect( { start: 10, end: 20 }, [{ range: { start: 0, end: 10 }, size: 1 }] @@ -48,7 +48,7 @@ suite('RangeMap', () => { [] ); - assert.deepEqual( + assert.deepStrictEqual( groupIntersect( { start: 2, end: 8 }, [{ range: { start: 0, end: 10 }, size: 1 }] @@ -56,7 +56,7 @@ suite('RangeMap', () => { [{ range: { start: 2, end: 8 }, size: 1 }] ); - assert.deepEqual( + assert.deepStrictEqual( groupIntersect( { start: 2, end: 8 }, [{ range: { start: 0, end: 10 }, size: 1 }, { range: { start: 10, end: 20 }, size: 5 }] @@ -64,7 +64,7 @@ suite('RangeMap', () => { [{ range: { start: 2, end: 8 }, size: 1 }] ); - assert.deepEqual( + assert.deepStrictEqual( groupIntersect( { start: 12, end: 18 }, [{ range: { start: 0, end: 10 }, size: 1 }, { range: { start: 10, end: 20 }, size: 5 }] @@ -72,7 +72,7 @@ suite('RangeMap', () => { [{ range: { start: 12, end: 18 }, size: 5 }] ); - assert.deepEqual( + assert.deepStrictEqual( groupIntersect( { start: 2, end: 18 }, [{ range: { start: 0, end: 10 }, size: 1 }, { range: { start: 10, end: 20 }, size: 5 }] @@ -80,7 +80,7 @@ suite('RangeMap', () => { [{ range: { start: 2, end: 10 }, size: 1 }, { range: { start: 10, end: 18 }, size: 5 }] ); - assert.deepEqual( + assert.deepStrictEqual( groupIntersect( { start: 2, end: 28 }, [{ range: { start: 0, end: 10 }, size: 1 }, { range: { start: 10, end: 20 }, size: 5 }, { range: { start: 20, end: 30 }, size: 10 }] @@ -90,14 +90,14 @@ suite('RangeMap', () => { }); test('consolidate', () => { - assert.deepEqual(consolidate([]), []); + assert.deepStrictEqual(consolidate([]), []); - assert.deepEqual( + assert.deepStrictEqual( consolidate([{ range: { start: 0, end: 10 }, size: 1 }]), [{ range: { start: 0, end: 10 }, size: 1 }] ); - assert.deepEqual( + assert.deepStrictEqual( consolidate([ { range: { start: 0, end: 10 }, size: 1 }, { range: { start: 10, end: 20 }, size: 1 } @@ -105,7 +105,7 @@ suite('RangeMap', () => { [{ range: { start: 0, end: 20 }, size: 1 }] ); - assert.deepEqual( + assert.deepStrictEqual( consolidate([ { range: { start: 0, end: 10 }, size: 1 }, { range: { start: 10, end: 20 }, size: 1 }, @@ -114,7 +114,7 @@ suite('RangeMap', () => { [{ range: { start: 0, end: 100 }, size: 1 }] ); - assert.deepEqual( + assert.deepStrictEqual( consolidate([ { range: { start: 0, end: 10 }, size: 1 }, { range: { start: 10, end: 20 }, size: 5 }, @@ -127,7 +127,7 @@ suite('RangeMap', () => { ] ); - assert.deepEqual( + assert.deepStrictEqual( consolidate([ { range: { start: 0, end: 10 }, size: 1 }, { range: { start: 10, end: 20 }, size: 2 }, @@ -141,8 +141,8 @@ suite('RangeMap', () => { }); test('empty', () => { - assert.equal(rangeMap.size, 0); - assert.equal(rangeMap.count, 0); + assert.strictEqual(rangeMap.size, 0); + assert.strictEqual(rangeMap.count, 0); }); const one = { size: 1 }; @@ -153,44 +153,44 @@ suite('RangeMap', () => { test('length & count', () => { rangeMap.splice(0, 0, [one]); - assert.equal(rangeMap.size, 1); - assert.equal(rangeMap.count, 1); + assert.strictEqual(rangeMap.size, 1); + assert.strictEqual(rangeMap.count, 1); }); test('length & count #2', () => { rangeMap.splice(0, 0, [one, one, one, one, one]); - assert.equal(rangeMap.size, 5); - assert.equal(rangeMap.count, 5); + assert.strictEqual(rangeMap.size, 5); + assert.strictEqual(rangeMap.count, 5); }); test('length & count #3', () => { rangeMap.splice(0, 0, [five]); - assert.equal(rangeMap.size, 5); - assert.equal(rangeMap.count, 1); + assert.strictEqual(rangeMap.size, 5); + assert.strictEqual(rangeMap.count, 1); }); test('length & count #4', () => { rangeMap.splice(0, 0, [five, five, five, five, five]); - assert.equal(rangeMap.size, 25); - assert.equal(rangeMap.count, 5); + assert.strictEqual(rangeMap.size, 25); + assert.strictEqual(rangeMap.count, 5); }); test('insert', () => { rangeMap.splice(0, 0, [five, five, five, five, five]); - assert.equal(rangeMap.size, 25); - assert.equal(rangeMap.count, 5); + assert.strictEqual(rangeMap.size, 25); + assert.strictEqual(rangeMap.count, 5); rangeMap.splice(0, 0, [five, five, five, five, five]); - assert.equal(rangeMap.size, 50); - assert.equal(rangeMap.count, 10); + assert.strictEqual(rangeMap.size, 50); + assert.strictEqual(rangeMap.count, 10); rangeMap.splice(5, 0, [ten, ten]); - assert.equal(rangeMap.size, 70); - assert.equal(rangeMap.count, 12); + assert.strictEqual(rangeMap.size, 70); + assert.strictEqual(rangeMap.count, 12); rangeMap.splice(12, 0, [{ size: 200 }]); - assert.equal(rangeMap.size, 270); - assert.equal(rangeMap.count, 13); + assert.strictEqual(rangeMap.size, 270); + assert.strictEqual(rangeMap.count, 13); }); test('delete', () => { @@ -198,45 +198,45 @@ suite('RangeMap', () => { five, five, five, five, five, five, five, five, five, five, five, five, five, five, five]); - assert.equal(rangeMap.size, 100); - assert.equal(rangeMap.count, 20); + assert.strictEqual(rangeMap.size, 100); + assert.strictEqual(rangeMap.count, 20); rangeMap.splice(10, 5); - assert.equal(rangeMap.size, 75); - assert.equal(rangeMap.count, 15); + assert.strictEqual(rangeMap.size, 75); + assert.strictEqual(rangeMap.count, 15); rangeMap.splice(0, 1); - assert.equal(rangeMap.size, 70); - assert.equal(rangeMap.count, 14); + assert.strictEqual(rangeMap.size, 70); + assert.strictEqual(rangeMap.count, 14); rangeMap.splice(1, 13); - assert.equal(rangeMap.size, 5); - assert.equal(rangeMap.count, 1); + assert.strictEqual(rangeMap.size, 5); + assert.strictEqual(rangeMap.count, 1); rangeMap.splice(1, 1); - assert.equal(rangeMap.size, 5); - assert.equal(rangeMap.count, 1); + assert.strictEqual(rangeMap.size, 5); + assert.strictEqual(rangeMap.count, 1); }); test('insert & delete', () => { - assert.equal(rangeMap.size, 0); - assert.equal(rangeMap.count, 0); + assert.strictEqual(rangeMap.size, 0); + assert.strictEqual(rangeMap.count, 0); rangeMap.splice(0, 0, [one]); - assert.equal(rangeMap.size, 1); - assert.equal(rangeMap.count, 1); + assert.strictEqual(rangeMap.size, 1); + assert.strictEqual(rangeMap.count, 1); rangeMap.splice(0, 1); - assert.equal(rangeMap.size, 0); - assert.equal(rangeMap.count, 0); + assert.strictEqual(rangeMap.size, 0); + assert.strictEqual(rangeMap.count, 0); }); test('insert & delete #2', () => { rangeMap.splice(0, 0, [one, one, one, one, one, one, one, one, one, one]); rangeMap.splice(2, 6); - assert.equal(rangeMap.count, 4); - assert.equal(rangeMap.size, 4); + assert.strictEqual(rangeMap.count, 4); + assert.strictEqual(rangeMap.size, 4); }); test('insert & delete #3', () => { @@ -245,8 +245,8 @@ suite('RangeMap', () => { two, two, two, two, two, two, two, two, two, two]); rangeMap.splice(8, 4); - assert.equal(rangeMap.count, 16); - assert.equal(rangeMap.size, 24); + assert.strictEqual(rangeMap.count, 16); + assert.strictEqual(rangeMap.size, 24); }); test('insert & delete #3', () => { @@ -255,91 +255,91 @@ suite('RangeMap', () => { two, two, two, two, two, two, two, two, two, two]); rangeMap.splice(5, 0, [three, three, three, three, three]); - assert.equal(rangeMap.count, 25); - assert.equal(rangeMap.size, 45); + assert.strictEqual(rangeMap.count, 25); + assert.strictEqual(rangeMap.size, 45); rangeMap.splice(4, 7); - assert.equal(rangeMap.count, 18); - assert.equal(rangeMap.size, 28); + assert.strictEqual(rangeMap.count, 18); + assert.strictEqual(rangeMap.size, 28); }); suite('indexAt, positionAt', () => { test('empty', () => { - assert.equal(rangeMap.indexAt(0), 0); - assert.equal(rangeMap.indexAt(10), 0); - assert.equal(rangeMap.indexAt(-1), -1); - assert.equal(rangeMap.positionAt(0), -1); - assert.equal(rangeMap.positionAt(10), -1); - assert.equal(rangeMap.positionAt(-1), -1); + assert.strictEqual(rangeMap.indexAt(0), 0); + assert.strictEqual(rangeMap.indexAt(10), 0); + assert.strictEqual(rangeMap.indexAt(-1), -1); + assert.strictEqual(rangeMap.positionAt(0), -1); + assert.strictEqual(rangeMap.positionAt(10), -1); + assert.strictEqual(rangeMap.positionAt(-1), -1); }); test('simple', () => { rangeMap.splice(0, 0, [one]); - assert.equal(rangeMap.indexAt(0), 0); - assert.equal(rangeMap.indexAt(1), 1); - assert.equal(rangeMap.positionAt(0), 0); - assert.equal(rangeMap.positionAt(1), -1); + assert.strictEqual(rangeMap.indexAt(0), 0); + assert.strictEqual(rangeMap.indexAt(1), 1); + assert.strictEqual(rangeMap.positionAt(0), 0); + assert.strictEqual(rangeMap.positionAt(1), -1); }); test('simple #2', () => { rangeMap.splice(0, 0, [ten]); - assert.equal(rangeMap.indexAt(0), 0); - assert.equal(rangeMap.indexAt(5), 0); - assert.equal(rangeMap.indexAt(9), 0); - assert.equal(rangeMap.indexAt(10), 1); - assert.equal(rangeMap.positionAt(0), 0); - assert.equal(rangeMap.positionAt(1), -1); + assert.strictEqual(rangeMap.indexAt(0), 0); + assert.strictEqual(rangeMap.indexAt(5), 0); + assert.strictEqual(rangeMap.indexAt(9), 0); + assert.strictEqual(rangeMap.indexAt(10), 1); + assert.strictEqual(rangeMap.positionAt(0), 0); + assert.strictEqual(rangeMap.positionAt(1), -1); }); test('insert', () => { rangeMap.splice(0, 0, [one, one, one, one, one, one, one, one, one, one]); - assert.equal(rangeMap.indexAt(0), 0); - assert.equal(rangeMap.indexAt(1), 1); - assert.equal(rangeMap.indexAt(5), 5); - assert.equal(rangeMap.indexAt(9), 9); - assert.equal(rangeMap.indexAt(10), 10); - assert.equal(rangeMap.indexAt(11), 10); + assert.strictEqual(rangeMap.indexAt(0), 0); + assert.strictEqual(rangeMap.indexAt(1), 1); + assert.strictEqual(rangeMap.indexAt(5), 5); + assert.strictEqual(rangeMap.indexAt(9), 9); + assert.strictEqual(rangeMap.indexAt(10), 10); + assert.strictEqual(rangeMap.indexAt(11), 10); rangeMap.splice(10, 0, [one, one, one, one, one, one, one, one, one, one]); - assert.equal(rangeMap.indexAt(10), 10); - assert.equal(rangeMap.indexAt(19), 19); - assert.equal(rangeMap.indexAt(20), 20); - assert.equal(rangeMap.indexAt(21), 20); - assert.equal(rangeMap.positionAt(0), 0); - assert.equal(rangeMap.positionAt(1), 1); - assert.equal(rangeMap.positionAt(19), 19); - assert.equal(rangeMap.positionAt(20), -1); + assert.strictEqual(rangeMap.indexAt(10), 10); + assert.strictEqual(rangeMap.indexAt(19), 19); + assert.strictEqual(rangeMap.indexAt(20), 20); + assert.strictEqual(rangeMap.indexAt(21), 20); + assert.strictEqual(rangeMap.positionAt(0), 0); + assert.strictEqual(rangeMap.positionAt(1), 1); + assert.strictEqual(rangeMap.positionAt(19), 19); + assert.strictEqual(rangeMap.positionAt(20), -1); }); test('delete', () => { rangeMap.splice(0, 0, [one, one, one, one, one, one, one, one, one, one]); rangeMap.splice(2, 6); - assert.equal(rangeMap.indexAt(0), 0); - assert.equal(rangeMap.indexAt(1), 1); - assert.equal(rangeMap.indexAt(3), 3); - assert.equal(rangeMap.indexAt(4), 4); - assert.equal(rangeMap.indexAt(5), 4); - assert.equal(rangeMap.positionAt(0), 0); - assert.equal(rangeMap.positionAt(1), 1); - assert.equal(rangeMap.positionAt(3), 3); - assert.equal(rangeMap.positionAt(4), -1); + assert.strictEqual(rangeMap.indexAt(0), 0); + assert.strictEqual(rangeMap.indexAt(1), 1); + assert.strictEqual(rangeMap.indexAt(3), 3); + assert.strictEqual(rangeMap.indexAt(4), 4); + assert.strictEqual(rangeMap.indexAt(5), 4); + assert.strictEqual(rangeMap.positionAt(0), 0); + assert.strictEqual(rangeMap.positionAt(1), 1); + assert.strictEqual(rangeMap.positionAt(3), 3); + assert.strictEqual(rangeMap.positionAt(4), -1); }); test('delete #2', () => { rangeMap.splice(0, 0, [ten, ten, ten, ten, ten, ten, ten, ten, ten, ten]); rangeMap.splice(2, 6); - assert.equal(rangeMap.indexAt(0), 0); - assert.equal(rangeMap.indexAt(1), 0); - assert.equal(rangeMap.indexAt(30), 3); - assert.equal(rangeMap.indexAt(40), 4); - assert.equal(rangeMap.indexAt(50), 4); - assert.equal(rangeMap.positionAt(0), 0); - assert.equal(rangeMap.positionAt(1), 10); - assert.equal(rangeMap.positionAt(2), 20); - assert.equal(rangeMap.positionAt(3), 30); - assert.equal(rangeMap.positionAt(4), -1); + assert.strictEqual(rangeMap.indexAt(0), 0); + assert.strictEqual(rangeMap.indexAt(1), 0); + assert.strictEqual(rangeMap.indexAt(30), 3); + assert.strictEqual(rangeMap.indexAt(40), 4); + assert.strictEqual(rangeMap.indexAt(50), 4); + assert.strictEqual(rangeMap.positionAt(0), 0); + assert.strictEqual(rangeMap.positionAt(1), 10); + assert.strictEqual(rangeMap.positionAt(2), 20); + assert.strictEqual(rangeMap.positionAt(3), 30); + assert.strictEqual(rangeMap.positionAt(4), -1); }); }); }); diff --git a/src/vs/base/test/browser/ui/menu/menubar.test.ts b/src/vs/base/test/browser/ui/menu/menubar.test.ts index 8bbeddcf..0a4688ab 100644 --- a/src/vs/base/test/browser/ui/menu/menubar.test.ts +++ b/src/vs/base/test/browser/ui/menu/menubar.test.ts @@ -56,7 +56,7 @@ function validateMenuBarItem(menubar: MenuBar, menubarContainer: HTMLElement, la assert(titleDiv !== null, `Title div not found for ${readableLabel} button.`); const mnem = getMnemonicFromTitleDiv(titleDiv!); - assert.equal(mnem, mnemonic, 'Mnemonic not correct'); + assert.strictEqual(mnem, mnemonic, 'Mnemonic not correct'); } suite('Menubar', () => { @@ -78,4 +78,4 @@ suite('Menubar', () => { test('Chinese File menu renders mnemonics', function () { validateMenuBarItem(menubar, container, '文件(&F)', '文件', 'F'); }); -}); \ No newline at end of file +}); diff --git a/src/vs/base/test/browser/ui/splitview/splitview.test.ts b/src/vs/base/test/browser/ui/splitview/splitview.test.ts index c36a51fd..3f37167c 100644 --- a/src/vs/base/test/browser/ui/splitview/splitview.test.ts +++ b/src/vs/base/test/browser/ui/splitview/splitview.test.ts @@ -77,7 +77,7 @@ suite('Splitview', () => { test('empty splitview has empty DOM', () => { const splitview = new SplitView(container); - assert.equal(container.firstElementChild!.firstElementChild!.childElementCount, 0, 'split view should be empty'); + assert.strictEqual(container.firstElementChild!.firstElementChild!.childElementCount, 0, 'split view should be empty'); splitview.dispose(); }); @@ -92,34 +92,34 @@ suite('Splitview', () => { splitview.addView(view3, 20); let viewQuery = container.querySelectorAll('.monaco-split-view2 > .monaco-scrollable-element > .split-view-container > .split-view-view'); - assert.equal(viewQuery.length, 3, 'split view should have 3 views'); + assert.strictEqual(viewQuery.length, 3, 'split view should have 3 views'); let sashQuery = container.querySelectorAll('.monaco-split-view2 > .sash-container > .monaco-sash'); - assert.equal(sashQuery.length, 2, 'split view should have 2 sashes'); + assert.strictEqual(sashQuery.length, 2, 'split view should have 2 sashes'); splitview.removeView(2); viewQuery = container.querySelectorAll('.monaco-split-view2 > .monaco-scrollable-element > .split-view-container > .split-view-view'); - assert.equal(viewQuery.length, 2, 'split view should have 2 views'); + assert.strictEqual(viewQuery.length, 2, 'split view should have 2 views'); sashQuery = container.querySelectorAll('.monaco-split-view2 > .sash-container > .monaco-sash'); - assert.equal(sashQuery.length, 1, 'split view should have 1 sash'); + assert.strictEqual(sashQuery.length, 1, 'split view should have 1 sash'); splitview.removeView(0); viewQuery = container.querySelectorAll('.monaco-split-view2 > .monaco-scrollable-element > .split-view-container > .split-view-view'); - assert.equal(viewQuery.length, 1, 'split view should have 1 view'); + assert.strictEqual(viewQuery.length, 1, 'split view should have 1 view'); sashQuery = container.querySelectorAll('.monaco-split-view2 > .sash-container > .monaco-sash'); - assert.equal(sashQuery.length, 0, 'split view should have no sashes'); + assert.strictEqual(sashQuery.length, 0, 'split view should have no sashes'); splitview.removeView(0); viewQuery = container.querySelectorAll('.monaco-split-view2 > .monaco-scrollable-element > .split-view-container > .split-view-view'); - assert.equal(viewQuery.length, 0, 'split view should have no views'); + assert.strictEqual(viewQuery.length, 0, 'split view should have no views'); sashQuery = container.querySelectorAll('.monaco-split-view2 > .sash-container > .monaco-sash'); - assert.equal(sashQuery.length, 0, 'split view should have no sashes'); + assert.strictEqual(sashQuery.length, 0, 'split view should have no sashes'); splitview.dispose(); view1.dispose(); @@ -138,7 +138,7 @@ suite('Splitview', () => { splitview.addView(view, 20); - assert.equal(view.size, 20, 'view has right size'); + assert.strictEqual(view.size, 20, 'view has right size'); assert(didLayout, 'layout is called'); assert(didLayout, 'render is called'); @@ -154,22 +154,22 @@ suite('Splitview', () => { splitview.layout(200); splitview.addView(view, 20); - assert.equal(view.size, 200, 'view is stretched'); + assert.strictEqual(view.size, 200, 'view is stretched'); splitview.layout(200); - assert.equal(view.size, 200, 'view stayed the same'); + assert.strictEqual(view.size, 200, 'view stayed the same'); splitview.layout(100); - assert.equal(view.size, 100, 'view is collapsed'); + assert.strictEqual(view.size, 100, 'view is collapsed'); splitview.layout(20); - assert.equal(view.size, 20, 'view is collapsed'); + assert.strictEqual(view.size, 20, 'view is collapsed'); splitview.layout(10); - assert.equal(view.size, 20, 'view is clamped'); + assert.strictEqual(view.size, 20, 'view is clamped'); splitview.layout(200); - assert.equal(view.size, 200, 'view is stretched'); + assert.strictEqual(view.size, 200, 'view is stretched'); splitview.dispose(); view.dispose(); @@ -186,27 +186,27 @@ suite('Splitview', () => { splitview.addView(view2, 20); splitview.addView(view3, 20); - assert.equal(view1.size, 160, 'view1 is stretched'); - assert.equal(view2.size, 20, 'view2 size is 20'); - assert.equal(view3.size, 20, 'view3 size is 20'); + assert.strictEqual(view1.size, 160, 'view1 is stretched'); + assert.strictEqual(view2.size, 20, 'view2 size is 20'); + assert.strictEqual(view3.size, 20, 'view3 size is 20'); splitview.resizeView(1, 40); - assert.equal(view1.size, 140, 'view1 is collapsed'); - assert.equal(view2.size, 40, 'view2 is stretched'); - assert.equal(view3.size, 20, 'view3 stays the same'); + assert.strictEqual(view1.size, 140, 'view1 is collapsed'); + assert.strictEqual(view2.size, 40, 'view2 is stretched'); + assert.strictEqual(view3.size, 20, 'view3 stays the same'); splitview.resizeView(0, 70); - assert.equal(view1.size, 70, 'view1 is collapsed'); - assert.equal(view2.size, 40, 'view2 stays the same'); - assert.equal(view3.size, 90, 'view3 is stretched'); + assert.strictEqual(view1.size, 70, 'view1 is collapsed'); + assert.strictEqual(view2.size, 40, 'view2 stays the same'); + assert.strictEqual(view3.size, 90, 'view3 is stretched'); splitview.resizeView(2, 40); - assert.equal(view1.size, 70, 'view1 stays the same'); - assert.equal(view2.size, 90, 'view2 is collapsed'); - assert.equal(view3.size, 40, 'view3 is stretched'); + assert.strictEqual(view1.size, 70, 'view1 stays the same'); + assert.strictEqual(view2.size, 90, 'view2 is collapsed'); + assert.strictEqual(view3.size, 40, 'view3 is stretched'); splitview.dispose(); view3.dispose(); @@ -225,34 +225,34 @@ suite('Splitview', () => { splitview.addView(view2, 20); splitview.addView(view3, 20); - assert.equal(view1.size, 160, 'view1 is stretched'); - assert.equal(view2.size, 20, 'view2 size is 20'); - assert.equal(view3.size, 20, 'view3 size is 20'); + assert.strictEqual(view1.size, 160, 'view1 is stretched'); + assert.strictEqual(view2.size, 20, 'view2 size is 20'); + assert.strictEqual(view3.size, 20, 'view3 size is 20'); view1.maximumSize = 20; - assert.equal(view1.size, 20, 'view1 is collapsed'); - assert.equal(view2.size, 20, 'view2 stays the same'); - assert.equal(view3.size, 160, 'view3 is stretched'); + assert.strictEqual(view1.size, 20, 'view1 is collapsed'); + assert.strictEqual(view2.size, 20, 'view2 stays the same'); + assert.strictEqual(view3.size, 160, 'view3 is stretched'); view3.maximumSize = 40; - assert.equal(view1.size, 20, 'view1 stays the same'); - assert.equal(view2.size, 140, 'view2 is stretched'); - assert.equal(view3.size, 40, 'view3 is collapsed'); + assert.strictEqual(view1.size, 20, 'view1 stays the same'); + assert.strictEqual(view2.size, 140, 'view2 is stretched'); + assert.strictEqual(view3.size, 40, 'view3 is collapsed'); view2.maximumSize = 200; - assert.equal(view1.size, 20, 'view1 stays the same'); - assert.equal(view2.size, 140, 'view2 stays the same'); - assert.equal(view3.size, 40, 'view3 stays the same'); + assert.strictEqual(view1.size, 20, 'view1 stays the same'); + assert.strictEqual(view2.size, 140, 'view2 stays the same'); + assert.strictEqual(view3.size, 40, 'view3 stays the same'); view3.maximumSize = Number.POSITIVE_INFINITY; view3.minimumSize = 100; - assert.equal(view1.size, 20, 'view1 is collapsed'); - assert.equal(view2.size, 80, 'view2 is collapsed'); - assert.equal(view3.size, 100, 'view3 is stretched'); + assert.strictEqual(view1.size, 20, 'view1 is collapsed'); + assert.strictEqual(view2.size, 80, 'view2 is collapsed'); + assert.strictEqual(view3.size, 100, 'view3 is stretched'); splitview.dispose(); view3.dispose(); @@ -272,41 +272,41 @@ suite('Splitview', () => { splitview.addView(view3, Sizing.Distribute); let sashes = getSashes(splitview); - assert.equal(sashes.length, 2, 'there are two sashes'); - assert.equal(sashes[0].state, SashState.Enabled, 'first sash is enabled'); - assert.equal(sashes[1].state, SashState.Enabled, 'second sash is enabled'); + assert.strictEqual(sashes.length, 2, 'there are two sashes'); + assert.strictEqual(sashes[0].state, SashState.Enabled, 'first sash is enabled'); + assert.strictEqual(sashes[1].state, SashState.Enabled, 'second sash is enabled'); splitview.layout(60); - assert.equal(sashes[0].state, SashState.Disabled, 'first sash is disabled'); - assert.equal(sashes[1].state, SashState.Disabled, 'second sash is disabled'); + assert.strictEqual(sashes[0].state, SashState.Disabled, 'first sash is disabled'); + assert.strictEqual(sashes[1].state, SashState.Disabled, 'second sash is disabled'); splitview.layout(20); - assert.equal(sashes[0].state, SashState.Disabled, 'first sash is disabled'); - assert.equal(sashes[1].state, SashState.Disabled, 'second sash is disabled'); + assert.strictEqual(sashes[0].state, SashState.Disabled, 'first sash is disabled'); + assert.strictEqual(sashes[1].state, SashState.Disabled, 'second sash is disabled'); splitview.layout(200); - assert.equal(sashes[0].state, SashState.Enabled, 'first sash is enabled'); - assert.equal(sashes[1].state, SashState.Enabled, 'second sash is enabled'); + assert.strictEqual(sashes[0].state, SashState.Enabled, 'first sash is enabled'); + assert.strictEqual(sashes[1].state, SashState.Enabled, 'second sash is enabled'); view1.maximumSize = 20; - assert.equal(sashes[0].state, SashState.Disabled, 'first sash is disabled'); - assert.equal(sashes[1].state, SashState.Enabled, 'second sash is enabled'); + assert.strictEqual(sashes[0].state, SashState.Disabled, 'first sash is disabled'); + assert.strictEqual(sashes[1].state, SashState.Enabled, 'second sash is enabled'); view2.maximumSize = 20; - assert.equal(sashes[0].state, SashState.Disabled, 'first sash is disabled'); - assert.equal(sashes[1].state, SashState.Disabled, 'second sash is disabled'); + assert.strictEqual(sashes[0].state, SashState.Disabled, 'first sash is disabled'); + assert.strictEqual(sashes[1].state, SashState.Disabled, 'second sash is disabled'); view1.maximumSize = 300; - assert.equal(sashes[0].state, SashState.Minimum, 'first sash is enabled'); - assert.equal(sashes[1].state, SashState.Minimum, 'second sash is enabled'); + assert.strictEqual(sashes[0].state, SashState.Minimum, 'first sash is enabled'); + assert.strictEqual(sashes[1].state, SashState.Minimum, 'second sash is enabled'); view2.maximumSize = 200; - assert.equal(sashes[0].state, SashState.Minimum, 'first sash is enabled'); - assert.equal(sashes[1].state, SashState.Minimum, 'second sash is enabled'); + assert.strictEqual(sashes[0].state, SashState.Minimum, 'first sash is enabled'); + assert.strictEqual(sashes[1].state, SashState.Minimum, 'second sash is enabled'); splitview.resizeView(0, 40); - assert.equal(sashes[0].state, SashState.Enabled, 'first sash is enabled'); - assert.equal(sashes[1].state, SashState.Enabled, 'second sash is enabled'); + assert.strictEqual(sashes[0].state, SashState.Enabled, 'first sash is enabled'); + assert.strictEqual(sashes[1].state, SashState.Enabled, 'second sash is enabled'); splitview.dispose(); view3.dispose(); @@ -322,7 +322,7 @@ suite('Splitview', () => { splitview.layout(986); splitview.addView(view1, 142, 0); - assert.equal(view1.size, 986, 'first view is stretched'); + assert.strictEqual(view1.size, 986, 'first view is stretched'); view2.onDidGetElement(() => { assert.throws(() => splitview.resizeView(1, 922)); @@ -330,13 +330,13 @@ suite('Splitview', () => { }); splitview.addView(view2, 66, 0); - assert.equal(view2.size, 66, 'second view is fixed'); - assert.equal(view1.size, 986 - 66, 'first view is collapsed'); + assert.strictEqual(view2.size, 66, 'second view is fixed'); + assert.strictEqual(view1.size, 986 - 66, 'first view is collapsed'); const viewContainers = container.querySelectorAll('.split-view-view'); - assert.equal(viewContainers.length, 2, 'there are two view containers'); - assert.equal((viewContainers.item(0) as HTMLElement).style.height, '66px', 'second view container is 66px'); - assert.equal((viewContainers.item(1) as HTMLElement).style.height, `${986 - 66}px`, 'first view container is 66px'); + assert.strictEqual(viewContainers.length, 2, 'there are two view containers'); + assert.strictEqual((viewContainers.item(0) as HTMLElement).style.height, '66px', 'second view container is 66px'); + assert.strictEqual((viewContainers.item(1) as HTMLElement).style.height, `${986 - 66}px`, 'first view container is 66px'); splitview.dispose(); view2.dispose(); @@ -351,16 +351,16 @@ suite('Splitview', () => { splitview.layout(200); splitview.addView(view1, Sizing.Distribute); - assert.equal(view1.size, 200); + assert.strictEqual(view1.size, 200); splitview.addView(view2, 50); - assert.deepEqual([view1.size, view2.size], [150, 50]); + assert.deepStrictEqual([view1.size, view2.size], [150, 50]); splitview.addView(view3, Sizing.Distribute); - assert.deepEqual([view1.size, view2.size, view3.size], [66, 66, 68]); + assert.deepStrictEqual([view1.size, view2.size, view3.size], [66, 66, 68]); splitview.removeView(1, Sizing.Distribute); - assert.deepEqual([view1.size, view3.size], [100, 100]); + assert.deepStrictEqual([view1.size, view3.size], [100, 100]); splitview.dispose(); view3.dispose(); @@ -379,7 +379,7 @@ suite('Splitview', () => { splitview.addView(view3, 25); splitview.layout(200); - assert.deepEqual([view1.size, view2.size, view3.size], [67, 67, 66]); + assert.deepStrictEqual([view1.size, view2.size, view3.size], [67, 67, 66]); splitview.dispose(); view3.dispose(); @@ -395,13 +395,13 @@ suite('Splitview', () => { splitview.layout(200); splitview.addView(view1, Sizing.Distribute); - assert.equal(view1.size, 200); + assert.strictEqual(view1.size, 200); splitview.addView(view2, Sizing.Split(0)); - assert.deepEqual([view1.size, view2.size], [100, 100]); + assert.deepStrictEqual([view1.size, view2.size], [100, 100]); splitview.addView(view3, Sizing.Split(1)); - assert.deepEqual([view1.size, view2.size, view3.size], [100, 50, 50]); + assert.deepStrictEqual([view1.size, view2.size, view3.size], [100, 50, 50]); splitview.dispose(); view3.dispose(); @@ -417,13 +417,13 @@ suite('Splitview', () => { splitview.layout(200); splitview.addView(view1, Sizing.Distribute); - assert.equal(view1.size, 200); + assert.strictEqual(view1.size, 200); splitview.addView(view2, Sizing.Split(0)); - assert.deepEqual([view1.size, view2.size], [100, 100]); + assert.deepStrictEqual([view1.size, view2.size], [100, 100]); splitview.addView(view3, Sizing.Split(0)); - assert.deepEqual([view1.size, view2.size, view3.size], [50, 100, 50]); + assert.deepStrictEqual([view1.size, view2.size, view3.size], [50, 100, 50]); splitview.dispose(); view3.dispose(); @@ -439,10 +439,10 @@ suite('Splitview', () => { splitview.addView(view1, Sizing.Distribute); splitview.addView(view2, Sizing.Distribute); - assert.deepEqual([view1.size, view2.size], [100, 100]); + assert.deepStrictEqual([view1.size, view2.size], [100, 100]); splitview.layout(100); - assert.deepEqual([view1.size, view2.size], [50, 50]); + assert.deepStrictEqual([view1.size, view2.size], [50, 50]); splitview.dispose(); view2.dispose(); @@ -457,10 +457,10 @@ suite('Splitview', () => { splitview.addView(view1, Sizing.Distribute); splitview.addView(view2, Sizing.Distribute); - assert.deepEqual([view1.size, view2.size], [100, 100]); + assert.deepStrictEqual([view1.size, view2.size], [100, 100]); splitview.layout(100); - assert.deepEqual([view1.size, view2.size], [80, 20]); + assert.deepStrictEqual([view1.size, view2.size], [80, 20]); splitview.dispose(); view2.dispose(); @@ -477,19 +477,19 @@ suite('Splitview', () => { splitview.addView(view1, Sizing.Distribute); splitview.addView(view2, Sizing.Distribute); splitview.addView(view3, Sizing.Distribute); - assert.deepEqual([view1.size, view2.size, view3.size], [66, 68, 66]); + assert.deepStrictEqual([view1.size, view2.size, view3.size], [66, 68, 66]); splitview.layout(180); - assert.deepEqual([view1.size, view2.size, view3.size], [66, 48, 66]); + assert.deepStrictEqual([view1.size, view2.size, view3.size], [66, 48, 66]); splitview.layout(124); - assert.deepEqual([view1.size, view2.size, view3.size], [66, 20, 38]); + assert.deepStrictEqual([view1.size, view2.size, view3.size], [66, 20, 38]); splitview.layout(60); - assert.deepEqual([view1.size, view2.size, view3.size], [20, 20, 20]); + assert.deepStrictEqual([view1.size, view2.size, view3.size], [20, 20, 20]); splitview.layout(200); - assert.deepEqual([view1.size, view2.size, view3.size], [20, 160, 20]); + assert.deepStrictEqual([view1.size, view2.size, view3.size], [20, 160, 20]); splitview.dispose(); view3.dispose(); @@ -507,19 +507,19 @@ suite('Splitview', () => { splitview.addView(view1, Sizing.Distribute); splitview.addView(view2, Sizing.Distribute); splitview.addView(view3, Sizing.Distribute); - assert.deepEqual([view1.size, view2.size, view3.size], [66, 68, 66]); + assert.deepStrictEqual([view1.size, view2.size, view3.size], [66, 68, 66]); splitview.layout(180); - assert.deepEqual([view1.size, view2.size, view3.size], [66, 48, 66]); + assert.deepStrictEqual([view1.size, view2.size, view3.size], [66, 48, 66]); splitview.layout(132); - assert.deepEqual([view1.size, view2.size, view3.size], [46, 20, 66]); + assert.deepStrictEqual([view1.size, view2.size, view3.size], [46, 20, 66]); splitview.layout(60); - assert.deepEqual([view1.size, view2.size, view3.size], [20, 20, 20]); + assert.deepStrictEqual([view1.size, view2.size, view3.size], [20, 20, 20]); splitview.layout(200); - assert.deepEqual([view1.size, view2.size, view3.size], [20, 160, 20]); + assert.deepStrictEqual([view1.size, view2.size, view3.size], [20, 160, 20]); splitview.dispose(); view3.dispose(); @@ -539,7 +539,7 @@ suite('Splitview', () => { splitview.addView(view3, Sizing.Distribute); splitview.layout(200, 100); - assert.deepEqual([view1.orthogonalSize, view2.orthogonalSize, view3.orthogonalSize], [100, 100, 100]); + assert.deepStrictEqual([view1.orthogonalSize, view2.orthogonalSize, view3.orthogonalSize], [100, 100, 100]); splitview.dispose(); view3.dispose(); diff --git a/src/vs/base/test/browser/ui/tree/asyncDataTree.test.ts b/src/vs/base/test/browser/ui/tree/asyncDataTree.test.ts index 8d66a9b3..445eebef 100644 --- a/src/vs/base/test/browser/ui/tree/asyncDataTree.test.ts +++ b/src/vs/base/test/browser/ui/tree/asyncDataTree.test.ts @@ -97,10 +97,10 @@ suite('AsyncDataTree', function () { const tree = new AsyncDataTree('test', container, new VirtualDelegate(), [new Renderer()], new DataSource(), { identityProvider: new IdentityProvider() }); tree.layout(200); - assert.equal(container.querySelectorAll('.monaco-list-row').length, 0); + assert.strictEqual(container.querySelectorAll('.monaco-list-row').length, 0); await tree.setInput(model.root); - assert.equal(container.querySelectorAll('.monaco-list-row').length, 1); + assert.strictEqual(container.querySelectorAll('.monaco-list-row').length, 1); let twistie = container.querySelector('.monaco-list-row:first-child .monaco-tl-twistie') as HTMLElement; assert(!twistie.classList.contains('collapsible')); assert(!twistie.classList.contains('collapsed')); @@ -112,14 +112,14 @@ suite('AsyncDataTree', function () { ]; await tree.updateChildren(model.root); - assert.equal(container.querySelectorAll('.monaco-list-row').length, 1); + assert.strictEqual(container.querySelectorAll('.monaco-list-row').length, 1); await tree.expand(model.get('a')); - assert.equal(container.querySelectorAll('.monaco-list-row').length, 4); + assert.strictEqual(container.querySelectorAll('.monaco-list-row').length, 4); model.get('a').children = []; await tree.updateChildren(model.root); - assert.equal(container.querySelectorAll('.monaco-list-row').length, 1); + assert.strictEqual(container.querySelectorAll('.monaco-list-row').length, 1); }); test('issue #68648', async () => { @@ -316,16 +316,16 @@ suite('AsyncDataTree', function () { const pUpdateChildrenA = tree.updateChildren(model.get('a')); const pExpandA = tree.expand(model.get('a')); - assert.equal(calls.length, 1, 'expand(a) still hasn\'t called getChildren(a)'); + assert.strictEqual(calls.length, 1, 'expand(a) still hasn\'t called getChildren(a)'); calls.pop()!(); - assert.equal(calls.length, 0, 'no pending getChildren calls'); + assert.strictEqual(calls.length, 0, 'no pending getChildren calls'); await pUpdateChildrenA; - assert.equal(calls.length, 0, 'expand(a) should not have forced a second refresh'); + assert.strictEqual(calls.length, 0, 'expand(a) should not have forced a second refresh'); const result = await pExpandA; - assert.equal(result, true, 'expand(a) should be done'); + assert.strictEqual(result, true, 'expand(a) should be done'); }); test('issue #80098 - first expand should call getChildren', async () => { @@ -358,16 +358,16 @@ suite('AsyncDataTree', function () { await pSetInput; const pExpandA = tree.expand(model.get('a')); - assert.equal(calls.length, 1, 'expand(a) should\'ve called getChildren(a)'); + assert.strictEqual(calls.length, 1, 'expand(a) should\'ve called getChildren(a)'); let race = await Promise.race([pExpandA.then(() => 'expand'), timeout(1).then(() => 'timeout')]); - assert.equal(race, 'timeout', 'expand(a) should not be yet done'); + assert.strictEqual(race, 'timeout', 'expand(a) should not be yet done'); calls.pop()!(); - assert.equal(calls.length, 0, 'no pending getChildren calls'); + assert.strictEqual(calls.length, 0, 'no pending getChildren calls'); race = await Promise.race([pExpandA.then(() => 'expand'), timeout(1).then(() => 'timeout')]); - assert.equal(race, 'expand', 'expand(a) should now be done'); + assert.strictEqual(race, 'expand', 'expand(a) should now be done'); }); test('issue #78388 - tree should react to hasChildren toggles', async () => { @@ -383,7 +383,7 @@ suite('AsyncDataTree', function () { tree.layout(200); await tree.setInput(model.root); - assert.equal(container.querySelectorAll('.monaco-list-row').length, 1); + assert.strictEqual(container.querySelectorAll('.monaco-list-row').length, 1); let twistie = container.querySelector('.monaco-list-row:first-child .monaco-tl-twistie') as HTMLElement; assert(!twistie.classList.contains('collapsible')); @@ -391,14 +391,14 @@ suite('AsyncDataTree', function () { model.get('a').children = [{ id: 'aa' }]; await tree.updateChildren(model.get('a'), false); - assert.equal(container.querySelectorAll('.monaco-list-row').length, 1); + assert.strictEqual(container.querySelectorAll('.monaco-list-row').length, 1); twistie = container.querySelector('.monaco-list-row:first-child .monaco-tl-twistie') as HTMLElement; assert(twistie.classList.contains('collapsible')); assert(twistie.classList.contains('collapsed')); model.get('a').children = []; await tree.updateChildren(model.get('a'), false); - assert.equal(container.querySelectorAll('.monaco-list-row').length, 1); + assert.strictEqual(container.querySelectorAll('.monaco-list-row').length, 1); twistie = container.querySelector('.monaco-list-row:first-child .monaco-tl-twistie') as HTMLElement; assert(!twistie.classList.contains('collapsible')); assert(!twistie.classList.contains('collapsed')); @@ -422,7 +422,7 @@ suite('AsyncDataTree', function () { await tree.setInput(model.root); await tree.expand(model.get('a')); - assert.deepEqual(Array.from(container.querySelectorAll('.monaco-list-row')).map(e => e.textContent), ['a', 'b1']); + assert.deepStrictEqual(Array.from(container.querySelectorAll('.monaco-list-row')).map(e => e.textContent), ['a', 'b1']); const a = model.get('a'); const b = model.get('b'); @@ -433,6 +433,6 @@ suite('AsyncDataTree', function () { tree.updateChildren(b, true, true) ]); - assert.deepEqual(Array.from(container.querySelectorAll('.monaco-list-row')).map(e => e.textContent), ['a', 'b2']); + assert.deepStrictEqual(Array.from(container.querySelectorAll('.monaco-list-row')).map(e => e.textContent), ['a', 'b2']); }); }); diff --git a/src/vs/base/test/browser/ui/tree/compressedObjectTreeModel.test.ts b/src/vs/base/test/browser/ui/tree/compressedObjectTreeModel.test.ts index 498b22e1..2e32a087 100644 --- a/src/vs/base/test/browser/ui/tree/compressedObjectTreeModel.test.ts +++ b/src/vs/base/test/browser/ui/tree/compressedObjectTreeModel.test.ts @@ -39,8 +39,8 @@ suite('CompressedObjectTree', function () { const compressed: IResolvedCompressedTreeElement> = { element: { elements: [1], incompressible: false } }; - assert.deepEqual(resolve(compress(decompressed)), compressed); - assert.deepEqual(resolve(decompress(compressed)), decompressed); + assert.deepStrictEqual(resolve(compress(decompressed)), compressed); + assert.deepStrictEqual(resolve(decompress(compressed)), decompressed); }); test('no compression', function () { @@ -61,8 +61,8 @@ suite('CompressedObjectTree', function () { ] }; - assert.deepEqual(resolve(compress(decompressed)), compressed); - assert.deepEqual(resolve(decompress(compressed)), decompressed); + assert.deepStrictEqual(resolve(compress(decompressed)), compressed); + assert.deepStrictEqual(resolve(decompress(compressed)), decompressed); }); test('single hierarchy', function () { @@ -84,8 +84,8 @@ suite('CompressedObjectTree', function () { element: { elements: [1, 11, 111, 1111], incompressible: false } }; - assert.deepEqual(resolve(compress(decompressed)), compressed); - assert.deepEqual(resolve(decompress(compressed)), decompressed); + assert.deepStrictEqual(resolve(compress(decompressed)), compressed); + assert.deepStrictEqual(resolve(decompress(compressed)), decompressed); }); test('deep compression', function () { @@ -116,8 +116,8 @@ suite('CompressedObjectTree', function () { ] }; - assert.deepEqual(resolve(compress(decompressed)), compressed); - assert.deepEqual(resolve(decompress(compressed)), decompressed); + assert.deepStrictEqual(resolve(compress(decompressed)), compressed); + assert.deepStrictEqual(resolve(decompress(compressed)), decompressed); }); test('double deep compression', function () { @@ -166,8 +166,8 @@ suite('CompressedObjectTree', function () { ] }; - assert.deepEqual(resolve(compress(decompressed)), compressed); - assert.deepEqual(resolve(decompress(compressed)), decompressed); + assert.deepStrictEqual(resolve(compress(decompressed)), compressed); + assert.deepStrictEqual(resolve(decompress(compressed)), decompressed); }); test('incompressible leaf', function () { @@ -192,8 +192,8 @@ suite('CompressedObjectTree', function () { ] }; - assert.deepEqual(resolve(compress(decompressed)), compressed); - assert.deepEqual(resolve(decompress(compressed)), decompressed); + assert.deepStrictEqual(resolve(compress(decompressed)), compressed); + assert.deepStrictEqual(resolve(decompress(compressed)), decompressed); }); test('incompressible branch', function () { @@ -218,8 +218,8 @@ suite('CompressedObjectTree', function () { ] }; - assert.deepEqual(resolve(compress(decompressed)), compressed); - assert.deepEqual(resolve(decompress(compressed)), decompressed); + assert.deepStrictEqual(resolve(compress(decompressed)), compressed); + assert.deepStrictEqual(resolve(decompress(compressed)), decompressed); }); test('incompressible chain', function () { @@ -249,8 +249,8 @@ suite('CompressedObjectTree', function () { ] }; - assert.deepEqual(resolve(compress(decompressed)), compressed); - assert.deepEqual(resolve(decompress(compressed)), decompressed); + assert.deepStrictEqual(resolve(compress(decompressed)), compressed); + assert.deepStrictEqual(resolve(decompress(compressed)), decompressed); }); test('incompressible tree', function () { @@ -285,8 +285,8 @@ suite('CompressedObjectTree', function () { ] }; - assert.deepEqual(resolve(compress(decompressed)), compressed); - assert.deepEqual(resolve(decompress(compressed)), decompressed); + assert.deepStrictEqual(resolve(compress(decompressed)), compressed); + assert.deepStrictEqual(resolve(decompress(compressed)), decompressed); }); }); @@ -319,8 +319,8 @@ suite('CompressedObjectTree', function () { const list: ITreeNode>[] = []; const model = new CompressedObjectTreeModel('test', toList(list)); assert(model); - assert.equal(list.length, 0); - assert.equal(model.size, 0); + assert.strictEqual(list.length, 0); + assert.strictEqual(model.size, 0); }); test('flat', () => withSmartSplice(options => { @@ -333,8 +333,8 @@ suite('CompressedObjectTree', function () { { element: 2 } ], options); - assert.deepEqual(toArray(list), [[0], [1], [2]]); - assert.equal(model.size, 3); + assert.deepStrictEqual(toArray(list), [[0], [1], [2]]); + assert.strictEqual(model.size, 3); model.setChildren(null, [ { element: 3 }, @@ -342,12 +342,12 @@ suite('CompressedObjectTree', function () { { element: 5 }, ], options); - assert.deepEqual(toArray(list), [[3], [4], [5]]); - assert.equal(model.size, 3); + assert.deepStrictEqual(toArray(list), [[3], [4], [5]]); + assert.strictEqual(model.size, 3); model.setChildren(null, [], options); - assert.deepEqual(toArray(list), []); - assert.equal(model.size, 0); + assert.deepStrictEqual(toArray(list), []); + assert.strictEqual(model.size, 0); })); test('nested', () => withSmartSplice(options => { @@ -366,24 +366,24 @@ suite('CompressedObjectTree', function () { { element: 2 } ], options); - assert.deepEqual(toArray(list), [[0], [10], [11], [12], [1], [2]]); - assert.equal(model.size, 6); + assert.deepStrictEqual(toArray(list), [[0], [10], [11], [12], [1], [2]]); + assert.strictEqual(model.size, 6); model.setChildren(12, [ { element: 120 }, { element: 121 } ], options); - assert.deepEqual(toArray(list), [[0], [10], [11], [12], [120], [121], [1], [2]]); - assert.equal(model.size, 8); + assert.deepStrictEqual(toArray(list), [[0], [10], [11], [12], [120], [121], [1], [2]]); + assert.strictEqual(model.size, 8); model.setChildren(0, [], options); - assert.deepEqual(toArray(list), [[0], [1], [2]]); - assert.equal(model.size, 3); + assert.deepStrictEqual(toArray(list), [[0], [1], [2]]); + assert.strictEqual(model.size, 3); model.setChildren(null, [], options); - assert.deepEqual(toArray(list), []); - assert.equal(model.size, 0); + assert.deepStrictEqual(toArray(list), []); + assert.strictEqual(model.size, 0); })); test('compressed', () => withSmartSplice(options => { @@ -404,8 +404,8 @@ suite('CompressedObjectTree', function () { } ], options); - assert.deepEqual(toArray(list), [[1, 11, 111], [1111], [1112], [1113]]); - assert.equal(model.size, 6); + assert.deepStrictEqual(toArray(list), [[1, 11, 111], [1111], [1112], [1113]]); + assert.strictEqual(model.size, 6); model.setChildren(11, [ { element: 111 }, @@ -413,30 +413,30 @@ suite('CompressedObjectTree', function () { { element: 113 }, ], options); - assert.deepEqual(toArray(list), [[1, 11], [111], [112], [113]]); - assert.equal(model.size, 5); + assert.deepStrictEqual(toArray(list), [[1, 11], [111], [112], [113]]); + assert.strictEqual(model.size, 5); model.setChildren(113, [ { element: 1131 } ], options); - assert.deepEqual(toArray(list), [[1, 11], [111], [112], [113, 1131]]); - assert.equal(model.size, 6); + assert.deepStrictEqual(toArray(list), [[1, 11], [111], [112], [113, 1131]]); + assert.strictEqual(model.size, 6); model.setChildren(1131, [ { element: 1132 } ], options); - assert.deepEqual(toArray(list), [[1, 11], [111], [112], [113, 1131, 1132]]); - assert.equal(model.size, 7); + assert.deepStrictEqual(toArray(list), [[1, 11], [111], [112], [113, 1131, 1132]]); + assert.strictEqual(model.size, 7); model.setChildren(1131, [ { element: 1132 }, { element: 1133 }, ], options); - assert.deepEqual(toArray(list), [[1, 11], [111], [112], [113, 1131], [1132], [1133]]); - assert.equal(model.size, 8); + assert.deepStrictEqual(toArray(list), [[1, 11], [111], [112], [113, 1131], [1132], [1133]]); + assert.strictEqual(model.size, 8); })); }); }); diff --git a/src/vs/base/test/browser/ui/tree/dataTree.test.ts b/src/vs/base/test/browser/ui/tree/dataTree.test.ts index 015a6783..a87a919a 100644 --- a/src/vs/base/test/browser/ui/tree/dataTree.test.ts +++ b/src/vs/base/test/browser/ui/tree/dataTree.test.ts @@ -77,20 +77,20 @@ suite('DataTree', function () { tree.setInput(root); let navigator = tree.navigate(); - assert.equal(navigator.next()!.value, 0); - assert.equal(navigator.next()!.value, 10); - assert.equal(navigator.next()!.value, 11); - assert.equal(navigator.next()!.value, 12); - assert.equal(navigator.next()!.value, 1); - assert.equal(navigator.next()!.value, 2); - assert.equal(navigator.next()!, null); + assert.strictEqual(navigator.next()!.value, 0); + assert.strictEqual(navigator.next()!.value, 10); + assert.strictEqual(navigator.next()!.value, 11); + assert.strictEqual(navigator.next()!.value, 12); + assert.strictEqual(navigator.next()!.value, 1); + assert.strictEqual(navigator.next()!.value, 2); + assert.strictEqual(navigator.next()!, null); tree.collapse(root.children![0]); navigator = tree.navigate(); - assert.equal(navigator.next()!.value, 0); - assert.equal(navigator.next()!.value, 1); - assert.equal(navigator.next()!.value, 2); - assert.equal(navigator.next()!, null); + assert.strictEqual(navigator.next()!.value, 0); + assert.strictEqual(navigator.next()!.value, 1); + assert.strictEqual(navigator.next()!.value, 2); + assert.strictEqual(navigator.next()!, null); tree.setSelection([root.children![1]]); tree.setFocus([root.children![2]]); @@ -98,36 +98,36 @@ suite('DataTree', function () { tree.setInput(empty); tree.setInput(root); navigator = tree.navigate(); - assert.equal(navigator.next()!.value, 0); - assert.equal(navigator.next()!.value, 10); - assert.equal(navigator.next()!.value, 11); - assert.equal(navigator.next()!.value, 12); - assert.equal(navigator.next()!.value, 1); - assert.equal(navigator.next()!.value, 2); - assert.equal(navigator.next()!, null); + assert.strictEqual(navigator.next()!.value, 0); + assert.strictEqual(navigator.next()!.value, 10); + assert.strictEqual(navigator.next()!.value, 11); + assert.strictEqual(navigator.next()!.value, 12); + assert.strictEqual(navigator.next()!.value, 1); + assert.strictEqual(navigator.next()!.value, 2); + assert.strictEqual(navigator.next()!, null); - assert.deepEqual(tree.getSelection(), []); - assert.deepEqual(tree.getFocus(), []); + assert.deepStrictEqual(tree.getSelection(), []); + assert.deepStrictEqual(tree.getFocus(), []); }); test('view state can be preserved', () => { tree.setInput(root); let navigator = tree.navigate(); - assert.equal(navigator.next()!.value, 0); - assert.equal(navigator.next()!.value, 10); - assert.equal(navigator.next()!.value, 11); - assert.equal(navigator.next()!.value, 12); - assert.equal(navigator.next()!.value, 1); - assert.equal(navigator.next()!.value, 2); - assert.equal(navigator.next()!, null); + assert.strictEqual(navigator.next()!.value, 0); + assert.strictEqual(navigator.next()!.value, 10); + assert.strictEqual(navigator.next()!.value, 11); + assert.strictEqual(navigator.next()!.value, 12); + assert.strictEqual(navigator.next()!.value, 1); + assert.strictEqual(navigator.next()!.value, 2); + assert.strictEqual(navigator.next()!, null); tree.collapse(root.children![0]); navigator = tree.navigate(); - assert.equal(navigator.next()!.value, 0); - assert.equal(navigator.next()!.value, 1); - assert.equal(navigator.next()!.value, 2); - assert.equal(navigator.next()!, null); + assert.strictEqual(navigator.next()!.value, 0); + assert.strictEqual(navigator.next()!.value, 1); + assert.strictEqual(navigator.next()!.value, 2); + assert.strictEqual(navigator.next()!, null); tree.setSelection([root.children![1]]); tree.setFocus([root.children![2]]); @@ -137,12 +137,12 @@ suite('DataTree', function () { tree.setInput(empty); tree.setInput(root, viewState); navigator = tree.navigate(); - assert.equal(navigator.next()!.value, 0); - assert.equal(navigator.next()!.value, 1); - assert.equal(navigator.next()!.value, 2); - assert.equal(navigator.next()!, null); + assert.strictEqual(navigator.next()!.value, 0); + assert.strictEqual(navigator.next()!.value, 1); + assert.strictEqual(navigator.next()!.value, 2); + assert.strictEqual(navigator.next()!, null); - assert.deepEqual(tree.getSelection(), [root.children![1]]); - assert.deepEqual(tree.getFocus(), [root.children![2]]); + assert.deepStrictEqual(tree.getSelection(), [root.children![1]]); + assert.deepStrictEqual(tree.getFocus(), [root.children![2]]); }); }); diff --git a/src/vs/base/test/browser/ui/tree/indexTreeModel.test.ts b/src/vs/base/test/browser/ui/tree/indexTreeModel.test.ts index a96007cb..47b67ec9 100644 --- a/src/vs/base/test/browser/ui/tree/indexTreeModel.test.ts +++ b/src/vs/base/test/browser/ui/tree/indexTreeModel.test.ts @@ -42,7 +42,7 @@ suite('IndexTreeModel', () => { const list: ITreeNode[] = []; const model = new IndexTreeModel('test', toList(list), -1); assert(model); - assert.equal(list.length, 0); + assert.strictEqual(list.length, 0); }); test('insert', () => withSmartSplice(options => { @@ -55,16 +55,16 @@ suite('IndexTreeModel', () => { { element: 2 } ], options); - assert.deepEqual(list.length, 3); - assert.deepEqual(list[0].element, 0); - assert.deepEqual(list[0].collapsed, false); - assert.deepEqual(list[0].depth, 1); - assert.deepEqual(list[1].element, 1); - assert.deepEqual(list[1].collapsed, false); - assert.deepEqual(list[1].depth, 1); - assert.deepEqual(list[2].element, 2); - assert.deepEqual(list[2].collapsed, false); - assert.deepEqual(list[2].depth, 1); + assert.deepStrictEqual(list.length, 3); + assert.deepStrictEqual(list[0].element, 0); + assert.deepStrictEqual(list[0].collapsed, false); + assert.deepStrictEqual(list[0].depth, 1); + assert.deepStrictEqual(list[1].element, 1); + assert.deepStrictEqual(list[1].collapsed, false); + assert.deepStrictEqual(list[1].depth, 1); + assert.deepStrictEqual(list[2].element, 2); + assert.deepStrictEqual(list[2].collapsed, false); + assert.deepStrictEqual(list[2].depth, 1); })); test('deep insert', () => withSmartSplice(options => { @@ -83,25 +83,25 @@ suite('IndexTreeModel', () => { { element: 2 } ]); - assert.deepEqual(list.length, 6); - assert.deepEqual(list[0].element, 0); - assert.deepEqual(list[0].collapsed, false); - assert.deepEqual(list[0].depth, 1); - assert.deepEqual(list[1].element, 10); - assert.deepEqual(list[1].collapsed, false); - assert.deepEqual(list[1].depth, 2); - assert.deepEqual(list[2].element, 11); - assert.deepEqual(list[2].collapsed, false); - assert.deepEqual(list[2].depth, 2); - assert.deepEqual(list[3].element, 12); - assert.deepEqual(list[3].collapsed, false); - assert.deepEqual(list[3].depth, 2); - assert.deepEqual(list[4].element, 1); - assert.deepEqual(list[4].collapsed, false); - assert.deepEqual(list[4].depth, 1); - assert.deepEqual(list[5].element, 2); - assert.deepEqual(list[5].collapsed, false); - assert.deepEqual(list[5].depth, 1); + assert.deepStrictEqual(list.length, 6); + assert.deepStrictEqual(list[0].element, 0); + assert.deepStrictEqual(list[0].collapsed, false); + assert.deepStrictEqual(list[0].depth, 1); + assert.deepStrictEqual(list[1].element, 10); + assert.deepStrictEqual(list[1].collapsed, false); + assert.deepStrictEqual(list[1].depth, 2); + assert.deepStrictEqual(list[2].element, 11); + assert.deepStrictEqual(list[2].collapsed, false); + assert.deepStrictEqual(list[2].depth, 2); + assert.deepStrictEqual(list[3].element, 12); + assert.deepStrictEqual(list[3].collapsed, false); + assert.deepStrictEqual(list[3].depth, 2); + assert.deepStrictEqual(list[4].element, 1); + assert.deepStrictEqual(list[4].collapsed, false); + assert.deepStrictEqual(list[4].depth, 1); + assert.deepStrictEqual(list[5].element, 2); + assert.deepStrictEqual(list[5].collapsed, false); + assert.deepStrictEqual(list[5].depth, 1); })); test('deep insert collapsed', () => withSmartSplice(options => { @@ -120,16 +120,16 @@ suite('IndexTreeModel', () => { { element: 2 } ], options); - assert.deepEqual(list.length, 3); - assert.deepEqual(list[0].element, 0); - assert.deepEqual(list[0].collapsed, true); - assert.deepEqual(list[0].depth, 1); - assert.deepEqual(list[1].element, 1); - assert.deepEqual(list[1].collapsed, false); - assert.deepEqual(list[1].depth, 1); - assert.deepEqual(list[2].element, 2); - assert.deepEqual(list[2].collapsed, false); - assert.deepEqual(list[2].depth, 1); + assert.deepStrictEqual(list.length, 3); + assert.deepStrictEqual(list[0].element, 0); + assert.deepStrictEqual(list[0].collapsed, true); + assert.deepStrictEqual(list[0].depth, 1); + assert.deepStrictEqual(list[1].element, 1); + assert.deepStrictEqual(list[1].collapsed, false); + assert.deepStrictEqual(list[1].depth, 1); + assert.deepStrictEqual(list[2].element, 2); + assert.deepStrictEqual(list[2].collapsed, false); + assert.deepStrictEqual(list[2].depth, 1); })); test('delete', () => withSmartSplice(options => { @@ -142,19 +142,19 @@ suite('IndexTreeModel', () => { { element: 2 } ], options); - assert.deepEqual(list.length, 3); + assert.deepStrictEqual(list.length, 3); model.splice([1], 1, undefined, options); - assert.deepEqual(list.length, 2); - assert.deepEqual(list[0].element, 0); - assert.deepEqual(list[0].collapsed, false); - assert.deepEqual(list[0].depth, 1); - assert.deepEqual(list[1].element, 2); - assert.deepEqual(list[1].collapsed, false); - assert.deepEqual(list[1].depth, 1); + assert.deepStrictEqual(list.length, 2); + assert.deepStrictEqual(list[0].element, 0); + assert.deepStrictEqual(list[0].collapsed, false); + assert.deepStrictEqual(list[0].depth, 1); + assert.deepStrictEqual(list[1].element, 2); + assert.deepStrictEqual(list[1].collapsed, false); + assert.deepStrictEqual(list[1].depth, 1); model.splice([0], 2, undefined, options); - assert.deepEqual(list.length, 0); + assert.deepStrictEqual(list.length, 0); })); test('nested delete', () => withSmartSplice(options => { @@ -173,22 +173,22 @@ suite('IndexTreeModel', () => { { element: 2 } ], options); - assert.deepEqual(list.length, 6); + assert.deepStrictEqual(list.length, 6); model.splice([1], 2, undefined, options); - assert.deepEqual(list.length, 4); - assert.deepEqual(list[0].element, 0); - assert.deepEqual(list[0].collapsed, false); - assert.deepEqual(list[0].depth, 1); - assert.deepEqual(list[1].element, 10); - assert.deepEqual(list[1].collapsed, false); - assert.deepEqual(list[1].depth, 2); - assert.deepEqual(list[2].element, 11); - assert.deepEqual(list[2].collapsed, false); - assert.deepEqual(list[2].depth, 2); - assert.deepEqual(list[3].element, 12); - assert.deepEqual(list[3].collapsed, false); - assert.deepEqual(list[3].depth, 2); + assert.deepStrictEqual(list.length, 4); + assert.deepStrictEqual(list[0].element, 0); + assert.deepStrictEqual(list[0].collapsed, false); + assert.deepStrictEqual(list[0].depth, 1); + assert.deepStrictEqual(list[1].element, 10); + assert.deepStrictEqual(list[1].collapsed, false); + assert.deepStrictEqual(list[1].depth, 2); + assert.deepStrictEqual(list[2].element, 11); + assert.deepStrictEqual(list[2].collapsed, false); + assert.deepStrictEqual(list[2].depth, 2); + assert.deepStrictEqual(list[3].element, 12); + assert.deepStrictEqual(list[3].collapsed, false); + assert.deepStrictEqual(list[3].depth, 2); })); test('deep delete', () => withSmartSplice(options => { @@ -207,16 +207,16 @@ suite('IndexTreeModel', () => { { element: 2 } ], options); - assert.deepEqual(list.length, 6); + assert.deepStrictEqual(list.length, 6); model.splice([0], 1, undefined, options); - assert.deepEqual(list.length, 2); - assert.deepEqual(list[0].element, 1); - assert.deepEqual(list[0].collapsed, false); - assert.deepEqual(list[0].depth, 1); - assert.deepEqual(list[1].element, 2); - assert.deepEqual(list[1].collapsed, false); - assert.deepEqual(list[1].depth, 1); + assert.deepStrictEqual(list.length, 2); + assert.deepStrictEqual(list[0].element, 1); + assert.deepStrictEqual(list[0].collapsed, false); + assert.deepStrictEqual(list[0].depth, 1); + assert.deepStrictEqual(list[1].element, 2); + assert.deepStrictEqual(list[1].collapsed, false); + assert.deepStrictEqual(list[1].depth, 1); })); test('smart splice deep', () => { @@ -269,13 +269,13 @@ suite('IndexTreeModel', () => { { element: 2 } ], options); - assert.deepEqual(list.length, 3); + assert.deepStrictEqual(list.length, 3); model.splice([0, 1], 1, undefined, options); - assert.deepEqual(list.length, 3); + assert.deepStrictEqual(list.length, 3); model.splice([0, 0], 2, undefined, options); - assert.deepEqual(list.length, 3); + assert.deepStrictEqual(list.length, 3); })); test('collapse', () => withSmartSplice(options => { @@ -294,19 +294,19 @@ suite('IndexTreeModel', () => { { element: 2 } ], options); - assert.deepEqual(list.length, 6); + assert.deepStrictEqual(list.length, 6); model.setCollapsed([0], true); - assert.deepEqual(list.length, 3); - assert.deepEqual(list[0].element, 0); - assert.deepEqual(list[0].collapsed, true); - assert.deepEqual(list[0].depth, 1); - assert.deepEqual(list[1].element, 1); - assert.deepEqual(list[1].collapsed, false); - assert.deepEqual(list[1].depth, 1); - assert.deepEqual(list[2].element, 2); - assert.deepEqual(list[2].collapsed, false); - assert.deepEqual(list[2].depth, 1); + assert.deepStrictEqual(list.length, 3); + assert.deepStrictEqual(list[0].element, 0); + assert.deepStrictEqual(list[0].collapsed, true); + assert.deepStrictEqual(list[0].depth, 1); + assert.deepStrictEqual(list[1].element, 1); + assert.deepStrictEqual(list[1].collapsed, false); + assert.deepStrictEqual(list[1].depth, 1); + assert.deepStrictEqual(list[2].element, 2); + assert.deepStrictEqual(list[2].collapsed, false); + assert.deepStrictEqual(list[2].depth, 1); })); test('updates collapsible', () => withSmartSplice(options => { @@ -325,12 +325,18 @@ suite('IndexTreeModel', () => { assert.strictEqual(list[1].collapsible, false); model.splice([0, 0], 1, [], options); - assert.strictEqual(list[0].collapsible, false); - assert.strictEqual(list[1], undefined); + { + const [first, second] = list; + assert.strictEqual(first.collapsible, false); + assert.strictEqual(second, undefined); + } model.splice([0, 0], 0, [{ element: 1 }], options); - assert.strictEqual(list[0].collapsible, true); - assert.strictEqual(list[1].collapsible, false); + { + const [first, second] = list; + assert.strictEqual(first.collapsible, true); + assert.strictEqual(second.collapsible, false); + } })); test('expand', () => withSmartSplice(options => { @@ -349,28 +355,28 @@ suite('IndexTreeModel', () => { { element: 2 } ], options); - assert.deepEqual(list.length, 3); + assert.deepStrictEqual(list.length, 3); model.setCollapsed([0], false); - assert.deepEqual(list.length, 6); - assert.deepEqual(list[0].element, 0); - assert.deepEqual(list[0].collapsed, false); - assert.deepEqual(list[0].depth, 1); - assert.deepEqual(list[1].element, 10); - assert.deepEqual(list[1].collapsed, false); - assert.deepEqual(list[1].depth, 2); - assert.deepEqual(list[2].element, 11); - assert.deepEqual(list[2].collapsed, false); - assert.deepEqual(list[2].depth, 2); - assert.deepEqual(list[3].element, 12); - assert.deepEqual(list[3].collapsed, false); - assert.deepEqual(list[3].depth, 2); - assert.deepEqual(list[4].element, 1); - assert.deepEqual(list[4].collapsed, false); - assert.deepEqual(list[4].depth, 1); - assert.deepEqual(list[5].element, 2); - assert.deepEqual(list[5].collapsed, false); - assert.deepEqual(list[5].depth, 1); + assert.deepStrictEqual(list.length, 6); + assert.deepStrictEqual(list[0].element, 0); + assert.deepStrictEqual(list[0].collapsed, false); + assert.deepStrictEqual(list[0].depth, 1); + assert.deepStrictEqual(list[1].element, 10); + assert.deepStrictEqual(list[1].collapsed, false); + assert.deepStrictEqual(list[1].depth, 2); + assert.deepStrictEqual(list[2].element, 11); + assert.deepStrictEqual(list[2].collapsed, false); + assert.deepStrictEqual(list[2].depth, 2); + assert.deepStrictEqual(list[3].element, 12); + assert.deepStrictEqual(list[3].collapsed, false); + assert.deepStrictEqual(list[3].depth, 2); + assert.deepStrictEqual(list[4].element, 1); + assert.deepStrictEqual(list[4].collapsed, false); + assert.deepStrictEqual(list[4].depth, 1); + assert.deepStrictEqual(list[5].element, 2); + assert.deepStrictEqual(list[5].collapsed, false); + assert.deepStrictEqual(list[5].depth, 1); })); test('smart diff consistency', () => { @@ -437,16 +443,16 @@ suite('IndexTreeModel', () => { } ]); - assert.deepEqual(list.length, 5); - assert.deepEqual(toArray(list), [1, 11, 111, 2, 21]); + assert.deepStrictEqual(list.length, 5); + assert.deepStrictEqual(toArray(list), [1, 11, 111, 2, 21]); model.setCollapsed([0, 0], true); - assert.deepEqual(list.length, 4); - assert.deepEqual(toArray(list), [1, 11, 2, 21]); + assert.deepStrictEqual(list.length, 4); + assert.deepStrictEqual(toArray(list), [1, 11, 2, 21]); model.setCollapsed([1], true); - assert.deepEqual(list.length, 3); - assert.deepEqual(toArray(list), [1, 11, 2]); + assert.deepStrictEqual(list.length, 3); + assert.deepStrictEqual(toArray(list), [1, 11, 2]); }); test('setCollapsible', () => { @@ -461,55 +467,55 @@ suite('IndexTreeModel', () => { } ]); - assert.deepEqual(list.length, 2); + assert.deepStrictEqual(list.length, 2); model.setCollapsible([0], false); - assert.deepEqual(list.length, 2); - assert.deepEqual(list[0].element, 0); - assert.deepEqual(list[0].collapsible, false); - assert.deepEqual(list[0].collapsed, false); - assert.deepEqual(list[1].element, 10); - assert.deepEqual(list[1].collapsible, false); - assert.deepEqual(list[1].collapsed, false); + assert.deepStrictEqual(list.length, 2); + assert.deepStrictEqual(list[0].element, 0); + assert.deepStrictEqual(list[0].collapsible, false); + assert.deepStrictEqual(list[0].collapsed, false); + assert.deepStrictEqual(list[1].element, 10); + assert.deepStrictEqual(list[1].collapsible, false); + assert.deepStrictEqual(list[1].collapsed, false); - assert.deepEqual(model.setCollapsed([0], true), false); - assert.deepEqual(list[0].element, 0); - assert.deepEqual(list[0].collapsible, false); - assert.deepEqual(list[0].collapsed, false); - assert.deepEqual(list[1].element, 10); - assert.deepEqual(list[1].collapsible, false); - assert.deepEqual(list[1].collapsed, false); + assert.deepStrictEqual(model.setCollapsed([0], true), false); + assert.deepStrictEqual(list[0].element, 0); + assert.deepStrictEqual(list[0].collapsible, false); + assert.deepStrictEqual(list[0].collapsed, false); + assert.deepStrictEqual(list[1].element, 10); + assert.deepStrictEqual(list[1].collapsible, false); + assert.deepStrictEqual(list[1].collapsed, false); - assert.deepEqual(model.setCollapsed([0], false), false); - assert.deepEqual(list[0].element, 0); - assert.deepEqual(list[0].collapsible, false); - assert.deepEqual(list[0].collapsed, false); - assert.deepEqual(list[1].element, 10); - assert.deepEqual(list[1].collapsible, false); - assert.deepEqual(list[1].collapsed, false); + assert.deepStrictEqual(model.setCollapsed([0], false), false); + assert.deepStrictEqual(list[0].element, 0); + assert.deepStrictEqual(list[0].collapsible, false); + assert.deepStrictEqual(list[0].collapsed, false); + assert.deepStrictEqual(list[1].element, 10); + assert.deepStrictEqual(list[1].collapsible, false); + assert.deepStrictEqual(list[1].collapsed, false); model.setCollapsible([0], true); - assert.deepEqual(list.length, 2); - assert.deepEqual(list[0].element, 0); - assert.deepEqual(list[0].collapsible, true); - assert.deepEqual(list[0].collapsed, false); - assert.deepEqual(list[1].element, 10); - assert.deepEqual(list[1].collapsible, false); - assert.deepEqual(list[1].collapsed, false); + assert.deepStrictEqual(list.length, 2); + assert.deepStrictEqual(list[0].element, 0); + assert.deepStrictEqual(list[0].collapsible, true); + assert.deepStrictEqual(list[0].collapsed, false); + assert.deepStrictEqual(list[1].element, 10); + assert.deepStrictEqual(list[1].collapsible, false); + assert.deepStrictEqual(list[1].collapsed, false); - assert.deepEqual(model.setCollapsed([0], true), true); - assert.deepEqual(list.length, 1); - assert.deepEqual(list[0].element, 0); - assert.deepEqual(list[0].collapsible, true); - assert.deepEqual(list[0].collapsed, true); + assert.deepStrictEqual(model.setCollapsed([0], true), true); + assert.deepStrictEqual(list.length, 1); + assert.deepStrictEqual(list[0].element, 0); + assert.deepStrictEqual(list[0].collapsible, true); + assert.deepStrictEqual(list[0].collapsed, true); - assert.deepEqual(model.setCollapsed([0], false), true); - assert.deepEqual(list[0].element, 0); - assert.deepEqual(list[0].collapsible, true); - assert.deepEqual(list[0].collapsed, false); - assert.deepEqual(list[1].element, 10); - assert.deepEqual(list[1].collapsible, false); - assert.deepEqual(list[1].collapsed, false); + assert.deepStrictEqual(model.setCollapsed([0], false), true); + assert.deepStrictEqual(list[0].element, 0); + assert.deepStrictEqual(list[0].collapsible, true); + assert.deepStrictEqual(list[0].collapsed, false); + assert.deepStrictEqual(list[1].element, 10); + assert.deepStrictEqual(list[1].collapsible, false); + assert.deepStrictEqual(list[1].collapsed, false); }); test('simple filter', () => { @@ -536,14 +542,14 @@ suite('IndexTreeModel', () => { } ]); - assert.deepEqual(list.length, 4); - assert.deepEqual(toArray(list), [0, 2, 4, 6]); + assert.deepStrictEqual(list.length, 4); + assert.deepStrictEqual(toArray(list), [0, 2, 4, 6]); model.setCollapsed([0], true); - assert.deepEqual(toArray(list), [0]); + assert.deepStrictEqual(toArray(list), [0]); model.setCollapsed([0], false); - assert.deepEqual(toArray(list), [0, 2, 4, 6]); + assert.deepStrictEqual(toArray(list), [0, 2, 4, 6]); }); test('recursive filter on initial model', () => { @@ -565,7 +571,7 @@ suite('IndexTreeModel', () => { } ]); - assert.deepEqual(toArray(list), []); + assert.deepStrictEqual(toArray(list), []); }); test('refilter', () => { @@ -593,18 +599,18 @@ suite('IndexTreeModel', () => { }, ]); - assert.deepEqual(toArray(list), [0, 1, 2, 3, 4, 5, 6, 7]); + assert.deepStrictEqual(toArray(list), [0, 1, 2, 3, 4, 5, 6, 7]); model.refilter(); - assert.deepEqual(toArray(list), [0, 1, 2, 3, 4, 5, 6, 7]); + assert.deepStrictEqual(toArray(list), [0, 1, 2, 3, 4, 5, 6, 7]); shouldFilter = true; model.refilter(); - assert.deepEqual(toArray(list), [0, 2, 4, 6]); + assert.deepStrictEqual(toArray(list), [0, 2, 4, 6]); shouldFilter = false; model.refilter(); - assert.deepEqual(toArray(list), [0, 1, 2, 3, 4, 5, 6, 7]); + assert.deepStrictEqual(toArray(list), [0, 1, 2, 3, 4, 5, 6, 7]); }); test('recursive filter', () => { @@ -640,17 +646,17 @@ suite('IndexTreeModel', () => { }, ]); - assert.deepEqual(list.length, 10); + assert.deepStrictEqual(list.length, 10); query = /build/; model.refilter(); - assert.deepEqual(toArray(list), ['vscode', '.build', 'github', 'build.js', 'build']); + assert.deepStrictEqual(toArray(list), ['vscode', '.build', 'github', 'build.js', 'build']); model.setCollapsed([0], true); - assert.deepEqual(toArray(list), ['vscode']); + assert.deepStrictEqual(toArray(list), ['vscode']); model.setCollapsed([0], false); - assert.deepEqual(toArray(list), ['vscode', '.build', 'github', 'build.js', 'build']); + assert.deepStrictEqual(toArray(list), ['vscode', '.build', 'github', 'build.js', 'build']); }); test('recursive filter with collapse', () => { @@ -686,17 +692,17 @@ suite('IndexTreeModel', () => { }, ]); - assert.deepEqual(list.length, 10); + assert.deepStrictEqual(list.length, 10); query = /gulp/; model.refilter(); - assert.deepEqual(toArray(list), ['vscode', 'build', 'gulpfile.js']); + assert.deepStrictEqual(toArray(list), ['vscode', 'build', 'gulpfile.js']); model.setCollapsed([0, 3], true); - assert.deepEqual(toArray(list), ['vscode', 'build']); + assert.deepStrictEqual(toArray(list), ['vscode', 'build']); model.setCollapsed([0], true); - assert.deepEqual(toArray(list), ['vscode']); + assert.deepStrictEqual(toArray(list), ['vscode']); }); test('recursive filter while collapsed', () => { @@ -732,24 +738,24 @@ suite('IndexTreeModel', () => { }, ]); - assert.deepEqual(toArray(list), ['vscode']); + assert.deepStrictEqual(toArray(list), ['vscode']); query = /gulp/; model.refilter(); - assert.deepEqual(toArray(list), ['vscode']); + assert.deepStrictEqual(toArray(list), ['vscode']); model.setCollapsed([0], false); - assert.deepEqual(toArray(list), ['vscode', 'build', 'gulpfile.js']); + assert.deepStrictEqual(toArray(list), ['vscode', 'build', 'gulpfile.js']); model.setCollapsed([0], true); - assert.deepEqual(toArray(list), ['vscode']); + assert.deepStrictEqual(toArray(list), ['vscode']); query = new RegExp(''); model.refilter(); - assert.deepEqual(toArray(list), ['vscode']); + assert.deepStrictEqual(toArray(list), ['vscode']); model.setCollapsed([0], false); - assert.deepEqual(list.length, 10); + assert.deepStrictEqual(list.length, 10); }); suite('getNodeLocation', () => { @@ -770,12 +776,12 @@ suite('IndexTreeModel', () => { { element: 2 } ]); - assert.deepEqual(model.getNodeLocation(list[0]), [0]); - assert.deepEqual(model.getNodeLocation(list[1]), [0, 0]); - assert.deepEqual(model.getNodeLocation(list[2]), [0, 1]); - assert.deepEqual(model.getNodeLocation(list[3]), [0, 2]); - assert.deepEqual(model.getNodeLocation(list[4]), [1]); - assert.deepEqual(model.getNodeLocation(list[5]), [2]); + assert.deepStrictEqual(model.getNodeLocation(list[0]), [0]); + assert.deepStrictEqual(model.getNodeLocation(list[1]), [0, 0]); + assert.deepStrictEqual(model.getNodeLocation(list[2]), [0, 1]); + assert.deepStrictEqual(model.getNodeLocation(list[3]), [0, 2]); + assert.deepStrictEqual(model.getNodeLocation(list[4]), [1]); + assert.deepStrictEqual(model.getNodeLocation(list[5]), [2]); }); test('with filter', () => { @@ -802,10 +808,10 @@ suite('IndexTreeModel', () => { } ]); - assert.deepEqual(model.getNodeLocation(list[0]), [0]); - assert.deepEqual(model.getNodeLocation(list[1]), [0, 1]); - assert.deepEqual(model.getNodeLocation(list[2]), [0, 3]); - assert.deepEqual(model.getNodeLocation(list[3]), [0, 5]); + assert.deepStrictEqual(model.getNodeLocation(list[0]), [0]); + assert.deepStrictEqual(model.getNodeLocation(list[1]), [0, 1]); + assert.deepStrictEqual(model.getNodeLocation(list[2]), [0, 3]); + assert.deepStrictEqual(model.getNodeLocation(list[3]), [0, 5]); }); }); @@ -826,21 +832,21 @@ suite('IndexTreeModel', () => { { element: 'platinum' } ]); - assert.deepEqual(toArray(list), ['silver', 'gold', 'platinum']); + assert.deepStrictEqual(toArray(list), ['silver', 'gold', 'platinum']); query = /platinum/; model.refilter(); - assert.deepEqual(toArray(list), ['platinum']); + assert.deepStrictEqual(toArray(list), ['platinum']); model.splice([0], Number.POSITIVE_INFINITY, [ { element: 'silver' }, { element: 'gold' }, { element: 'platinum' } ]); - assert.deepEqual(toArray(list), ['platinum']); + assert.deepStrictEqual(toArray(list), ['platinum']); model.refilter(); - assert.deepEqual(toArray(list), ['platinum']); + assert.deepStrictEqual(toArray(list), ['platinum']); }); test('explicit hidden nodes should have renderNodeCount == 0, issue #83211', () => { @@ -859,18 +865,18 @@ suite('IndexTreeModel', () => { { element: 'b', children: [{ element: 'bb' }] } ]); - assert.deepEqual(toArray(list), ['a', 'aa', 'b', 'bb']); - assert.deepEqual(model.getListIndex([0]), 0); - assert.deepEqual(model.getListIndex([0, 0]), 1); - assert.deepEqual(model.getListIndex([1]), 2); - assert.deepEqual(model.getListIndex([1, 0]), 3); + assert.deepStrictEqual(toArray(list), ['a', 'aa', 'b', 'bb']); + assert.deepStrictEqual(model.getListIndex([0]), 0); + assert.deepStrictEqual(model.getListIndex([0, 0]), 1); + assert.deepStrictEqual(model.getListIndex([1]), 2); + assert.deepStrictEqual(model.getListIndex([1, 0]), 3); query = /b/; model.refilter(); - assert.deepEqual(toArray(list), ['b', 'bb']); - assert.deepEqual(model.getListIndex([0]), -1); - assert.deepEqual(model.getListIndex([0, 0]), -1); - assert.deepEqual(model.getListIndex([1]), 0); - assert.deepEqual(model.getListIndex([1, 0]), 1); + assert.deepStrictEqual(toArray(list), ['b', 'bb']); + assert.deepStrictEqual(model.getListIndex([0]), -1); + assert.deepStrictEqual(model.getListIndex([0, 0]), -1); + assert.deepStrictEqual(model.getListIndex([1]), 0); + assert.deepStrictEqual(model.getListIndex([1, 0]), 1); }); }); diff --git a/src/vs/base/test/browser/ui/tree/objectTreeModel.test.ts b/src/vs/base/test/browser/ui/tree/objectTreeModel.test.ts index 4663962b..b0410877 100644 --- a/src/vs/base/test/browser/ui/tree/objectTreeModel.test.ts +++ b/src/vs/base/test/browser/ui/tree/objectTreeModel.test.ts @@ -28,8 +28,8 @@ suite('ObjectTreeModel', function () { const list: ITreeNode[] = []; const model = new ObjectTreeModel('test', toList(list)); assert(model); - assert.equal(list.length, 0); - assert.equal(model.size, 0); + assert.strictEqual(list.length, 0); + assert.strictEqual(model.size, 0); }); test('flat', () => { @@ -42,8 +42,8 @@ suite('ObjectTreeModel', function () { { element: 2 } ]); - assert.deepEqual(toArray(list), [0, 1, 2]); - assert.equal(model.size, 3); + assert.deepStrictEqual(toArray(list), [0, 1, 2]); + assert.strictEqual(model.size, 3); model.setChildren(null, [ { element: 3 }, @@ -51,12 +51,12 @@ suite('ObjectTreeModel', function () { { element: 5 }, ]); - assert.deepEqual(toArray(list), [3, 4, 5]); - assert.equal(model.size, 3); + assert.deepStrictEqual(toArray(list), [3, 4, 5]); + assert.strictEqual(model.size, 3); model.setChildren(null); - assert.deepEqual(toArray(list), []); - assert.equal(model.size, 0); + assert.deepStrictEqual(toArray(list), []); + assert.strictEqual(model.size, 0); }); test('nested', () => { @@ -75,24 +75,24 @@ suite('ObjectTreeModel', function () { { element: 2 } ]); - assert.deepEqual(toArray(list), [0, 10, 11, 12, 1, 2]); - assert.equal(model.size, 6); + assert.deepStrictEqual(toArray(list), [0, 10, 11, 12, 1, 2]); + assert.strictEqual(model.size, 6); model.setChildren(12, [ { element: 120 }, { element: 121 } ]); - assert.deepEqual(toArray(list), [0, 10, 11, 12, 120, 121, 1, 2]); - assert.equal(model.size, 8); + assert.deepStrictEqual(toArray(list), [0, 10, 11, 12, 120, 121, 1, 2]); + assert.strictEqual(model.size, 8); model.setChildren(0); - assert.deepEqual(toArray(list), [0, 1, 2]); - assert.equal(model.size, 3); + assert.deepStrictEqual(toArray(list), [0, 1, 2]); + assert.strictEqual(model.size, 3); model.setChildren(null); - assert.deepEqual(toArray(list), []); - assert.equal(model.size, 0); + assert.deepStrictEqual(toArray(list), []); + assert.strictEqual(model.size, 0); }); test('setChildren on collapsed node', () => { @@ -103,17 +103,17 @@ suite('ObjectTreeModel', function () { { element: 0, collapsed: true } ]); - assert.deepEqual(toArray(list), [0]); + assert.deepStrictEqual(toArray(list), [0]); model.setChildren(0, [ { element: 1 }, { element: 2 } ]); - assert.deepEqual(toArray(list), [0]); + assert.deepStrictEqual(toArray(list), [0]); model.setCollapsed(0, false); - assert.deepEqual(toArray(list), [0, 1, 2]); + assert.deepStrictEqual(toArray(list), [0, 1, 2]); }); test('setChildren on expanded, unrevealed node', () => { @@ -129,17 +129,17 @@ suite('ObjectTreeModel', function () { { element: 2 } ]); - assert.deepEqual(toArray(list), [1, 2]); + assert.deepStrictEqual(toArray(list), [1, 2]); model.setChildren(11, [ { element: 111 }, { element: 112 } ]); - assert.deepEqual(toArray(list), [1, 2]); + assert.deepStrictEqual(toArray(list), [1, 2]); model.setCollapsed(1, false); - assert.deepEqual(toArray(list), [1, 11, 111, 112, 2]); + assert.deepStrictEqual(toArray(list), [1, 11, 111, 112, 2]); }); test('collapse state is preserved with strict identity', () => { @@ -148,26 +148,26 @@ suite('ObjectTreeModel', function () { const data = [{ element: 'father', children: [{ element: 'child' }] }]; model.setChildren(null, data); - assert.deepEqual(toArray(list), ['father']); + assert.deepStrictEqual(toArray(list), ['father']); model.setCollapsed('father', false); - assert.deepEqual(toArray(list), ['father', 'child']); + assert.deepStrictEqual(toArray(list), ['father', 'child']); model.setChildren(null, data); - assert.deepEqual(toArray(list), ['father', 'child']); + assert.deepStrictEqual(toArray(list), ['father', 'child']); const data2 = [{ element: 'father', children: [{ element: 'child' }] }, { element: 'uncle' }]; model.setChildren(null, data2); - assert.deepEqual(toArray(list), ['father', 'child', 'uncle']); + assert.deepStrictEqual(toArray(list), ['father', 'child', 'uncle']); model.setChildren(null, [{ element: 'uncle' }]); - assert.deepEqual(toArray(list), ['uncle']); + assert.deepStrictEqual(toArray(list), ['uncle']); model.setChildren(null, data2); - assert.deepEqual(toArray(list), ['father', 'uncle']); + assert.deepStrictEqual(toArray(list), ['father', 'uncle']); model.setChildren(null, data); - assert.deepEqual(toArray(list), ['father']); + assert.deepStrictEqual(toArray(list), ['father']); }); test('sorter', () => { @@ -182,7 +182,7 @@ suite('ObjectTreeModel', function () { ]; model.setChildren(null, data); - assert.deepEqual(toArray(list), ['airplanes', 'jet', 'passenger', 'bicycles', 'dutch', 'electric', 'mountain', 'cars', 'compact', 'convertible', 'sedan']); + assert.deepStrictEqual(toArray(list), ['airplanes', 'jet', 'passenger', 'bicycles', 'dutch', 'electric', 'mountain', 'cars', 'compact', 'convertible', 'sedan']); }); test('resort', () => { @@ -197,29 +197,29 @@ suite('ObjectTreeModel', function () { ]; model.setChildren(null, data); - assert.deepEqual(toArray(list), ['cars', 'sedan', 'convertible', 'compact', 'airplanes', 'passenger', 'jet', 'bicycles', 'dutch', 'mountain', 'electric']); + assert.deepStrictEqual(toArray(list), ['cars', 'sedan', 'convertible', 'compact', 'airplanes', 'passenger', 'jet', 'bicycles', 'dutch', 'mountain', 'electric']); // lexicographical compare = (a, b) => a < b ? -1 : 1; // non-recursive model.resort(null, false); - assert.deepEqual(toArray(list), ['airplanes', 'passenger', 'jet', 'bicycles', 'dutch', 'mountain', 'electric', 'cars', 'sedan', 'convertible', 'compact']); + assert.deepStrictEqual(toArray(list), ['airplanes', 'passenger', 'jet', 'bicycles', 'dutch', 'mountain', 'electric', 'cars', 'sedan', 'convertible', 'compact']); // recursive model.resort(); - assert.deepEqual(toArray(list), ['airplanes', 'jet', 'passenger', 'bicycles', 'dutch', 'electric', 'mountain', 'cars', 'compact', 'convertible', 'sedan']); + assert.deepStrictEqual(toArray(list), ['airplanes', 'jet', 'passenger', 'bicycles', 'dutch', 'electric', 'mountain', 'cars', 'compact', 'convertible', 'sedan']); // reverse compare = (a, b) => a < b ? 1 : -1; // scoped model.resort('cars'); - assert.deepEqual(toArray(list), ['airplanes', 'jet', 'passenger', 'bicycles', 'dutch', 'electric', 'mountain', 'cars', 'sedan', 'convertible', 'compact']); + assert.deepStrictEqual(toArray(list), ['airplanes', 'jet', 'passenger', 'bicycles', 'dutch', 'electric', 'mountain', 'cars', 'sedan', 'convertible', 'compact']); // recursive model.resort(); - assert.deepEqual(toArray(list), ['cars', 'sedan', 'convertible', 'compact', 'bicycles', 'mountain', 'electric', 'dutch', 'airplanes', 'passenger', 'jet']); + assert.deepStrictEqual(toArray(list), ['cars', 'sedan', 'convertible', 'compact', 'bicycles', 'mountain', 'electric', 'dutch', 'airplanes', 'passenger', 'jet']); }); test('expandTo', () => { @@ -238,9 +238,9 @@ suite('ObjectTreeModel', function () { { element: 2 } ]); - assert.deepEqual(toArray(list), [0, 1, 2]); + assert.deepStrictEqual(toArray(list), [0, 1, 2]); model.expandTo(1000); - assert.deepEqual(toArray(list), [0, 10, 100, 1000, 11, 12, 1, 2]); + assert.deepStrictEqual(toArray(list), [0, 10, 100, 1000, 11, 12, 1, 2]); }); test('issue #95641', () => { @@ -258,19 +258,19 @@ suite('ObjectTreeModel', function () { const model = new ObjectTreeModel('test', toList(list), { filter }); model.setChildren(null, [{ element: 'file', children: [{ element: 'hello' }] }]); - assert.deepEqual(toArray(list), ['file', 'hello']); + assert.deepStrictEqual(toArray(list), ['file', 'hello']); fn = (el: string) => el === 'world'; model.refilter(); - assert.deepEqual(toArray(list), []); + assert.deepStrictEqual(toArray(list), []); model.setChildren('file', [{ element: 'world' }]); - assert.deepEqual(toArray(list), ['file', 'world']); + assert.deepStrictEqual(toArray(list), ['file', 'world']); model.setChildren('file', [{ element: 'hello' }]); - assert.deepEqual(toArray(list), []); + assert.deepStrictEqual(toArray(list), []); model.setChildren('file', [{ element: 'world' }]); - assert.deepEqual(toArray(list), ['file', 'world']); + assert.deepStrictEqual(toArray(list), ['file', 'world']); }); }); diff --git a/src/vs/base/test/common/async.test.ts b/src/vs/base/test/common/async.test.ts index 83b2eb05..08ff52df 100644 --- a/src/vs/base/test/common/async.test.ts +++ b/src/vs/base/test/common/async.test.ts @@ -8,6 +8,7 @@ import * as async from 'vs/base/common/async'; import { isPromiseCanceledError } from 'vs/base/common/errors'; import { URI } from 'vs/base/common/uri'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; +import { Event } from 'vs/base/common/event'; suite('Async', () => { @@ -484,13 +485,11 @@ suite('Async', () => { }); }); - test('Queue - events', function (done) { + test('Queue - events', function () { let queue = new async.Queue(); let finished = false; - queue.onFinished(() => { - done(); - }); + const onFinished = Event.toPromise(queue.onFinished); let res: number[] = []; @@ -508,6 +507,8 @@ suite('Async', () => { assert.ok(!finished); }); }); + + return onFinished; }); test('ResourceQueue - simple', function () { @@ -529,7 +530,7 @@ suite('Async', () => { return new Promise(c => setTimeout(() => c(), 0)).then(() => { const r1Queue2 = queue.queueFor(URI.file('/some/path')); - assert.notEqual(r1Queue, r1Queue2); // previous one got disposed after finishing + assert.notStrictEqual(r1Queue, r1Queue2); // previous one got disposed after finishing }); }); diff --git a/src/vs/base/test/common/cache.test.ts b/src/vs/base/test/common/cache.test.ts index 81b3214e..ecfbb5e8 100644 --- a/src/vs/base/test/common/cache.test.ts +++ b/src/vs/base/test/common/cache.test.ts @@ -14,9 +14,9 @@ suite('Cache', () => { const cache = new Cache(_ => Promise.resolve(counter++)); return cache.get().promise - .then(c => assert.equal(c, 0), () => assert.fail('Unexpected assertion error')) + .then(c => assert.strictEqual(c, 0), () => assert.fail('Unexpected assertion error')) .then(() => cache.get().promise) - .then(c => assert.equal(c, 0), () => assert.fail('Unexpected assertion error')); + .then(c => assert.strictEqual(c, 0), () => assert.fail('Unexpected assertion error')); }); test('simple error', () => { @@ -24,9 +24,9 @@ suite('Cache', () => { const cache = new Cache(_ => Promise.reject(new Error(String(counter++)))); return cache.get().promise - .then(() => assert.fail('Unexpected assertion error'), err => assert.equal(err.message, 0)) + .then(() => assert.fail('Unexpected assertion error'), err => assert.strictEqual(err.message, '0')) .then(() => cache.get().promise) - .then(() => assert.fail('Unexpected assertion error'), err => assert.equal(err.message, 0)); + .then(() => assert.fail('Unexpected assertion error'), err => assert.strictEqual(err.message, '0')); }); test('should retry cancellations', () => { @@ -37,29 +37,29 @@ suite('Cache', () => { return Promise.resolve(timeout(2, token).then(() => counter2++)); }); - assert.equal(counter1, 0); - assert.equal(counter2, 0); + assert.strictEqual(counter1, 0); + assert.strictEqual(counter2, 0); let result = cache.get(); - assert.equal(counter1, 1); - assert.equal(counter2, 0); + assert.strictEqual(counter1, 1); + assert.strictEqual(counter2, 0); result.promise.then(undefined, () => assert(true)); result.dispose(); - assert.equal(counter1, 1); - assert.equal(counter2, 0); + assert.strictEqual(counter1, 1); + assert.strictEqual(counter2, 0); result = cache.get(); - assert.equal(counter1, 2); - assert.equal(counter2, 0); + assert.strictEqual(counter1, 2); + assert.strictEqual(counter2, 0); return result.promise .then(c => { - assert.equal(counter1, 2); - assert.equal(counter2, 1); + assert.strictEqual(counter1, 2); + assert.strictEqual(counter2, 1); }) .then(() => cache.get().promise) .then(c => { - assert.equal(counter1, 2); - assert.equal(counter2, 1); + assert.strictEqual(counter1, 2); + assert.strictEqual(counter2, 1); }); }); }); diff --git a/src/vs/base/test/common/cancellation.test.ts b/src/vs/base/test/common/cancellation.test.ts index 8f3c82c3..d1c55d1a 100644 --- a/src/vs/base/test/common/cancellation.test.ts +++ b/src/vs/base/test/common/cancellation.test.ts @@ -12,7 +12,7 @@ suite('CancellationToken', function () { assert.strictEqual(typeof CancellationToken.None.onCancellationRequested, 'function'); }); - test('cancel before token', function (done) { + test('cancel before token', function () { const source = new CancellationTokenSource(); assert.strictEqual(source.token.isCancellationRequested, false); @@ -20,9 +20,8 @@ suite('CancellationToken', function () { assert.strictEqual(source.token.isCancellationRequested, true); - source.token.onCancellationRequested(function () { - assert.ok(true); - done(); + return new Promise(resolve => { + source.token.onCancellationRequested(() => resolve()); }); }); diff --git a/src/vs/base/test/common/codicons.test.ts b/src/vs/base/test/common/codicons.test.ts new file mode 100644 index 00000000..02a30dbe --- /dev/null +++ b/src/vs/base/test/common/codicons.test.ts @@ -0,0 +1,28 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { getCodiconAriaLabel } from 'vs/base/common/codicons'; + +suite('Codicon', () => { + test('Can get proper aria labels', () => { + // note, the spaces in the results are important + const testCases = new Map([ + ['', ''], + ['asdf', 'asdf'], + ['asdf$(squirrel)asdf', 'asdf squirrel asdf'], + ['asdf $(squirrel) asdf', 'asdf squirrel asdf'], + ['$(rocket)asdf', 'rocket asdf'], + ['$(rocket) asdf', 'rocket asdf'], + ['$(rocket)$(rocket)$(rocket)asdf', 'rocket rocket rocket asdf'], + ['$(rocket) asdf $(rocket)', 'rocket asdf rocket'], + ['$(rocket)asdf$(rocket)', 'rocket asdf rocket'], + ]); + + for (const [input, expected] of testCases) { + assert.strictEqual(getCodiconAriaLabel(input), expected); + } + }); +}); diff --git a/src/vs/base/test/common/collections.test.ts b/src/vs/base/test/common/collections.test.ts index e5449f46..881525a0 100644 --- a/src/vs/base/test/common/collections.test.ts +++ b/src/vs/base/test/common/collections.test.ts @@ -14,18 +14,18 @@ suite('Collections', () => { let count = 0; collections.forEach({ toString: 123 }, () => count++); - assert.equal(count, 1); + assert.strictEqual(count, 1); count = 0; let dict = Object.create(null); dict['toString'] = 123; collections.forEach(dict, () => count++); - assert.equal(count, 1); + assert.strictEqual(count, 1); collections.forEach(dict, () => false); collections.forEach(dict, (x, remove) => remove()); - assert.equal(dict['toString'], null); + assert.strictEqual(dict['toString'], undefined); // don't iterate over properties that are not on the object itself let test = Object.create({ 'derived': true }); @@ -45,12 +45,34 @@ suite('Collections', () => { let grouped = collections.groupBy(source, x => x.key); // Group 1 - assert.equal(grouped[group1].length, 2); - assert.equal(grouped[group1][0].value, value1); - assert.equal(grouped[group1][1].value, value2); + assert.strictEqual(grouped[group1].length, 2); + assert.strictEqual(grouped[group1][0].value, value1); + assert.strictEqual(grouped[group1][1].value, value2); // Group 2 - assert.equal(grouped[group2].length, 1); - assert.equal(grouped[group2][0].value, value3); + assert.strictEqual(grouped[group2].length, 1); + assert.strictEqual(grouped[group2][0].value, value3); + }); + + test('groupByNumber', () => { + + const group1 = 1, group2 = 2; + const value1 = 'a', value2 = 'b', value3 = 'c'; + let source = [ + { key: group1, value: value1 }, + { key: group1, value: value2 }, + { key: group2, value: value3 }, + ]; + + let grouped = collections.groupByNumber(source, x => x.key); + + // Group 1 + assert.strictEqual(grouped.get(group1)!.length, 2); + assert.strictEqual(grouped.get(group1)![0].value, value1); + assert.strictEqual(grouped.get(group1)![1].value, value2); + + // Group 2 + assert.strictEqual(grouped.get(group2)!.length, 1); + assert.strictEqual(grouped.get(group2)![0].value, value3); }); }); diff --git a/src/vs/base/test/common/color.test.ts b/src/vs/base/test/common/color.test.ts index 5ee0207a..6d00abf8 100644 --- a/src/vs/base/test/common/color.test.ts +++ b/src/vs/base/test/common/color.test.ts @@ -20,11 +20,11 @@ suite('Color', () => { test('getLighterColor', () => { let color1 = new Color(new HSLA(60, 1, 0.5, 1)), color2 = new Color(new HSLA(0, 0, 0.753, 1)); - assert.deepEqual(color1.hsla, Color.getLighterColor(color1, color2).hsla); - assert.deepEqual(new HSLA(0, 0, 0.916, 1), Color.getLighterColor(color2, color1).hsla); - assert.deepEqual(new HSLA(0, 0, 0.851, 1), Color.getLighterColor(color2, color1, 0.3).hsla); - assert.deepEqual(new HSLA(0, 0, 0.981, 1), Color.getLighterColor(color2, color1, 0.7).hsla); - assert.deepEqual(new HSLA(0, 0, 1, 1), Color.getLighterColor(color2, color1, 1).hsla); + assert.deepStrictEqual(color1.hsla, Color.getLighterColor(color1, color2).hsla); + assert.deepStrictEqual(new HSLA(0, 0, 0.916, 1), Color.getLighterColor(color2, color1).hsla); + assert.deepStrictEqual(new HSLA(0, 0, 0.851, 1), Color.getLighterColor(color2, color1, 0.3).hsla); + assert.deepStrictEqual(new HSLA(0, 0, 0.981, 1), Color.getLighterColor(color2, color1, 0.7).hsla); + assert.deepStrictEqual(new HSLA(0, 0, 1, 1), Color.getLighterColor(color2, color1, 1).hsla); }); @@ -38,164 +38,164 @@ suite('Color', () => { test('getDarkerColor', () => { let color1 = new Color(new HSLA(60, 1, 0.5, 1)), color2 = new Color(new HSLA(0, 0, 0.753, 1)); - assert.deepEqual(color2.hsla, Color.getDarkerColor(color2, color1).hsla); - assert.deepEqual(new HSLA(60, 1, 0.392, 1), Color.getDarkerColor(color1, color2).hsla); - assert.deepEqual(new HSLA(60, 1, 0.435, 1), Color.getDarkerColor(color1, color2, 0.3).hsla); - assert.deepEqual(new HSLA(60, 1, 0.349, 1), Color.getDarkerColor(color1, color2, 0.7).hsla); - assert.deepEqual(new HSLA(60, 1, 0.284, 1), Color.getDarkerColor(color1, color2, 1).hsla); + assert.deepStrictEqual(color2.hsla, Color.getDarkerColor(color2, color1).hsla); + assert.deepStrictEqual(new HSLA(60, 1, 0.392, 1), Color.getDarkerColor(color1, color2).hsla); + assert.deepStrictEqual(new HSLA(60, 1, 0.435, 1), Color.getDarkerColor(color1, color2, 0.3).hsla); + assert.deepStrictEqual(new HSLA(60, 1, 0.349, 1), Color.getDarkerColor(color1, color2, 0.7).hsla); + assert.deepStrictEqual(new HSLA(60, 1, 0.284, 1), Color.getDarkerColor(color1, color2, 1).hsla); // Abyss theme - assert.deepEqual(new HSLA(355, 0.874, 0.157, 1), Color.getDarkerColor(Color.fromHex('#770811'), Color.fromHex('#000c18'), 0.4).hsla); + assert.deepStrictEqual(new HSLA(355, 0.874, 0.157, 1), Color.getDarkerColor(Color.fromHex('#770811'), Color.fromHex('#000c18'), 0.4).hsla); }); test('luminance', () => { - assert.deepEqual(0, new Color(new RGBA(0, 0, 0, 1)).getRelativeLuminance()); - assert.deepEqual(1, new Color(new RGBA(255, 255, 255, 1)).getRelativeLuminance()); + assert.deepStrictEqual(0, new Color(new RGBA(0, 0, 0, 1)).getRelativeLuminance()); + assert.deepStrictEqual(1, new Color(new RGBA(255, 255, 255, 1)).getRelativeLuminance()); - assert.deepEqual(0.2126, new Color(new RGBA(255, 0, 0, 1)).getRelativeLuminance()); - assert.deepEqual(0.7152, new Color(new RGBA(0, 255, 0, 1)).getRelativeLuminance()); - assert.deepEqual(0.0722, new Color(new RGBA(0, 0, 255, 1)).getRelativeLuminance()); + assert.deepStrictEqual(0.2126, new Color(new RGBA(255, 0, 0, 1)).getRelativeLuminance()); + assert.deepStrictEqual(0.7152, new Color(new RGBA(0, 255, 0, 1)).getRelativeLuminance()); + assert.deepStrictEqual(0.0722, new Color(new RGBA(0, 0, 255, 1)).getRelativeLuminance()); - assert.deepEqual(0.9278, new Color(new RGBA(255, 255, 0, 1)).getRelativeLuminance()); - assert.deepEqual(0.7874, new Color(new RGBA(0, 255, 255, 1)).getRelativeLuminance()); - assert.deepEqual(0.2848, new Color(new RGBA(255, 0, 255, 1)).getRelativeLuminance()); + assert.deepStrictEqual(0.9278, new Color(new RGBA(255, 255, 0, 1)).getRelativeLuminance()); + assert.deepStrictEqual(0.7874, new Color(new RGBA(0, 255, 255, 1)).getRelativeLuminance()); + assert.deepStrictEqual(0.2848, new Color(new RGBA(255, 0, 255, 1)).getRelativeLuminance()); - assert.deepEqual(0.5271, new Color(new RGBA(192, 192, 192, 1)).getRelativeLuminance()); + assert.deepStrictEqual(0.5271, new Color(new RGBA(192, 192, 192, 1)).getRelativeLuminance()); - assert.deepEqual(0.2159, new Color(new RGBA(128, 128, 128, 1)).getRelativeLuminance()); - assert.deepEqual(0.0459, new Color(new RGBA(128, 0, 0, 1)).getRelativeLuminance()); - assert.deepEqual(0.2003, new Color(new RGBA(128, 128, 0, 1)).getRelativeLuminance()); - assert.deepEqual(0.1544, new Color(new RGBA(0, 128, 0, 1)).getRelativeLuminance()); - assert.deepEqual(0.0615, new Color(new RGBA(128, 0, 128, 1)).getRelativeLuminance()); - assert.deepEqual(0.17, new Color(new RGBA(0, 128, 128, 1)).getRelativeLuminance()); - assert.deepEqual(0.0156, new Color(new RGBA(0, 0, 128, 1)).getRelativeLuminance()); + assert.deepStrictEqual(0.2159, new Color(new RGBA(128, 128, 128, 1)).getRelativeLuminance()); + assert.deepStrictEqual(0.0459, new Color(new RGBA(128, 0, 0, 1)).getRelativeLuminance()); + assert.deepStrictEqual(0.2003, new Color(new RGBA(128, 128, 0, 1)).getRelativeLuminance()); + assert.deepStrictEqual(0.1544, new Color(new RGBA(0, 128, 0, 1)).getRelativeLuminance()); + assert.deepStrictEqual(0.0615, new Color(new RGBA(128, 0, 128, 1)).getRelativeLuminance()); + assert.deepStrictEqual(0.17, new Color(new RGBA(0, 128, 128, 1)).getRelativeLuminance()); + assert.deepStrictEqual(0.0156, new Color(new RGBA(0, 0, 128, 1)).getRelativeLuminance()); }); test('blending', () => { - assert.deepEqual(new Color(new RGBA(0, 0, 0, 0)).blend(new Color(new RGBA(243, 34, 43))), new Color(new RGBA(243, 34, 43))); - assert.deepEqual(new Color(new RGBA(255, 255, 255)).blend(new Color(new RGBA(243, 34, 43))), new Color(new RGBA(255, 255, 255))); - assert.deepEqual(new Color(new RGBA(122, 122, 122, 0.7)).blend(new Color(new RGBA(243, 34, 43))), new Color(new RGBA(158, 95, 98))); - assert.deepEqual(new Color(new RGBA(0, 0, 0, 0.58)).blend(new Color(new RGBA(255, 255, 255, 0.33))), new Color(new RGBA(49, 49, 49, 0.719))); + assert.deepStrictEqual(new Color(new RGBA(0, 0, 0, 0)).blend(new Color(new RGBA(243, 34, 43))), new Color(new RGBA(243, 34, 43))); + assert.deepStrictEqual(new Color(new RGBA(255, 255, 255)).blend(new Color(new RGBA(243, 34, 43))), new Color(new RGBA(255, 255, 255))); + assert.deepStrictEqual(new Color(new RGBA(122, 122, 122, 0.7)).blend(new Color(new RGBA(243, 34, 43))), new Color(new RGBA(158, 95, 98))); + assert.deepStrictEqual(new Color(new RGBA(0, 0, 0, 0.58)).blend(new Color(new RGBA(255, 255, 255, 0.33))), new Color(new RGBA(49, 49, 49, 0.719))); }); suite('HSLA', () => { test('HSLA.toRGBA', () => { - assert.deepEqual(HSLA.toRGBA(new HSLA(0, 0, 0, 0)), new RGBA(0, 0, 0, 0)); - assert.deepEqual(HSLA.toRGBA(new HSLA(0, 0, 0, 1)), new RGBA(0, 0, 0, 1)); - assert.deepEqual(HSLA.toRGBA(new HSLA(0, 0, 1, 1)), new RGBA(255, 255, 255, 1)); + assert.deepStrictEqual(HSLA.toRGBA(new HSLA(0, 0, 0, 0)), new RGBA(0, 0, 0, 0)); + assert.deepStrictEqual(HSLA.toRGBA(new HSLA(0, 0, 0, 1)), new RGBA(0, 0, 0, 1)); + assert.deepStrictEqual(HSLA.toRGBA(new HSLA(0, 0, 1, 1)), new RGBA(255, 255, 255, 1)); - assert.deepEqual(HSLA.toRGBA(new HSLA(0, 1, 0.5, 1)), new RGBA(255, 0, 0, 1)); - assert.deepEqual(HSLA.toRGBA(new HSLA(120, 1, 0.5, 1)), new RGBA(0, 255, 0, 1)); - assert.deepEqual(HSLA.toRGBA(new HSLA(240, 1, 0.5, 1)), new RGBA(0, 0, 255, 1)); + assert.deepStrictEqual(HSLA.toRGBA(new HSLA(0, 1, 0.5, 1)), new RGBA(255, 0, 0, 1)); + assert.deepStrictEqual(HSLA.toRGBA(new HSLA(120, 1, 0.5, 1)), new RGBA(0, 255, 0, 1)); + assert.deepStrictEqual(HSLA.toRGBA(new HSLA(240, 1, 0.5, 1)), new RGBA(0, 0, 255, 1)); - assert.deepEqual(HSLA.toRGBA(new HSLA(60, 1, 0.5, 1)), new RGBA(255, 255, 0, 1)); - assert.deepEqual(HSLA.toRGBA(new HSLA(180, 1, 0.5, 1)), new RGBA(0, 255, 255, 1)); - assert.deepEqual(HSLA.toRGBA(new HSLA(300, 1, 0.5, 1)), new RGBA(255, 0, 255, 1)); + assert.deepStrictEqual(HSLA.toRGBA(new HSLA(60, 1, 0.5, 1)), new RGBA(255, 255, 0, 1)); + assert.deepStrictEqual(HSLA.toRGBA(new HSLA(180, 1, 0.5, 1)), new RGBA(0, 255, 255, 1)); + assert.deepStrictEqual(HSLA.toRGBA(new HSLA(300, 1, 0.5, 1)), new RGBA(255, 0, 255, 1)); - assert.deepEqual(HSLA.toRGBA(new HSLA(0, 0, 0.753, 1)), new RGBA(192, 192, 192, 1)); + assert.deepStrictEqual(HSLA.toRGBA(new HSLA(0, 0, 0.753, 1)), new RGBA(192, 192, 192, 1)); - assert.deepEqual(HSLA.toRGBA(new HSLA(0, 0, 0.502, 1)), new RGBA(128, 128, 128, 1)); - assert.deepEqual(HSLA.toRGBA(new HSLA(0, 1, 0.251, 1)), new RGBA(128, 0, 0, 1)); - assert.deepEqual(HSLA.toRGBA(new HSLA(60, 1, 0.251, 1)), new RGBA(128, 128, 0, 1)); - assert.deepEqual(HSLA.toRGBA(new HSLA(120, 1, 0.251, 1)), new RGBA(0, 128, 0, 1)); - assert.deepEqual(HSLA.toRGBA(new HSLA(300, 1, 0.251, 1)), new RGBA(128, 0, 128, 1)); - assert.deepEqual(HSLA.toRGBA(new HSLA(180, 1, 0.251, 1)), new RGBA(0, 128, 128, 1)); - assert.deepEqual(HSLA.toRGBA(new HSLA(240, 1, 0.251, 1)), new RGBA(0, 0, 128, 1)); + assert.deepStrictEqual(HSLA.toRGBA(new HSLA(0, 0, 0.502, 1)), new RGBA(128, 128, 128, 1)); + assert.deepStrictEqual(HSLA.toRGBA(new HSLA(0, 1, 0.251, 1)), new RGBA(128, 0, 0, 1)); + assert.deepStrictEqual(HSLA.toRGBA(new HSLA(60, 1, 0.251, 1)), new RGBA(128, 128, 0, 1)); + assert.deepStrictEqual(HSLA.toRGBA(new HSLA(120, 1, 0.251, 1)), new RGBA(0, 128, 0, 1)); + assert.deepStrictEqual(HSLA.toRGBA(new HSLA(300, 1, 0.251, 1)), new RGBA(128, 0, 128, 1)); + assert.deepStrictEqual(HSLA.toRGBA(new HSLA(180, 1, 0.251, 1)), new RGBA(0, 128, 128, 1)); + assert.deepStrictEqual(HSLA.toRGBA(new HSLA(240, 1, 0.251, 1)), new RGBA(0, 0, 128, 1)); }); test('HSLA.fromRGBA', () => { - assert.deepEqual(HSLA.fromRGBA(new RGBA(0, 0, 0, 0)), new HSLA(0, 0, 0, 0)); - assert.deepEqual(HSLA.fromRGBA(new RGBA(0, 0, 0, 1)), new HSLA(0, 0, 0, 1)); - assert.deepEqual(HSLA.fromRGBA(new RGBA(255, 255, 255, 1)), new HSLA(0, 0, 1, 1)); + assert.deepStrictEqual(HSLA.fromRGBA(new RGBA(0, 0, 0, 0)), new HSLA(0, 0, 0, 0)); + assert.deepStrictEqual(HSLA.fromRGBA(new RGBA(0, 0, 0, 1)), new HSLA(0, 0, 0, 1)); + assert.deepStrictEqual(HSLA.fromRGBA(new RGBA(255, 255, 255, 1)), new HSLA(0, 0, 1, 1)); - assert.deepEqual(HSLA.fromRGBA(new RGBA(255, 0, 0, 1)), new HSLA(0, 1, 0.5, 1)); - assert.deepEqual(HSLA.fromRGBA(new RGBA(0, 255, 0, 1)), new HSLA(120, 1, 0.5, 1)); - assert.deepEqual(HSLA.fromRGBA(new RGBA(0, 0, 255, 1)), new HSLA(240, 1, 0.5, 1)); + assert.deepStrictEqual(HSLA.fromRGBA(new RGBA(255, 0, 0, 1)), new HSLA(0, 1, 0.5, 1)); + assert.deepStrictEqual(HSLA.fromRGBA(new RGBA(0, 255, 0, 1)), new HSLA(120, 1, 0.5, 1)); + assert.deepStrictEqual(HSLA.fromRGBA(new RGBA(0, 0, 255, 1)), new HSLA(240, 1, 0.5, 1)); - assert.deepEqual(HSLA.fromRGBA(new RGBA(255, 255, 0, 1)), new HSLA(60, 1, 0.5, 1)); - assert.deepEqual(HSLA.fromRGBA(new RGBA(0, 255, 255, 1)), new HSLA(180, 1, 0.5, 1)); - assert.deepEqual(HSLA.fromRGBA(new RGBA(255, 0, 255, 1)), new HSLA(300, 1, 0.5, 1)); + assert.deepStrictEqual(HSLA.fromRGBA(new RGBA(255, 255, 0, 1)), new HSLA(60, 1, 0.5, 1)); + assert.deepStrictEqual(HSLA.fromRGBA(new RGBA(0, 255, 255, 1)), new HSLA(180, 1, 0.5, 1)); + assert.deepStrictEqual(HSLA.fromRGBA(new RGBA(255, 0, 255, 1)), new HSLA(300, 1, 0.5, 1)); - assert.deepEqual(HSLA.fromRGBA(new RGBA(192, 192, 192, 1)), new HSLA(0, 0, 0.753, 1)); + assert.deepStrictEqual(HSLA.fromRGBA(new RGBA(192, 192, 192, 1)), new HSLA(0, 0, 0.753, 1)); - assert.deepEqual(HSLA.fromRGBA(new RGBA(128, 128, 128, 1)), new HSLA(0, 0, 0.502, 1)); - assert.deepEqual(HSLA.fromRGBA(new RGBA(128, 0, 0, 1)), new HSLA(0, 1, 0.251, 1)); - assert.deepEqual(HSLA.fromRGBA(new RGBA(128, 128, 0, 1)), new HSLA(60, 1, 0.251, 1)); - assert.deepEqual(HSLA.fromRGBA(new RGBA(0, 128, 0, 1)), new HSLA(120, 1, 0.251, 1)); - assert.deepEqual(HSLA.fromRGBA(new RGBA(128, 0, 128, 1)), new HSLA(300, 1, 0.251, 1)); - assert.deepEqual(HSLA.fromRGBA(new RGBA(0, 128, 128, 1)), new HSLA(180, 1, 0.251, 1)); - assert.deepEqual(HSLA.fromRGBA(new RGBA(0, 0, 128, 1)), new HSLA(240, 1, 0.251, 1)); + assert.deepStrictEqual(HSLA.fromRGBA(new RGBA(128, 128, 128, 1)), new HSLA(0, 0, 0.502, 1)); + assert.deepStrictEqual(HSLA.fromRGBA(new RGBA(128, 0, 0, 1)), new HSLA(0, 1, 0.251, 1)); + assert.deepStrictEqual(HSLA.fromRGBA(new RGBA(128, 128, 0, 1)), new HSLA(60, 1, 0.251, 1)); + assert.deepStrictEqual(HSLA.fromRGBA(new RGBA(0, 128, 0, 1)), new HSLA(120, 1, 0.251, 1)); + assert.deepStrictEqual(HSLA.fromRGBA(new RGBA(128, 0, 128, 1)), new HSLA(300, 1, 0.251, 1)); + assert.deepStrictEqual(HSLA.fromRGBA(new RGBA(0, 128, 128, 1)), new HSLA(180, 1, 0.251, 1)); + assert.deepStrictEqual(HSLA.fromRGBA(new RGBA(0, 0, 128, 1)), new HSLA(240, 1, 0.251, 1)); }); }); suite('HSVA', () => { test('HSVA.toRGBA', () => { - assert.deepEqual(HSVA.toRGBA(new HSVA(0, 0, 0, 0)), new RGBA(0, 0, 0, 0)); - assert.deepEqual(HSVA.toRGBA(new HSVA(0, 0, 0, 1)), new RGBA(0, 0, 0, 1)); - assert.deepEqual(HSVA.toRGBA(new HSVA(0, 0, 1, 1)), new RGBA(255, 255, 255, 1)); + assert.deepStrictEqual(HSVA.toRGBA(new HSVA(0, 0, 0, 0)), new RGBA(0, 0, 0, 0)); + assert.deepStrictEqual(HSVA.toRGBA(new HSVA(0, 0, 0, 1)), new RGBA(0, 0, 0, 1)); + assert.deepStrictEqual(HSVA.toRGBA(new HSVA(0, 0, 1, 1)), new RGBA(255, 255, 255, 1)); - assert.deepEqual(HSVA.toRGBA(new HSVA(0, 1, 1, 1)), new RGBA(255, 0, 0, 1)); - assert.deepEqual(HSVA.toRGBA(new HSVA(120, 1, 1, 1)), new RGBA(0, 255, 0, 1)); - assert.deepEqual(HSVA.toRGBA(new HSVA(240, 1, 1, 1)), new RGBA(0, 0, 255, 1)); + assert.deepStrictEqual(HSVA.toRGBA(new HSVA(0, 1, 1, 1)), new RGBA(255, 0, 0, 1)); + assert.deepStrictEqual(HSVA.toRGBA(new HSVA(120, 1, 1, 1)), new RGBA(0, 255, 0, 1)); + assert.deepStrictEqual(HSVA.toRGBA(new HSVA(240, 1, 1, 1)), new RGBA(0, 0, 255, 1)); - assert.deepEqual(HSVA.toRGBA(new HSVA(60, 1, 1, 1)), new RGBA(255, 255, 0, 1)); - assert.deepEqual(HSVA.toRGBA(new HSVA(180, 1, 1, 1)), new RGBA(0, 255, 255, 1)); - assert.deepEqual(HSVA.toRGBA(new HSVA(300, 1, 1, 1)), new RGBA(255, 0, 255, 1)); + assert.deepStrictEqual(HSVA.toRGBA(new HSVA(60, 1, 1, 1)), new RGBA(255, 255, 0, 1)); + assert.deepStrictEqual(HSVA.toRGBA(new HSVA(180, 1, 1, 1)), new RGBA(0, 255, 255, 1)); + assert.deepStrictEqual(HSVA.toRGBA(new HSVA(300, 1, 1, 1)), new RGBA(255, 0, 255, 1)); - assert.deepEqual(HSVA.toRGBA(new HSVA(0, 0, 0.753, 1)), new RGBA(192, 192, 192, 1)); + assert.deepStrictEqual(HSVA.toRGBA(new HSVA(0, 0, 0.753, 1)), new RGBA(192, 192, 192, 1)); - assert.deepEqual(HSVA.toRGBA(new HSVA(0, 0, 0.502, 1)), new RGBA(128, 128, 128, 1)); - assert.deepEqual(HSVA.toRGBA(new HSVA(0, 1, 0.502, 1)), new RGBA(128, 0, 0, 1)); - assert.deepEqual(HSVA.toRGBA(new HSVA(60, 1, 0.502, 1)), new RGBA(128, 128, 0, 1)); - assert.deepEqual(HSVA.toRGBA(new HSVA(120, 1, 0.502, 1)), new RGBA(0, 128, 0, 1)); - assert.deepEqual(HSVA.toRGBA(new HSVA(300, 1, 0.502, 1)), new RGBA(128, 0, 128, 1)); - assert.deepEqual(HSVA.toRGBA(new HSVA(180, 1, 0.502, 1)), new RGBA(0, 128, 128, 1)); - assert.deepEqual(HSVA.toRGBA(new HSVA(240, 1, 0.502, 1)), new RGBA(0, 0, 128, 1)); + assert.deepStrictEqual(HSVA.toRGBA(new HSVA(0, 0, 0.502, 1)), new RGBA(128, 128, 128, 1)); + assert.deepStrictEqual(HSVA.toRGBA(new HSVA(0, 1, 0.502, 1)), new RGBA(128, 0, 0, 1)); + assert.deepStrictEqual(HSVA.toRGBA(new HSVA(60, 1, 0.502, 1)), new RGBA(128, 128, 0, 1)); + assert.deepStrictEqual(HSVA.toRGBA(new HSVA(120, 1, 0.502, 1)), new RGBA(0, 128, 0, 1)); + assert.deepStrictEqual(HSVA.toRGBA(new HSVA(300, 1, 0.502, 1)), new RGBA(128, 0, 128, 1)); + assert.deepStrictEqual(HSVA.toRGBA(new HSVA(180, 1, 0.502, 1)), new RGBA(0, 128, 128, 1)); + assert.deepStrictEqual(HSVA.toRGBA(new HSVA(240, 1, 0.502, 1)), new RGBA(0, 0, 128, 1)); - assert.deepEqual(HSVA.toRGBA(new HSVA(360, 0, 0, 0)), new RGBA(0, 0, 0, 0)); - assert.deepEqual(HSVA.toRGBA(new HSVA(360, 0, 0, 1)), new RGBA(0, 0, 0, 1)); - assert.deepEqual(HSVA.toRGBA(new HSVA(360, 0, 1, 1)), new RGBA(255, 255, 255, 1)); - assert.deepEqual(HSVA.toRGBA(new HSVA(360, 1, 1, 1)), new RGBA(255, 0, 0, 1)); - assert.deepEqual(HSVA.toRGBA(new HSVA(360, 0, 0.753, 1)), new RGBA(192, 192, 192, 1)); - assert.deepEqual(HSVA.toRGBA(new HSVA(360, 0, 0.502, 1)), new RGBA(128, 128, 128, 1)); - assert.deepEqual(HSVA.toRGBA(new HSVA(360, 1, 0.502, 1)), new RGBA(128, 0, 0, 1)); + assert.deepStrictEqual(HSVA.toRGBA(new HSVA(360, 0, 0, 0)), new RGBA(0, 0, 0, 0)); + assert.deepStrictEqual(HSVA.toRGBA(new HSVA(360, 0, 0, 1)), new RGBA(0, 0, 0, 1)); + assert.deepStrictEqual(HSVA.toRGBA(new HSVA(360, 0, 1, 1)), new RGBA(255, 255, 255, 1)); + assert.deepStrictEqual(HSVA.toRGBA(new HSVA(360, 1, 1, 1)), new RGBA(255, 0, 0, 1)); + assert.deepStrictEqual(HSVA.toRGBA(new HSVA(360, 0, 0.753, 1)), new RGBA(192, 192, 192, 1)); + assert.deepStrictEqual(HSVA.toRGBA(new HSVA(360, 0, 0.502, 1)), new RGBA(128, 128, 128, 1)); + assert.deepStrictEqual(HSVA.toRGBA(new HSVA(360, 1, 0.502, 1)), new RGBA(128, 0, 0, 1)); }); test('HSVA.fromRGBA', () => { - assert.deepEqual(HSVA.fromRGBA(new RGBA(0, 0, 0, 0)), new HSVA(0, 0, 0, 0)); - assert.deepEqual(HSVA.fromRGBA(new RGBA(0, 0, 0, 1)), new HSVA(0, 0, 0, 1)); - assert.deepEqual(HSVA.fromRGBA(new RGBA(255, 255, 255, 1)), new HSVA(0, 0, 1, 1)); + assert.deepStrictEqual(HSVA.fromRGBA(new RGBA(0, 0, 0, 0)), new HSVA(0, 0, 0, 0)); + assert.deepStrictEqual(HSVA.fromRGBA(new RGBA(0, 0, 0, 1)), new HSVA(0, 0, 0, 1)); + assert.deepStrictEqual(HSVA.fromRGBA(new RGBA(255, 255, 255, 1)), new HSVA(0, 0, 1, 1)); - assert.deepEqual(HSVA.fromRGBA(new RGBA(255, 0, 0, 1)), new HSVA(0, 1, 1, 1)); - assert.deepEqual(HSVA.fromRGBA(new RGBA(0, 255, 0, 1)), new HSVA(120, 1, 1, 1)); - assert.deepEqual(HSVA.fromRGBA(new RGBA(0, 0, 255, 1)), new HSVA(240, 1, 1, 1)); + assert.deepStrictEqual(HSVA.fromRGBA(new RGBA(255, 0, 0, 1)), new HSVA(0, 1, 1, 1)); + assert.deepStrictEqual(HSVA.fromRGBA(new RGBA(0, 255, 0, 1)), new HSVA(120, 1, 1, 1)); + assert.deepStrictEqual(HSVA.fromRGBA(new RGBA(0, 0, 255, 1)), new HSVA(240, 1, 1, 1)); - assert.deepEqual(HSVA.fromRGBA(new RGBA(255, 255, 0, 1)), new HSVA(60, 1, 1, 1)); - assert.deepEqual(HSVA.fromRGBA(new RGBA(0, 255, 255, 1)), new HSVA(180, 1, 1, 1)); - assert.deepEqual(HSVA.fromRGBA(new RGBA(255, 0, 255, 1)), new HSVA(300, 1, 1, 1)); + assert.deepStrictEqual(HSVA.fromRGBA(new RGBA(255, 255, 0, 1)), new HSVA(60, 1, 1, 1)); + assert.deepStrictEqual(HSVA.fromRGBA(new RGBA(0, 255, 255, 1)), new HSVA(180, 1, 1, 1)); + assert.deepStrictEqual(HSVA.fromRGBA(new RGBA(255, 0, 255, 1)), new HSVA(300, 1, 1, 1)); - assert.deepEqual(HSVA.fromRGBA(new RGBA(192, 192, 192, 1)), new HSVA(0, 0, 0.753, 1)); + assert.deepStrictEqual(HSVA.fromRGBA(new RGBA(192, 192, 192, 1)), new HSVA(0, 0, 0.753, 1)); - assert.deepEqual(HSVA.fromRGBA(new RGBA(128, 128, 128, 1)), new HSVA(0, 0, 0.502, 1)); - assert.deepEqual(HSVA.fromRGBA(new RGBA(128, 0, 0, 1)), new HSVA(0, 1, 0.502, 1)); - assert.deepEqual(HSVA.fromRGBA(new RGBA(128, 128, 0, 1)), new HSVA(60, 1, 0.502, 1)); - assert.deepEqual(HSVA.fromRGBA(new RGBA(0, 128, 0, 1)), new HSVA(120, 1, 0.502, 1)); - assert.deepEqual(HSVA.fromRGBA(new RGBA(128, 0, 128, 1)), new HSVA(300, 1, 0.502, 1)); - assert.deepEqual(HSVA.fromRGBA(new RGBA(0, 128, 128, 1)), new HSVA(180, 1, 0.502, 1)); - assert.deepEqual(HSVA.fromRGBA(new RGBA(0, 0, 128, 1)), new HSVA(240, 1, 0.502, 1)); + assert.deepStrictEqual(HSVA.fromRGBA(new RGBA(128, 128, 128, 1)), new HSVA(0, 0, 0.502, 1)); + assert.deepStrictEqual(HSVA.fromRGBA(new RGBA(128, 0, 0, 1)), new HSVA(0, 1, 0.502, 1)); + assert.deepStrictEqual(HSVA.fromRGBA(new RGBA(128, 128, 0, 1)), new HSVA(60, 1, 0.502, 1)); + assert.deepStrictEqual(HSVA.fromRGBA(new RGBA(0, 128, 0, 1)), new HSVA(120, 1, 0.502, 1)); + assert.deepStrictEqual(HSVA.fromRGBA(new RGBA(128, 0, 128, 1)), new HSVA(300, 1, 0.502, 1)); + assert.deepStrictEqual(HSVA.fromRGBA(new RGBA(0, 128, 128, 1)), new HSVA(180, 1, 0.502, 1)); + assert.deepStrictEqual(HSVA.fromRGBA(new RGBA(0, 0, 128, 1)), new HSVA(240, 1, 0.502, 1)); }); test('Keep hue value when saturation is 0', () => { - assert.deepEqual(HSVA.toRGBA(new HSVA(10, 0, 0, 0)), HSVA.toRGBA(new HSVA(20, 0, 0, 0))); - assert.deepEqual(new Color(new HSVA(10, 0, 0, 0)).rgba, new Color(new HSVA(20, 0, 0, 0)).rgba); - assert.notDeepEqual(new Color(new HSVA(10, 0, 0, 0)).hsva, new Color(new HSVA(20, 0, 0, 0)).hsva); + assert.deepStrictEqual(HSVA.toRGBA(new HSVA(10, 0, 0, 0)), HSVA.toRGBA(new HSVA(20, 0, 0, 0))); + assert.deepStrictEqual(new Color(new HSVA(10, 0, 0, 0)).rgba, new Color(new HSVA(20, 0, 0, 0)).rgba); + assert.notDeepStrictEqual(new Color(new HSVA(10, 0, 0, 0)).hsva, new Color(new HSVA(20, 0, 0, 0)).hsva); }); test('bug#36240', () => { - assert.deepEqual(HSVA.fromRGBA(new RGBA(92, 106, 196, 1)), new HSVA(232, 0.531, 0.769, 1)); - assert.deepEqual(HSVA.toRGBA(HSVA.fromRGBA(new RGBA(92, 106, 196, 1))), new RGBA(92, 106, 196, 1)); + assert.deepStrictEqual(HSVA.fromRGBA(new RGBA(92, 106, 196, 1)), new HSVA(232, 0.531, 0.769, 1)); + assert.deepStrictEqual(HSVA.toRGBA(HSVA.fromRGBA(new RGBA(92, 106, 196, 1))), new RGBA(92, 106, 196, 1)); }); }); @@ -204,49 +204,49 @@ suite('Color', () => { test('parseHex', () => { // invalid - assert.deepEqual(Color.Format.CSS.parseHex(''), null); - assert.deepEqual(Color.Format.CSS.parseHex('#'), null); - assert.deepEqual(Color.Format.CSS.parseHex('#0102030'), null); + assert.deepStrictEqual(Color.Format.CSS.parseHex(''), null); + assert.deepStrictEqual(Color.Format.CSS.parseHex('#'), null); + assert.deepStrictEqual(Color.Format.CSS.parseHex('#0102030'), null); // somewhat valid - assert.deepEqual(Color.Format.CSS.parseHex('#FFFFG0')!.rgba, new RGBA(255, 255, 0, 1)); - assert.deepEqual(Color.Format.CSS.parseHex('#FFFFg0')!.rgba, new RGBA(255, 255, 0, 1)); - assert.deepEqual(Color.Format.CSS.parseHex('#-FFF00')!.rgba, new RGBA(15, 255, 0, 1)); + assert.deepStrictEqual(Color.Format.CSS.parseHex('#FFFFG0')!.rgba, new RGBA(255, 255, 0, 1)); + assert.deepStrictEqual(Color.Format.CSS.parseHex('#FFFFg0')!.rgba, new RGBA(255, 255, 0, 1)); + assert.deepStrictEqual(Color.Format.CSS.parseHex('#-FFF00')!.rgba, new RGBA(15, 255, 0, 1)); // valid - assert.deepEqual(Color.Format.CSS.parseHex('#000000')!.rgba, new RGBA(0, 0, 0, 1)); - assert.deepEqual(Color.Format.CSS.parseHex('#FFFFFF')!.rgba, new RGBA(255, 255, 255, 1)); + assert.deepStrictEqual(Color.Format.CSS.parseHex('#000000')!.rgba, new RGBA(0, 0, 0, 1)); + assert.deepStrictEqual(Color.Format.CSS.parseHex('#FFFFFF')!.rgba, new RGBA(255, 255, 255, 1)); - assert.deepEqual(Color.Format.CSS.parseHex('#FF0000')!.rgba, new RGBA(255, 0, 0, 1)); - assert.deepEqual(Color.Format.CSS.parseHex('#00FF00')!.rgba, new RGBA(0, 255, 0, 1)); - assert.deepEqual(Color.Format.CSS.parseHex('#0000FF')!.rgba, new RGBA(0, 0, 255, 1)); + assert.deepStrictEqual(Color.Format.CSS.parseHex('#FF0000')!.rgba, new RGBA(255, 0, 0, 1)); + assert.deepStrictEqual(Color.Format.CSS.parseHex('#00FF00')!.rgba, new RGBA(0, 255, 0, 1)); + assert.deepStrictEqual(Color.Format.CSS.parseHex('#0000FF')!.rgba, new RGBA(0, 0, 255, 1)); - assert.deepEqual(Color.Format.CSS.parseHex('#FFFF00')!.rgba, new RGBA(255, 255, 0, 1)); - assert.deepEqual(Color.Format.CSS.parseHex('#00FFFF')!.rgba, new RGBA(0, 255, 255, 1)); - assert.deepEqual(Color.Format.CSS.parseHex('#FF00FF')!.rgba, new RGBA(255, 0, 255, 1)); + assert.deepStrictEqual(Color.Format.CSS.parseHex('#FFFF00')!.rgba, new RGBA(255, 255, 0, 1)); + assert.deepStrictEqual(Color.Format.CSS.parseHex('#00FFFF')!.rgba, new RGBA(0, 255, 255, 1)); + assert.deepStrictEqual(Color.Format.CSS.parseHex('#FF00FF')!.rgba, new RGBA(255, 0, 255, 1)); - assert.deepEqual(Color.Format.CSS.parseHex('#C0C0C0')!.rgba, new RGBA(192, 192, 192, 1)); + assert.deepStrictEqual(Color.Format.CSS.parseHex('#C0C0C0')!.rgba, new RGBA(192, 192, 192, 1)); - assert.deepEqual(Color.Format.CSS.parseHex('#808080')!.rgba, new RGBA(128, 128, 128, 1)); - assert.deepEqual(Color.Format.CSS.parseHex('#800000')!.rgba, new RGBA(128, 0, 0, 1)); - assert.deepEqual(Color.Format.CSS.parseHex('#808000')!.rgba, new RGBA(128, 128, 0, 1)); - assert.deepEqual(Color.Format.CSS.parseHex('#008000')!.rgba, new RGBA(0, 128, 0, 1)); - assert.deepEqual(Color.Format.CSS.parseHex('#800080')!.rgba, new RGBA(128, 0, 128, 1)); - assert.deepEqual(Color.Format.CSS.parseHex('#008080')!.rgba, new RGBA(0, 128, 128, 1)); - assert.deepEqual(Color.Format.CSS.parseHex('#000080')!.rgba, new RGBA(0, 0, 128, 1)); + assert.deepStrictEqual(Color.Format.CSS.parseHex('#808080')!.rgba, new RGBA(128, 128, 128, 1)); + assert.deepStrictEqual(Color.Format.CSS.parseHex('#800000')!.rgba, new RGBA(128, 0, 0, 1)); + assert.deepStrictEqual(Color.Format.CSS.parseHex('#808000')!.rgba, new RGBA(128, 128, 0, 1)); + assert.deepStrictEqual(Color.Format.CSS.parseHex('#008000')!.rgba, new RGBA(0, 128, 0, 1)); + assert.deepStrictEqual(Color.Format.CSS.parseHex('#800080')!.rgba, new RGBA(128, 0, 128, 1)); + assert.deepStrictEqual(Color.Format.CSS.parseHex('#008080')!.rgba, new RGBA(0, 128, 128, 1)); + assert.deepStrictEqual(Color.Format.CSS.parseHex('#000080')!.rgba, new RGBA(0, 0, 128, 1)); - assert.deepEqual(Color.Format.CSS.parseHex('#010203')!.rgba, new RGBA(1, 2, 3, 1)); - assert.deepEqual(Color.Format.CSS.parseHex('#040506')!.rgba, new RGBA(4, 5, 6, 1)); - assert.deepEqual(Color.Format.CSS.parseHex('#070809')!.rgba, new RGBA(7, 8, 9, 1)); - assert.deepEqual(Color.Format.CSS.parseHex('#0a0A0a')!.rgba, new RGBA(10, 10, 10, 1)); - assert.deepEqual(Color.Format.CSS.parseHex('#0b0B0b')!.rgba, new RGBA(11, 11, 11, 1)); - assert.deepEqual(Color.Format.CSS.parseHex('#0c0C0c')!.rgba, new RGBA(12, 12, 12, 1)); - assert.deepEqual(Color.Format.CSS.parseHex('#0d0D0d')!.rgba, new RGBA(13, 13, 13, 1)); - assert.deepEqual(Color.Format.CSS.parseHex('#0e0E0e')!.rgba, new RGBA(14, 14, 14, 1)); - assert.deepEqual(Color.Format.CSS.parseHex('#0f0F0f')!.rgba, new RGBA(15, 15, 15, 1)); - assert.deepEqual(Color.Format.CSS.parseHex('#a0A0a0')!.rgba, new RGBA(160, 160, 160, 1)); - assert.deepEqual(Color.Format.CSS.parseHex('#CFA')!.rgba, new RGBA(204, 255, 170, 1)); - assert.deepEqual(Color.Format.CSS.parseHex('#CFA8')!.rgba, new RGBA(204, 255, 170, 0.533)); + assert.deepStrictEqual(Color.Format.CSS.parseHex('#010203')!.rgba, new RGBA(1, 2, 3, 1)); + assert.deepStrictEqual(Color.Format.CSS.parseHex('#040506')!.rgba, new RGBA(4, 5, 6, 1)); + assert.deepStrictEqual(Color.Format.CSS.parseHex('#070809')!.rgba, new RGBA(7, 8, 9, 1)); + assert.deepStrictEqual(Color.Format.CSS.parseHex('#0a0A0a')!.rgba, new RGBA(10, 10, 10, 1)); + assert.deepStrictEqual(Color.Format.CSS.parseHex('#0b0B0b')!.rgba, new RGBA(11, 11, 11, 1)); + assert.deepStrictEqual(Color.Format.CSS.parseHex('#0c0C0c')!.rgba, new RGBA(12, 12, 12, 1)); + assert.deepStrictEqual(Color.Format.CSS.parseHex('#0d0D0d')!.rgba, new RGBA(13, 13, 13, 1)); + assert.deepStrictEqual(Color.Format.CSS.parseHex('#0e0E0e')!.rgba, new RGBA(14, 14, 14, 1)); + assert.deepStrictEqual(Color.Format.CSS.parseHex('#0f0F0f')!.rgba, new RGBA(15, 15, 15, 1)); + assert.deepStrictEqual(Color.Format.CSS.parseHex('#a0A0a0')!.rgba, new RGBA(160, 160, 160, 1)); + assert.deepStrictEqual(Color.Format.CSS.parseHex('#CFA')!.rgba, new RGBA(204, 255, 170, 1)); + assert.deepStrictEqual(Color.Format.CSS.parseHex('#CFA8')!.rgba, new RGBA(204, 255, 170, 0.533)); }); }); }); diff --git a/src/vs/base/test/common/console.test.ts b/src/vs/base/test/common/console.test.ts index 178c3037..36439bc8 100644 --- a/src/vs/base/test/common/console.test.ts +++ b/src/vs/base/test/common/console.test.ts @@ -13,36 +13,36 @@ suite('Console', () => { let stack = 'at vscode.commands.registerCommand (/Users/someone/Desktop/test-ts/out/src/extension.js:18:17)'; let frame = getFirstFrame(stack)!; - assert.equal(frame.uri.fsPath, normalize('/Users/someone/Desktop/test-ts/out/src/extension.js')); - assert.equal(frame.line, 18); - assert.equal(frame.column, 17); + assert.strictEqual(frame.uri.fsPath, normalize('/Users/someone/Desktop/test-ts/out/src/extension.js')); + assert.strictEqual(frame.line, 18); + assert.strictEqual(frame.column, 17); stack = 'at /Users/someone/Desktop/test-ts/out/src/extension.js:18:17'; frame = getFirstFrame(stack)!; - assert.equal(frame.uri.fsPath, normalize('/Users/someone/Desktop/test-ts/out/src/extension.js')); - assert.equal(frame.line, 18); - assert.equal(frame.column, 17); + assert.strictEqual(frame.uri.fsPath, normalize('/Users/someone/Desktop/test-ts/out/src/extension.js')); + assert.strictEqual(frame.line, 18); + assert.strictEqual(frame.column, 17); stack = 'at c:\\Users\\someone\\Desktop\\end-js\\extension.js:18:17'; frame = getFirstFrame(stack)!; - assert.equal(frame.uri.fsPath, 'c:\\Users\\someone\\Desktop\\end-js\\extension.js'); - assert.equal(frame.line, 18); - assert.equal(frame.column, 17); + assert.strictEqual(frame.uri.fsPath, 'c:\\Users\\someone\\Desktop\\end-js\\extension.js'); + assert.strictEqual(frame.line, 18); + assert.strictEqual(frame.column, 17); stack = 'at e.$executeContributedCommand(c:\\Users\\someone\\Desktop\\end-js\\extension.js:18:17)'; frame = getFirstFrame(stack)!; - assert.equal(frame.uri.fsPath, 'c:\\Users\\someone\\Desktop\\end-js\\extension.js'); - assert.equal(frame.line, 18); - assert.equal(frame.column, 17); + assert.strictEqual(frame.uri.fsPath, 'c:\\Users\\someone\\Desktop\\end-js\\extension.js'); + assert.strictEqual(frame.line, 18); + assert.strictEqual(frame.column, 17); stack = 'at /Users/someone/Desktop/test-ts/out/src/extension.js:18:17\nat /Users/someone/Desktop/test-ts/out/src/other.js:28:27\nat /Users/someone/Desktop/test-ts/out/src/more.js:38:37'; frame = getFirstFrame(stack)!; - assert.equal(frame.uri.fsPath, normalize('/Users/someone/Desktop/test-ts/out/src/extension.js')); - assert.equal(frame.line, 18); - assert.equal(frame.column, 17); + assert.strictEqual(frame.uri.fsPath, normalize('/Users/someone/Desktop/test-ts/out/src/extension.js')); + assert.strictEqual(frame.line, 18); + assert.strictEqual(frame.column, 17); }); }); diff --git a/src/vs/base/test/common/decorators.test.ts b/src/vs/base/test/common/decorators.test.ts index 9a01f60a..0e88e4f2 100644 --- a/src/vs/base/test/common/decorators.test.ts +++ b/src/vs/base/test/common/decorators.test.ts @@ -22,35 +22,35 @@ suite('Decorators', () => { } const foo = new Foo(42); - assert.equal(foo.count, 0); - assert.equal(foo.answer(), 42); - assert.equal(foo.count, 1); - assert.equal(foo.answer(), 42); - assert.equal(foo.count, 1); + assert.strictEqual(foo.count, 0); + assert.strictEqual(foo.answer(), 42); + assert.strictEqual(foo.count, 1); + assert.strictEqual(foo.answer(), 42); + assert.strictEqual(foo.count, 1); const foo2 = new Foo(1337); - assert.equal(foo2.count, 0); - assert.equal(foo2.answer(), 1337); - assert.equal(foo2.count, 1); - assert.equal(foo2.answer(), 1337); - assert.equal(foo2.count, 1); + assert.strictEqual(foo2.count, 0); + assert.strictEqual(foo2.answer(), 1337); + assert.strictEqual(foo2.count, 1); + assert.strictEqual(foo2.answer(), 1337); + assert.strictEqual(foo2.count, 1); - assert.equal(foo.answer(), 42); - assert.equal(foo.count, 1); + assert.strictEqual(foo.answer(), 42); + assert.strictEqual(foo.count, 1); const foo3 = new Foo(null); - assert.equal(foo3.count, 0); - assert.equal(foo3.answer(), null); - assert.equal(foo3.count, 1); - assert.equal(foo3.answer(), null); - assert.equal(foo3.count, 1); + assert.strictEqual(foo3.count, 0); + assert.strictEqual(foo3.answer(), null); + assert.strictEqual(foo3.count, 1); + assert.strictEqual(foo3.answer(), null); + assert.strictEqual(foo3.count, 1); const foo4 = new Foo(undefined); - assert.equal(foo4.count, 0); - assert.equal(foo4.answer(), undefined); - assert.equal(foo4.count, 1); - assert.equal(foo4.answer(), undefined); - assert.equal(foo4.count, 1); + assert.strictEqual(foo4.count, 0); + assert.strictEqual(foo4.answer(), undefined); + assert.strictEqual(foo4.count, 1); + assert.strictEqual(foo4.answer(), undefined); + assert.strictEqual(foo4.count, 1); }); test('memoize should memoize getters', () => { @@ -67,35 +67,35 @@ suite('Decorators', () => { } const foo = new Foo(42); - assert.equal(foo.count, 0); - assert.equal(foo.answer, 42); - assert.equal(foo.count, 1); - assert.equal(foo.answer, 42); - assert.equal(foo.count, 1); + assert.strictEqual(foo.count, 0); + assert.strictEqual(foo.answer, 42); + assert.strictEqual(foo.count, 1); + assert.strictEqual(foo.answer, 42); + assert.strictEqual(foo.count, 1); const foo2 = new Foo(1337); - assert.equal(foo2.count, 0); - assert.equal(foo2.answer, 1337); - assert.equal(foo2.count, 1); - assert.equal(foo2.answer, 1337); - assert.equal(foo2.count, 1); + assert.strictEqual(foo2.count, 0); + assert.strictEqual(foo2.answer, 1337); + assert.strictEqual(foo2.count, 1); + assert.strictEqual(foo2.answer, 1337); + assert.strictEqual(foo2.count, 1); - assert.equal(foo.answer, 42); - assert.equal(foo.count, 1); + assert.strictEqual(foo.answer, 42); + assert.strictEqual(foo.count, 1); const foo3 = new Foo(null); - assert.equal(foo3.count, 0); - assert.equal(foo3.answer, null); - assert.equal(foo3.count, 1); - assert.equal(foo3.answer, null); - assert.equal(foo3.count, 1); + assert.strictEqual(foo3.count, 0); + assert.strictEqual(foo3.answer, null); + assert.strictEqual(foo3.count, 1); + assert.strictEqual(foo3.answer, null); + assert.strictEqual(foo3.count, 1); const foo4 = new Foo(undefined); - assert.equal(foo4.count, 0); - assert.equal(foo4.answer, undefined); - assert.equal(foo4.count, 1); - assert.equal(foo4.answer, undefined); - assert.equal(foo4.count, 1); + assert.strictEqual(foo4.count, 0); + assert.strictEqual(foo4.answer, undefined); + assert.strictEqual(foo4.count, 1); + assert.strictEqual(foo4.answer, undefined); + assert.strictEqual(foo4.count, 1); }); test('memoized property should not be enumerable', () => { @@ -107,7 +107,7 @@ suite('Decorators', () => { } const foo = new Foo(); - assert.equal(foo.answer, 42); + assert.strictEqual(foo.answer, 42); assert(!Object.keys(foo).some(k => /\$memoize\$/.test(k))); }); @@ -121,13 +121,13 @@ suite('Decorators', () => { } const foo = new Foo(); - assert.equal(foo.answer, 42); + assert.strictEqual(foo.answer, 42); try { (foo as any)['$memoize$answer'] = 1337; assert(false); } catch (e) { - assert.equal(foo.answer, 42); + assert.strictEqual(foo.answer, 42); } }); @@ -142,15 +142,15 @@ suite('Decorators', () => { } const foo = new Foo(); - assert.equal(foo.answer, 1); - assert.equal(foo.answer, 1); + assert.strictEqual(foo.answer, 1); + assert.strictEqual(foo.answer, 1); memoizer.clear(); - assert.equal(foo.answer, 2); - assert.equal(foo.answer, 2); + assert.strictEqual(foo.answer, 2); + assert.strictEqual(foo.answer, 2); memoizer.clear(); - assert.equal(foo.answer, 3); - assert.equal(foo.answer, 3); - assert.equal(foo.answer, 3); + assert.strictEqual(foo.answer, 3); + assert.strictEqual(foo.answer, 3); + assert.strictEqual(foo.answer, 3); }); test('throttle', () => { @@ -179,10 +179,10 @@ suite('Decorators', () => { t.report(1); t.report(2); t.report(3); - assert.deepEqual(spy.args, [[1]]); + assert.deepStrictEqual(spy.args, [[1]]); clock.tick(200); - assert.deepEqual(spy.args, [[1], [5]]); + assert.deepStrictEqual(spy.args, [[1], [5]]); spy.reset(); t.report(4); @@ -190,9 +190,9 @@ suite('Decorators', () => { clock.tick(50); t.report(6); - assert.deepEqual(spy.args, [[4]]); + assert.deepStrictEqual(spy.args, [[4]]); clock.tick(60); - assert.deepEqual(spy.args, [[4], [11]]); + assert.deepStrictEqual(spy.args, [[4], [11]]); } finally { clock.restore(); } diff --git a/src/vs/base/test/common/event.test.ts b/src/vs/base/test/common/event.test.ts index 1dda7864..a0f55cd4 100644 --- a/src/vs/base/test/common/event.test.ts +++ b/src/vs/base/test/common/event.test.ts @@ -644,7 +644,7 @@ suite('Event utils', () => { emitter.fire(1); emitter.fire(2); emitter.fire(3); - assert.deepEqual(result, []); + assert.deepStrictEqual(result, [] as number[]); const listener = bufferedEvent(num => result.push(num)); assert.deepStrictEqual(result, [1, 2, 3]); @@ -666,7 +666,7 @@ suite('Event utils', () => { emitter.fire(1); emitter.fire(2); emitter.fire(3); - assert.deepEqual(result, []); + assert.deepStrictEqual(result, [] as number[]); const listener = bufferedEvent(num => result.push(num)); assert.deepStrictEqual(result, []); @@ -688,7 +688,7 @@ suite('Event utils', () => { emitter.fire(1); emitter.fire(2); emitter.fire(3); - assert.deepEqual(result, []); + assert.deepStrictEqual(result, [] as number[]); bufferedEvent(num => result.push(num)); assert.deepStrictEqual(result, [-2, -1, 0, 1, 2, 3]); diff --git a/src/vs/base/test/common/filters.test.ts b/src/vs/base/test/common/filters.test.ts index 3489c5fd..34c96318 100644 --- a/src/vs/base/test/common/filters.test.ts +++ b/src/vs/base/test/common/filters.test.ts @@ -28,22 +28,22 @@ suite('Filters', () => { counters = [0, 0]; filter = or(newFilter(0, false), newFilter(1, false)); filterNotOk(filter, 'anything', 'anything'); - assert.deepEqual(counters, [1, 1]); + assert.deepStrictEqual(counters, [1, 1]); counters = [0, 0]; filter = or(newFilter(0, true), newFilter(1, false)); filterOk(filter, 'anything', 'anything'); - assert.deepEqual(counters, [1, 0]); + assert.deepStrictEqual(counters, [1, 0]); counters = [0, 0]; filter = or(newFilter(0, true), newFilter(1, true)); filterOk(filter, 'anything', 'anything'); - assert.deepEqual(counters, [1, 0]); + assert.deepStrictEqual(counters, [1, 0]); counters = [0, 0]; filter = or(newFilter(0, false), newFilter(1, true)); filterOk(filter, 'anything', 'anything'); - assert.deepEqual(counters, [1, 1]); + assert.deepStrictEqual(counters, [1, 1]); }); test('PrefixFilter - case sensitive', function () { @@ -201,7 +201,7 @@ suite('Filters', () => { filterOk(matchesWords, 'öäk', 'Öhm: Älles Klar', [{ start: 0, end: 1 }, { start: 5, end: 6 }, { start: 11, end: 12 }]); // assert.ok(matchesWords('gipu', 'Category: Git: Pull', true) === null); - // assert.deepEqual(matchesWords('pu', 'Category: Git: Pull', true), [{ start: 15, end: 17 }]); + // assert.deepStrictEqual(matchesWords('pu', 'Category: Git: Pull', true), [{ start: 15, end: 17 }]); filterOk(matchesWords, 'bar', 'foo-bar'); filterOk(matchesWords, 'bar test', 'foo-bar test'); @@ -476,9 +476,9 @@ suite('Filters', () => { // assertTopScore(fuzzyScore, 'Editor.r', 0, 'diffEditor.renderSideBySide', 'editor.overviewRulerlanes', 'editor.renderControlCharacter', 'editor.renderWhitespace'); assertTopScore(fuzzyScore, '-mo', 1, '-ms-ime-mode', '-moz-columns'); - // // dupe, issue #14861 + // dupe, issue #14861 assertTopScore(fuzzyScore, 'convertModelPosition', 0, 'convertModelPositionToViewPosition', 'convertViewToModelPosition'); - // // dupe, issue #14942 + // dupe, issue #14942 assertTopScore(fuzzyScore, 'is', 0, 'isValidViewletId', 'import statement'); assertTopScore(fuzzyScore, 'title', 1, 'files.trimTrailingWhitespace', 'window.title'); diff --git a/src/vs/base/test/common/glob.test.ts b/src/vs/base/test/common/glob.test.ts index dc40c7b4..8c29e3da 100644 --- a/src/vs/base/test/common/glob.test.ts +++ b/src/vs/base/test/common/glob.test.ts @@ -661,15 +661,15 @@ suite('Glob', () => { }); test('split glob aware', function () { - assert.deepEqual(glob.splitGlobAware('foo,bar', ','), ['foo', 'bar']); - assert.deepEqual(glob.splitGlobAware('foo', ','), ['foo']); - assert.deepEqual(glob.splitGlobAware('{foo,bar}', ','), ['{foo,bar}']); - assert.deepEqual(glob.splitGlobAware('foo,bar,{foo,bar}', ','), ['foo', 'bar', '{foo,bar}']); - assert.deepEqual(glob.splitGlobAware('{foo,bar},foo,bar,{foo,bar}', ','), ['{foo,bar}', 'foo', 'bar', '{foo,bar}']); + assert.deepStrictEqual(glob.splitGlobAware('foo,bar', ','), ['foo', 'bar']); + assert.deepStrictEqual(glob.splitGlobAware('foo', ','), ['foo']); + assert.deepStrictEqual(glob.splitGlobAware('{foo,bar}', ','), ['{foo,bar}']); + assert.deepStrictEqual(glob.splitGlobAware('foo,bar,{foo,bar}', ','), ['foo', 'bar', '{foo,bar}']); + assert.deepStrictEqual(glob.splitGlobAware('{foo,bar},foo,bar,{foo,bar}', ','), ['{foo,bar}', 'foo', 'bar', '{foo,bar}']); - assert.deepEqual(glob.splitGlobAware('[foo,bar]', ','), ['[foo,bar]']); - assert.deepEqual(glob.splitGlobAware('foo,bar,[foo,bar]', ','), ['foo', 'bar', '[foo,bar]']); - assert.deepEqual(glob.splitGlobAware('[foo,bar],foo,bar,[foo,bar]', ','), ['[foo,bar]', 'foo', 'bar', '[foo,bar]']); + assert.deepStrictEqual(glob.splitGlobAware('[foo,bar]', ','), ['[foo,bar]']); + assert.deepStrictEqual(glob.splitGlobAware('foo,bar,[foo,bar]', ','), ['foo', 'bar', '[foo,bar]']); + assert.deepStrictEqual(glob.splitGlobAware('[foo,bar],foo,bar,[foo,bar]', ','), ['[foo,bar]', 'foo', 'bar', '[foo,bar]']); }); test('expression with disabled glob', function () { diff --git a/src/vs/base/test/common/history.test.ts b/src/vs/base/test/common/history.test.ts index 8b92348b..74521107 100644 --- a/src/vs/base/test/common/history.test.ts +++ b/src/vs/base/test/common/history.test.ts @@ -10,28 +10,28 @@ suite('History Navigator', () => { test('create reduces the input to limit', () => { const testObject = new HistoryNavigator(['1', '2', '3', '4'], 2); - assert.deepEqual(['3', '4'], toArray(testObject)); + assert.deepStrictEqual(['3', '4'], toArray(testObject)); }); test('create sets the position to last', () => { const testObject = new HistoryNavigator(['1', '2', '3', '4'], 100); - assert.equal(testObject.current(), null); - assert.equal(testObject.next(), null); - assert.equal(testObject.previous(), '4'); + assert.strictEqual(testObject.current(), null); + assert.strictEqual(testObject.next(), null); + assert.strictEqual(testObject.previous(), '4'); }); test('last returns last element', () => { const testObject = new HistoryNavigator(['1', '2', '3', '4'], 100); - assert.equal(testObject.first(), '1'); - assert.equal(testObject.last(), '4'); + assert.strictEqual(testObject.first(), '1'); + assert.strictEqual(testObject.last(), '4'); }); test('first returns first element', () => { const testObject = new HistoryNavigator(['1', '2', '3', '4'], 3); - assert.equal('2', testObject.first()); + assert.strictEqual('2', testObject.first()); }); test('next returns next element', () => { @@ -39,18 +39,18 @@ suite('History Navigator', () => { testObject.first(); - assert.equal(testObject.next(), '3'); - assert.equal(testObject.next(), '4'); - assert.equal(testObject.next(), null); + assert.strictEqual(testObject.next(), '3'); + assert.strictEqual(testObject.next(), '4'); + assert.strictEqual(testObject.next(), null); }); test('previous returns previous element', () => { const testObject = new HistoryNavigator(['1', '2', '3', '4'], 3); - assert.equal(testObject.previous(), '4'); - assert.equal(testObject.previous(), '3'); - assert.equal(testObject.previous(), '2'); - assert.equal(testObject.previous(), null); + assert.strictEqual(testObject.previous(), '4'); + assert.strictEqual(testObject.previous(), '3'); + assert.strictEqual(testObject.previous(), '2'); + assert.strictEqual(testObject.previous(), null); }); test('next on last element returs null and remains on last', () => { @@ -59,8 +59,8 @@ suite('History Navigator', () => { testObject.first(); testObject.last(); - assert.equal(testObject.current(), '4'); - assert.equal(testObject.next(), null); + assert.strictEqual(testObject.current(), '4'); + assert.strictEqual(testObject.next(), null); }); test('previous on first element returs null and remains on first', () => { @@ -68,8 +68,8 @@ suite('History Navigator', () => { testObject.first(); - assert.equal(testObject.current(), '2'); - assert.equal(testObject.previous(), null); + assert.strictEqual(testObject.current(), '2'); + assert.strictEqual(testObject.previous(), null); }); test('add reduces the input to limit', () => { @@ -77,7 +77,7 @@ suite('History Navigator', () => { testObject.add('5'); - assert.deepEqual(toArray(testObject), ['4', '5']); + assert.deepStrictEqual(toArray(testObject), ['4', '5']); }); test('adding existing element changes the position', () => { @@ -85,7 +85,7 @@ suite('History Navigator', () => { testObject.add('2'); - assert.deepEqual(toArray(testObject), ['1', '3', '4', '2']); + assert.deepStrictEqual(toArray(testObject), ['1', '3', '4', '2']); }); test('add resets the navigator to last', () => { @@ -94,8 +94,8 @@ suite('History Navigator', () => { testObject.first(); testObject.add('5'); - assert.equal(testObject.previous(), '5'); - assert.equal(testObject.next(), null); + assert.strictEqual(testObject.previous(), '5'); + assert.strictEqual(testObject.next(), null); }); test('adding an existing item changes the order', () => { @@ -103,7 +103,7 @@ suite('History Navigator', () => { testObject.add('1'); - assert.deepEqual(['2', '3', '1'], toArray(testObject)); + assert.deepStrictEqual(['2', '3', '1'], toArray(testObject)); }); test('previous returns null if the current position is the first one', () => { @@ -111,7 +111,7 @@ suite('History Navigator', () => { testObject.first(); - assert.deepEqual(testObject.previous(), null); + assert.deepStrictEqual(testObject.previous(), null); }); test('previous returns object if the current position is not the first one', () => { @@ -120,7 +120,7 @@ suite('History Navigator', () => { testObject.first(); testObject.next(); - assert.deepEqual(testObject.previous(), '1'); + assert.deepStrictEqual(testObject.previous(), '1'); }); test('next returns null if the current position is the last one', () => { @@ -128,7 +128,7 @@ suite('History Navigator', () => { testObject.last(); - assert.deepEqual(testObject.next(), null); + assert.deepStrictEqual(testObject.next(), null); }); test('next returns object if the current position is not the last one', () => { @@ -137,14 +137,14 @@ suite('History Navigator', () => { testObject.last(); testObject.previous(); - assert.deepEqual(testObject.next(), '3'); + assert.deepStrictEqual(testObject.next(), '3'); }); test('clear', () => { const testObject = new HistoryNavigator(['a', 'b', 'c']); - assert.equal(testObject.previous(), 'c'); + assert.strictEqual(testObject.previous(), 'c'); testObject.clear(); - assert.equal(testObject.current(), undefined); + assert.strictEqual(testObject.current(), null); }); function toArray(historyNavigator: HistoryNavigator): Array { diff --git a/src/vs/base/test/common/iconLabels.test.ts b/src/vs/base/test/common/iconLabels.test.ts index ab17675c..1402dca9 100644 --- a/src/vs/base/test/common/iconLabels.test.ts +++ b/src/vs/base/test/common/iconLabels.test.ts @@ -16,7 +16,7 @@ function filterOk(filter: IIconFilter, word: string, target: IParsedLabelWithIco let r = filter(word, target); assert(r); if (highlights) { - assert.deepEqual(r, highlights); + assert.deepStrictEqual(r, highlights); } } diff --git a/src/vs/base/test/common/iterator.test.ts b/src/vs/base/test/common/iterator.test.ts index a7cdd692..186ebc86 100644 --- a/src/vs/base/test/common/iterator.test.ts +++ b/src/vs/base/test/common/iterator.test.ts @@ -19,10 +19,10 @@ suite('Iterable', function () { test('first', function () { - assert.equal(Iterable.first([]), undefined); - assert.equal(Iterable.first([1]), 1); - assert.equal(Iterable.first(customIterable), 'one'); - assert.equal(Iterable.first(customIterable), 'one'); // fresh + assert.strictEqual(Iterable.first([]), undefined); + assert.strictEqual(Iterable.first([1]), 1); + assert.strictEqual(Iterable.first(customIterable), 'one'); + assert.strictEqual(Iterable.first(customIterable), 'one'); // fresh }); test('equals', () => { diff --git a/src/vs/base/test/common/json.test.ts b/src/vs/base/test/common/json.test.ts index bd66bc67..16f49198 100644 --- a/src/vs/base/test/common/json.test.ts +++ b/src/vs/base/test/common/json.test.ts @@ -12,15 +12,15 @@ function assertKinds(text: string, ...kinds: SyntaxKind[]): void { let scanner = createScanner(text); let kind: SyntaxKind; while ((kind = scanner.scan()) !== SyntaxKind.EOF) { - assert.equal(kind, kinds.shift()); + assert.strictEqual(kind, kinds.shift()); } - assert.equal(kinds.length, 0); + assert.strictEqual(kinds.length, 0); } function assertScanError(text: string, expectedKind: SyntaxKind, scanError: ScanError): void { let scanner = createScanner(text); scanner.scan(); - assert.equal(scanner.getToken(), expectedKind); - assert.equal(scanner.getTokenError(), scanError); + assert.strictEqual(scanner.getToken(), expectedKind); + assert.strictEqual(scanner.getTokenError(), scanError); } function assertValidParse(input: string, expected: any, options?: ParseOptions): void { @@ -30,7 +30,7 @@ function assertValidParse(input: string, expected: any, options?: ParseOptions): if (errors.length !== 0) { assert(false, getParseErrorMessage(errors[0].error)); } - assert.deepEqual(actual, expected); + assert.deepStrictEqual(actual, expected); } function assertInvalidParse(input: string, expected: any, options?: ParseOptions): void { @@ -38,18 +38,18 @@ function assertInvalidParse(input: string, expected: any, options?: ParseOptions let actual = parse(input, errors, options); assert(errors.length > 0); - assert.deepEqual(actual, expected); + assert.deepStrictEqual(actual, expected); } function assertTree(input: string, expected: any, expectedErrors: number[] = [], options?: ParseOptions): void { let errors: ParseError[] = []; let actual = parseTree(input, errors, options); - assert.deepEqual(errors.map(e => e.error, expected), expectedErrors); + assert.deepStrictEqual(errors.map(e => e.error, expected), expectedErrors); let checkParent = (node: Node) => { if (node.children) { for (let child of node.children) { - assert.equal(node, child.parent); + assert.strictEqual(node, child.parent); delete (child).parent; // delete to avoid recursion in deep equal checkParent(child); } @@ -57,7 +57,7 @@ function assertTree(input: string, expected: any, expectedErrors: number[] = [], }; checkParent(actual); - assert.deepEqual(actual, expected); + assert.deepStrictEqual(actual, expected); } suite('JSON', () => { diff --git a/src/vs/base/test/common/jsonEdit.test.ts b/src/vs/base/test/common/jsonEdit.test.ts index 09936392..9b398ad0 100644 --- a/src/vs/base/test/common/jsonEdit.test.ts +++ b/src/vs/base/test/common/jsonEdit.test.ts @@ -19,7 +19,7 @@ suite('JSON - edits', () => { lastEditOffset = edit.offset; content = content.substring(0, edit.offset) + edit.content + content.substring(edit.offset + edit.length); } - assert.equal(content, expected); + assert.strictEqual(content, expected); } let formatterOptions: FormattingOptions = { diff --git a/src/vs/base/test/common/jsonFormatter.test.ts b/src/vs/base/test/common/jsonFormatter.test.ts index 71aa3d8d..68dbad4c 100644 --- a/src/vs/base/test/common/jsonFormatter.test.ts +++ b/src/vs/base/test/common/jsonFormatter.test.ts @@ -28,7 +28,7 @@ suite('JSON - formatter', () => { content = content.substring(0, edit.offset) + edit.content + content.substring(edit.offset + edit.length); } - assert.equal(content, expected); + assert.strictEqual(content, expected); } test('object - single property', () => { @@ -438,4 +438,4 @@ suite('JSON - formatter', () => { format(content, expected); }); -}); \ No newline at end of file +}); diff --git a/src/vs/base/test/common/labels.test.ts b/src/vs/base/test/common/labels.test.ts index 90aa411b..b01853ba 100644 --- a/src/vs/base/test/common/labels.test.ts +++ b/src/vs/base/test/common/labels.test.ts @@ -118,13 +118,13 @@ suite('Labels', () => { assert.strictEqual(labels.template('${separator}Foo${separator}Bar', { value: 'something', separator: { label: ' - ' } }), 'Foo - Bar'); assert.strictEqual(labels.template('${value} Foo${separator}Bar', { value: 'something', separator: { label: ' - ' } }), 'something Foo - Bar'); - // // real world example (macOS) + // real world example (macOS) let t = '${activeEditorShort}${separator}${rootName}'; assert.strictEqual(labels.template(t, { activeEditorShort: '', rootName: '', separator: { label: ' - ' } }), ''); assert.strictEqual(labels.template(t, { activeEditorShort: '', rootName: 'root', separator: { label: ' - ' } }), 'root'); assert.strictEqual(labels.template(t, { activeEditorShort: 'markdown.txt', rootName: 'root', separator: { label: ' - ' } }), 'markdown.txt - root'); - // // real world example (other) + // real world example (other) t = '${dirty}${activeEditorShort}${separator}${rootName}${separator}${appName}'; assert.strictEqual(labels.template(t, { dirty: '', activeEditorShort: '', rootName: '', appName: '', separator: { label: ' - ' } }), ''); assert.strictEqual(labels.template(t, { dirty: '', activeEditorShort: '', rootName: '', appName: 'Visual Studio Code', separator: { label: ' - ' } }), 'Visual Studio Code'); diff --git a/src/vs/base/test/common/lazy.test.ts b/src/vs/base/test/common/lazy.test.ts index 04d4a256..1f9ab65f 100644 --- a/src/vs/base/test/common/lazy.test.ts +++ b/src/vs/base/test/common/lazy.test.ts @@ -37,14 +37,14 @@ suite('Lazy', () => { assert.strictEqual(outerLazy.hasValue(), false); assert.strictEqual(innerLazy.hasValue(), false); - assert.deepEqual(innerLazy.getValue(), [1, 11]); + assert.deepStrictEqual(innerLazy.getValue(), [1, 11]); assert.strictEqual(outerLazy.hasValue(), true); assert.strictEqual(innerLazy.hasValue(), true); assert.strictEqual(outerLazy.getValue(), 1); // make sure we did not evaluate again assert.strictEqual(outerLazy.getValue(), 1); - assert.deepEqual(innerLazy.getValue(), [1, 11]); + assert.deepStrictEqual(innerLazy.getValue(), [1, 11]); }); test('map should handle error values', () => { diff --git a/src/vs/base/test/common/marshalling.test.ts b/src/vs/base/test/common/marshalling.test.ts index c886abea..7485a52b 100644 --- a/src/vs/base/test/common/marshalling.test.ts +++ b/src/vs/base/test/common/marshalling.test.ts @@ -13,10 +13,10 @@ suite('Marshalling', () => { let raw = stringify(value); let clone = parse(raw); - assert.equal(value.source, clone.source); - assert.equal(value.global, clone.global); - assert.equal(value.ignoreCase, clone.ignoreCase); - assert.equal(value.multiline, clone.multiline); + assert.strictEqual(value.source, clone.source); + assert.strictEqual(value.global, clone.global); + assert.strictEqual(value.ignoreCase, clone.ignoreCase); + assert.strictEqual(value.multiline, clone.multiline); }); test('URI', () => { @@ -24,15 +24,15 @@ suite('Marshalling', () => { const raw = stringify(value); const clone = parse(raw); - assert.equal(value.scheme, clone.scheme); - assert.equal(value.authority, clone.authority); - assert.equal(value.path, clone.path); - assert.equal(value.query, clone.query); - assert.equal(value.fragment, clone.fragment); + assert.strictEqual(value.scheme, clone.scheme); + assert.strictEqual(value.authority, clone.authority); + assert.strictEqual(value.path, clone.path); + assert.strictEqual(value.query, clone.query); + assert.strictEqual(value.fragment, clone.fragment); }); test('Bug 16793:# in folder name => mirror models get out of sync', () => { const uri1 = URI.file('C:\\C#\\file.txt'); - assert.equal(parse(stringify(uri1)).toString(), uri1.toString()); + assert.strictEqual(parse(stringify(uri1)).toString(), uri1.toString()); }); -}); \ No newline at end of file +}); diff --git a/src/vs/base/test/common/normalization.test.ts b/src/vs/base/test/common/normalization.test.ts index aebe84a0..4f08e472 100644 --- a/src/vs/base/test/common/normalization.test.ts +++ b/src/vs/base/test/common/normalization.test.ts @@ -9,57 +9,57 @@ import { removeAccents } from 'vs/base/common/normalization'; suite('Normalization', () => { test('removeAccents', function () { - assert.equal(removeAccents('joào'), 'joao'); - assert.equal(removeAccents('joáo'), 'joao'); - assert.equal(removeAccents('joâo'), 'joao'); - assert.equal(removeAccents('joäo'), 'joao'); - // assert.equal(strings.removeAccents('joæo'), 'joao'); // not an accent - assert.equal(removeAccents('joão'), 'joao'); - assert.equal(removeAccents('joåo'), 'joao'); - assert.equal(removeAccents('joåo'), 'joao'); - assert.equal(removeAccents('joāo'), 'joao'); + assert.strictEqual(removeAccents('joào'), 'joao'); + assert.strictEqual(removeAccents('joáo'), 'joao'); + assert.strictEqual(removeAccents('joâo'), 'joao'); + assert.strictEqual(removeAccents('joäo'), 'joao'); + // assert.strictEqual(strings.removeAccents('joæo'), 'joao'); // not an accent + assert.strictEqual(removeAccents('joão'), 'joao'); + assert.strictEqual(removeAccents('joåo'), 'joao'); + assert.strictEqual(removeAccents('joåo'), 'joao'); + assert.strictEqual(removeAccents('joāo'), 'joao'); - assert.equal(removeAccents('fôo'), 'foo'); - assert.equal(removeAccents('föo'), 'foo'); - assert.equal(removeAccents('fòo'), 'foo'); - assert.equal(removeAccents('fóo'), 'foo'); - // assert.equal(strings.removeAccents('fœo'), 'foo'); - // assert.equal(strings.removeAccents('føo'), 'foo'); - assert.equal(removeAccents('fōo'), 'foo'); - assert.equal(removeAccents('fõo'), 'foo'); + assert.strictEqual(removeAccents('fôo'), 'foo'); + assert.strictEqual(removeAccents('föo'), 'foo'); + assert.strictEqual(removeAccents('fòo'), 'foo'); + assert.strictEqual(removeAccents('fóo'), 'foo'); + // assert.strictEqual(strings.removeAccents('fœo'), 'foo'); + // assert.strictEqual(strings.removeAccents('føo'), 'foo'); + assert.strictEqual(removeAccents('fōo'), 'foo'); + assert.strictEqual(removeAccents('fõo'), 'foo'); - assert.equal(removeAccents('andrè'), 'andre'); - assert.equal(removeAccents('andré'), 'andre'); - assert.equal(removeAccents('andrê'), 'andre'); - assert.equal(removeAccents('andrë'), 'andre'); - assert.equal(removeAccents('andrē'), 'andre'); - assert.equal(removeAccents('andrė'), 'andre'); - assert.equal(removeAccents('andrę'), 'andre'); + assert.strictEqual(removeAccents('andrè'), 'andre'); + assert.strictEqual(removeAccents('andré'), 'andre'); + assert.strictEqual(removeAccents('andrê'), 'andre'); + assert.strictEqual(removeAccents('andrë'), 'andre'); + assert.strictEqual(removeAccents('andrē'), 'andre'); + assert.strictEqual(removeAccents('andrė'), 'andre'); + assert.strictEqual(removeAccents('andrę'), 'andre'); - assert.equal(removeAccents('hvîc'), 'hvic'); - assert.equal(removeAccents('hvïc'), 'hvic'); - assert.equal(removeAccents('hvíc'), 'hvic'); - assert.equal(removeAccents('hvīc'), 'hvic'); - assert.equal(removeAccents('hvįc'), 'hvic'); - assert.equal(removeAccents('hvìc'), 'hvic'); + assert.strictEqual(removeAccents('hvîc'), 'hvic'); + assert.strictEqual(removeAccents('hvïc'), 'hvic'); + assert.strictEqual(removeAccents('hvíc'), 'hvic'); + assert.strictEqual(removeAccents('hvīc'), 'hvic'); + assert.strictEqual(removeAccents('hvįc'), 'hvic'); + assert.strictEqual(removeAccents('hvìc'), 'hvic'); - assert.equal(removeAccents('ûdo'), 'udo'); - assert.equal(removeAccents('üdo'), 'udo'); - assert.equal(removeAccents('ùdo'), 'udo'); - assert.equal(removeAccents('údo'), 'udo'); - assert.equal(removeAccents('ūdo'), 'udo'); + assert.strictEqual(removeAccents('ûdo'), 'udo'); + assert.strictEqual(removeAccents('üdo'), 'udo'); + assert.strictEqual(removeAccents('ùdo'), 'udo'); + assert.strictEqual(removeAccents('údo'), 'udo'); + assert.strictEqual(removeAccents('ūdo'), 'udo'); - assert.equal(removeAccents('heÿ'), 'hey'); + assert.strictEqual(removeAccents('heÿ'), 'hey'); - // assert.equal(strings.removeAccents('gruß'), 'grus'); - assert.equal(removeAccents('gruś'), 'grus'); - assert.equal(removeAccents('gruš'), 'grus'); + // assert.strictEqual(strings.removeAccents('gruß'), 'grus'); + assert.strictEqual(removeAccents('gruś'), 'grus'); + assert.strictEqual(removeAccents('gruš'), 'grus'); - assert.equal(removeAccents('çool'), 'cool'); - assert.equal(removeAccents('ćool'), 'cool'); - assert.equal(removeAccents('čool'), 'cool'); + assert.strictEqual(removeAccents('çool'), 'cool'); + assert.strictEqual(removeAccents('ćool'), 'cool'); + assert.strictEqual(removeAccents('čool'), 'cool'); - assert.equal(removeAccents('ñice'), 'nice'); - assert.equal(removeAccents('ńice'), 'nice'); + assert.strictEqual(removeAccents('ñice'), 'nice'); + assert.strictEqual(removeAccents('ńice'), 'nice'); }); }); diff --git a/src/vs/base/test/common/objects.test.ts b/src/vs/base/test/common/objects.test.ts index 38e2954a..d9855429 100644 --- a/src/vs/base/test/common/objects.test.ts +++ b/src/vs/base/test/common/objects.test.ts @@ -60,10 +60,10 @@ suite('Objects', () => { assert(foo.bar); assert(Array.isArray(foo.bar)); - assert.equal(foo.bar.length, 3); - assert.equal(foo.bar[0], 1); - assert.equal(foo.bar[1], 2); - assert.equal(foo.bar[2], 3); + assert.strictEqual(foo.bar.length, 3); + assert.strictEqual(foo.bar[0], 1); + assert.strictEqual(foo.bar[1], 2); + assert.strictEqual(foo.bar[2], 3); }); test('mixin - no overwrite', function () { @@ -77,7 +77,7 @@ suite('Objects', () => { objects.mixin(foo, bar, false); - assert.equal(foo.bar, '123'); + assert.strictEqual(foo.bar, '123'); }); test('cloneAndChange', () => { @@ -86,7 +86,7 @@ suite('Objects', () => { o1: o1, o2: o1 }; - assert.deepEqual(objects.cloneAndChange(o, () => { }), o); + assert.deepStrictEqual(objects.cloneAndChange(o, () => { }), o); }); test('safeStringify', () => { @@ -121,7 +121,7 @@ suite('Objects', () => { let result = objects.safeStringify(circular); - assert.deepEqual(JSON.parse(result), { + assert.deepStrictEqual(JSON.parse(result), { a: 42, b: '[Circular]', c: [ @@ -147,12 +147,12 @@ suite('Objects', () => { }; let diff = objects.distinct(base, base); - assert.deepEqual(diff, {}); + assert.strictEqual(Object.keys(diff).length, 0); let obj = {}; diff = objects.distinct(base, obj); - assert.deepEqual(diff, {}); + assert.strictEqual(Object.keys(diff).length, 0); obj = { one: 'one', @@ -160,7 +160,7 @@ suite('Objects', () => { }; diff = objects.distinct(base, obj); - assert.deepEqual(diff, {}); + assert.strictEqual(Object.keys(diff).length, 0); obj = { three: { @@ -170,7 +170,7 @@ suite('Objects', () => { }; diff = objects.distinct(base, obj); - assert.deepEqual(diff, {}); + assert.strictEqual(Object.keys(diff).length, 0); obj = { one: 'two', @@ -182,10 +182,9 @@ suite('Objects', () => { }; diff = objects.distinct(base, obj); - assert.deepEqual(diff, { - one: 'two', - four: true - }); + assert.strictEqual(Object.keys(diff).length, 2); + assert.strictEqual(diff.one, 'two'); + assert.strictEqual(diff.four, true); obj = { one: null, @@ -197,10 +196,9 @@ suite('Objects', () => { }; diff = objects.distinct(base, obj); - assert.deepEqual(diff, { - one: null, - four: undefined - }); + assert.strictEqual(Object.keys(diff).length, 2); + assert.strictEqual(diff.one, null); + assert.strictEqual(diff.four, undefined); obj = { one: 'two', @@ -210,7 +208,11 @@ suite('Objects', () => { }; diff = objects.distinct(base, obj); - assert.deepEqual(diff, obj); + assert.strictEqual(Object.keys(diff).length, 4); + assert.strictEqual(diff.one, 'two'); + assert.strictEqual(diff.two, 3); + assert.strictEqual(diff.three?.['3'], false); + assert.strictEqual(diff.four, true); }); test('getCaseInsensitive', () => { @@ -219,10 +221,10 @@ suite('Objects', () => { mIxEdCaSe: 456 }; - assert.equal(obj1.lowercase, objects.getCaseInsensitive(obj1, 'lowercase')); - assert.equal(obj1.lowercase, objects.getCaseInsensitive(obj1, 'lOwErCaSe')); + assert.strictEqual(obj1.lowercase, objects.getCaseInsensitive(obj1, 'lowercase')); + assert.strictEqual(obj1.lowercase, objects.getCaseInsensitive(obj1, 'lOwErCaSe')); - assert.equal(obj1.mIxEdCaSe, objects.getCaseInsensitive(obj1, 'MIXEDCASE')); - assert.equal(obj1.mIxEdCaSe, objects.getCaseInsensitive(obj1, 'mixedcase')); + assert.strictEqual(obj1.mIxEdCaSe, objects.getCaseInsensitive(obj1, 'MIXEDCASE')); + assert.strictEqual(obj1.mIxEdCaSe, objects.getCaseInsensitive(obj1, 'mixedcase')); }); }); diff --git a/src/vs/base/test/common/paging.test.ts b/src/vs/base/test/common/paging.test.ts index da7ef18f..9aa967f5 100644 --- a/src/vs/base/test/common/paging.test.ts +++ b/src/vs/base/test/common/paging.test.ts @@ -148,7 +148,7 @@ suite('PagedModel', () => { const model = new PagedModel(pager); - assert.equal(state, 'idle'); + assert.strictEqual(state, 'idle'); const tokenSource1 = new CancellationTokenSource(); const promise1 = model.resolve(5, tokenSource1.token).then( @@ -156,7 +156,7 @@ suite('PagedModel', () => { err => assert(isPromiseCanceledError(err)) ); - assert.equal(state, 'resolving'); + assert.strictEqual(state, 'resolving'); const tokenSource2 = new CancellationTokenSource(); const promise2 = model.resolve(6, tokenSource2.token).then( @@ -164,17 +164,17 @@ suite('PagedModel', () => { err => assert(isPromiseCanceledError(err)) ); - assert.equal(state, 'resolving'); + assert.strictEqual(state, 'resolving'); setTimeout(() => { - assert.equal(state, 'resolving'); + assert.strictEqual(state, 'resolving'); tokenSource1.cancel(); - assert.equal(state, 'resolving'); + assert.strictEqual(state, 'resolving'); setTimeout(() => { - assert.equal(state, 'resolving'); + assert.strictEqual(state, 'resolving'); tokenSource2.cancel(); - assert.equal(state, 'idle'); + assert.strictEqual(state, 'idle'); }, 10); }, 10); diff --git a/src/vs/base/test/common/path.test.ts b/src/vs/base/test/common/path.test.ts index d74ce9f7..a896579d 100644 --- a/src/vs/base/test/common/path.test.ts +++ b/src/vs/base/test/common/path.test.ts @@ -353,11 +353,11 @@ suite('Paths (Node Implementation)', () => { assert.strictEqual(path.posix.extname('file.\\\\'), '.\\\\'); // Tests from VSCode - assert.equal(path.extname('far.boo'), '.boo'); - assert.equal(path.extname('far.b'), '.b'); - assert.equal(path.extname('far.'), '.'); - assert.equal(path.extname('far.boo/boo.far'), '.far'); - assert.equal(path.extname('far.boo/boo'), ''); + assert.strictEqual(path.extname('far.boo'), '.boo'); + assert.strictEqual(path.extname('far.b'), '.b'); + assert.strictEqual(path.extname('far.'), '.'); + assert.strictEqual(path.extname('far.boo/boo.far'), '.far'); + assert.strictEqual(path.extname('far.boo/boo'), ''); }); (isWeb && isWindows ? test.skip : test)('resolve', () => { // TODO@sbatten fails on windows & browser only @@ -501,25 +501,25 @@ suite('Paths (Node Implementation)', () => { controlCharFilename); // Tests from VSCode - assert.equal(path.basename('foo/bar'), 'bar'); - assert.equal(path.posix.basename('foo\\bar'), 'foo\\bar'); - assert.equal(path.win32.basename('foo\\bar'), 'bar'); - assert.equal(path.basename('/foo/bar'), 'bar'); - assert.equal(path.posix.basename('\\foo\\bar'), '\\foo\\bar'); - assert.equal(path.win32.basename('\\foo\\bar'), 'bar'); - assert.equal(path.basename('./bar'), 'bar'); - assert.equal(path.posix.basename('.\\bar'), '.\\bar'); - assert.equal(path.win32.basename('.\\bar'), 'bar'); - assert.equal(path.basename('/bar'), 'bar'); - assert.equal(path.posix.basename('\\bar'), '\\bar'); - assert.equal(path.win32.basename('\\bar'), 'bar'); - assert.equal(path.basename('bar/'), 'bar'); - assert.equal(path.posix.basename('bar\\'), 'bar\\'); - assert.equal(path.win32.basename('bar\\'), 'bar'); - assert.equal(path.basename('bar'), 'bar'); - assert.equal(path.basename('////////'), ''); - assert.equal(path.posix.basename('\\\\\\\\'), '\\\\\\\\'); - assert.equal(path.win32.basename('\\\\\\\\'), ''); + assert.strictEqual(path.basename('foo/bar'), 'bar'); + assert.strictEqual(path.posix.basename('foo\\bar'), 'foo\\bar'); + assert.strictEqual(path.win32.basename('foo\\bar'), 'bar'); + assert.strictEqual(path.basename('/foo/bar'), 'bar'); + assert.strictEqual(path.posix.basename('\\foo\\bar'), '\\foo\\bar'); + assert.strictEqual(path.win32.basename('\\foo\\bar'), 'bar'); + assert.strictEqual(path.basename('./bar'), 'bar'); + assert.strictEqual(path.posix.basename('.\\bar'), '.\\bar'); + assert.strictEqual(path.win32.basename('.\\bar'), 'bar'); + assert.strictEqual(path.basename('/bar'), 'bar'); + assert.strictEqual(path.posix.basename('\\bar'), '\\bar'); + assert.strictEqual(path.win32.basename('\\bar'), 'bar'); + assert.strictEqual(path.basename('bar/'), 'bar'); + assert.strictEqual(path.posix.basename('bar\\'), 'bar\\'); + assert.strictEqual(path.win32.basename('bar\\'), 'bar'); + assert.strictEqual(path.basename('bar'), 'bar'); + assert.strictEqual(path.basename('////////'), ''); + assert.strictEqual(path.posix.basename('\\\\\\\\'), '\\\\\\\\'); + assert.strictEqual(path.win32.basename('\\\\\\\\'), ''); }); test('relative', () => { diff --git a/src/vs/base/test/common/processes.test.ts b/src/vs/base/test/common/processes.test.ts index b5a89e2a..8df2b7b7 100644 --- a/src/vs/base/test/common/processes.test.ts +++ b/src/vs/base/test/common/processes.test.ts @@ -27,7 +27,7 @@ suite('Processes', () => { GDK_PIXBUF_MODULEDIR: 'x', }; processes.sanitizeProcessEnvironment(env); - assert.equal(env['FOO'], 'bar'); - assert.equal(Object.keys(env).length, 1); + assert.strictEqual(env['FOO'], 'bar'); + assert.strictEqual(Object.keys(env).length, 1); }); }); diff --git a/src/vs/base/test/common/resourceTree.test.ts b/src/vs/base/test/common/resourceTree.test.ts index 2b2b7f68..965e49bb 100644 --- a/src/vs/base/test/common/resourceTree.test.ts +++ b/src/vs/base/test/common/resourceTree.test.ts @@ -10,63 +10,63 @@ import { URI } from 'vs/base/common/uri'; suite('ResourceTree', function () { test('ctor', function () { const tree = new ResourceTree(null); - assert.equal(tree.root.childrenCount, 0); + assert.strictEqual(tree.root.childrenCount, 0); }); test('simple', function () { const tree = new ResourceTree(null); tree.add(URI.file('/foo/bar.txt'), 'bar contents'); - assert.equal(tree.root.childrenCount, 1); + assert.strictEqual(tree.root.childrenCount, 1); let foo = tree.root.get('foo')!; assert(foo); - assert.equal(foo.childrenCount, 1); + assert.strictEqual(foo.childrenCount, 1); let bar = foo.get('bar.txt')!; assert(bar); - assert.equal(bar.element, 'bar contents'); + assert.strictEqual(bar.element, 'bar contents'); tree.add(URI.file('/hello.txt'), 'hello contents'); - assert.equal(tree.root.childrenCount, 2); + assert.strictEqual(tree.root.childrenCount, 2); let hello = tree.root.get('hello.txt')!; assert(hello); - assert.equal(hello.element, 'hello contents'); + assert.strictEqual(hello.element, 'hello contents'); tree.delete(URI.file('/foo/bar.txt')); - assert.equal(tree.root.childrenCount, 1); + assert.strictEqual(tree.root.childrenCount, 1); hello = tree.root.get('hello.txt')!; assert(hello); - assert.equal(hello.element, 'hello contents'); + assert.strictEqual(hello.element, 'hello contents'); }); test('folders with data', function () { const tree = new ResourceTree(null); - assert.equal(tree.root.childrenCount, 0); + assert.strictEqual(tree.root.childrenCount, 0); tree.add(URI.file('/foo'), 'foo'); - assert.equal(tree.root.childrenCount, 1); - assert.equal(tree.root.get('foo')!.element, 'foo'); + assert.strictEqual(tree.root.childrenCount, 1); + assert.strictEqual(tree.root.get('foo')!.element, 'foo'); tree.add(URI.file('/bar'), 'bar'); - assert.equal(tree.root.childrenCount, 2); - assert.equal(tree.root.get('bar')!.element, 'bar'); + assert.strictEqual(tree.root.childrenCount, 2); + assert.strictEqual(tree.root.get('bar')!.element, 'bar'); tree.add(URI.file('/foo/file.txt'), 'file'); - assert.equal(tree.root.childrenCount, 2); - assert.equal(tree.root.get('foo')!.element, 'foo'); - assert.equal(tree.root.get('bar')!.element, 'bar'); - assert.equal(tree.root.get('foo')!.get('file.txt')!.element, 'file'); + assert.strictEqual(tree.root.childrenCount, 2); + assert.strictEqual(tree.root.get('foo')!.element, 'foo'); + assert.strictEqual(tree.root.get('bar')!.element, 'bar'); + assert.strictEqual(tree.root.get('foo')!.get('file.txt')!.element, 'file'); tree.delete(URI.file('/foo')); - assert.equal(tree.root.childrenCount, 1); + assert.strictEqual(tree.root.childrenCount, 1); assert(!tree.root.get('foo')); - assert.equal(tree.root.get('bar')!.element, 'bar'); + assert.strictEqual(tree.root.get('bar')!.element, 'bar'); tree.delete(URI.file('/bar')); - assert.equal(tree.root.childrenCount, 0); + assert.strictEqual(tree.root.childrenCount, 0); assert(!tree.root.get('foo')); assert(!tree.root.get('bar')); }); diff --git a/src/vs/base/test/common/stream.test.ts b/src/vs/base/test/common/stream.test.ts index 9b4d73f7..4cb309a8 100644 --- a/src/vs/base/test/common/stream.test.ts +++ b/src/vs/base/test/common/stream.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { isReadableStream, newWriteableStream, Readable, consumeReadable, peekReadable, consumeStream, ReadableStream, toStream, toReadable, transform, peekStream, isReadableBufferedStream, listenStream } from 'vs/base/common/stream'; +import { isReadableStream, newWriteableStream, Readable, consumeReadable, peekReadable, consumeStream, ReadableStream, toStream, toReadable, transform, peekStream, isReadableBufferedStream, listenStream, prefixedReadable, prefixedStream } from 'vs/base/common/stream'; import { timeout } from 'vs/base/common/async'; suite('Stream', () => { @@ -386,7 +386,7 @@ suite('Stream', () => { test('toReadable', async () => { const readable = toReadable('1,2,3,4,5'); - const consumed = await consumeReadable(readable, strings => strings.join()); + const consumed = consumeReadable(readable, strings => strings.join()); assert.strictEqual(consumed, '1,2,3,4,5'); }); @@ -424,4 +424,49 @@ suite('Stream', () => { assert.strictEqual(listener1Called, true); assert.strictEqual(listener2Called, true); }); + + test('prefixedReadable', () => { + + // Basic + let readable = prefixedReadable('1,2', arrayToReadable(['3', '4', '5']), val => val.join(',')); + assert.strictEqual(consumeReadable(readable, val => val.join(',')), '1,2,3,4,5'); + + // Empty + readable = prefixedReadable('empty', arrayToReadable([]), val => val.join(',')); + assert.strictEqual(consumeReadable(readable, val => val.join(',')), 'empty'); + }); + + test('prefixedStream', async () => { + + // Basic + let stream = newWriteableStream(strings => strings.join()); + stream.write('3'); + stream.write('4'); + stream.write('5'); + stream.end(); + + let prefixStream = prefixedStream('1,2', stream, val => val.join(',')); + assert.strictEqual(await consumeStream(prefixStream, val => val.join(',')), '1,2,3,4,5'); + + // Empty + stream = newWriteableStream(strings => strings.join()); + stream.end(); + + prefixStream = prefixedStream('1,2', stream, val => val.join(',')); + assert.strictEqual(await consumeStream(prefixStream, val => val.join(',')), '1,2'); + + // Error + stream = newWriteableStream(strings => strings.join()); + stream.error(new Error('fail')); + + prefixStream = prefixedStream('error', stream, val => val.join(',')); + + let error; + try { + await consumeStream(prefixStream, val => val.join(',')); + } catch (e) { + error = e; + } + assert.ok(error); + }); }); diff --git a/src/vs/base/test/node/processes/processes.test.ts b/src/vs/base/test/node/processes/processes.test.ts index 5930f128..c289b23e 100644 --- a/src/vs/base/test/node/processes/processes.test.ts +++ b/src/vs/base/test/node/processes/processes.test.ts @@ -46,11 +46,11 @@ suite('Processes', () => { counter++; if (counter === 1) { - assert.equal(msgFromChild, msg1); + assert.strictEqual(msgFromChild, msg1); } else if (counter === 2) { - assert.equal(msgFromChild, msg2); + assert.strictEqual(msgFromChild, msg2); } else if (counter === 3) { - assert.equal(msgFromChild, msg3); + assert.strictEqual(msgFromChild, msg3); child.kill(); done(); diff --git a/src/vs/code/browser/workbench/workbench-web.html b/src/vs/code/browser/workbench/workbench-web.html deleted file mode 100644 index df8ec875..00000000 --- a/src/vs/code/browser/workbench/workbench-web.html +++ /dev/null @@ -1,80 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/vs/code/browser/workbench/workbench.ts b/src/vs/code/browser/workbench/workbench.ts index 337e04c1..4aebea3e 100644 --- a/src/vs/code/browser/workbench/workbench.ts +++ b/src/vs/code/browser/workbench/workbench.ts @@ -282,23 +282,28 @@ class WorkspaceProvider implements IWorkspaceProvider { public readonly payload: object ) { } - async open(workspace: IWorkspace, options?: { reuse?: boolean, payload?: object }): Promise { + async open(workspace: IWorkspace, options?: { reuse?: boolean, payload?: object }): Promise { if (options?.reuse && !options.payload && this.isSame(this.workspace, workspace)) { - return; // return early if workspace and environment is not changing and we are reusing window + return true; // return early if workspace and environment is not changing and we are reusing window } const targetHref = this.createTargetUrl(workspace, options); if (targetHref) { if (options?.reuse) { window.location.href = targetHref; + return true; } else { + let result; if (isStandalone) { - window.open(targetHref, '_blank', 'toolbar=no'); // ensures to open another 'standalone' window! + result = window.open(targetHref, '_blank', 'toolbar=no'); // ensures to open another 'standalone' window! } else { - window.open(targetHref); + result = window.open(targetHref); } + + return !!result; } } + return false; } private createTargetUrl(workspace: IWorkspace, options?: { reuse?: boolean, payload?: object }): string | undefined { @@ -408,13 +413,6 @@ class WindowIndicator implements IWindowIndicator { const config: IWorkbenchConstructionOptions & { folderUri?: UriComponents, workspaceUri?: UriComponents } = JSON.parse(configElementAttribute); - // Revive static extension locations - if (Array.isArray(config.staticExtensions)) { - config.staticExtensions.forEach(extension => { - extension.extensionLocation = URI.revive(extension.extensionLocation); - }); - } - // Find workspace to open and payload let foundWorkspace = false; let workspace: IWorkspace; @@ -522,7 +520,10 @@ class WindowIndicator implements IWindowIndicator { // Finally create workbench create(document.body, { ...config, - logLevel: logLevel ? parseLogLevel(logLevel) : undefined, + developmentOptions: { + logLevel: logLevel ? parseLogLevel(logLevel) : undefined, + ...config.developmentOptions + }, settingsSyncOptions, homeIndicator, windowIndicator, diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcess.js b/src/vs/code/electron-browser/sharedProcess/sharedProcess.js index 50d8d3f7..6c6e3f8c 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcess.js +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcess.js @@ -16,10 +16,15 @@ // Load shared process into window bootstrapWindow.load(['vs/code/electron-browser/sharedProcess/sharedProcessMain'], function (sharedProcess, configuration) { return sharedProcess.main(configuration); - }); - - - //#region Globals + }, + { + configureDeveloperSettings: function () { + return { + disallowReloadKeybinding: true + }; + } + } + ); /** * @returns {{ avoidMonkeyPatchFromAppInsights: () => void; }} @@ -30,12 +35,27 @@ } /** - * @returns {{ load: (modules: string[], resultCallback: (result, configuration: object) => any, options?: object) => unknown }} + * @typedef {import('../../../base/parts/sandbox/common/sandboxTypes').ISandboxConfiguration} ISandboxConfiguration + * + * @returns {{ + * load: ( + * modules: string[], + * resultCallback: (result, configuration: ISandboxConfiguration) => unknown, + * options?: { + * configureDeveloperSettings?: (config: ISandboxConfiguration) => { + * forceEnableDeveloperKeybindings?: boolean, + * disallowReloadKeybinding?: boolean, + * removeDeveloperKeybindingsAfterLoad?: boolean + * }, + * canModifyDOM?: (config: ISandboxConfiguration) => void, + * beforeLoaderConfig?: (loaderConfig: object) => void, + * beforeRequire?: () => void + * } + * ) => Promise + * }} */ function bootstrapWindowLib() { // @ts-ignore (defined in bootstrap-window.js) return window.MonacoBootstrapWindow; } - - //#endregion }()); diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index 93484491..0f2a79ca 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as fs from 'fs'; -import { release } from 'os'; +import { release, hostname } from 'os'; import { gracefulify } from 'graceful-fs'; import { ipcRenderer } from 'electron'; import product from 'vs/platform/product/common/product'; @@ -30,7 +30,7 @@ import { TelemetryAppenderChannel } from 'vs/platform/telemetry/common/telemetry import { TelemetryService } from 'vs/platform/telemetry/common/telemetryService'; import { AppInsightsAppender } from 'vs/platform/telemetry/node/appInsightsAppender'; import { ILogService, ILoggerService, MultiplexLogService, ConsoleLogger } from 'vs/platform/log/common/log'; -import { LogLevelChannelClient, FollowerLogService } from 'vs/platform/log/common/logIpc'; +import { LogLevelChannelClient, FollowerLogService, LoggerChannelClient } from 'vs/platform/log/common/logIpc'; import { LocalizationsService } from 'vs/platform/localizations/node/localizations'; import { ILocalizationsService } from 'vs/platform/localizations/common/localizations'; import { combinedDisposable, Disposable, toDisposable } from 'vs/base/common/lifecycle'; @@ -42,7 +42,6 @@ import { StorageDataCleaner } from 'vs/code/electron-browser/sharedProcess/contr import { LogsDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/logsDataCleaner'; import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/services'; import { MessagePortMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; -import { SpdLogLogger } from 'vs/platform/log/node/spdlogLog'; import { DiagnosticsService } from 'vs/platform/diagnostics/node/diagnosticsService'; import { IDiagnosticsService } from 'vs/platform/diagnostics/common/diagnostics'; import { FileService } from 'vs/platform/files/common/fileService'; @@ -55,7 +54,6 @@ import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyn import { UserDataSyncStoreService, UserDataSyncStoreManagementService } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; import { UserDataSyncUtilServiceClient, UserDataAutoSyncChannel, UserDataSyncMachinesServiceChannel, UserDataSyncAccountServiceChannel, UserDataSyncStoreManagementServiceChannel } from 'vs/platform/userDataSync/common/userDataSyncIpc'; import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; -import { LoggerService } from 'vs/platform/log/node/loggerService'; import { UserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSyncLog'; import { UserDataAutoSyncService } from 'vs/platform/userDataSync/electron-sandbox/userDataAutoSyncService'; import { NativeStorageService } from 'vs/platform/storage/electron-sandbox/storageService'; @@ -79,7 +77,6 @@ import { LocalizationsUpdater } from 'vs/code/electron-browser/sharedProcess/con import { DeprecatedExtensionsCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/deprecatedExtensionsCleaner'; import { onUnexpectedError, setUnexpectedErrorHandler } from 'vs/base/common/errors'; import { toErrorMessage } from 'vs/base/common/errorMessage'; -import { join } from 'vs/base/common/path'; import { TerminalIpcChannels } from 'vs/platform/terminal/common/terminal'; import { PtyHostService } from 'vs/platform/terminal/node/ptyHostService'; import { ILocalPtyService } from 'vs/platform/terminal/electron-sandbox/terminal'; @@ -87,6 +84,8 @@ import { UserDataSyncChannel } from 'vs/platform/userDataSync/common/userDataSyn import { IChecksumService } from 'vs/platform/checksum/common/checksumService'; import { ChecksumService } from 'vs/platform/checksum/node/checksumService'; import { CustomEndpointTelemetryService } from 'vs/platform/telemetry/node/customEndpointTelemetryService'; +import { URI } from 'vs/base/common/uri'; +import { joinPath } from 'vs/base/common/resources'; class SharedProcessMain extends Disposable { @@ -148,25 +147,29 @@ class SharedProcessMain extends Disposable { const productService = { _serviceBrand: undefined, ...product }; services.set(IProductService, productService); + // Main Process + const mainRouter = new StaticRouter(ctx => ctx === 'main'); + const mainProcessService = new MessagePortMainProcessService(this.server, mainRouter); + services.set(IMainProcessService, mainProcessService); + // Environment const environmentService = new NativeEnvironmentService(this.configuration.args, productService); services.set(INativeEnvironmentService, environmentService); + // Logger + const logLevelClient = new LogLevelChannelClient(this.server.getChannel('logLevel', mainRouter)); + const loggerService = new LoggerChannelClient(this.configuration.logLevel, logLevelClient.onDidChangeLogLevel, mainProcessService.getChannel('logger')); + services.set(ILoggerService, loggerService); + // Log - const mainRouter = new StaticRouter(ctx => ctx === 'main'); - const logLevelClient = new LogLevelChannelClient(this.server.getChannel('logLevel', mainRouter)); // we only use this for log levels const multiplexLogger = this._register(new MultiplexLogService([ this._register(new ConsoleLogger(this.configuration.logLevel)), - this._register(new SpdLogLogger('sharedprocess', join(environmentService.logsPath, 'sharedprocess.log'), true, this.configuration.logLevel)) + this._register(loggerService.createLogger(joinPath(URI.file(environmentService.logsPath), 'sharedprocess.log'), { name: 'sharedprocess' })) ])); const logService = this._register(new FollowerLogService(logLevelClient, multiplexLogger)); services.set(ILogService, logService); - // Main Process - const mainProcessService = new MessagePortMainProcessService(this.server, mainRouter); - services.set(IMainProcessService, mainProcessService); - // Files const fileService = this._register(new FileService(logService)); services.set(IFileService, fileService); @@ -205,18 +208,14 @@ class SharedProcessMain extends Disposable { const activeWindowRouter = new StaticRouter(ctx => activeWindowManager.getActiveClientId().then(id => ctx === id)); services.set(IExtensionRecommendationNotificationService, new ExtensionRecommendationNotificationServiceChannelClient(this.server.getChannel('extensionRecommendationNotification', activeWindowRouter))); - // Logger - const loggerService = this._register(new LoggerService(logService, fileService)); - services.set(ILoggerService, loggerService); - // Telemetry - const { appRoot, extensionsPath, extensionDevelopmentLocationURI, isBuilt, installSourcePath } = environmentService; - let telemetryService: ITelemetryService; let telemetryAppender: ITelemetryAppender; - if (!extensionDevelopmentLocationURI && !environmentService.disableTelemetry && productService.enableTelemetry) { + if (!environmentService.isExtensionDevelopment && !environmentService.disableTelemetry && productService.enableTelemetry) { telemetryAppender = new TelemetryLogAppender(loggerService, environmentService); + const { appRoot, extensionsPath, isBuilt, installSourcePath } = environmentService; + // Application Insights if (productService.aiConfig && productService.aiConfig.asimovKey && isBuilt) { const appInsightsAppender = new AppInsightsAppender('monacoworkbench', null, productService.aiConfig.asimovKey); @@ -226,7 +225,7 @@ class SharedProcessMain extends Disposable { telemetryService = new TelemetryService({ appender: telemetryAppender, - commonProperties: resolveCommonProperties(fileService, release(), process.arch, productService.commit, productService.version, this.configuration.machineId, productService.msftInternalDomains, installSourcePath), + commonProperties: resolveCommonProperties(fileService, release(), hostname(), process.arch, productService.commit, productService.version, this.configuration.machineId, productService.msftInternalDomains, installSourcePath), sendErrorTelemetry: true, piiPaths: [appRoot, extensionsPath] }, configurationService); @@ -273,7 +272,7 @@ class SharedProcessMain extends Disposable { services.set(IUserDataSyncService, new SyncDescriptor(UserDataSyncService)); // Terminal - services.set(ILocalPtyService, this._register(new PtyHostService(logService))); + services.set(ILocalPtyService, this._register(new PtyHostService(logService, telemetryService))); return new InstantiationService(services); } diff --git a/src/vs/code/electron-browser/workbench/workbench.html b/src/vs/code/electron-browser/workbench/workbench.html index f36737f2..2933be27 100644 --- a/src/vs/code/electron-browser/workbench/workbench.html +++ b/src/vs/code/electron-browser/workbench/workbench.html @@ -3,7 +3,7 @@ - + diff --git a/src/vs/code/electron-browser/workbench/workbench.js b/src/vs/code/electron-browser/workbench/workbench.js index 0c52f3cd..6a92a3c2 100644 --- a/src/vs/code/electron-browser/workbench/workbench.js +++ b/src/vs/code/electron-browser/workbench/workbench.js @@ -32,15 +32,38 @@ return require('vs/workbench/electron-browser/desktop.main').main(configuration); }, { - removeDeveloperKeybindingsAfterLoad: true, + configureDeveloperSettings: function (windowConfig) { + return { + // disable automated devtools opening on error when running extension tests + // as this can lead to undeterministic test exectuion (devtools steals focus) + forceDisableShowDevtoolsOnError: typeof windowConfig.extensionTestsPath === 'string', + // enable devtools keybindings in extension development window + forceEnableDeveloperKeybindings: Array.isArray(windowConfig.extensionDevelopmentPath) && windowConfig.extensionDevelopmentPath.length > 0, + removeDeveloperKeybindingsAfterLoad: true + }; + }, canModifyDOM: function (windowConfig) { showPartsSplash(windowConfig); }, - beforeLoaderConfig: function (windowConfig, loaderConfig) { + beforeLoaderConfig: function (loaderConfig) { loaderConfig.recordStats = true; }, beforeRequire: function () { performance.mark('code/willLoadWorkbenchMain'); + + // It looks like browsers only lazily enable + // the element when needed. Since we + // leverage canvas elements in our code in many + // locations, we try to help the browser to + // initialize canvas when it is idle, right + // before we wait for the scripts to be loaded. + // @ts-ignore + window.requestIdleCallback(() => { + const canvas = document.createElement('canvas'); + const context = canvas.getContext('2d'); + context.clearRect(0, 0, canvas.width, canvas.height); + canvas.remove(); + }, { timeout: 50 }); } } ); @@ -62,12 +85,27 @@ } }); - //region Helpers + //#region Helpers /** + * @typedef {import('../../../platform/windows/common/windows').INativeWindowConfiguration} INativeWindowConfiguration + * * @returns {{ - * load: (modules: string[], resultCallback: (result, configuration: import('../../../platform/windows/common/windows').INativeWindowConfiguration) => any, options: object) => unknown, - * globals: () => typeof import('../../../base/parts/sandbox/electron-sandbox/globals') + * load: ( + * modules: string[], + * resultCallback: (result, configuration: INativeWindowConfiguration) => unknown, + * options?: { + * configureDeveloperSettings?: (config: INativeWindowConfiguration & object) => { + * forceDisableShowDevtoolsOnError?: boolean, + * forceEnableDeveloperKeybindings?: boolean, + * disallowReloadKeybinding?: boolean, + * removeDeveloperKeybindingsAfterLoad?: boolean + * }, + * canModifyDOM?: (config: INativeWindowConfiguration & object) => void, + * beforeLoaderConfig?: (loaderConfig: object) => void, + * beforeRequire?: () => void + * } + * ) => Promise * }} */ function bootstrapWindowLib() { diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index d523f5c0..28fc931c 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { release } from 'os'; +import { release, hostname } from 'os'; import { statSync } from 'fs'; import { app, ipcMain, systemPreferences, contentTracing, protocol, BrowserWindow, dialog, session } from 'electron'; import { IProcessEnvironment, isWindows, isMacintosh, isLinux, isLinuxSnap } from 'vs/base/common/platform'; @@ -35,7 +35,6 @@ import { TelemetryService, ITelemetryServiceConfig } from 'vs/platform/telemetry import { resolveCommonProperties } from 'vs/platform/telemetry/common/commonProperties'; 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'; import { IWindowsMainService, ICodeWindow, OpenContext, WindowError } from 'vs/platform/windows/electron-main/windows'; import { URI } from 'vs/base/common/uri'; @@ -72,7 +71,7 @@ import { withNullAsUndefined } from 'vs/base/common/types'; import { mnemonicButtonLabel, getPathLabel } from 'vs/base/common/labels'; import { WebviewMainService } from 'vs/platform/webview/electron-main/webviewMainService'; import { IWebviewManagerService } from 'vs/platform/webview/common/webviewManagerService'; -import { IFileService } from 'vs/platform/files/common/files'; +import { FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files'; import { stripComments } from 'vs/base/common/json'; import { generateUuid } from 'vs/base/common/uuid'; import { VSBuffer } from 'vs/base/common/buffer'; @@ -94,6 +93,7 @@ import { ISignService } from 'vs/platform/sign/common/sign'; * even if the user starts many instances (e.g. from the command line). */ export class CodeApplication extends Disposable { + private windowsMainService: IWindowsMainService | undefined; private nativeHostMainService: INativeHostMainService | undefined; @@ -187,7 +187,7 @@ export class CodeApplication extends Disposable { const uri = URI.parse(source); if (uri.scheme === Schemas.vscodeWebview) { - return uri.path === '/index.html' || uri.path === '/electron-browser/index.html'; + return uri.path === '/index.html' || uri.path === '/electron-browser-index.html'; } const srcUri = uri.fsPath.toLowerCase(); @@ -226,17 +226,18 @@ export class CodeApplication extends Disposable { this.nativeHostMainService?.openExternal(undefined, url); }); - const webviewFrameUrl = 'about:blank?webviewFrame'; + const isUrlFromWebview = (requestingUrl: string) => + requestingUrl.startsWith(`${Schemas.vscodeWebview}://`); session.defaultSession.setPermissionRequestHandler((_webContents, permission /* 'media' | 'geolocation' | 'notifications' | 'midiSysex' | 'pointerLock' | 'fullscreen' | 'openExternal' */, callback, details) => { - if (details.requestingUrl === webviewFrameUrl) { + if (isUrlFromWebview(details.requestingUrl)) { return callback(permission === 'clipboard-read'); } return callback(false); }); session.defaultSession.setPermissionCheckHandler((_webContents, permission /* 'media' */, _origin, details) => { - if (details.requestingUrl === webviewFrameUrl) { + if (isUrlFromWebview(details.requestingUrl)) { return permission === 'clipboard-read'; } return false; @@ -282,71 +283,73 @@ export class CodeApplication extends Disposable { //#region Bootstrap IPC Handlers let slowShellResolveWarningShown = false; - ipcMain.on('vscode:fetchShellEnv', async event => { + ipcMain.handle('vscode:fetchShellEnv', event => { + return new Promise(async resolve => { - // DO NOT remove: not only usual windows are fetching the - // shell environment but also shared process, issue reporter - // etc, so we need to reply via `webContents` always - const webContents = event.sender; + // DO NOT remove: not only usual windows are fetching the + // shell environment but also shared process, issue reporter + // etc, so we need to reply via `webContents` always + const webContents = event.sender; - let replied = false; + let replied = false; - function acceptShellEnv(env: NodeJS.ProcessEnv): void { - clearTimeout(shellEnvSlowWarningHandle); - clearTimeout(shellEnvTimeoutErrorHandle); + function acceptShellEnv(env: IProcessEnvironment): void { + clearTimeout(shellEnvSlowWarningHandle); + clearTimeout(shellEnvTimeoutErrorHandle); - if (!replied) { - replied = true; + if (!replied) { + replied = true; - if (!webContents.isDestroyed()) { - webContents.send('vscode:acceptShellEnv', env); + if (!webContents.isDestroyed()) { + resolve(env); + } } } - } - // Handle slow shell environment resolve calls: - // - a warning after 3s but continue to resolve (only once in active window) - // - an error after 10s and stop trying to resolve (in every window where this happens) - const cts = new CancellationTokenSource(); + // Handle slow shell environment resolve calls: + // - a warning after 3s but continue to resolve (only once in active window) + // - an error after 10s and stop trying to resolve (in every window where this happens) + const cts = new CancellationTokenSource(); - const shellEnvSlowWarningHandle = setTimeout(() => { - if (!slowShellResolveWarningShown) { - this.windowsMainService?.sendToFocused('vscode:showShellEnvSlowWarning', cts.token); - slowShellResolveWarningShown = true; + const shellEnvSlowWarningHandle = setTimeout(() => { + if (!slowShellResolveWarningShown) { + this.windowsMainService?.sendToFocused('vscode:showShellEnvSlowWarning', cts.token); + slowShellResolveWarningShown = true; + } + }, 3000); + + const window = this.windowsMainService?.getWindowByWebContents(event.sender); // Note: this can be `undefined` for the shared process!! + const shellEnvTimeoutErrorHandle = setTimeout(() => { + cts.dispose(true); + window?.sendWhenReady('vscode:showShellEnvTimeoutError', CancellationToken.None); + acceptShellEnv({}); + }, 10000); + + // Prefer to use the args and env from the target window + // when resolving the shell env. It is possible that + // a first window was opened from the UI but a second + // from the CLI and that has implications for whether to + // resolve the shell environment or not. + // + // Window can be undefined for e.g. the shared process + // that is not part of our windows registry! + let args: NativeParsedArgs; + let env: IProcessEnvironment; + if (window?.config) { + args = window.config; + env = { ...process.env, ...window.config.userEnv }; + } else { + args = this.environmentMainService.args; + env = process.env; } - }, 3000); - const window = this.windowsMainService?.getWindowByWebContents(event.sender); // Note: this can be `undefined` for the shared process!! - const shellEnvTimeoutErrorHandle = setTimeout(() => { - cts.dispose(true); - window?.sendWhenReady('vscode:showShellEnvTimeoutError', CancellationToken.None); - acceptShellEnv({}); - }, 10000); - - // Prefer to use the args and env from the target window - // when resolving the shell env. It is possible that - // a first window was opened from the UI but a second - // from the CLI and that has implications for whether to - // resolve the shell environment or not. - // - // Window can be undefined for e.g. the shared process - // that is not part of our windows registry! - let args: NativeParsedArgs; - let env: NodeJS.ProcessEnv; - if (window?.config) { - args = window.config; - env = { ...process.env, ...window.config.userEnv }; - } else { - args = this.environmentMainService.args; - env = process.env; - } - - // Resolve shell env - const shellEnv = await resolveShellEnv(this.logService, args, env); - acceptShellEnv(shellEnv); + // Resolve shell env + const shellEnv = await resolveShellEnv(this.logService, args, env); + acceptShellEnv(shellEnv); + }); }); - ipcMain.handle('vscode:writeNlsFile', async (event, path: unknown, data: unknown) => { + ipcMain.handle('vscode:writeNlsFile', (event, path: unknown, data: unknown) => { const uri = this.validateNlsPath([path]); if (!uri || typeof data !== 'string') { throw new Error('Invalid operation (vscode:writeNlsFile)'); @@ -416,6 +419,18 @@ export class CodeApplication extends Disposable { this.logService.debug(`from: ${this.environmentMainService.appRoot}`); this.logService.debug('args:', this.environmentMainService.args); + // TODO@bpasero TODO@deepak1556 workaround for #120655 + try { + const cachedDataPath = URI.file(this.environmentMainService.chromeCachedDataDir); + this.logService.trace(`Deleting Chrome cached data path: ${cachedDataPath.fsPath}`); + + await this.fileService.del(cachedDataPath, { recursive: true }); + } catch (error) { + if ((error).fileOperationResult !== FileOperationResult.FILE_NOT_FOUND) { + this.logService.error(error); + } + } + // Make sure we associate the program with the app user model id // This will help Windows to associate the running program with // any shortcut that is pinned to the taskbar and prevent showing @@ -439,9 +454,6 @@ export class CodeApplication extends Disposable { this.logService.error(error); } - // Setup Protocol Handler - const fileProtocolHandler = this._register(this.mainInstantiationService.createInstance(FileProtocolHandler)); - // Main process server (electron IPC based) const mainProcessElectronServer = new ElectronIPCServer(); @@ -471,7 +483,7 @@ export class CodeApplication extends Disposable { appInstantiationService.invokeFunction(accessor => this.initChannels(accessor, mainProcessElectronServer, sharedProcessClient)); // Open Windows - const windows = appInstantiationService.invokeFunction(accessor => this.openFirstWindow(accessor, mainProcessElectronServer, fileProtocolHandler)); + const windows = appInstantiationService.invokeFunction(accessor => this.openFirstWindow(accessor, mainProcessElectronServer)); // Post Open Windows Tasks appInstantiationService.invokeFunction(accessor => this.afterWindowOpen(accessor, sharedProcess)); @@ -553,7 +565,7 @@ export class CodeApplication extends Disposable { services.set(IDiagnosticsService, ProxyChannel.toService(getDelayedChannel(sharedProcessReady.then(client => client.getChannel('diagnostics'))))); // Issues - services.set(IIssueMainService, new SyncDescriptor(IssueMainService, [machineId, this.userEnv])); + services.set(IIssueMainService, new SyncDescriptor(IssueMainService, [this.userEnv])); // Encryption services.set(IEncryptionMainService, new SyncDescriptor(EncryptionMainService, [machineId])); @@ -592,7 +604,7 @@ export class CodeApplication extends Disposable { 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, this.productService.commit, this.productService.version, machineId, this.productService.msftInternalDomains, this.environmentMainService.installSourcePath); + const commonProperties = resolveCommonProperties(this.fileService, release(), hostname(), 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 }; @@ -675,13 +687,14 @@ export class CodeApplication extends Disposable { // Logger const loggerChannel = new LoggerChannel(accessor.get(ILoggerService),); mainProcessElectronServer.registerChannel('logger', loggerChannel); + sharedProcessClient.then(client => client.registerChannel('logger', loggerChannel)); // Extension Host Debug Broadcasting const electronExtensionHostDebugBroadcastChannel = new ElectronExtensionHostDebugBroadcastChannel(accessor.get(IWindowsMainService)); mainProcessElectronServer.registerChannel('extensionhostdebugservice', electronExtensionHostDebugBroadcastChannel); } - private openFirstWindow(accessor: ServicesAccessor, mainProcessElectronServer: ElectronIPCServer, fileProtocolHandler: FileProtocolHandler): ICodeWindow[] { + private openFirstWindow(accessor: ServicesAccessor, mainProcessElectronServer: ElectronIPCServer): ICodeWindow[] { const windowsMainService = this.windowsMainService = accessor.get(IWindowsMainService); const urlService = accessor.get(IURLService); const nativeHostMainService = accessor.get(INativeHostMainService); @@ -689,9 +702,6 @@ export class CodeApplication extends Disposable { // Signal phase: ready (services set) this.lifecycleMainService.phase = LifecycleMainPhase.Ready; - // Forward windows main service to protocol handler - fileProtocolHandler.injectWindowsMainService(this.windowsMainService); - // Check for initial URLs to handle from protocol link invocations const pendingWindowOpenablesFromProtocolLinks: IWindowOpenable[] = []; const pendingProtocolLinksToHandle = [ @@ -906,6 +916,8 @@ export class CodeApplication extends Disposable { if (hasWorkspaceFileExtension(path)) { return { workspaceUri: remoteUri }; + } else if (/:[\d]+$/.test(path)) { // path with :line:column syntax + return { fileUri: remoteUri }; } else { return { folderUri: remoteUri }; } @@ -938,6 +950,8 @@ export class CodeApplication extends Disposable { this.lifecycleMainService.phase = LifecycleMainPhase.AfterWindowOpen; // Observe shared process for errors + let willShutdown = false; + once(this.lifecycleMainService.onWillShutdown)(() => willShutdown = true); const telemetryService = accessor.get(ITelemetryService); this._register(sharedProcess.onDidError(({ type, details }) => { @@ -955,16 +969,19 @@ export class CodeApplication extends Disposable { type: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true }; reason: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true }; visible: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true }; + shuttingdown: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true }; }; type SharedProcessErrorEvent = { type: WindowError; reason: string | undefined; visible: boolean; + shuttingdown: boolean; }; telemetryService.publicLog2('sharedprocesserror', { type, reason: typeof details !== 'string' ? details?.reason : undefined, - visible: sharedProcess.isVisible() + visible: sharedProcess.isVisible(), + shuttingdown: willShutdown }); })); diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index 19403af6..b4650d50 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -8,6 +8,7 @@ import { app, dialog } from 'electron'; import { promises, unlinkSync } from 'fs'; import { localize } from 'vs/nls'; import { isWindows, IProcessEnvironment, isMacintosh } from 'vs/base/common/platform'; +import { mark } from 'vs/base/common/performance'; import product from 'vs/platform/product/common/product'; import { parseMainProcessArgv, addArg } from 'vs/platform/environment/node/argvHelper'; import { createWaitMarkerFile } from 'vs/platform/environment/node/wait'; @@ -54,6 +55,8 @@ 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'; +import { IProtocolMainService } from 'vs/platform/protocol/electron-main/protocol'; +import { ProtocolMainService } from 'vs/platform/protocol/electron-main/protocolMainService'; /** * The main VS Code entry point. @@ -80,11 +83,8 @@ class CodeMain { // default electron error dialog popping up setUnexpectedErrorHandler(err => console.error(err)); - // Resolve command line arguments - const args = this.resolveArgs(); - // Create services - const [instantiationService, instanceEnvironment, environmentService, configurationService, stateService, bufferLogService, productService] = this.createServices(args); + const [instantiationService, instanceEnvironment, environmentService, configurationService, stateService, bufferLogService, productService] = this.createServices(); try { @@ -108,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, productService, true); + const mainProcessNodeIpcServer = await this.claimInstance(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()); @@ -126,7 +126,7 @@ class CodeMain { } } - private createServices(args: NativeParsedArgs): [IInstantiationService, IProcessEnvironment, IEnvironmentMainService, ConfigurationService, StateService, BufferLogService, IProductService] { + private createServices(): [IInstantiationService, IProcessEnvironment, IEnvironmentMainService, ConfigurationService, StateService, BufferLogService, IProductService] { const services = new ServiceCollection(); // Product @@ -134,7 +134,7 @@ class CodeMain { services.set(IProductService, productService); // Environment - const environmentMainService = new EnvironmentMainService(args, productService); + const environmentMainService = new EnvironmentMainService(this.resolveArgs(), productService); const instanceEnvironment = this.patchEnvironment(environmentMainService); // Patch `process.env` with the instance's environment services.set(IEnvironmentMainService, environmentMainService); @@ -178,6 +178,9 @@ class CodeMain { // Tunnel services.set(ITunnelService, new SyncDescriptor(TunnelService)); + // Protocol + services.set(IProtocolMainService, new SyncDescriptor(ProtocolMainService)); + return [new InstantiationService(services, true), instanceEnvironment, environmentMainService, configurationService, stateService, bufferLogService, productService]; } @@ -201,7 +204,7 @@ class CodeMain { private initServices(environmentMainService: IEnvironmentMainService, configurationService: ConfigurationService, stateService: StateService): Promise { // Environment service (paths) - const environmentServiceInitialization = Promise.all([ + const environmentServiceInitialization = Promise.all([ environmentMainService.extensionsPath, environmentMainService.nodeCachedDataDir, environmentMainService.logsPath, @@ -219,14 +222,16 @@ class CodeMain { return Promise.all([environmentServiceInitialization, configurationServiceInitialization, stateServiceInitialization]); } - private async doStartup(args: NativeParsedArgs, logService: ILogService, environmentMainService: IEnvironmentMainService, lifecycleMainService: ILifecycleMainService, instantiationService: IInstantiationService, productService: IProductService, retry: boolean): Promise { + private async claimInstance(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 // that another instance is already running. let mainProcessNodeIpcServer: NodeIPCServer; try { + mark('code/willStartMainServer'); mainProcessNodeIpcServer = await nodeIPCServe(environmentMainService.mainIPCHandle); + mark('code/didStartMainServer'); once(lifecycleMainService.onWillShutdown)(() => mainProcessNodeIpcServer.dispose()); } catch (error) { @@ -271,7 +276,7 @@ class CodeMain { throw error; } - return this.doStartup(args, logService, environmentMainService, lifecycleMainService, instantiationService, productService, false); + return this.claimInstance(logService, environmentMainService, lifecycleMainService, instantiationService, productService, false); } // Tests from CLI require to be the only instance currently @@ -287,7 +292,7 @@ class CodeMain { // Skip this if we are running with --wait where it is expected that we wait for a while. // Also skip when gathering diagnostics (--status) which can take a longer time. let startupWarningDialogHandle: NodeJS.Timeout | undefined = undefined; - if (!args.wait && !args.status) { + if (!environmentMainService.args.wait && !environmentMainService.args.status) { startupWarningDialogHandle = setTimeout(() => { this.showStartupWarningDialog( localize('secondInstanceNoResponse', "Another instance of {0} is running but not responding", productService.nameShort), @@ -300,7 +305,7 @@ class CodeMain { const launchService = ProxyChannel.toService(client.getChannel('launch'), { disableMarshalling: true }); // Process Info - if (args.status) { + if (environmentMainService.args.status) { return instantiationService.invokeFunction(async () => { const diagnosticsService = new DiagnosticsService(NullTelemetryService, productService); const mainProcessInfo = await launchService.getMainProcessInfo(); @@ -319,7 +324,7 @@ class CodeMain { // Send environment over... logService.trace('Sending env to running instance...'); - await launchService.start(args, process.env as IProcessEnvironment); + await launchService.start(environmentMainService.args, process.env as IProcessEnvironment); // Cleanup client.dispose(); @@ -333,7 +338,7 @@ class CodeMain { } // Print --status usage info - if (args.status) { + if (environmentMainService.args.status) { logService.warn('Warning: The --status argument can only be used if Code is already running. Please run it again after Code has started.'); throw new ExpectedError('Terminating...'); diff --git a/src/vs/code/electron-sandbox/issue/issueReporter.js b/src/vs/code/electron-sandbox/issue/issueReporter.js index a51159e5..caa0b29f 100644 --- a/src/vs/code/electron-sandbox/issue/issueReporter.js +++ b/src/vs/code/electron-sandbox/issue/issueReporter.js @@ -11,19 +11,40 @@ // Load issue reporter into window bootstrapWindow.load(['vs/code/electron-sandbox/issue/issueReporterMain'], function (issueReporter, configuration) { - issueReporter.startup(configuration); - }, { forceEnableDeveloperKeybindings: true, disallowReloadKeybinding: true }); - - - //#region Globals + return issueReporter.startup(configuration); + }, + { + configureDeveloperSettings: function () { + return { + forceEnableDeveloperKeybindings: true, + disallowReloadKeybinding: true + }; + } + } + ); /** - * @returns {{ load: (modules: string[], resultCallback: (result, configuration: object) => any, options?: object) => unknown }} + * @typedef {import('../../../base/parts/sandbox/common/sandboxTypes').ISandboxConfiguration} ISandboxConfiguration + * + * @returns {{ + * load: ( + * modules: string[], + * resultCallback: (result, configuration: ISandboxConfiguration) => unknown, + * options?: { + * configureDeveloperSettings?: (config: ISandboxConfiguration) => { + * forceEnableDeveloperKeybindings?: boolean, + * disallowReloadKeybinding?: boolean, + * removeDeveloperKeybindingsAfterLoad?: boolean + * }, + * canModifyDOM?: (config: ISandboxConfiguration) => void, + * beforeLoaderConfig?: (loaderConfig: object) => void, + * beforeRequire?: () => void + * } + * ) => Promise + * }} */ function bootstrapWindowLib() { // @ts-ignore (defined in bootstrap-window.js) return window.MonacoBootstrapWindow; } - - //#endregion }()); diff --git a/src/vs/code/electron-sandbox/issue/issueReporterMain.ts b/src/vs/code/electron-sandbox/issue/issueReporterMain.ts index 4217d4b5..ad1abc73 100644 --- a/src/vs/code/electron-sandbox/issue/issueReporterMain.ts +++ b/src/vs/code/electron-sandbox/issue/issueReporterMain.ts @@ -11,10 +11,10 @@ import { ipcRenderer } from 'vs/base/parts/sandbox/electron-sandbox/globals'; import { applyZoom, zoomIn, zoomOut } from 'vs/platform/windows/electron-sandbox/window'; import { $, reset, safeInnerHtml, windowOpenNoOpener } from 'vs/base/browser/dom'; import { Button } from 'vs/base/browser/ui/button/button'; -import * as collections from 'vs/base/common/collections'; +import { groupBy } from 'vs/base/common/collections'; import { debounce } from 'vs/base/common/decorators'; import { Disposable } from 'vs/base/common/lifecycle'; -import * as platform from 'vs/base/common/platform'; +import { isWindows, isLinux, isLinuxSnap, isMacintosh } from 'vs/base/common/platform'; import { escape } from 'vs/base/common/strings'; import { normalizeGitHubUrl } from 'vs/platform/issue/common/issueReporterUtil'; import { IssueReporterData as IssueReporterModelData, IssueReporterModel } from 'vs/code/electron-sandbox/issue/issueReporterModel'; @@ -23,8 +23,7 @@ import { localize } from 'vs/nls'; import { isRemoteDiagnosticError, SystemInfo } from 'vs/platform/diagnostics/common/diagnostics'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/services'; -import { IssueReporterData, IssueReporterExtensionData, IssueReporterFeatures, IssueReporterStyles, IssueType } from 'vs/platform/issue/common/issue'; -import { IWindowConfiguration } from 'vs/platform/windows/common/windows'; +import { IssueReporterWindowConfiguration, IssueReporterData, IssueReporterExtensionData, IssueReporterStyles, IssueType } from 'vs/platform/issue/common/issue'; import { Codicon } from 'vs/base/common/codicons'; import { renderIcon } from 'vs/base/browser/ui/iconLabel/iconLabels'; import { ElectronIPCMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService'; @@ -43,28 +42,8 @@ enum IssueSource { Marketplace = 'marketplace' } -export interface IssueReporterConfiguration extends IWindowConfiguration { - windowId: number; - disableExtensions: boolean; - data: IssueReporterData; - features: IssueReporterFeatures; - os: { - type: string; - arch: string; - release: string; - }, - product: { - nameShort: string; - version: string; - commit: string | undefined; - date: string | undefined; - reportIssueUrl: string | undefined; - reportMarketplaceIssueUrl: string | undefined; - } -} - -export function startup(configuration: IssueReporterConfiguration) { - const platformClass = platform.isWindows ? 'windows' : platform.isLinux ? 'linux' : 'mac'; +export function startup(configuration: IssueReporterWindowConfiguration) { + const platformClass = isWindows ? 'windows' : isLinux ? 'linux' : 'mac'; document.body.classList.add(platformClass); // used by our fonts safeInnerHtml(document.body, BaseHtml()); @@ -86,7 +65,7 @@ export class IssueReporter extends Disposable { private readonly previewButton!: Button; - constructor(private readonly configuration: IssueReporterConfiguration) { + constructor(private readonly configuration: IssueReporterWindowConfiguration) { super(); this.initServices(configuration); @@ -95,8 +74,8 @@ export class IssueReporter extends Disposable { this.issueReporterModel = new IssueReporterModel({ issueType: configuration.data.issueType || IssueType.Bug, versionInfo: { - vscodeVersion: `${configuration.product.nameShort} ${configuration.product.version} (${configuration.product.commit || 'Commit unknown'}, ${configuration.product.date || 'Date unknown'})`, - os: `${this.configuration.os.type} ${this.configuration.os.arch} ${this.configuration.os.release}${platform.isLinuxSnap ? ' snap' : ''}` + vscodeVersion: `${configuration.product.nameShort} ${!!configuration.product.darwinUniversalAssetId ? `${configuration.product.version} (Universal)` : configuration.product.version} (${configuration.product.commit || 'Commit unknown'}, ${configuration.product.date || 'Date unknown'})`, + os: `${this.configuration.os.type} ${this.configuration.os.arch} ${this.configuration.os.release}${isLinuxSnap ? ' snap' : ''}` }, extensionsDisabled: !!configuration.disableExtensions, fileOnExtension: configuration.data.extensionId ? !targetExtension?.isBuiltin : undefined, @@ -257,7 +236,7 @@ export class IssueReporter extends Disposable { private handleExtensionData(extensions: IssueReporterExtensionData[]) { const installedExtensions = extensions.filter(x => !x.isBuiltin); - const { nonThemes, themes } = collections.groupBy(installedExtensions, ext => { + const { nonThemes, themes } = groupBy(installedExtensions, ext => { return ext.isTheme ? 'themes' : 'nonThemes'; }); @@ -272,7 +251,7 @@ export class IssueReporter extends Disposable { this.updateExtensionSelector(installedExtensions); } - private initServices(configuration: IssueReporterConfiguration): void { + private initServices(configuration: IssueReporterWindowConfiguration): void { const serviceCollection = new ServiceCollection(); const mainProcessService = new ElectronIPCMainProcessService(configuration.windowId); serviceCollection.set(IMainProcessService, mainProcessService); @@ -400,7 +379,7 @@ export class IssueReporter extends Disposable { }); document.onkeydown = async (e: KeyboardEvent) => { - const cmdOrCtrlKey = platform.isMacintosh ? e.metaKey : e.ctrlKey; + const cmdOrCtrlKey = isMacintosh ? e.metaKey : e.ctrlKey; // Cmd/Ctrl+Enter previews issue and closes window if (cmdOrCtrlKey && e.keyCode === 13) { if (await this.createIssue()) { @@ -434,7 +413,7 @@ export class IssueReporter extends Disposable { // With latest electron upgrade, cmd+a is no longer propagating correctly for inputs in this window on mac // Manually perform the selection - if (platform.isMacintosh) { + if (isMacintosh) { if (cmdOrCtrlKey && e.keyCode === 65 && e.target) { if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) { (e.target).select(); diff --git a/src/vs/code/electron-sandbox/issue/test/testReporterModel.test.ts b/src/vs/code/electron-sandbox/issue/test/testReporterModel.test.ts index 215f6649..769fcd02 100644 --- a/src/vs/code/electron-sandbox/issue/test/testReporterModel.test.ts +++ b/src/vs/code/electron-sandbox/issue/test/testReporterModel.test.ts @@ -12,7 +12,7 @@ suite('IssueReporter', () => { test('sets defaults to include all data', () => { const issueReporterModel = new IssueReporterModel(); - assert.deepEqual(issueReporterModel.getData(), { + assert.deepStrictEqual(issueReporterModel.getData(), { allExtensions: [], includeSystemInfo: true, includeWorkspaceInfo: true, @@ -25,7 +25,7 @@ suite('IssueReporter', () => { test('serializes model skeleton when no data is provided', () => { const issueReporterModel = new IssueReporterModel({}); - assert.equal(issueReporterModel.serialize(), + assert.strictEqual(issueReporterModel.serialize(), ` Issue Type: Bug @@ -55,7 +55,7 @@ Extensions: none } } }); - assert.equal(issueReporterModel.serialize(), + assert.strictEqual(issueReporterModel.serialize(), ` Issue Type: Bug @@ -98,7 +98,7 @@ OS version: undefined }, experimentInfo: 'vsliv695:30137379\nvsins829:30139715' }); - assert.equal(issueReporterModel.serialize(), + assert.strictEqual(issueReporterModel.serialize(), ` Issue Type: Bug @@ -152,7 +152,7 @@ vsins829:30139715 } } }); - assert.equal(issueReporterModel.serialize(), + assert.strictEqual(issueReporterModel.serialize(), ` Issue Type: Bug @@ -208,7 +208,7 @@ OS version: undefined ] } }); - assert.equal(issueReporterModel.serialize(), + assert.strictEqual(issueReporterModel.serialize(), ` Issue Type: Bug @@ -256,7 +256,7 @@ Remote OS version: Linux x64 4.18.0 gpuStatus: {} } }); - assert.equal(issueReporterModel.serialize(), + assert.strictEqual(issueReporterModel.serialize(), ` Issue Type: Bug @@ -291,7 +291,7 @@ OS version: undefined 'https://github.com/repo/issues/new', 'https://github.com/repo/issues/new/' ].forEach(url => { - assert.equal('https://github.com/repo', normalizeGitHubUrl(url)); + assert.strictEqual('https://github.com/repo', normalizeGitHubUrl(url)); }); }); @@ -306,7 +306,7 @@ OS version: undefined fileOnExtension: true }); - assert.equal(issueReporterModel.fileOnExtension(), true); + assert.strictEqual(issueReporterModel.fileOnExtension(), true); }); }); }); diff --git a/src/vs/code/electron-sandbox/processExplorer/processExplorer.js b/src/vs/code/electron-sandbox/processExplorer/processExplorer.js index 36fb6ee5..7aeb6f35 100644 --- a/src/vs/code/electron-sandbox/processExplorer/processExplorer.js +++ b/src/vs/code/electron-sandbox/processExplorer/processExplorer.js @@ -11,19 +11,37 @@ // Load process explorer into window bootstrapWindow.load(['vs/code/electron-sandbox/processExplorer/processExplorerMain'], function (processExplorer, configuration) { - processExplorer.startup(configuration.windowId, configuration.data); - }, { forceEnableDeveloperKeybindings: true }); - - - //#region Globals + return processExplorer.startup(configuration); + }, { + configureDeveloperSettings: function () { + return { + forceEnableDeveloperKeybindings: true + }; + }, + }); /** - * @returns {{ load: (modules: string[], resultCallback: (result, configuration: object) => any, options?: object) => unknown }} + * @typedef {import('../../../base/parts/sandbox/common/sandboxTypes').ISandboxConfiguration} ISandboxConfiguration + * + * @returns {{ + * load: ( + * modules: string[], + * resultCallback: (result, configuration: ISandboxConfiguration) => unknown, + * options?: { + * configureDeveloperSettings?: (config: ISandboxConfiguration) => { + * forceEnableDeveloperKeybindings?: boolean, + * disallowReloadKeybinding?: boolean, + * removeDeveloperKeybindingsAfterLoad?: boolean + * }, + * canModifyDOM?: (config: ISandboxConfiguration) => void, + * beforeLoaderConfig?: (loaderConfig: object) => void, + * beforeRequire?: () => void + * } + * ) => Promise + * }} */ function bootstrapWindowLib() { // @ts-ignore (defined in bootstrap-window.js) return window.MonacoBootstrapWindow; } - - //#endregion }()); diff --git a/src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts b/src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts index e4e7be12..546c167d 100644 --- a/src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts +++ b/src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts @@ -9,13 +9,12 @@ import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; import { NativeHostService } from 'vs/platform/native/electron-sandbox/nativeHostService'; import { ipcRenderer } from 'vs/base/parts/sandbox/electron-sandbox/globals'; import { localize } from 'vs/nls'; -import { ProcessExplorerStyles, ProcessExplorerData } from 'vs/platform/issue/common/issue'; +import { ProcessExplorerStyles, ProcessExplorerData, ProcessExplorerWindowConfiguration } from 'vs/platform/issue/common/issue'; import { applyZoom, zoomIn, zoomOut } from 'vs/platform/windows/electron-sandbox/window'; import { IContextMenuItem } from 'vs/base/parts/contextmenu/common/contextmenu'; import { popup } from 'vs/base/parts/contextmenu/electron-sandbox/contextmenu'; import { ProcessItem } from 'vs/base/common/processes'; -import * as dom from 'vs/base/browser/dom'; -import { DisposableStore } from 'vs/base/common/lifecycle'; +import { append, $ } from 'vs/base/browser/dom'; import { isRemoteDiagnosticError, IRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnostics'; import { ElectronIPCMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService'; import { ByteSize } from 'vs/platform/files/common/files'; @@ -105,11 +104,11 @@ class ProcessHeaderTreeRenderer implements ITreeRenderer, index: number, templateData: IProcessItemTemplateData, height: number | undefined): void { @@ -128,8 +127,8 @@ class MachineRenderer implements ITreeRenderer, index: number, templateData: IProcessRowTemplateData, height: number | undefined): void { @@ -144,8 +143,8 @@ class ErrorRenderer implements ITreeRenderer, index: number, templateData: IProcessRowTemplateData, height: number | undefined): void { @@ -163,12 +162,12 @@ class ProcessRenderer implements ITreeRendererObject.create(null); - const row = dom.append(container, dom.$('.row')); + const row = append(container, $('.row')); - data.name = dom.append(row, dom.$('.nameLabel')); - data.CPU = dom.append(row, dom.$('.cpu')); - data.memory = dom.append(row, dom.$('.memory')); - data.PID = dom.append(row, dom.$('.pid')); + data.name = append(row, $('.nameLabel')); + data.CPU = append(row, $('.cpu')); + data.memory = append(row, $('.memory')); + data.PID = append(row, $('.pid')); return data; } @@ -226,8 +225,6 @@ class ProcessExplorer { private mapPidToWindowTitle = new Map(); - private listeners = new DisposableStore(); - private nativeHostService: INativeHostService; private tree: DataTree | undefined; @@ -237,6 +234,7 @@ class ProcessExplorer { this.nativeHostService = new NativeHostService(windowId, mainProcessService) as INativeHostService; this.applyStyles(data.styles); + this.setEventHandlers(data); // Map window process pids to titles, annotate process names with this when rendering to distinguish between them ipcRenderer.on('vscode:windowsInfoResponse', (event: unknown, windows: any[]) => { @@ -263,6 +261,32 @@ class ProcessExplorer { this.lastRequestTime = Date.now(); ipcRenderer.send('vscode:windowsInfoRequest'); ipcRenderer.send('vscode:listProcesses'); + + + } + + private setEventHandlers(data: ProcessExplorerData): void { + document.onkeydown = (e: KeyboardEvent) => { + const cmdOrCtrlKey = data.platform === 'darwin' ? e.metaKey : e.ctrlKey; + + // Cmd/Ctrl + w closes issue window + if (cmdOrCtrlKey && e.keyCode === 87) { + e.stopPropagation(); + e.preventDefault(); + + ipcRenderer.send('vscode:closeProcessExplorer'); + } + + // Cmd/Ctrl + zooms in + if (cmdOrCtrlKey && e.keyCode === 187) { + zoomIn(); + } + + // Cmd/Ctrl - zooms out + if (cmdOrCtrlKey && e.keyCode === 189) { + zoomOut(); + } + }; } private async createProcessTree(processRoots: MachineProcessInformation[]): Promise { @@ -449,39 +473,12 @@ class ProcessExplorer { } }, 200); } - - public dispose() { - this.listeners.dispose(); - } } -export function startup(windowId: number, data: ProcessExplorerData): void { - const platformClass = data.platform === 'win32' ? 'windows' : data.platform === 'linux' ? 'linux' : 'mac'; +export function startup(configuration: ProcessExplorerWindowConfiguration): void { + const platformClass = configuration.data.platform === 'win32' ? 'windows' : configuration.data.platform === 'linux' ? 'linux' : 'mac'; document.body.classList.add(platformClass); // used by our fonts - applyZoom(data.zoomLevel); + applyZoom(configuration.data.zoomLevel); - const processExplorer = new ProcessExplorer(windowId, data); - - document.onkeydown = (e: KeyboardEvent) => { - const cmdOrCtrlKey = data.platform === 'darwin' ? e.metaKey : e.ctrlKey; - - // Cmd/Ctrl + w closes issue window - if (cmdOrCtrlKey && e.keyCode === 87) { - e.stopPropagation(); - e.preventDefault(); - - processExplorer.dispose(); - ipcRenderer.send('vscode:closeProcessExplorer'); - } - - // Cmd/Ctrl + zooms in - if (cmdOrCtrlKey && e.keyCode === 187) { - zoomIn(); - } - - // Cmd/Ctrl - zooms out - if (cmdOrCtrlKey && e.keyCode === 189) { - zoomOut(); - } - }; + new ProcessExplorer(configuration.windowId, configuration.data); } diff --git a/src/vs/code/electron-sandbox/workbench/workbench.html b/src/vs/code/electron-sandbox/workbench/workbench.html index f36737f2..2933be27 100644 --- a/src/vs/code/electron-sandbox/workbench/workbench.html +++ b/src/vs/code/electron-sandbox/workbench/workbench.html @@ -3,7 +3,7 @@ - + diff --git a/src/vs/code/electron-sandbox/workbench/workbench.js b/src/vs/code/electron-sandbox/workbench/workbench.js index 1946d7f5..38cd78a8 100644 --- a/src/vs/code/electron-sandbox/workbench/workbench.js +++ b/src/vs/code/electron-sandbox/workbench/workbench.js @@ -32,15 +32,38 @@ return require('vs/workbench/electron-sandbox/desktop.main').main(configuration); }, { - removeDeveloperKeybindingsAfterLoad: true, + configureDeveloperSettings: function (windowConfig) { + return { + // disable automated devtools opening on error when running extension tests + // as this can lead to undeterministic test exectuion (devtools steals focus) + forceDisableShowDevtoolsOnError: typeof windowConfig.extensionTestsPath === 'string', + // enable devtools keybindings in extension development window + forceEnableDeveloperKeybindings: Array.isArray(windowConfig.extensionDevelopmentPath) && windowConfig.extensionDevelopmentPath.length > 0, + removeDeveloperKeybindingsAfterLoad: true + }; + }, canModifyDOM: function (windowConfig) { // TODO@sandbox part-splash is non-sandboxed only }, - beforeLoaderConfig: function (windowConfig, loaderConfig) { + beforeLoaderConfig: function (loaderConfig) { loaderConfig.recordStats = true; }, beforeRequire: function () { performance.mark('code/willLoadWorkbenchMain'); + + // It looks like browsers only lazily enable + // the element when needed. Since we + // leverage canvas elements in our code in many + // locations, we try to help the browser to + // initialize canvas when it is idle, right + // before we wait for the scripts to be loaded. + // @ts-ignore + window.requestIdleCallback(() => { + const canvas = document.createElement('canvas'); + const context = canvas.getContext('2d'); + context.clearRect(0, 0, canvas.width, canvas.height); + canvas.remove(); + }, { timeout: 50 }); } } ); @@ -62,12 +85,26 @@ } }); - //region Helpers + //#region Helpers /** + * @typedef {import('../../../platform/windows/common/windows').INativeWindowConfiguration} INativeWindowConfiguration + * * @returns {{ - * load: (modules: string[], resultCallback: (result, configuration: import('../../../platform/windows/common/windows').INativeWindowConfiguration) => any, options: object) => unknown, - * globals: () => typeof import('../../../base/parts/sandbox/electron-sandbox/globals') + * load: ( + * modules: string[], + * resultCallback: (result, configuration: INativeWindowConfiguration) => unknown, + * options?: { + * configureDeveloperSettings?: (config: INativeWindowConfiguration & object) => { + * forceEnableDeveloperKeybindings?: boolean, + * disallowReloadKeybinding?: boolean, + * removeDeveloperKeybindingsAfterLoad?: boolean + * }, + * canModifyDOM?: (config: INativeWindowConfiguration & object) => void, + * beforeLoaderConfig?: (loaderConfig: object) => void, + * beforeRequire?: () => void + * } + * ) => Promise * }} */ function bootstrapWindowLib() { diff --git a/src/vs/code/node/cli.ts b/src/vs/code/node/cli.ts index c1285622..0ce6c5ee 100644 --- a/src/vs/code/node/cli.ts +++ b/src/vs/code/node/cli.ts @@ -14,7 +14,7 @@ import product from 'vs/platform/product/common/product'; import { isAbsolute, join } from 'vs/base/common/path'; import { whenDeleted, writeFileSync } from 'vs/base/node/pfs'; import { findFreePort, randomPort } from 'vs/base/node/ports'; -import { isWindows, isLinux } from 'vs/base/common/platform'; +import { isWindows, isLinux, IProcessEnvironment } from 'vs/base/common/platform'; import type { ProfilingSession, Target } from 'v8-inspect-profiler'; import { isString } from 'vs/base/common/types'; import { hasStdinWithoutTty, stdinDataListener, getStdinFilePath, readFromStdin } from 'vs/platform/environment/node/stdin'; @@ -116,9 +116,8 @@ export async function main(argv: string[]): Promise { // Just Code else { - const env: NodeJS.ProcessEnv = { + const env: IProcessEnvironment = { ...process.env, - 'VSCODE_CLI': '1', // this will signal Code that it was spawned from this module 'ELECTRON_NO_ATTACH_CONSOLE': '1' }; diff --git a/src/vs/code/node/cliProcessMain.ts b/src/vs/code/node/cliProcessMain.ts index d50ab90f..11716b33 100644 --- a/src/vs/code/node/cliProcessMain.ts +++ b/src/vs/code/node/cliProcessMain.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { release } from 'os'; +import { release, hostname } from 'os'; import * as fs from 'fs'; import { gracefulify } from 'graceful-fs'; import { isAbsolute, join } from 'vs/base/common/path'; @@ -135,8 +135,6 @@ class CliMain extends Disposable { const stateService = new StateService(environmentService, logService); services.set(IStateService, stateService); - const { appRoot, extensionsPath, extensionDevelopmentLocationURI, isBuilt, installSourcePath } = environmentService; - // Request services.set(IRequestService, new SyncDescriptor(RequestService)); @@ -150,15 +148,17 @@ class CliMain extends Disposable { // Telemetry const appenders: AppInsightsAppender[] = []; - if (isBuilt && !extensionDevelopmentLocationURI && !environmentService.disableTelemetry && productService.enableTelemetry) { + if (environmentService.isBuilt && !environmentService.isExtensionDevelopment && !environmentService.disableTelemetry && productService.enableTelemetry) { if (productService.aiConfig && productService.aiConfig.asimovKey) { appenders.push(new AppInsightsAppender('monacoworkbench', null, productService.aiConfig.asimovKey)); } + const { appRoot, extensionsPath, installSourcePath } = environmentService; + const config: ITelemetryServiceConfig = { appender: combinedAppender(...appenders), sendErrorTelemetry: false, - commonProperties: resolveCommonProperties(fileService, release(), process.arch, productService.commit, productService.version, stateService.getItem('telemetry.machineId'), productService.msftInternalDomains, installSourcePath), + commonProperties: resolveCommonProperties(fileService, release(), hostname(), process.arch, productService.commit, productService.version, stateService.getItem('telemetry.machineId'), productService.msftInternalDomains, installSourcePath), piiPaths: [appRoot, extensionsPath] }; diff --git a/src/vs/editor/browser/config/configuration.ts b/src/vs/editor/browser/config/configuration.ts index 545d1321..ddb077f5 100644 --- a/src/vs/editor/browser/config/configuration.ts +++ b/src/vs/editor/browser/config/configuration.ts @@ -112,7 +112,7 @@ class CSSBasedConfiguration extends Disposable { this._evictUntrustedReadingsTimeout = -1; } - public dispose(): void { + public override dispose(): void { if (this._evictUntrustedReadingsTimeout !== -1) { clearTimeout(this._evictUntrustedReadingsTimeout); this._evictUntrustedReadingsTimeout = -1; @@ -353,11 +353,11 @@ export class Configuration extends CommonEditorConfiguration { this._recomputeOptions(); } - public observeReferenceElement(dimension?: IDimension): void { + public override observeReferenceElement(dimension?: IDimension): void { this._elementSizeObserver.observe(dimension); } - public updatePixelRatio(): void { + public override updatePixelRatio(): void { this._recomputeOptions(); } diff --git a/src/vs/editor/browser/config/elementSizeObserver.ts b/src/vs/editor/browser/config/elementSizeObserver.ts index 06b42c6f..cf42a075 100644 --- a/src/vs/editor/browser/config/elementSizeObserver.ts +++ b/src/vs/editor/browser/config/elementSizeObserver.ts @@ -52,7 +52,7 @@ export class ElementSizeObserver extends Disposable { this.measureReferenceDomElement(false, dimension); } - public dispose(): void { + public override dispose(): void { this.stopObserving(); super.dispose(); } diff --git a/src/vs/editor/browser/controller/mouseHandler.ts b/src/vs/editor/browser/controller/mouseHandler.ts index 005fe7ad..0b26756b 100644 --- a/src/vs/editor/browser/controller/mouseHandler.ts +++ b/src/vs/editor/browser/controller/mouseHandler.ts @@ -133,13 +133,13 @@ export class MouseHandler extends ViewEventHandler { this._context.addEventHandler(this); } - public dispose(): void { + public override dispose(): void { this._context.removeEventHandler(this); super.dispose(); } // --- begin event handlers - public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { + public override onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { if (e.hasChanged(EditorOption.layoutInfo)) { // layout change const height = this._context.configuration.options.get(EditorOption.layoutInfo).height; @@ -150,14 +150,14 @@ export class MouseHandler extends ViewEventHandler { } return false; } - public onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean { + public override onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean { this._mouseDownOperation.onCursorStateChanged(e); return false; } - public onFocusChanged(e: viewEvents.ViewFocusChangedEvent): boolean { + public override onFocusChanged(e: viewEvents.ViewFocusChangedEvent): boolean { return false; } - public onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean { + public override onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean { this._mouseDownOperation.onScrollChanged(); return false; } @@ -311,7 +311,7 @@ class MouseDownOperation extends Disposable { this._lastMouseEvent = null; } - public dispose(): void { + public override dispose(): void { super.dispose(); } diff --git a/src/vs/editor/browser/controller/mouseTarget.ts b/src/vs/editor/browser/controller/mouseTarget.ts index bc0478ca..d2af49b2 100644 --- a/src/vs/editor/browser/controller/mouseTarget.ts +++ b/src/vs/editor/browser/controller/mouseTarget.ts @@ -422,7 +422,7 @@ class HitTestRequest extends BareHitTestRequest { } } - public toString(): string { + public override toString(): string { return `pos(${this.pos.x},${this.pos.y}), editorPos(${this.editorPos.x},${this.editorPos.y}), mouseVerticalOffset: ${this.mouseVerticalOffset}, mouseContentHorizontalOffset: ${this.mouseContentHorizontalOffset}\n\ttarget: ${this.target ? (this.target).outerHTML : null}`; } diff --git a/src/vs/editor/browser/controller/pointerHandler.ts b/src/vs/editor/browser/controller/pointerHandler.ts index ee291a86..5c4df88f 100644 --- a/src/vs/editor/browser/controller/pointerHandler.ts +++ b/src/vs/editor/browser/controller/pointerHandler.ts @@ -87,7 +87,7 @@ export class PointerEventHandler extends MouseHandler { } } - public _onMouseDown(e: EditorMouseEvent): void { + public override _onMouseDown(e: EditorMouseEvent): void { if ((e.browserEvent as any).pointerType === 'touch') { return; } diff --git a/src/vs/editor/browser/controller/textAreaHandler.ts b/src/vs/editor/browser/controller/textAreaHandler.ts index 19847829..fe0400bd 100644 --- a/src/vs/editor/browser/controller/textAreaHandler.ts +++ b/src/vs/editor/browser/controller/textAreaHandler.ts @@ -132,7 +132,7 @@ export class TextAreaHandler extends ViewPart { this.textArea.setAttribute('aria-haspopup', 'false'); this.textArea.setAttribute('aria-autocomplete', 'both'); - if (options.get(EditorOption.domReadOnly)) { + if (options.get(EditorOption.domReadOnly) && options.get(EditorOption.readOnly)) { this.textArea.setAttribute('readonly', 'true'); } @@ -330,7 +330,7 @@ export class TextAreaHandler extends ViewPart { })); } - public dispose(): void { + public override dispose(): void { super.dispose(); } @@ -416,8 +416,8 @@ export class TextAreaHandler extends ViewPart { this._accessibilitySupport = options.get(EditorOption.accessibilitySupport); 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 = 1000; + // If a screen reader is attached and the default value is not set we shuold automatically increase the page size to 500 for a better experience + this._accessibilityPageSize = 500; } else { this._accessibilityPageSize = accessibilityPageSize; } @@ -425,7 +425,7 @@ export class TextAreaHandler extends ViewPart { // --- begin event handlers - public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { + public override onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { const options = this._context.configuration.options; const layoutInfo = options.get(EditorOption.layoutInfo); @@ -440,8 +440,8 @@ export class TextAreaHandler extends ViewPart { this.textArea.setAttribute('aria-label', this._getAriaLabel(options)); this.textArea.setAttribute('tabindex', String(options.get(EditorOption.tabIndex))); - if (e.hasChanged(EditorOption.domReadOnly)) { - if (options.get(EditorOption.domReadOnly)) { + if (e.hasChanged(EditorOption.domReadOnly) || e.hasChanged(EditorOption.readOnly)) { + if (options.get(EditorOption.domReadOnly) && options.get(EditorOption.readOnly)) { this.textArea.setAttribute('readonly', 'true'); } else { this.textArea.removeAttribute('readonly'); @@ -454,34 +454,34 @@ export class TextAreaHandler extends ViewPart { return true; } - public onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean { + public override onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean { this._selections = e.selections.slice(0); this._modelSelections = e.modelSelections.slice(0); this._textAreaInput.writeScreenReaderContent('selection changed'); return true; } - public onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean { + public override onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean { // true for inline decorations that can end up relayouting text return true; } - public onFlushed(e: viewEvents.ViewFlushedEvent): boolean { + public override onFlushed(e: viewEvents.ViewFlushedEvent): boolean { return true; } - public onLinesChanged(e: viewEvents.ViewLinesChangedEvent): boolean { + public override onLinesChanged(e: viewEvents.ViewLinesChangedEvent): boolean { return true; } - public onLinesDeleted(e: viewEvents.ViewLinesDeletedEvent): boolean { + public override onLinesDeleted(e: viewEvents.ViewLinesDeletedEvent): boolean { return true; } - public onLinesInserted(e: viewEvents.ViewLinesInsertedEvent): boolean { + public override onLinesInserted(e: viewEvents.ViewLinesInsertedEvent): boolean { return true; } - public onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean { + public override onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean { this._scrollLeft = e.scrollLeft; this._scrollTop = e.scrollTop; return true; } - public onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean { + public override onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean { return true; } diff --git a/src/vs/editor/browser/controller/textAreaInput.ts b/src/vs/editor/browser/controller/textAreaInput.ts index 63722bb8..37dbdb7f 100644 --- a/src/vs/editor/browser/controller/textAreaInput.ts +++ b/src/vs/editor/browser/controller/textAreaInput.ts @@ -524,7 +524,7 @@ export class TextAreaInput extends Disposable { }); } - public dispose(): void { + public override dispose(): void { super.dispose(); if (this._selectionChangeListener) { this._selectionChangeListener.dispose(); diff --git a/src/vs/editor/browser/core/editorState.ts b/src/vs/editor/browser/core/editorState.ts index 5d4c1ffb..294a7c85 100644 --- a/src/vs/editor/browser/core/editorState.ts +++ b/src/vs/editor/browser/core/editorState.ts @@ -93,7 +93,7 @@ export class EditorStateCancellationTokenSource extends EditorKeybindingCancella private readonly _listener = new DisposableStore(); - constructor(readonly editor: IActiveCodeEditor, flags: CodeEditorStateFlag, range?: IRange, parent?: CancellationToken) { + constructor(editor: IActiveCodeEditor, flags: CodeEditorStateFlag, range?: IRange, parent?: CancellationToken) { super(editor, parent); if (flags & CodeEditorStateFlag.Position) { @@ -119,7 +119,7 @@ export class EditorStateCancellationTokenSource extends EditorKeybindingCancella } } - dispose() { + override dispose() { this._listener.dispose(); super.dispose(); } @@ -137,7 +137,7 @@ export class TextModelCancellationTokenSource extends CancellationTokenSource im this._listener = model.onDidChangeContent(() => this.cancel()); } - dispose() { + override dispose() { this._listener.dispose(); super.dispose(); } diff --git a/src/vs/editor/browser/core/keybindingCancellation.ts b/src/vs/editor/browser/core/keybindingCancellation.ts index 206fbeac..2fc418a3 100644 --- a/src/vs/editor/browser/core/keybindingCancellation.ts +++ b/src/vs/editor/browser/core/keybindingCancellation.ts @@ -81,7 +81,7 @@ export class EditorKeybindingCancellationTokenSource extends CancellationTokenSo this._unregister = editor.invokeWithinContext(accessor => accessor.get(IEditorCancellationTokens).add(editor, this)); } - dispose(): void { + override dispose(): void { this._unregister(); super.dispose(); } diff --git a/src/vs/editor/browser/core/markdownRenderer.ts b/src/vs/editor/browser/core/markdownRenderer.ts index ec24b6b5..f8422e25 100644 --- a/src/vs/editor/browser/core/markdownRenderer.ts +++ b/src/vs/editor/browser/core/markdownRenderer.ts @@ -58,7 +58,7 @@ export class MarkdownRenderer { if (!markdown) { element = document.createElement('span'); } else { - element = renderMarkdown(markdown, { ...this._getRenderOptions(disposeables), ...options }, markedOptions); + element = renderMarkdown(markdown, { ...this._getRenderOptions(markdown, disposeables), ...options }, markedOptions); } return { @@ -67,7 +67,7 @@ export class MarkdownRenderer { }; } - protected _getRenderOptions(disposeables: DisposableStore): MarkdownRenderOptions { + protected _getRenderOptions(markdown: IMarkdownString, disposeables: DisposableStore): MarkdownRenderOptions { return { baseUrl: this._options.baseUrl, codeBlockRenderer: async (languageAlias, value) => { @@ -103,7 +103,7 @@ export class MarkdownRenderer { }, asyncRenderCallback: () => this._onDidRenderAsync.fire(), actionHandler: { - callback: (content) => this._openerService.open(content, { fromUserGesture: true, allowContributedOpeners: true }).catch(onUnexpectedError), + callback: (content) => this._openerService.open(content, { fromUserGesture: true, allowContributedOpeners: true, allowCommands: markdown.isTrusted }).catch(onUnexpectedError), disposeables } }; diff --git a/src/vs/editor/browser/services/bulkEditService.ts b/src/vs/editor/browser/services/bulkEditService.ts index 5a49c330..340cd1c2 100644 --- a/src/vs/editor/browser/services/bulkEditService.ts +++ b/src/vs/editor/browser/services/bulkEditService.ts @@ -47,7 +47,7 @@ export class ResourceTextEdit extends ResourceEdit { readonly resource: URI, readonly textEdit: TextEdit, readonly versionId?: number, - readonly metadata?: WorkspaceEditMetadata + metadata?: WorkspaceEditMetadata ) { super(metadata); } @@ -58,7 +58,7 @@ export class ResourceFileEdit extends ResourceEdit { readonly oldResource: URI | undefined, readonly newResource: URI | undefined, readonly options?: WorkspaceFileEditOptions, - readonly metadata?: WorkspaceEditMetadata + metadata?: WorkspaceEditMetadata ) { super(metadata); } diff --git a/src/vs/editor/browser/services/codeEditorServiceImpl.ts b/src/vs/editor/browser/services/codeEditorServiceImpl.ts index 08a4d4c6..1451ddac 100644 --- a/src/vs/editor/browser/services/codeEditorServiceImpl.ts +++ b/src/vs/editor/browser/services/codeEditorServiceImpl.ts @@ -11,7 +11,6 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { AbstractCodeEditorService } from 'vs/editor/browser/services/abstractCodeEditorService'; import { IContentDecorationRenderOptions, IDecorationRenderOptions, IThemeDecorationRenderOptions, isThemeColor } from 'vs/editor/common/editorCommon'; import { IModelDecorationOptions, IModelDecorationOverviewRulerOptions, OverviewRulerLane, TrackedRangeStickiness } from 'vs/editor/common/model'; -import { IResourceEditorInput } from 'vs/platform/editor/common/editor'; import { IColorTheme, IThemeService, ThemeColor } from 'vs/platform/theme/common/themeService'; export class RefCountedStyleSheet { @@ -172,9 +171,6 @@ export abstract class CodeEditorServiceImpl extends AbstractCodeEditorService { } return provider.resolveDecorationCSSRules(); } - - abstract getActiveCodeEditor(): ICodeEditor | null; - abstract openCodeEditor(input: IResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise; } interface IModelDecorationOptionsProvider extends IDisposable { diff --git a/src/vs/editor/browser/services/openerService.ts b/src/vs/editor/browser/services/openerService.ts index 2abada60..bc54879c 100644 --- a/src/vs/editor/browser/services/openerService.ts +++ b/src/vs/editor/browser/services/openerService.ts @@ -21,10 +21,15 @@ class CommandOpener implements IOpener { constructor(@ICommandService private readonly _commandService: ICommandService) { } - async open(target: URI | string) { + async open(target: URI | string, options?: OpenOptions): Promise { if (!matchesScheme(target, Schemas.command)) { return false; } + if (!options?.allowCommands) { + // silently ignore commands when command-links are disabled, also + // surpress other openers by returning TRUE + return true; + } // run command or bail out if command isn't known if (typeof target === 'string') { target = URI.parse(target); diff --git a/src/vs/editor/browser/view/viewImpl.ts b/src/vs/editor/browser/view/viewImpl.ts index 5589fd7f..67823047 100644 --- a/src/vs/editor/browser/view/viewImpl.ts +++ b/src/vs/editor/browser/view/viewImpl.ts @@ -300,32 +300,32 @@ export class View extends ViewEventHandler { } // --- begin event handlers - public handleEvents(events: viewEvents.ViewEvent[]): void { + public override handleEvents(events: viewEvents.ViewEvent[]): void { super.handleEvents(events); this._scheduleRender(); } - public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { + public override onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { this._configPixelRatio = this._context.configuration.options.get(EditorOption.pixelRatio); this.domNode.setClassName(this._getEditorClassName()); this._applyLayout(); return false; } - public onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean { + public override onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean { this._selections = e.selections; return false; } - public onFocusChanged(e: viewEvents.ViewFocusChangedEvent): boolean { + public override onFocusChanged(e: viewEvents.ViewFocusChangedEvent): boolean { this.domNode.setClassName(this._getEditorClassName()); return false; } - public onThemeChanged(e: viewEvents.ViewThemeChangedEvent): boolean { + public override onThemeChanged(e: viewEvents.ViewThemeChangedEvent): boolean { this.domNode.setClassName(this._getEditorClassName()); return false; } // --- end event handlers - public dispose(): void { + public override dispose(): void { if (this._renderAnimationFrame !== null) { this._renderAnimationFrame.dispose(); this._renderAnimationFrame = null; diff --git a/src/vs/editor/browser/view/viewOverlays.ts b/src/vs/editor/browser/view/viewOverlays.ts index 130f1f09..0088f3d8 100644 --- a/src/vs/editor/browser/view/viewOverlays.ts +++ b/src/vs/editor/browser/view/viewOverlays.ts @@ -36,7 +36,7 @@ export class ViewOverlays extends ViewPart implements IVisibleLinesHost onBrowserDesperateReveal(this.scrollbarDomNode.domNode, true, false))); } - public dispose(): void { + public override dispose(): void { super.dispose(); } @@ -133,7 +133,7 @@ export class EditorScrollbar extends ViewPart { // --- begin event handlers - public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { + public override onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { if ( e.hasChanged(EditorOption.scrollbar) || e.hasChanged(EditorOption.mouseWheelScrollSensitivity) @@ -157,10 +157,10 @@ export class EditorScrollbar extends ViewPart { } return true; } - public onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean { + public override onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean { return true; } - public onThemeChanged(e: viewEvents.ViewThemeChangedEvent): boolean { + public override onThemeChanged(e: viewEvents.ViewThemeChangedEvent): boolean { this.scrollbar.updateClassName('editor-scrollable' + ' ' + getThemeTypeSelector(this._context.theme.type)); return true; } diff --git a/src/vs/editor/browser/viewParts/glyphMargin/glyphMargin.ts b/src/vs/editor/browser/viewParts/glyphMargin/glyphMargin.ts index 0cdd9c61..3ccec5f6 100644 --- a/src/vs/editor/browser/viewParts/glyphMargin/glyphMargin.ts +++ b/src/vs/editor/browser/viewParts/glyphMargin/glyphMargin.ts @@ -98,7 +98,7 @@ export class GlyphMarginOverlay extends DedupOverlay { this._context.addEventHandler(this); } - public dispose(): void { + public override dispose(): void { this._context.removeEventHandler(this); this._renderResult = null; super.dispose(); @@ -106,7 +106,7 @@ export class GlyphMarginOverlay extends DedupOverlay { // --- begin event handlers - public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { + public override onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { const options = this._context.configuration.options; const layoutInfo = options.get(EditorOption.layoutInfo); @@ -116,25 +116,25 @@ export class GlyphMarginOverlay extends DedupOverlay { this._glyphMarginWidth = layoutInfo.glyphMarginWidth; return true; } - public onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean { + public override onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean { return true; } - public onFlushed(e: viewEvents.ViewFlushedEvent): boolean { + public override onFlushed(e: viewEvents.ViewFlushedEvent): boolean { return true; } - public onLinesChanged(e: viewEvents.ViewLinesChangedEvent): boolean { + public override onLinesChanged(e: viewEvents.ViewLinesChangedEvent): boolean { return true; } - public onLinesDeleted(e: viewEvents.ViewLinesDeletedEvent): boolean { + public override onLinesDeleted(e: viewEvents.ViewLinesDeletedEvent): boolean { return true; } - public onLinesInserted(e: viewEvents.ViewLinesInsertedEvent): boolean { + public override onLinesInserted(e: viewEvents.ViewLinesInsertedEvent): boolean { return true; } - public onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean { + public override onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean { return e.scrollTopChanged; } - public onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean { + public override onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean { return true; } diff --git a/src/vs/editor/browser/viewParts/indentGuides/indentGuides.ts b/src/vs/editor/browser/viewParts/indentGuides/indentGuides.ts index f94c0624..de40ce80 100644 --- a/src/vs/editor/browser/viewParts/indentGuides/indentGuides.ts +++ b/src/vs/editor/browser/viewParts/indentGuides/indentGuides.ts @@ -45,7 +45,7 @@ export class IndentGuidesOverlay extends DynamicViewOverlay { this._context.addEventHandler(this); } - public dispose(): void { + public override dispose(): void { this._context.removeEventHandler(this); this._renderResult = null; super.dispose(); @@ -53,7 +53,7 @@ export class IndentGuidesOverlay extends DynamicViewOverlay { // --- begin event handlers - public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { + public override onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { const options = this._context.configuration.options; const wrappingInfo = options.get(EditorOption.wrappingInfo); const fontInfo = options.get(EditorOption.fontInfo); @@ -65,7 +65,7 @@ export class IndentGuidesOverlay extends DynamicViewOverlay { this._maxIndentLeft = wrappingInfo.wrappingColumn === -1 ? -1 : (wrappingInfo.wrappingColumn * fontInfo.typicalHalfwidthCharacterWidth); return true; } - public onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean { + public override onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean { const selection = e.selections[0]; const newPrimaryLineNumber = selection.isEmpty() ? selection.positionLineNumber : 0; @@ -76,29 +76,29 @@ export class IndentGuidesOverlay extends DynamicViewOverlay { return false; } - public onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean { + public override onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean { // true for inline decorations return true; } - public onFlushed(e: viewEvents.ViewFlushedEvent): boolean { + public override onFlushed(e: viewEvents.ViewFlushedEvent): boolean { return true; } - public onLinesChanged(e: viewEvents.ViewLinesChangedEvent): boolean { + public override onLinesChanged(e: viewEvents.ViewLinesChangedEvent): boolean { return true; } - public onLinesDeleted(e: viewEvents.ViewLinesDeletedEvent): boolean { + public override onLinesDeleted(e: viewEvents.ViewLinesDeletedEvent): boolean { return true; } - public onLinesInserted(e: viewEvents.ViewLinesInsertedEvent): boolean { + public override onLinesInserted(e: viewEvents.ViewLinesInsertedEvent): boolean { return true; } - public onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean { + public override onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean { return e.scrollTopChanged;// || e.scrollWidthChanged; } - public onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean { + public override onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean { return true; } - public onLanguageConfigurationChanged(e: viewEvents.ViewLanguageConfigurationEvent): boolean { + public override onLanguageConfigurationChanged(e: viewEvents.ViewLanguageConfigurationEvent): boolean { return true; } diff --git a/src/vs/editor/browser/viewParts/lineNumbers/lineNumbers.ts b/src/vs/editor/browser/viewParts/lineNumbers/lineNumbers.ts index 0eceebf7..1c3f5d41 100644 --- a/src/vs/editor/browser/viewParts/lineNumbers/lineNumbers.ts +++ b/src/vs/editor/browser/viewParts/lineNumbers/lineNumbers.ts @@ -54,7 +54,7 @@ export class LineNumbersOverlay extends DynamicViewOverlay { this._lineNumbersWidth = layoutInfo.lineNumbersWidth; } - public dispose(): void { + public override dispose(): void { this._context.removeEventHandler(this); this._renderResult = null; super.dispose(); @@ -62,11 +62,11 @@ export class LineNumbersOverlay extends DynamicViewOverlay { // --- begin event handlers - public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { + public override onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { this._readConfig(); return true; } - public onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean { + public override onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean { const primaryViewPosition = e.selections[0].getPosition(); this._lastCursorModelPosition = this._context.model.coordinatesConverter.convertViewPositionToModelPosition(primaryViewPosition); @@ -80,22 +80,22 @@ export class LineNumbersOverlay extends DynamicViewOverlay { } return shouldRender; } - public onFlushed(e: viewEvents.ViewFlushedEvent): boolean { + public override onFlushed(e: viewEvents.ViewFlushedEvent): boolean { return true; } - public onLinesChanged(e: viewEvents.ViewLinesChangedEvent): boolean { + public override onLinesChanged(e: viewEvents.ViewLinesChangedEvent): boolean { return true; } - public onLinesDeleted(e: viewEvents.ViewLinesDeletedEvent): boolean { + public override onLinesDeleted(e: viewEvents.ViewLinesDeletedEvent): boolean { return true; } - public onLinesInserted(e: viewEvents.ViewLinesInsertedEvent): boolean { + public override onLinesInserted(e: viewEvents.ViewLinesInsertedEvent): boolean { return true; } - public onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean { + public override onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean { return e.scrollTopChanged; } - public onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean { + public override onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean { return true; } diff --git a/src/vs/editor/browser/viewParts/lines/viewLine.ts b/src/vs/editor/browser/viewParts/lines/viewLine.ts index 0c73f13a..edec7f7a 100644 --- a/src/vs/editor/browser/viewParts/lines/viewLine.ts +++ b/src/vs/editor/browser/viewParts/lines/viewLine.ts @@ -662,7 +662,7 @@ class RenderedViewLine implements IRenderedViewLine { } class WebKitRenderedViewLine extends RenderedViewLine { - protected _readVisibleRangesForRange(domNode: FastDomNode, startColumn: number, endColumn: number, context: DomReadingContext): HorizontalRange[] | null { + protected override _readVisibleRangesForRange(domNode: FastDomNode, startColumn: number, endColumn: number, context: DomReadingContext): HorizontalRange[] | null { const output = super._readVisibleRangesForRange(domNode, startColumn, endColumn, context); if (!output || output.length === 0 || startColumn === endColumn || (startColumn === 1 && endColumn === this._characterMapping.length)) { diff --git a/src/vs/editor/browser/viewParts/lines/viewLines.ts b/src/vs/editor/browser/viewParts/lines/viewLines.ts index 6e92c558..8d9601ff 100644 --- a/src/vs/editor/browser/viewParts/lines/viewLines.ts +++ b/src/vs/editor/browser/viewParts/lines/viewLines.ts @@ -152,7 +152,7 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, this._horizontalRevealRequest = null; } - public dispose(): void { + public override dispose(): void { this._asyncUpdateLineWidths.dispose(); this._asyncCheckMonospaceFontAssumptions.dispose(); super.dispose(); @@ -172,7 +172,7 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, // ---- begin view event handlers - public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { + public override onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { this._visibleLines.onConfigurationChanged(e); if (e.hasChanged(EditorOption.wrappingInfo)) { this._maxLineWidth = 0; @@ -217,7 +217,7 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, return false; } - public onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean { + public override onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean { const rendStartLineNumber = this._visibleLines.getStartLineNumber(); const rendEndLineNumber = this._visibleLines.getEndLineNumber(); let r = false; @@ -226,7 +226,7 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, } return r; } - public onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean { + public override onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean { if (true/*e.inlineDecorationsChanged*/) { const rendStartLineNumber = this._visibleLines.getStartLineNumber(); const rendEndLineNumber = this._visibleLines.getEndLineNumber(); @@ -236,21 +236,21 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, } return true; } - public onFlushed(e: viewEvents.ViewFlushedEvent): boolean { + public override onFlushed(e: viewEvents.ViewFlushedEvent): boolean { const shouldRender = this._visibleLines.onFlushed(e); this._maxLineWidth = 0; return shouldRender; } - public onLinesChanged(e: viewEvents.ViewLinesChangedEvent): boolean { + public override onLinesChanged(e: viewEvents.ViewLinesChangedEvent): boolean { return this._visibleLines.onLinesChanged(e); } - public onLinesDeleted(e: viewEvents.ViewLinesDeletedEvent): boolean { + public override onLinesDeleted(e: viewEvents.ViewLinesDeletedEvent): boolean { return this._visibleLines.onLinesDeleted(e); } - public onLinesInserted(e: viewEvents.ViewLinesInsertedEvent): boolean { + public override onLinesInserted(e: viewEvents.ViewLinesInsertedEvent): boolean { return this._visibleLines.onLinesInserted(e); } - public onRevealRangeRequest(e: viewEvents.ViewRevealRangeRequestEvent): boolean { + public override onRevealRangeRequest(e: viewEvents.ViewRevealRangeRequestEvent): boolean { // Using the future viewport here in order to handle multiple // incoming reveal range requests that might all desire to be animated const desiredScrollTop = this._computeScrollTopToRevealRange(this._context.viewLayout.getFutureViewport(), e.source, e.range, e.selections, e.verticalType); @@ -286,7 +286,7 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, return true; } - public onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean { + public override onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean { if (this._horizontalRevealRequest && e.scrollLeftChanged) { // cancel any outstanding horizontal reveal request if someone else scrolls horizontally. this._horizontalRevealRequest = null; @@ -303,14 +303,14 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, return this._visibleLines.onScrollChanged(e) || true; } - public onTokensChanged(e: viewEvents.ViewTokensChangedEvent): boolean { + public override onTokensChanged(e: viewEvents.ViewTokensChangedEvent): boolean { return this._visibleLines.onTokensChanged(e); } - public onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean { + public override onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean { this._context.model.setMaxLineWidth(this._maxLineWidth); return this._visibleLines.onZonesChanged(e); } - public onThemeChanged(e: viewEvents.ViewThemeChangedEvent): boolean { + public override onThemeChanged(e: viewEvents.ViewThemeChangedEvent): boolean { return this._onOptionsMaybeChanged(); } diff --git a/src/vs/editor/browser/viewParts/linesDecorations/linesDecorations.ts b/src/vs/editor/browser/viewParts/linesDecorations/linesDecorations.ts index 6e40faf7..bdc2eed9 100644 --- a/src/vs/editor/browser/viewParts/linesDecorations/linesDecorations.ts +++ b/src/vs/editor/browser/viewParts/linesDecorations/linesDecorations.ts @@ -30,7 +30,7 @@ export class LinesDecorationsOverlay extends DedupOverlay { this._context.addEventHandler(this); } - public dispose(): void { + public override dispose(): void { this._context.removeEventHandler(this); this._renderResult = null; super.dispose(); @@ -38,32 +38,32 @@ export class LinesDecorationsOverlay extends DedupOverlay { // --- begin event handlers - public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { + public override onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { const options = this._context.configuration.options; const layoutInfo = options.get(EditorOption.layoutInfo); this._decorationsLeft = layoutInfo.decorationsLeft; this._decorationsWidth = layoutInfo.decorationsWidth; return true; } - public onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean { + public override onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean { return true; } - public onFlushed(e: viewEvents.ViewFlushedEvent): boolean { + public override onFlushed(e: viewEvents.ViewFlushedEvent): boolean { return true; } - public onLinesChanged(e: viewEvents.ViewLinesChangedEvent): boolean { + public override onLinesChanged(e: viewEvents.ViewLinesChangedEvent): boolean { return true; } - public onLinesDeleted(e: viewEvents.ViewLinesDeletedEvent): boolean { + public override onLinesDeleted(e: viewEvents.ViewLinesDeletedEvent): boolean { return true; } - public onLinesInserted(e: viewEvents.ViewLinesInsertedEvent): boolean { + public override onLinesInserted(e: viewEvents.ViewLinesInsertedEvent): boolean { return true; } - public onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean { + public override onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean { return e.scrollTopChanged; } - public onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean { + public override onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean { return true; } diff --git a/src/vs/editor/browser/viewParts/margin/margin.ts b/src/vs/editor/browser/viewParts/margin/margin.ts index 8181e2f8..11ba874c 100644 --- a/src/vs/editor/browser/viewParts/margin/margin.ts +++ b/src/vs/editor/browser/viewParts/margin/margin.ts @@ -45,7 +45,7 @@ export class Margin extends ViewPart { this._domNode.appendChild(this._glyphMarginBackgroundDomNode); } - public dispose(): void { + public override dispose(): void { super.dispose(); } @@ -55,7 +55,7 @@ export class Margin extends ViewPart { // --- begin event handlers - public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { + public override onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { const options = this._context.configuration.options; const layoutInfo = options.get(EditorOption.layoutInfo); @@ -66,7 +66,7 @@ export class Margin extends ViewPart { return true; } - public onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean { + public override onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean { return super.onScrollChanged(e) || e.scrollTopChanged; } diff --git a/src/vs/editor/browser/viewParts/marginDecorations/marginDecorations.ts b/src/vs/editor/browser/viewParts/marginDecorations/marginDecorations.ts index 7f5d18d8..89a154d5 100644 --- a/src/vs/editor/browser/viewParts/marginDecorations/marginDecorations.ts +++ b/src/vs/editor/browser/viewParts/marginDecorations/marginDecorations.ts @@ -20,7 +20,7 @@ export class MarginViewLineDecorationsOverlay extends DedupOverlay { this._context.addEventHandler(this); } - public dispose(): void { + public override dispose(): void { this._context.removeEventHandler(this); this._renderResult = null; super.dispose(); @@ -28,28 +28,28 @@ export class MarginViewLineDecorationsOverlay extends DedupOverlay { // --- begin event handlers - public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { + public override onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { return true; } - public onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean { + public override onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean { return true; } - public onFlushed(e: viewEvents.ViewFlushedEvent): boolean { + public override onFlushed(e: viewEvents.ViewFlushedEvent): boolean { return true; } - public onLinesChanged(e: viewEvents.ViewLinesChangedEvent): boolean { + public override onLinesChanged(e: viewEvents.ViewLinesChangedEvent): boolean { return true; } - public onLinesDeleted(e: viewEvents.ViewLinesDeletedEvent): boolean { + public override onLinesDeleted(e: viewEvents.ViewLinesDeletedEvent): boolean { return true; } - public onLinesInserted(e: viewEvents.ViewLinesInsertedEvent): boolean { + public override onLinesInserted(e: viewEvents.ViewLinesInsertedEvent): boolean { return true; } - public onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean { + public override onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean { return e.scrollTopChanged; } - public onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean { + public override onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean { return true; } diff --git a/src/vs/editor/browser/viewParts/minimap/minimap.ts b/src/vs/editor/browser/viewParts/minimap/minimap.ts index 348315fa..0ea038e6 100644 --- a/src/vs/editor/browser/viewParts/minimap/minimap.ts +++ b/src/vs/editor/browser/viewParts/minimap/minimap.ts @@ -759,7 +759,7 @@ export class Minimap extends ViewPart implements IMinimapModel { this._actual = new InnerMinimap(context.theme, this); } - public dispose(): void { + public override dispose(): void { this._actual.dispose(); super.dispose(); } @@ -781,27 +781,27 @@ export class Minimap extends ViewPart implements IMinimapModel { // ---- begin view event handlers - public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { + public override onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { return this._onOptionsMaybeChanged(); } - public onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean { + public override onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean { this._selections = e.selections; this._minimapSelections = null; return this._actual.onSelectionChanged(); } - public onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean { + public override onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean { if (e.affectsMinimap) { return this._actual.onDecorationsChanged(); } return false; } - public onFlushed(e: viewEvents.ViewFlushedEvent): boolean { + public override onFlushed(e: viewEvents.ViewFlushedEvent): boolean { if (this._samplingState) { this._shouldCheckSampling = true; } return this._actual.onFlushed(); } - public onLinesChanged(e: viewEvents.ViewLinesChangedEvent): boolean { + public override onLinesChanged(e: viewEvents.ViewLinesChangedEvent): boolean { if (this._samplingState) { const minimapLineRange = this._samplingState.modelLineRangeToMinimapLineRange(e.fromLineNumber, e.toLineNumber); if (minimapLineRange) { @@ -813,7 +813,7 @@ export class Minimap extends ViewPart implements IMinimapModel { return this._actual.onLinesChanged(e.fromLineNumber, e.toLineNumber); } } - public onLinesDeleted(e: viewEvents.ViewLinesDeletedEvent): boolean { + public override onLinesDeleted(e: viewEvents.ViewLinesDeletedEvent): boolean { if (this._samplingState) { const [changeStartIndex, changeEndIndex] = this._samplingState.onLinesDeleted(e); if (changeStartIndex <= changeEndIndex) { @@ -825,7 +825,7 @@ export class Minimap extends ViewPart implements IMinimapModel { return this._actual.onLinesDeleted(e.fromLineNumber, e.toLineNumber); } } - public onLinesInserted(e: viewEvents.ViewLinesInsertedEvent): boolean { + public override onLinesInserted(e: viewEvents.ViewLinesInsertedEvent): boolean { if (this._samplingState) { this._samplingState.onLinesInserted(e); this._shouldCheckSampling = true; @@ -834,16 +834,16 @@ export class Minimap extends ViewPart implements IMinimapModel { return this._actual.onLinesInserted(e.fromLineNumber, e.toLineNumber); } } - public onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean { + public override onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean { return this._actual.onScrollChanged(); } - public onThemeChanged(e: viewEvents.ViewThemeChangedEvent): boolean { + public override onThemeChanged(e: viewEvents.ViewThemeChangedEvent): boolean { this._context.model.invalidateMinimapColorCache(); this._actual.onThemeChanged(); this._onOptionsMaybeChanged(); return true; } - public onTokensChanged(e: viewEvents.ViewTokensChangedEvent): boolean { + public override onTokensChanged(e: viewEvents.ViewTokensChangedEvent): boolean { if (this._samplingState) { let ranges: { fromLineNumber: number; toLineNumber: number; }[] = []; for (const range of e.ranges) { @@ -861,11 +861,11 @@ export class Minimap extends ViewPart implements IMinimapModel { return this._actual.onTokensChanged(e.ranges); } } - public onTokensColorsChanged(e: viewEvents.ViewTokensColorsChangedEvent): boolean { + public override onTokensColorsChanged(e: viewEvents.ViewTokensColorsChangedEvent): boolean { this._onOptionsMaybeChanged(); return this._actual.onTokensColorsChanged(); } - public onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean { + public override onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean { return this._actual.onZonesChanged(); } @@ -1211,7 +1211,7 @@ class InnerMinimap extends Disposable { this._model.setScrollTop(scrollTop); } - public dispose(): void { + public override dispose(): void { this._mouseDownListener.dispose(); this._sliderMouseMoveMonitor.dispose(); this._sliderMouseDownListener.dispose(); diff --git a/src/vs/editor/browser/viewParts/overlayWidgets/overlayWidgets.ts b/src/vs/editor/browser/viewParts/overlayWidgets/overlayWidgets.ts index b51be8aa..ea3881e8 100644 --- a/src/vs/editor/browser/viewParts/overlayWidgets/overlayWidgets.ts +++ b/src/vs/editor/browser/viewParts/overlayWidgets/overlayWidgets.ts @@ -52,7 +52,7 @@ export class ViewOverlayWidgets extends ViewPart { this._domNode.setClassName('overlayWidgets'); } - public dispose(): void { + public override dispose(): void { super.dispose(); this._widgets = {}; } @@ -63,7 +63,7 @@ export class ViewOverlayWidgets extends ViewPart { // ---- begin view event handlers - public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { + public override onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { const options = this._context.configuration.options; const layoutInfo = options.get(EditorOption.layoutInfo); diff --git a/src/vs/editor/browser/viewParts/overviewRuler/decorationsOverviewRuler.ts b/src/vs/editor/browser/viewParts/overviewRuler/decorationsOverviewRuler.ts index 287011ba..02cc65c1 100644 --- a/src/vs/editor/browser/viewParts/overviewRuler/decorationsOverviewRuler.ts +++ b/src/vs/editor/browser/viewParts/overviewRuler/decorationsOverviewRuler.ts @@ -237,7 +237,7 @@ export class DecorationsOverviewRuler extends ViewPart { this._cursorPositions = []; } - public dispose(): void { + public override dispose(): void { super.dispose(); this._tokensColorTrackerListener.dispose(); } @@ -267,10 +267,10 @@ export class DecorationsOverviewRuler extends ViewPart { // ---- begin view event handlers - public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { + public override onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { return this._updateSettings(false); } - public onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean { + public override onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean { this._cursorPositions = []; for (let i = 0, len = e.selections.length; i < len; i++) { this._cursorPositions[i] = e.selections[i].getPosition(); @@ -278,22 +278,22 @@ export class DecorationsOverviewRuler extends ViewPart { this._cursorPositions.sort(Position.compare); return true; } - public onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean { + public override onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean { if (e.affectsOverviewRuler) { return true; } return false; } - public onFlushed(e: viewEvents.ViewFlushedEvent): boolean { + public override onFlushed(e: viewEvents.ViewFlushedEvent): boolean { return true; } - public onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean { + public override onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean { return e.scrollHeightChanged; } - public onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean { + public override onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean { return true; } - public onThemeChanged(e: viewEvents.ViewThemeChangedEvent): boolean { + public override onThemeChanged(e: viewEvents.ViewThemeChangedEvent): boolean { // invalidate color cache this._context.model.invalidateOverviewRulerColorCache(); return this._updateSettings(false); diff --git a/src/vs/editor/browser/viewParts/overviewRuler/overviewRuler.ts b/src/vs/editor/browser/viewParts/overviewRuler/overviewRuler.ts index b75ba0ae..d13dc67d 100644 --- a/src/vs/editor/browser/viewParts/overviewRuler/overviewRuler.ts +++ b/src/vs/editor/browser/viewParts/overviewRuler/overviewRuler.ts @@ -39,14 +39,14 @@ export class OverviewRuler extends ViewEventHandler implements IOverviewRuler { this._context.addEventHandler(this); } - public dispose(): void { + public override dispose(): void { this._context.removeEventHandler(this); super.dispose(); } // ---- begin view event handlers - public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { + public override onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { const options = this._context.configuration.options; if (e.hasChanged(EditorOption.lineHeight)) { @@ -65,18 +65,18 @@ export class OverviewRuler extends ViewEventHandler implements IOverviewRuler { return true; } - public onFlushed(e: viewEvents.ViewFlushedEvent): boolean { + public override onFlushed(e: viewEvents.ViewFlushedEvent): boolean { this._render(); return true; } - public onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean { + public override onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean { if (e.scrollHeightChanged) { this._zoneManager.setOuterHeight(e.scrollHeight); this._render(); } return true; } - public onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean { + public override onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean { this._render(); return true; } diff --git a/src/vs/editor/browser/viewParts/rulers/rulers.ts b/src/vs/editor/browser/viewParts/rulers/rulers.ts index 696e088d..fef13c81 100644 --- a/src/vs/editor/browser/viewParts/rulers/rulers.ts +++ b/src/vs/editor/browser/viewParts/rulers/rulers.ts @@ -32,19 +32,19 @@ export class Rulers extends ViewPart { this._typicalHalfwidthCharacterWidth = options.get(EditorOption.fontInfo).typicalHalfwidthCharacterWidth; } - public dispose(): void { + public override dispose(): void { super.dispose(); } // --- begin event handlers - public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { + public override onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { const options = this._context.configuration.options; this._rulers = options.get(EditorOption.rulers); this._typicalHalfwidthCharacterWidth = options.get(EditorOption.fontInfo).typicalHalfwidthCharacterWidth; return true; } - public onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean { + public override onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean { return e.scrollHeightChanged; } diff --git a/src/vs/editor/browser/viewParts/scrollDecoration/scrollDecoration.ts b/src/vs/editor/browser/viewParts/scrollDecoration/scrollDecoration.ts index 12e4df8b..5e5d5b6d 100644 --- a/src/vs/editor/browser/viewParts/scrollDecoration/scrollDecoration.ts +++ b/src/vs/editor/browser/viewParts/scrollDecoration/scrollDecoration.ts @@ -37,7 +37,7 @@ export class ScrollDecorationViewPart extends ViewPart { this._domNode.setAttribute('aria-hidden', 'true'); } - public dispose(): void { + public override dispose(): void { super.dispose(); } @@ -67,7 +67,7 @@ export class ScrollDecorationViewPart extends ViewPart { // --- begin event handlers - public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { + public override onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { const options = this._context.configuration.options; const scrollbar = options.get(EditorOption.scrollbar); this._useShadows = scrollbar.useShadows; @@ -75,7 +75,7 @@ export class ScrollDecorationViewPart extends ViewPart { this._updateShouldShow(); return true; } - public onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean { + public override onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean { this._scrollTop = e.scrollTop; return this._updateShouldShow(); } diff --git a/src/vs/editor/browser/viewParts/selections/selections.ts b/src/vs/editor/browser/viewParts/selections/selections.ts index 383f55a3..2ecb8afc 100644 --- a/src/vs/editor/browser/viewParts/selections/selections.ts +++ b/src/vs/editor/browser/viewParts/selections/selections.ts @@ -86,7 +86,7 @@ export class SelectionsOverlay extends DynamicViewOverlay { this._context.addEventHandler(this); } - public dispose(): void { + public override dispose(): void { this._context.removeEventHandler(this); this._renderResult = null; super.dispose(); @@ -94,37 +94,37 @@ export class SelectionsOverlay extends DynamicViewOverlay { // --- begin event handlers - public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { + public override onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { const options = this._context.configuration.options; this._lineHeight = options.get(EditorOption.lineHeight); this._roundedSelection = options.get(EditorOption.roundedSelection); this._typicalHalfwidthCharacterWidth = options.get(EditorOption.fontInfo).typicalHalfwidthCharacterWidth; return true; } - public onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean { + public override onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean { this._selections = e.selections.slice(0); return true; } - public onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean { + public override onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean { // true for inline decorations that can end up relayouting text return true;//e.inlineDecorationsChanged; } - public onFlushed(e: viewEvents.ViewFlushedEvent): boolean { + public override onFlushed(e: viewEvents.ViewFlushedEvent): boolean { return true; } - public onLinesChanged(e: viewEvents.ViewLinesChangedEvent): boolean { + public override onLinesChanged(e: viewEvents.ViewLinesChangedEvent): boolean { return true; } - public onLinesDeleted(e: viewEvents.ViewLinesDeletedEvent): boolean { + public override onLinesDeleted(e: viewEvents.ViewLinesDeletedEvent): boolean { return true; } - public onLinesInserted(e: viewEvents.ViewLinesInsertedEvent): boolean { + public override onLinesInserted(e: viewEvents.ViewLinesInsertedEvent): boolean { return true; } - public onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean { + public override onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean { return e.scrollTopChanged; } - public onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean { + public override onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean { return true; } diff --git a/src/vs/editor/browser/viewParts/viewCursors/viewCursors.ts b/src/vs/editor/browser/viewParts/viewCursors/viewCursors.ts index ec5077ea..7454608e 100644 --- a/src/vs/editor/browser/viewParts/viewCursors/viewCursors.ts +++ b/src/vs/editor/browser/viewParts/viewCursors/viewCursors.ts @@ -74,7 +74,7 @@ export class ViewCursors extends ViewPart { this._updateBlinking(); } - public dispose(): void { + public override dispose(): void { super.dispose(); this._startCursorBlinkAnimation.dispose(); this._cursorFlatBlinkInterval.dispose(); @@ -85,17 +85,17 @@ export class ViewCursors extends ViewPart { } // --- begin event handlers - public onCompositionStart(e: viewEvents.ViewCompositionStartEvent): boolean { + public override onCompositionStart(e: viewEvents.ViewCompositionStartEvent): boolean { this._isComposingInput = true; this._updateBlinking(); return true; } - public onCompositionEnd(e: viewEvents.ViewCompositionEndEvent): boolean { + public override onCompositionEnd(e: viewEvents.ViewCompositionEndEvent): boolean { this._isComposingInput = false; this._updateBlinking(); return true; } - public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { + public override onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { const options = this._context.configuration.options; this._readOnly = options.get(EditorOption.readOnly); @@ -138,7 +138,7 @@ export class ViewCursors extends ViewPart { } } - public onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean { + public override onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean { const positions: Position[] = []; for (let i = 0, len = e.selections.length; i < len; i++) { positions[i] = e.selections[i].getPosition(); @@ -154,31 +154,31 @@ export class ViewCursors extends ViewPart { return true; } - public onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean { + public override onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean { // true for inline decorations that can end up relayouting text return true; } - public onFlushed(e: viewEvents.ViewFlushedEvent): boolean { + public override onFlushed(e: viewEvents.ViewFlushedEvent): boolean { return true; } - public onFocusChanged(e: viewEvents.ViewFocusChangedEvent): boolean { + public override onFocusChanged(e: viewEvents.ViewFocusChangedEvent): boolean { this._editorHasFocus = e.isFocused; this._updateBlinking(); return false; } - public onLinesChanged(e: viewEvents.ViewLinesChangedEvent): boolean { + public override onLinesChanged(e: viewEvents.ViewLinesChangedEvent): boolean { return true; } - public onLinesDeleted(e: viewEvents.ViewLinesDeletedEvent): boolean { + public override onLinesDeleted(e: viewEvents.ViewLinesDeletedEvent): boolean { return true; } - public onLinesInserted(e: viewEvents.ViewLinesInsertedEvent): boolean { + public override onLinesInserted(e: viewEvents.ViewLinesInsertedEvent): boolean { return true; } - public onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean { + public override onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean { return true; } - public onTokensChanged(e: viewEvents.ViewTokensChangedEvent): boolean { + public override onTokensChanged(e: viewEvents.ViewTokensChangedEvent): boolean { const shouldRender = (position: Position) => { for (let i = 0, len = e.ranges.length; i < len; i++) { if (e.ranges[i].fromLineNumber <= position.lineNumber && position.lineNumber <= e.ranges[i].toLineNumber) { @@ -197,7 +197,7 @@ export class ViewCursors extends ViewPart { } return false; } - public onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean { + public override onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean { return true; } diff --git a/src/vs/editor/browser/viewParts/viewZones/viewZones.ts b/src/vs/editor/browser/viewParts/viewZones/viewZones.ts index a73026a3..7d6ae188 100644 --- a/src/vs/editor/browser/viewParts/viewZones/viewZones.ts +++ b/src/vs/editor/browser/viewParts/viewZones/viewZones.ts @@ -66,7 +66,7 @@ export class ViewZones extends ViewPart { this._zones = {}; } - public dispose(): void { + public override dispose(): void { super.dispose(); this._zones = {}; } @@ -97,7 +97,7 @@ export class ViewZones extends ViewPart { return hadAChange; } - public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { + public override onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { const options = this._context.configuration.options; const layoutInfo = options.get(EditorOption.layoutInfo); @@ -112,23 +112,23 @@ export class ViewZones extends ViewPart { return true; } - public onLineMappingChanged(e: viewEvents.ViewLineMappingChangedEvent): boolean { + public override onLineMappingChanged(e: viewEvents.ViewLineMappingChangedEvent): boolean { return this._recomputeWhitespacesProps(); } - public onLinesDeleted(e: viewEvents.ViewLinesDeletedEvent): boolean { + public override onLinesDeleted(e: viewEvents.ViewLinesDeletedEvent): boolean { return true; } - public onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean { + public override onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean { return e.scrollTopChanged || e.scrollWidthChanged; } - public onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean { + public override onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean { return true; } - public onLinesInserted(e: viewEvents.ViewLinesInsertedEvent): boolean { + public override onLinesInserted(e: viewEvents.ViewLinesInsertedEvent): boolean { return true; } diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index 24444710..9d89b72d 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -346,7 +346,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE return editorCommon.EditorType.ICodeEditor; } - public dispose(): void { + public override dispose(): void { this._codeEditorService.removeCodeEditor(this); this._focusTracker.dispose(); @@ -1906,7 +1906,7 @@ export class EditorModeContext extends Disposable { update(); } - dispose() { + override dispose() { super.dispose(); } diff --git a/src/vs/editor/browser/widget/diffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditorWidget.ts index 6256e77b..19170cfe 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget.ts @@ -619,7 +619,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE return instantiationService.createInstance(CodeEditorWidget, container, options, editorWidgetOptions); } - public dispose(): void { + public override dispose(): void { this._codeEditorService.removeDiffEditor(this); if (this._beginUpdateDecorationsTimeout !== -1) { @@ -2239,7 +2239,7 @@ class InlineViewZonesComputer extends ViewZonesComputer { this._lineBreaksComputer = this._modifiedEditor._getViewModel()!.createLineBreaksComputer(); } - public getViewZones(): IEditorsZones { + public override getViewZones(): IEditorsZones { const result = super.getViewZones(); this._finalize(result); return result; diff --git a/src/vs/editor/browser/widget/diffNavigator.ts b/src/vs/editor/browser/widget/diffNavigator.ts index f341549a..a69b401c 100644 --- a/src/vs/editor/browser/widget/diffNavigator.ts +++ b/src/vs/editor/browser/widget/diffNavigator.ts @@ -221,7 +221,7 @@ export class DiffNavigator extends Disposable implements IDiffNavigator { this._move(false, scrollType); } - dispose(): void { + override dispose(): void { super.dispose(); this.ranges = []; this.disposed = true; diff --git a/src/vs/editor/browser/widget/diffReview.ts b/src/vs/editor/browser/widget/diffReview.ts index 2fb0eaed..8a568c82 100644 --- a/src/vs/editor/browser/widget/diffReview.ts +++ b/src/vs/editor/browser/widget/diffReview.ts @@ -107,10 +107,7 @@ export class DiffReview extends Disposable { this.actionBarContainer.domNode )); - this._actionBar.push(new Action('diffreview.close', nls.localize('label.close', "Close"), 'close-diff-review ' + ThemeIcon.asClassName(diffReviewCloseIcon), true, () => { - this.hide(); - return Promise.resolve(null); - }), { label: false, icon: true }); + this._actionBar.push(new Action('diffreview.close', nls.localize('label.close', "Close"), 'close-diff-review ' + ThemeIcon.asClassName(diffReviewCloseIcon), true, async () => this.hide()), { label: false, icon: true }); this.domNode = createFastDomNode(document.createElement('div')); this.domNode.setClassName('diff-review monaco-editor-background'); diff --git a/src/vs/editor/browser/widget/embeddedCodeEditorWidget.ts b/src/vs/editor/browser/widget/embeddedCodeEditorWidget.ts index 3836a4ec..911e5935 100644 --- a/src/vs/editor/browser/widget/embeddedCodeEditorWidget.ts +++ b/src/vs/editor/browser/widget/embeddedCodeEditorWidget.ts @@ -57,7 +57,7 @@ export class EmbeddedCodeEditorWidget extends CodeEditorWidget { super.updateOptions(this._overwriteOptions); } - updateOptions(newOptions: IEditorOptions): void { + override updateOptions(newOptions: IEditorOptions): void { objects.mixin(this._overwriteOptions, newOptions, true); super.updateOptions(this._overwriteOptions); } @@ -102,7 +102,7 @@ export class EmbeddedDiffEditorWidget extends DiffEditorWidget { super.updateOptions(this._overwriteOptions); } - updateOptions(newOptions: IEditorOptions): void { + override updateOptions(newOptions: IEditorOptions): void { objects.mixin(this._overwriteOptions, newOptions, true); super.updateOptions(this._overwriteOptions); } diff --git a/src/vs/editor/common/commands/shiftCommand.ts b/src/vs/editor/common/commands/shiftCommand.ts index 52473fd2..ceb3be68 100644 --- a/src/vs/editor/common/commands/shiftCommand.ts +++ b/src/vs/editor/common/commands/shiftCommand.ts @@ -24,6 +24,9 @@ export interface IShiftCommandOpts { const repeatCache: { [str: string]: string[]; } = Object.create(null); export function cachedStringRepeat(str: string, count: number): string { + if (count <= 0) { + return ''; + } if (!repeatCache[str]) { repeatCache[str] = ['', str]; } diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 56bb100d..ee5f4095 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -906,7 +906,7 @@ class EditorBooleanOption extends SimpleEditorOption extends SimpleEditorOption extends SimpleEditorOption extends SimpleEditorOption extends this._allowedValues = allowedValues; } - public validate(input: any): V { + public override validate(input: any): V { return stringSet(input, this.defaultValue, this._allowedValues); } } @@ -1112,7 +1112,7 @@ class EditorAccessibilitySupport extends BaseEditorOption { ); } - public validate(input: any): number { + public override validate(input: any): number { let r = EditorFloatOption.float(input, this.defaultValue); if (r === 0) { return EDITOR_FONT_DEFAULTS.fontSize; } return EditorFloatOption.clamp(r, 6, 100); } - public compute(env: IEnvironmentalOptions, options: IComputedEditorOptions, value: number): number { + public override compute(env: IEnvironmentalOptions, options: IComputedEditorOptions, value: number): number { // The final fontSize respects the editor zoom level. // So take the result from env.fontInfo return env.fontInfo.fontSize; @@ -1643,6 +1643,7 @@ class EditorGoToLocation extends BaseEditorOption { ); } - public compute(env: IEnvironmentalOptions, options: IComputedEditorOptions, value: number): number { + public override compute(env: IEnvironmentalOptions, options: IComputedEditorOptions, value: number): number { // The lineHeight is computed from the fontSize if it is 0. // Moreover, the final lineHeight respects the editor zoom level. // So take the result from env.fontInfo @@ -2766,7 +2772,7 @@ export type ValidQuickSuggestionsOptions = boolean | Readonly { - public readonly defaultValue: Readonly>; + public override readonly defaultValue: Readonly>; constructor() { const defaults: ValidQuickSuggestionsOptions = { @@ -3895,8 +3901,7 @@ export const EditorOptions = { accessibilitySupport: register(new EditorAccessibilitySupport()), accessibilityPageSize: register(new EditorIntOption(EditorOption.accessibilityPageSize, 'accessibilityPageSize', 10, 1, Constants.MAX_SAFE_SMALL_INTEGER, { - description: nls.localize('accessibilityPageSize', "Controls the number of lines in the editor that can be read out by a screen reader at once. When we detect a screen reader we automatically set the default to be 2000. Warning: this has a performance implication for numbers larger than the default."), - deprecationMessage: nls.localize('accessibilityPageSize.deprecated', "This setting is deprecated, editor will automatically choose the accessibility page size when we detect a screen reader. 2000 lines will be the new default.") + description: nls.localize('accessibilityPageSize', "Controls the number of lines in the editor that can be read out by a screen reader at once. When we detect a screen reader we automatically set the default to be 500. Warning: this has a performance implication for numbers larger than the default.") })), ariaLabel: register(new EditorStringOption( EditorOption.ariaLabel, 'ariaLabel', nls.localize('editorViewAccessibleLabel', "Editor content") diff --git a/src/vs/editor/common/controller/cursor.ts b/src/vs/editor/common/controller/cursor.ts index 36c9691b..41f2a9b1 100644 --- a/src/vs/editor/common/controller/cursor.ts +++ b/src/vs/editor/common/controller/cursor.ts @@ -156,7 +156,7 @@ export class Cursor extends Disposable { this._prevEditOperationType = EditOperationType.Other; } - public dispose(): void { + public override dispose(): void { this._cursors.dispose(); this._autoClosedActions = dispose(this._autoClosedActions); super.dispose(); diff --git a/src/vs/editor/common/controller/cursorCommon.ts b/src/vs/editor/common/controller/cursorCommon.ts index ce63ac7b..0077027b 100644 --- a/src/vs/editor/common/controller/cursorCommon.ts +++ b/src/vs/editor/common/controller/cursorCommon.ts @@ -562,14 +562,14 @@ export class CursorColumns { * ATTENTION: This works with 0-based columns (as opposed to the regular 1-based columns) */ public static prevRenderTabStop(column: number, tabSize: number): number { - return column - 1 - (column - 1) % tabSize; + return Math.max(0, column - 1 - (column - 1) % tabSize); } /** * ATTENTION: This works with 0-based columns (as opposed to the regular 1-based columns) */ public static prevIndentTabStop(column: number, indentSize: number): number { - return column - 1 - (column - 1) % indentSize; + return Math.max(0, column - 1 - (column - 1) % indentSize); } } diff --git a/src/vs/editor/common/controller/cursorTypeOperations.ts b/src/vs/editor/common/controller/cursorTypeOperations.ts index ff4c4c56..4acc0ae7 100644 --- a/src/vs/editor/common/controller/cursorTypeOperations.ts +++ b/src/vs/editor/common/controller/cursorTypeOperations.ts @@ -957,7 +957,7 @@ export class TypeWithAutoClosingCommand extends ReplaceCommandWithOffsetCursorSt this.enclosingRange = null; } - public computeCursorState(model: ITextModel, helper: ICursorStateComputerData): Selection { + public override computeCursorState(model: ITextModel, helper: ICursorStateComputerData): Selection { let inverseEditOperations = helper.getInverseEditOperations(); let range = inverseEditOperations[0].range; this.closeCharacterRange = new Range(range.startLineNumber, range.endColumn - this._closeCharacter.length, range.endLineNumber, range.endColumn); diff --git a/src/vs/editor/common/core/selection.ts b/src/vs/editor/common/core/selection.ts index 8f192adc..3ee2e70a 100644 --- a/src/vs/editor/common/core/selection.ts +++ b/src/vs/editor/common/core/selection.ts @@ -76,7 +76,7 @@ export class Selection extends Range { /** * Transform to a human-readable representation. */ - public toString(): string { + public override toString(): string { return '[' + this.selectionStartLineNumber + ',' + this.selectionStartColumn + ' -> ' + this.positionLineNumber + ',' + this.positionColumn + ']'; } @@ -114,7 +114,7 @@ export class Selection extends Range { /** * Create a new selection with a different `positionLineNumber` and `positionColumn`. */ - public setEndPosition(endLineNumber: number, endColumn: number): Selection { + public override setEndPosition(endLineNumber: number, endColumn: number): Selection { if (this.getDirection() === SelectionDirection.LTR) { return new Selection(this.startLineNumber, this.startColumn, endLineNumber, endColumn); } @@ -131,7 +131,7 @@ export class Selection extends Range { /** * Create a new selection with a different `selectionStartLineNumber` and `selectionStartColumn`. */ - public setStartPosition(startLineNumber: number, startColumn: number): Selection { + public override setStartPosition(startLineNumber: number, startColumn: number): Selection { if (this.getDirection() === SelectionDirection.LTR) { return new Selection(startLineNumber, startColumn, this.endLineNumber, this.endColumn); } @@ -143,7 +143,7 @@ export class Selection extends Range { /** * Create a `Selection` from one or two positions */ - public static fromPositions(start: IPosition, end: IPosition = start): Selection { + public static override fromPositions(start: IPosition, end: IPosition = start): Selection { return new Selection(start.lineNumber, start.column, end.lineNumber, end.column); } diff --git a/src/vs/editor/common/model/mirrorTextModel.ts b/src/vs/editor/common/model/mirrorTextModel.ts index 9e14ea63..26a31412 100644 --- a/src/vs/editor/common/model/mirrorTextModel.ts +++ b/src/vs/editor/common/model/mirrorTextModel.ts @@ -25,7 +25,11 @@ export interface IModelChangedEvent { readonly versionId: number; } -export class MirrorTextModel { +export interface IMirrorTextModel { + readonly version: number; +} + +export class MirrorTextModel implements IMirrorTextModel { protected _uri: URI; protected _lines: string[]; diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts index 9814d83c..61452e16 100644 --- a/src/vs/editor/common/model/textModel.ts +++ b/src/vs/editor/common/model/textModel.ts @@ -57,9 +57,9 @@ interface ITextStream { on(event: string, callback: any): void; } -export function createTextBufferFactoryFromStream(stream: ITextStream, filter?: (chunk: string) => string, validator?: (chunk: string) => Error | undefined): Promise; -export function createTextBufferFactoryFromStream(stream: VSBufferReadableStream, filter?: (chunk: VSBuffer) => VSBuffer, validator?: (chunk: VSBuffer) => Error | undefined): Promise; -export function createTextBufferFactoryFromStream(stream: ITextStream | VSBufferReadableStream, filter?: (chunk: any) => string | VSBuffer, validator?: (chunk: any) => Error | undefined): Promise { +export function createTextBufferFactoryFromStream(stream: ITextStream): Promise; +export function createTextBufferFactoryFromStream(stream: VSBufferReadableStream): Promise; +export function createTextBufferFactoryFromStream(stream: ITextStream | VSBufferReadableStream): Promise { return new Promise((resolve, reject) => { const builder = createTextBufferBuilder(); @@ -67,18 +67,6 @@ export function createTextBufferFactoryFromStream(stream: ITextStream | VSBuffer listenStream(stream, { onData: chunk => { - if (validator) { - const error = validator(chunk); - if (error) { - done = true; - reject(error); - } - } - - if (filter) { - chunk = filter(chunk); - } - builder.acceptChunk((typeof chunk === 'string') ? chunk : chunk.toString()); }, onError: error => { @@ -384,7 +372,7 @@ export class TextModel extends Disposable implements model.ITextModel { this._tokenization = new TextModelTokenization(this); } - public dispose(): void { + public override dispose(): void { this._isDisposing = true; this._onWillDispose.fire(); this._languageRegistryListener.dispose(); @@ -2106,10 +2094,42 @@ export class TextModel extends Disposable implements model.ITextModel { return this._matchBracket(this.validatePosition(position)); } + private _establishBracketSearchOffsets(position: Position, lineTokens: LineTokens, modeBrackets: RichEditBrackets, tokenIndex: number) { + const tokenCount = lineTokens.getCount(); + const currentLanguageId = lineTokens.getLanguageId(tokenIndex); + + // limit search to not go before `maxBracketLength` + let searchStartOffset = Math.max(0, position.column - 1 - modeBrackets.maxBracketLength); + for (let i = tokenIndex - 1; i >= 0; i--) { + const tokenEndOffset = lineTokens.getEndOffset(i); + if (tokenEndOffset <= searchStartOffset) { + break; + } + if (ignoreBracketsInToken(lineTokens.getStandardTokenType(i)) || lineTokens.getLanguageId(i) !== currentLanguageId) { + searchStartOffset = tokenEndOffset; + break; + } + } + + // limit search to not go after `maxBracketLength` + let searchEndOffset = Math.min(lineTokens.getLineContent().length, position.column - 1 + modeBrackets.maxBracketLength); + for (let i = tokenIndex + 1; i < tokenCount; i++) { + const tokenStartOffset = lineTokens.getStartOffset(i); + if (tokenStartOffset >= searchEndOffset) { + break; + } + if (ignoreBracketsInToken(lineTokens.getStandardTokenType(i)) || lineTokens.getLanguageId(i) !== currentLanguageId) { + searchEndOffset = tokenStartOffset; + break; + } + } + + return { searchStartOffset, searchEndOffset }; + } + private _matchBracket(position: Position): [Range, Range] | null { const lineNumber = position.lineNumber; const lineTokens = this._getLineTokens(lineNumber); - const tokenCount = lineTokens.getCount(); const lineText = this._buffer.getLineContent(lineNumber); const tokenIndex = lineTokens.findTokenIndexAtOffset(position.column - 1); @@ -2120,19 +2140,8 @@ export class TextModel extends Disposable implements model.ITextModel { // check that the token is not to be ignored if (currentModeBrackets && !ignoreBracketsInToken(lineTokens.getStandardTokenType(tokenIndex))) { - // limit search to not go before `maxBracketLength` - let searchStartOffset = Math.max(0, position.column - 1 - currentModeBrackets.maxBracketLength); - for (let i = tokenIndex - 1; i >= 0; i--) { - const tokenEndOffset = lineTokens.getEndOffset(i); - if (tokenEndOffset <= searchStartOffset) { - break; - } - if (ignoreBracketsInToken(lineTokens.getStandardTokenType(i))) { - searchStartOffset = tokenEndOffset; - } - } - // limit search to not go after `maxBracketLength` - const searchEndOffset = Math.min(lineText.length, position.column - 1 + currentModeBrackets.maxBracketLength); + + let { searchStartOffset, searchEndOffset } = this._establishBracketSearchOffsets(position, lineTokens, currentModeBrackets, tokenIndex); // it might be the case that [currentTokenStart -> currentTokenEnd] contains multiple brackets // `bestResult` will contain the most right-side result @@ -2171,18 +2180,9 @@ export class TextModel extends Disposable implements model.ITextModel { // check that previous token is not to be ignored if (prevModeBrackets && !ignoreBracketsInToken(lineTokens.getStandardTokenType(prevTokenIndex))) { - // limit search in case previous token is very large, there's no need to go beyond `maxBracketLength` - const searchStartOffset = Math.max(0, position.column - 1 - prevModeBrackets.maxBracketLength); - let searchEndOffset = Math.min(lineText.length, position.column - 1 + prevModeBrackets.maxBracketLength); - for (let i = prevTokenIndex + 1; i < tokenCount; i++) { - const tokenStartOffset = lineTokens.getStartOffset(i); - if (tokenStartOffset >= searchEndOffset) { - break; - } - if (ignoreBracketsInToken(lineTokens.getStandardTokenType(i))) { - searchEndOffset = tokenStartOffset; - } - } + + let { searchStartOffset, searchEndOffset } = this._establishBracketSearchOffsets(position, lineTokens, prevModeBrackets, prevTokenIndex); + const foundBracket = BracketsUtils.findPrevBracketInRange(prevModeBrackets.reversedRegex, lineNumber, lineText, searchStartOffset, searchEndOffset); // check that we didn't hit a bracket too far away from position diff --git a/src/vs/editor/common/model/textModelTokens.ts b/src/vs/editor/common/model/textModelTokens.ts index 3cf7bc9a..5696219c 100644 --- a/src/vs/editor/common/model/textModelTokens.ts +++ b/src/vs/editor/common/model/textModelTokens.ts @@ -246,7 +246,7 @@ export class TextModelTokenization extends Disposable { this._resetTokenizationState(); } - public dispose(): void { + public override dispose(): void { this._isDisposed = true; super.dispose(); } diff --git a/src/vs/editor/common/modes/supports/tokenization.ts b/src/vs/editor/common/modes/supports/tokenization.ts index e28c23d3..a8ffd6ba 100644 --- a/src/vs/editor/common/modes/supports/tokenization.ts +++ b/src/vs/editor/common/modes/supports/tokenization.ts @@ -313,11 +313,21 @@ export class ThemeTrieElementRule { export class ExternalThemeTrieElement { public readonly mainRule: ThemeTrieElementRule; - public readonly children: { [segment: string]: ExternalThemeTrieElement }; + public readonly children: Map; - constructor(mainRule: ThemeTrieElementRule, children?: { [segment: string]: ExternalThemeTrieElement }) { + constructor( + mainRule: ThemeTrieElementRule, + children: Map | { [key: string]: ExternalThemeTrieElement } = new Map() + ) { this.mainRule = mainRule; - this.children = children || Object.create(null); + if (children instanceof Map) { + this.children = children; + } else { + this.children = new Map(); + for (const key in children) { + this.children.set(key, children[key]); + } + } } } @@ -336,9 +346,9 @@ export class ThemeTrieElement { * used for testing purposes */ public toExternalThemeTrieElement(): ExternalThemeTrieElement { - let children: { [segment: string]: ExternalThemeTrieElement } = Object.create(null); + const children = new Map(); this._children.forEach((element, index) => { - children[index] = element.toExternalThemeTrieElement(); + children.set(index, element.toExternalThemeTrieElement()); }); return new ExternalThemeTrieElement(this._mainRule, children); } diff --git a/src/vs/editor/common/services/editorSimpleWorker.ts b/src/vs/editor/common/services/editorSimpleWorker.ts index 5bdbc995..4ad0c9d3 100644 --- a/src/vs/editor/common/services/editorSimpleWorker.ts +++ b/src/vs/editor/common/services/editorSimpleWorker.ts @@ -13,7 +13,7 @@ import { IRange, Range } from 'vs/editor/common/core/range'; import { DiffComputer } from 'vs/editor/common/diff/diffComputer'; import { IChange } from 'vs/editor/common/editorCommon'; import { EndOfLineSequence, IWordAtPosition } from 'vs/editor/common/model'; -import { IModelChangedEvent, MirrorTextModel as BaseMirrorModel } from 'vs/editor/common/model/mirrorTextModel'; +import { IMirrorTextModel, IModelChangedEvent, MirrorTextModel as BaseMirrorModel } from 'vs/editor/common/model/mirrorTextModel'; import { ensureValidWordDefinition, getWordAtText } from 'vs/editor/common/model/wordHelper'; import { IInplaceReplaceSupportResult, ILink, TextEdit } from 'vs/editor/common/modes'; import { ILinkComputerTarget, computeLinks } from 'vs/editor/common/modes/linkComputer'; @@ -24,7 +24,7 @@ import * as types from 'vs/base/common/types'; import { EditorWorkerHost } from 'vs/editor/common/services/editorWorkerServiceImpl'; import { StopWatch } from 'vs/base/common/stopwatch'; -export interface IMirrorModel { +export interface IMirrorModel extends IMirrorTextModel { readonly uri: URI; readonly version: number; getValue(): string; @@ -96,10 +96,6 @@ class MirrorModel extends BaseMirrorModel implements ICommonModel { return this._uri; } - public get version(): number { - return this._versionId; - } - public get eol(): string { return this._eol; } diff --git a/src/vs/editor/common/services/editorWorkerServiceImpl.ts b/src/vs/editor/common/services/editorWorkerServiceImpl.ts index dc6a0f70..2763dd12 100644 --- a/src/vs/editor/common/services/editorWorkerServiceImpl.ts +++ b/src/vs/editor/common/services/editorWorkerServiceImpl.ts @@ -77,7 +77,7 @@ export class EditorWorkerServiceImpl extends Disposable implements IEditorWorker this._register(modes.CompletionProviderRegistry.register('*', new WordBasedCompletionItemProvider(this._workerManager, configurationService, this._modelService))); } - public dispose(): void { + public override dispose(): void { super.dispose(); } @@ -225,7 +225,7 @@ class WorkerManager extends Disposable { this._register(this._modelService.onModelRemoved(_ => this._checkStopEmptyWorker())); } - public dispose(): void { + public override dispose(): void { if (this._editorWorkerClient) { this._editorWorkerClient.dispose(); this._editorWorkerClient = null; @@ -292,7 +292,7 @@ class EditorModelManager extends Disposable { } } - public dispose(): void { + public override dispose(): void { for (let modelUrl in this._syncedModels) { dispose(this._syncedModels[modelUrl]); } @@ -523,7 +523,7 @@ export class EditorWorkerClient extends Disposable { }); } - dispose(): void { + override dispose(): void { super.dispose(); this._disposed = true; } diff --git a/src/vs/editor/common/services/markerDecorationsServiceImpl.ts b/src/vs/editor/common/services/markerDecorationsServiceImpl.ts index a80302dd..93947019 100644 --- a/src/vs/editor/common/services/markerDecorationsServiceImpl.ts +++ b/src/vs/editor/common/services/markerDecorationsServiceImpl.ts @@ -81,7 +81,7 @@ export class MarkerDecorationsService extends Disposable implements IMarkerDecor this._register(this._markerService.onMarkerChanged(this._handleMarkerChange, this)); } - dispose() { + override dispose() { super.dispose(); this._markerDecorations.forEach(value => value.dispose()); this._markerDecorations.clear(); diff --git a/src/vs/editor/common/services/modeService.ts b/src/vs/editor/common/services/modeService.ts index e52fbf61..8c383913 100644 --- a/src/vs/editor/common/services/modeService.ts +++ b/src/vs/editor/common/services/modeService.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { Event } from 'vs/base/common/event'; -import { IDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { IMode, LanguageId, LanguageIdentifier } from 'vs/editor/common/modes'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; @@ -22,7 +21,7 @@ export interface ILanguageExtensionPoint { configuration?: URI; } -export interface ILanguageSelection extends IDisposable { +export interface ILanguageSelection { readonly languageIdentifier: LanguageIdentifier; readonly onDidChange: Event; } diff --git a/src/vs/editor/common/services/modeServiceImpl.ts b/src/vs/editor/common/services/modeServiceImpl.ts index f5a6eba1..8698618c 100644 --- a/src/vs/editor/common/services/modeServiceImpl.ts +++ b/src/vs/editor/common/services/modeServiceImpl.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Emitter, Event } from 'vs/base/common/event'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { IMode, LanguageId, LanguageIdentifier } from 'vs/editor/common/modes'; import { FrankensteinMode } from 'vs/editor/common/modes/abstractMode'; @@ -13,20 +13,28 @@ import { LanguagesRegistry } from 'vs/editor/common/services/languagesRegistry'; import { ILanguageSelection, IModeService } from 'vs/editor/common/services/modeService'; import { firstOrDefault } from 'vs/base/common/arrays'; -class LanguageSelection extends Disposable implements ILanguageSelection { +class LanguageSelection implements ILanguageSelection { public languageIdentifier: LanguageIdentifier; private readonly _selector: () => LanguageIdentifier; - - private readonly _onDidChange: Emitter = this._register(new Emitter()); - public readonly onDidChange: Event = this._onDidChange.event; + private readonly _onDidChange: Emitter; + public readonly onDidChange: Event; constructor(onLanguagesMaybeChanged: Event, selector: () => LanguageIdentifier) { - super(); this._selector = selector; this.languageIdentifier = this._selector(); - this._register(onLanguagesMaybeChanged(() => this._evaluate())); + + let listener: IDisposable; + this._onDidChange = new Emitter({ + onFirstListenerAdd: () => { + listener = onLanguagesMaybeChanged(() => this._evaluate()); + }, + onLastListenerRemove: () => { + listener.dispose(); + } + }); + this.onDidChange = this._onDidChange.event; } private _evaluate(): void { @@ -49,7 +57,7 @@ export class ModeServiceImpl extends Disposable implements IModeService { private readonly _onDidCreateMode = this._register(new Emitter()); public readonly onDidCreateMode: Event = this._onDidCreateMode.event; - protected readonly _onLanguagesMaybeChanged = this._register(new Emitter()); + protected readonly _onLanguagesMaybeChanged = this._register(new Emitter({ leakWarningThreshold: 200 /* https://github.com/microsoft/vscode/issues/119968 */ })); public readonly onLanguagesMaybeChanged: Event = this._onLanguagesMaybeChanged.event; constructor(warnOnOverwrite = false) { diff --git a/src/vs/editor/common/services/modelServiceImpl.ts b/src/vs/editor/common/services/modelServiceImpl.ts index c7bcc283..3357e282 100644 --- a/src/vs/editor/common/services/modelServiceImpl.ts +++ b/src/vs/editor/common/services/modelServiceImpl.ts @@ -78,10 +78,6 @@ class ModelData implements IDisposable { this._languageSelectionListener.dispose(); this._languageSelectionListener = null; } - if (this._languageSelection) { - this._languageSelection.dispose(); - this._languageSelection = null; - } } public dispose(): void { @@ -778,7 +774,7 @@ export class ModelSemanticColoring extends Disposable { this._fetchDocumentSemanticTokens.schedule(0); } - public dispose(): void { + public override dispose(): void { if (this._currentDocumentResponse) { this._currentDocumentResponse.dispose(); this._currentDocumentResponse = null; diff --git a/src/vs/editor/common/services/resolverService.ts b/src/vs/editor/common/services/resolverService.ts index 74470a81..38e885c2 100644 --- a/src/vs/editor/common/services/resolverService.ts +++ b/src/vs/editor/common/services/resolverService.ts @@ -57,11 +57,6 @@ export interface ITextEditorModel extends IEditorModel { */ isReadonly(): boolean; - /** - * Figure out if this model is resolved or not. - */ - isResolved(): this is IResolvedTextEditorModel; - /** * The mode id of the text model if known. */ diff --git a/src/vs/editor/common/services/webWorker.ts b/src/vs/editor/common/services/webWorker.ts index 55dfe84a..2e3e7e67 100644 --- a/src/vs/editor/common/services/webWorker.ts +++ b/src/vs/editor/common/services/webWorker.ts @@ -76,7 +76,7 @@ class MonacoWebWorkerImpl extends EditorWorkerClient implements MonacoWebWork } // foreign host request - public fhr(method: string, args: any[]): Promise { + public override fhr(method: string, args: any[]): Promise { if (!this._foreignModuleHost || typeof this._foreignModuleHost[method] !== 'function') { return Promise.reject(new Error('Missing method ' + method + ' or missing main thread foreign host.')); } diff --git a/src/vs/editor/common/standalone/standaloneEnums.ts b/src/vs/editor/common/standalone/standaloneEnums.ts index 0425aad4..9860ffa9 100644 --- a/src/vs/editor/common/standalone/standaloneEnums.ts +++ b/src/vs/editor/common/standalone/standaloneEnums.ts @@ -365,6 +365,7 @@ export enum InlineHintKind { * But these are "more general", as they should work across browsers & OS`s. */ export enum KeyCode { + DependsOnKbLayout = -1, /** * Placed first to cover the 0 value of the enum. */ diff --git a/src/vs/editor/common/viewLayout/viewLayout.ts b/src/vs/editor/common/viewLayout/viewLayout.ts index 9d5583ee..2f9d18b1 100644 --- a/src/vs/editor/common/viewLayout/viewLayout.ts +++ b/src/vs/editor/common/viewLayout/viewLayout.ts @@ -178,7 +178,7 @@ export class ViewLayout extends Disposable implements IViewLayout { this._updateHeight(); } - public dispose(): void { + public override dispose(): void { super.dispose(); } diff --git a/src/vs/editor/common/viewModel/monospaceLineBreaksComputer.ts b/src/vs/editor/common/viewModel/monospaceLineBreaksComputer.ts index 9640d007..57370e06 100644 --- a/src/vs/editor/common/viewModel/monospaceLineBreaksComputer.ts +++ b/src/vs/editor/common/viewModel/monospaceLineBreaksComputer.ts @@ -32,7 +32,7 @@ class WrappingCharacterClassifier extends CharacterClassifier { } } - public get(charCode: number): CharacterClass { + public override get(charCode: number): CharacterClass { if (charCode >= 0 && charCode < 256) { return this._asciiMap[charCode]; } else { diff --git a/src/vs/editor/common/viewModel/viewModelImpl.ts b/src/vs/editor/common/viewModel/viewModelImpl.ts index 82dc9853..fccd9d50 100644 --- a/src/vs/editor/common/viewModel/viewModelImpl.ts +++ b/src/vs/editor/common/viewModel/viewModelImpl.ts @@ -142,7 +142,7 @@ export class ViewModel extends Disposable implements IViewModel { this._updateConfigurationViewLineCountNow(); } - public dispose(): void { + public override dispose(): void { // First remove listeners, as disposing the lines might end up sending // model decoration changed events ... and we no longer care about them ... super.dispose(); @@ -816,7 +816,23 @@ export class ViewModel extends Disposable implements IViewModel { const fontInfo = this._configuration.options.get(EditorOption.fontInfo); const colorMap = this._getColorMap(); - const fontFamily = fontInfo.fontFamily === EDITOR_FONT_DEFAULTS.fontFamily ? fontInfo.fontFamily : `'${fontInfo.fontFamily}', ${EDITOR_FONT_DEFAULTS.fontFamily}`; + const hasBadChars = (/[:;\\\/<>]/.test(fontInfo.fontFamily)); + const useDefaultFontFamily = (hasBadChars || fontInfo.fontFamily === EDITOR_FONT_DEFAULTS.fontFamily); + let fontFamily: string; + if (useDefaultFontFamily) { + fontFamily = EDITOR_FONT_DEFAULTS.fontFamily; + } else { + fontFamily = fontInfo.fontFamily; + fontFamily = fontFamily.replace(/"/g, '\''); + const hasQuotesOrIsList = /[,']/.test(fontFamily); + if (!hasQuotesOrIsList) { + const needsQuotes = /[+ ]/.test(fontFamily); + if (needsQuotes) { + fontFamily = `'${fontFamily}'`; + } + } + fontFamily = `${fontFamily}, ${EDITOR_FONT_DEFAULTS.fontFamily}`; + } return { mode: languageId.language, diff --git a/src/vs/editor/contrib/codeAction/codeActionModel.ts b/src/vs/editor/contrib/codeAction/codeActionModel.ts index d6e380dd..9f230ac4 100644 --- a/src/vs/editor/contrib/codeAction/codeActionModel.ts +++ b/src/vs/editor/contrib/codeAction/codeActionModel.ts @@ -207,7 +207,7 @@ export class CodeActionModel extends Disposable { this._update(); } - dispose(): void { + override dispose(): void { if (this.#isDisposed) { return; } diff --git a/src/vs/editor/contrib/codeAction/codeActionUi.ts b/src/vs/editor/contrib/codeAction/codeActionUi.ts index 099789d9..7a7abf66 100644 --- a/src/vs/editor/contrib/codeAction/codeActionUi.ts +++ b/src/vs/editor/contrib/codeAction/codeActionUi.ts @@ -52,7 +52,7 @@ export class CodeActionUi extends Disposable { }); } - dispose() { + override dispose() { this.#disposed = true; super.dispose(); } diff --git a/src/vs/editor/contrib/codeAction/lightBulbWidget.ts b/src/vs/editor/contrib/codeAction/lightBulbWidget.ts index 8eac5802..7c0fedd4 100644 --- a/src/vs/editor/contrib/codeAction/lightBulbWidget.ts +++ b/src/vs/editor/contrib/codeAction/lightBulbWidget.ts @@ -126,7 +126,7 @@ export class LightBulbWidget extends Disposable implements IContentWidget { this._register(this._keybindingService.onDidUpdateKeybindings(this._updateLightBulbTitleAndIcon, this)); } - dispose(): void { + override dispose(): void { super.dispose(); this._editor.removeContentWidget(this); } diff --git a/src/vs/editor/contrib/codeAction/test/codeAction.test.ts b/src/vs/editor/contrib/codeAction/test/codeAction.test.ts index c4987968..847f0682 100644 --- a/src/vs/editor/contrib/codeAction/test/codeAction.test.ts +++ b/src/vs/editor/contrib/codeAction/test/codeAction.test.ts @@ -128,8 +128,8 @@ suite('CodeAction', () => { ]; 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); + assert.strictEqual(actions.length, 6); + assert.deepStrictEqual(actions, expected); }); test('getCodeActions should filter by scope', async function () { @@ -143,20 +143,20 @@ suite('CodeAction', () => { { const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: modes.CodeActionTriggerType.Auto, filter: { include: new CodeActionKind('a') } }, Progress.None, CancellationToken.None); - assert.equal(actions.length, 2); + assert.strictEqual(actions.length, 2); assert.strictEqual(actions[0].action.title, 'a'); assert.strictEqual(actions[1].action.title, 'a.b'); } { const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: modes.CodeActionTriggerType.Auto, filter: { include: new CodeActionKind('a.b') } }, Progress.None, CancellationToken.None); - assert.equal(actions.length, 1); + assert.strictEqual(actions.length, 1); assert.strictEqual(actions[0].action.title, 'a.b'); } { const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: modes.CodeActionTriggerType.Auto, filter: { include: new CodeActionKind('a.b.c') } }, Progress.None, CancellationToken.None); - assert.equal(actions.length, 0); + assert.strictEqual(actions.length, 0); } }); @@ -175,7 +175,7 @@ suite('CodeAction', () => { disposables.add(modes.CodeActionProviderRegistry.register('fooLang', provider)); const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: modes.CodeActionTriggerType.Auto, filter: { include: new CodeActionKind('a') } }, Progress.None, CancellationToken.None); - assert.equal(actions.length, 1); + assert.strictEqual(actions.length, 1); assert.strictEqual(actions[0].action.title, 'a'); }); @@ -189,13 +189,13 @@ suite('CodeAction', () => { { const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: modes.CodeActionTriggerType.Auto }, Progress.None, CancellationToken.None); - assert.equal(actions.length, 1); + assert.strictEqual(actions.length, 1); assert.strictEqual(actions[0].action.title, 'b'); } { const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: modes.CodeActionTriggerType.Auto, filter: { include: CodeActionKind.Source, includeSourceActions: true } }, Progress.None, CancellationToken.None); - assert.equal(actions.length, 1); + assert.strictEqual(actions.length, 1); assert.strictEqual(actions[0].action.title, 'a'); } }); @@ -217,7 +217,7 @@ suite('CodeAction', () => { includeSourceActions: true, } }, Progress.None, CancellationToken.None); - assert.equal(actions.length, 1); + assert.strictEqual(actions.length, 1); assert.strictEqual(actions[0].action.title, 'b'); } }); @@ -254,7 +254,7 @@ suite('CodeAction', () => { } }, Progress.None, CancellationToken.None); assert.strictEqual(didInvoke, false); - assert.equal(actions.length, 1); + assert.strictEqual(actions.length, 1); assert.strictEqual(actions[0].action.title, 'a'); } }); diff --git a/src/vs/editor/contrib/codeAction/test/codeActionKeybindingResolver.test.ts b/src/vs/editor/contrib/codeAction/test/codeActionKeybindingResolver.test.ts index b8785616..8de4036c 100644 --- a/src/vs/editor/contrib/codeAction/test/codeActionKeybindingResolver.test.ts +++ b/src/vs/editor/contrib/codeAction/test/codeActionKeybindingResolver.test.ts @@ -35,19 +35,19 @@ suite('CodeActionKeybindingResolver', () => { }, }).getResolver(); - assert.equal( + assert.strictEqual( resolver({ title: '' }), undefined); - assert.equal( + assert.strictEqual( resolver({ title: '', kind: CodeActionKind.Refactor.value }), refactorKeybinding.resolvedKeybinding); - assert.equal( + assert.strictEqual( resolver({ title: '', kind: CodeActionKind.Refactor.append('extract').value }), refactorKeybinding.resolvedKeybinding); - assert.equal( + assert.strictEqual( resolver({ title: '', kind: CodeActionKind.QuickFix.value }), undefined); }); @@ -59,11 +59,11 @@ suite('CodeActionKeybindingResolver', () => { }, }).getResolver(); - assert.equal( + assert.strictEqual( resolver({ title: '', kind: CodeActionKind.Refactor.value }), refactorKeybinding.resolvedKeybinding); - assert.equal( + assert.strictEqual( resolver({ title: '', kind: CodeActionKind.Refactor.append('extract').value }), refactorExtractKeybinding.resolvedKeybinding); }); @@ -75,7 +75,7 @@ suite('CodeActionKeybindingResolver', () => { }, }).getResolver(); - assert.equal( + assert.strictEqual( resolver({ title: '', kind: CodeActionKind.SourceOrganizeImports.value }), organizeImportsKeybinding.resolvedKeybinding); }); diff --git a/src/vs/editor/contrib/codeAction/test/codeActionModel.test.ts b/src/vs/editor/contrib/codeAction/test/codeActionModel.test.ts index 5edcc7ae..711d8d1f 100644 --- a/src/vs/editor/contrib/codeAction/test/codeActionModel.test.ts +++ b/src/vs/editor/contrib/codeAction/test/codeActionModel.test.ts @@ -65,7 +65,7 @@ suite('CodeActionModel', () => { e.actions.then(fixes => { model.dispose(); - assert.equal(fixes.validActions.length, 1); + assert.strictEqual(fixes.validActions.length, 1); done(); }, done); })); @@ -101,11 +101,11 @@ suite('CodeActionModel', () => { disposables.add(model.onDidChangeState((e: CodeActionsState.State) => { assertType(e.type === CodeActionsState.Type.Triggered); - assert.equal(e.trigger.type, modes.CodeActionTriggerType.Auto); + assert.strictEqual(e.trigger.type, modes.CodeActionTriggerType.Auto); assert.ok(e.actions); e.actions.then(fixes => { model.dispose(); - assert.equal(fixes.validActions.length, 1); + assert.strictEqual(fixes.validActions.length, 1); resolve(undefined); }, reject); })); @@ -139,13 +139,14 @@ suite('CodeActionModel', () => { disposables.add(model.onDidChangeState((e: CodeActionsState.State) => { assertType(e.type === CodeActionsState.Type.Triggered); - assert.equal(e.trigger.type, modes.CodeActionTriggerType.Auto); + assert.strictEqual(e.trigger.type, modes.CodeActionTriggerType.Auto); const selection = e.rangeOrSelection; - assert.deepEqual(selection.selectionStartLineNumber, 1); - assert.deepEqual(selection.selectionStartColumn, 1); - assert.deepEqual(selection.endLineNumber, 4); - assert.deepEqual(selection.endColumn, 1); - assert.deepEqual(e.position, { lineNumber: 3, column: 1 }); + assert.strictEqual(selection.selectionStartLineNumber, 1); + assert.strictEqual(selection.selectionStartColumn, 1); + assert.strictEqual(selection.endLineNumber, 4); + assert.strictEqual(selection.endColumn, 1); + assert.strictEqual(e.position.lineNumber, 3); + assert.strictEqual(e.position.column, 1); model.dispose(); resolve(undefined); }, 5)); @@ -164,7 +165,7 @@ suite('CodeActionModel', () => { disposables.add(model.onDidChangeState((e: CodeActionsState.State) => { assertType(e.type === CodeActionsState.Type.Triggered); - assert.equal(e.trigger.type, modes.CodeActionTriggerType.Auto); + assert.strictEqual(e.trigger.type, modes.CodeActionTriggerType.Auto); ++triggerCount; // give time for second trigger before completing test diff --git a/src/vs/editor/contrib/codelens/codelensController.ts b/src/vs/editor/contrib/codelens/codelensController.ts index 232c4196..933fb0e8 100644 --- a/src/vs/editor/contrib/codelens/codelensController.ts +++ b/src/vs/editor/contrib/codelens/codelensController.ts @@ -99,9 +99,10 @@ export class CodeLensContribution implements IEditorContribution { const editorFontInfo = this._editor.getOption(EditorOption.fontInfo); const fontFamilyVar = `--codelens-font-family${this._styleClassName}`; + const fontFeaturesVar = `--codelens-font-features${this._styleClassName}`; let newStyle = ` - .monaco-editor .codelens-decoration.${this._styleClassName} { line-height: ${codeLensHeight}px; font-size: ${fontSize}px; padding-right: ${Math.round(fontSize * 0.5)}px; font-feature-settings: ${editorFontInfo.fontFeatureSettings} } + .monaco-editor .codelens-decoration.${this._styleClassName} { line-height: ${codeLensHeight}px; font-size: ${fontSize}px; padding-right: ${Math.round(fontSize * 0.5)}px; font-feature-settings: var(${fontFeaturesVar}) } .monaco-editor .codelens-decoration.${this._styleClassName} span.codicon { line-height: ${codeLensHeight}px; font-size: ${fontSize}px; } `; if (fontFamily) { @@ -109,6 +110,7 @@ export class CodeLensContribution implements IEditorContribution { } this._styleElement.textContent = newStyle; this._editor.getContainerDomNode().style.setProperty(fontFamilyVar, fontFamily ?? 'inherit'); + this._editor.getContainerDomNode().style.setProperty(fontFeaturesVar, editorFontInfo.fontFeatureSettings); // this._editor.changeViewZones(accessor => { diff --git a/src/vs/editor/contrib/colorPicker/colorContributions.ts b/src/vs/editor/contrib/colorPicker/colorContributions.ts index 90f91a84..d7eefbf4 100644 --- a/src/vs/editor/contrib/colorPicker/colorContributions.ts +++ b/src/vs/editor/contrib/colorPicker/colorContributions.ts @@ -26,7 +26,7 @@ export class ColorContribution extends Disposable implements IEditorContribution this._register(_editor.onMouseDown((e) => this.onMouseDown(e))); } - dispose(): void { + override dispose(): void { super.dispose(); } diff --git a/src/vs/editor/contrib/colorPicker/colorDetector.ts b/src/vs/editor/contrib/colorPicker/colorDetector.ts index ccd09304..4579e3c2 100644 --- a/src/vs/editor/contrib/colorPicker/colorDetector.ts +++ b/src/vs/editor/contrib/colorPicker/colorDetector.ts @@ -92,7 +92,7 @@ export class ColorDetector extends Disposable implements IEditorContribution { return editor.getContribution(this.ID); } - dispose(): void { + override dispose(): void { this.stop(); this.removeAllDecorations(); super.dispose(); diff --git a/src/vs/editor/contrib/dnd/dnd.ts b/src/vs/editor/contrib/dnd/dnd.ts index d535bb90..1f15af68 100644 --- a/src/vs/editor/contrib/dnd/dnd.ts +++ b/src/vs/editor/contrib/dnd/dnd.ts @@ -232,7 +232,7 @@ export class DragAndDropController extends Disposable implements IEditorContribu target.type === MouseTargetType.GUTTER_LINE_DECORATIONS; } - public dispose(): void { + public override dispose(): void { this._removeDecoration(); this._dragSelection = null; this._mouseDown = false; diff --git a/src/vs/editor/contrib/documentSymbols/test/outlineModel.test.ts b/src/vs/editor/contrib/documentSymbols/test/outlineModel.test.ts index 37cd2ad1..c7725ed5 100644 --- a/src/vs/editor/contrib/documentSymbols/test/outlineModel.test.ts +++ b/src/vs/editor/contrib/documentSymbols/test/outlineModel.test.ts @@ -26,16 +26,16 @@ suite('OutlineModel', function () { }); await OutlineModel.create(model, CancellationToken.None); - assert.equal(count, 1); + assert.strictEqual(count, 1); // cached await OutlineModel.create(model, CancellationToken.None); - assert.equal(count, 1); + assert.strictEqual(count, 1); // new version model.applyEdits([{ text: 'XXX', range: new Range(1, 1, 1, 1) }]); await OutlineModel.create(model, CancellationToken.None); - assert.equal(count, 2); + assert.strictEqual(count, 2); reg.dispose(); }); @@ -56,17 +56,17 @@ suite('OutlineModel', function () { } }); - assert.equal(isCancelled, false); + assert.strictEqual(isCancelled, false); let s1 = new CancellationTokenSource(); OutlineModel.create(model, s1.token); let s2 = new CancellationTokenSource(); OutlineModel.create(model, s2.token); s1.cancel(); - assert.equal(isCancelled, false); + assert.strictEqual(isCancelled, false); s2.cancel(); - assert.equal(isCancelled, true); + assert.strictEqual(isCancelled, true); reg.dispose(); }); @@ -101,15 +101,15 @@ suite('OutlineModel', function () { data.sort(Range.compareRangesUsingStarts); // model does this group.updateMarker(data); - assert.equal(data.length, 0); // all 'stolen' - assert.equal(e0.marker!.count, 1); - assert.equal(e1.marker, undefined); - assert.equal(e2.marker!.count, 2); + assert.strictEqual(data.length, 0); // all 'stolen' + assert.strictEqual(e0.marker!.count, 1); + assert.strictEqual(e1.marker, undefined); + assert.strictEqual(e2.marker!.count, 2); group.updateMarker([]); - assert.equal(e0.marker, undefined); - assert.equal(e1.marker, undefined); - assert.equal(e2.marker, undefined); + assert.strictEqual(e0.marker, undefined); + assert.strictEqual(e1.marker, undefined); + assert.strictEqual(e2.marker, undefined); }); test('OutlineElement - updateMarker, 2', function () { @@ -128,9 +128,9 @@ suite('OutlineModel', function () { ]; group.updateMarker(data); - assert.equal(p.marker!.count, 0); - assert.equal(c1.marker!.count, 1); - assert.equal(c2.marker, undefined); + assert.strictEqual(p.marker!.count, 0); + assert.strictEqual(c1.marker!.count, 1); + assert.strictEqual(c2.marker, undefined); data = [ fakeMarker(new Range(2, 4, 5, 4)), @@ -138,18 +138,18 @@ suite('OutlineModel', function () { fakeMarker(new Range(7, 6, 7, 8)), ]; group.updateMarker(data); - assert.equal(p.marker!.count, 0); - assert.equal(c1.marker!.count, 2); - assert.equal(c2.marker!.count, 1); + assert.strictEqual(p.marker!.count, 0); + assert.strictEqual(c1.marker!.count, 2); + assert.strictEqual(c2.marker!.count, 1); data = [ fakeMarker(new Range(1, 4, 1, 11)), fakeMarker(new Range(7, 6, 7, 8)), ]; group.updateMarker(data); - assert.equal(p.marker!.count, 1); - assert.equal(c1.marker, undefined); - assert.equal(c2.marker!.count, 1); + assert.strictEqual(p.marker!.count, 1); + assert.strictEqual(c1.marker, undefined); + assert.strictEqual(c2.marker!.count, 1); }); test('OutlineElement - updateMarker/multiple groups', function () { @@ -179,9 +179,9 @@ suite('OutlineModel', function () { model.updateMarker(data); - assert.equal(model.children.get('g1')!.children.get('c1')!.marker!.count, 2); - assert.equal(model.children.get('g2')!.children.get('c2')!.children.get('c2.1')!.marker!.count, 1); - assert.equal(model.children.get('g2')!.children.get('c2')!.children.get('c2.2')!.marker!.count, 1); + assert.strictEqual(model.children.get('g1')!.children.get('c1')!.marker!.count, 2); + assert.strictEqual(model.children.get('g2')!.children.get('c2')!.children.get('c2.1')!.marker!.count, 1); + assert.strictEqual(model.children.get('g2')!.children.get('c2')!.children.get('c2.2')!.marker!.count, 1); }); }); diff --git a/src/vs/editor/contrib/find/findController.ts b/src/vs/editor/contrib/find/findController.ts index 8d4e6649..596463ec 100644 --- a/src/vs/editor/contrib/find/findController.ts +++ b/src/vs/editor/contrib/find/findController.ts @@ -139,7 +139,7 @@ export class CommonFindController extends Disposable implements IEditorContribut })); } - public dispose(): void { + public override dispose(): void { this.disposeModel(); super.dispose(); } @@ -422,7 +422,7 @@ export class FindController extends CommonFindController implements IFindControl this._findOptionsWidget = null; } - protected async _start(opts: IFindStartOptions): Promise { + protected override async _start(opts: IFindStartOptions): Promise { if (!this._widget) { this._createFindWidget(); } @@ -459,7 +459,7 @@ export class FindController extends CommonFindController implements IFindControl } } - public highlightFindOptions(ignoreWhenVisible: boolean = false): void { + public override highlightFindOptions(ignoreWhenVisible: boolean = false): void { if (!this._widget) { this._createFindWidget(); } diff --git a/src/vs/editor/contrib/find/findOptionsWidget.ts b/src/vs/editor/contrib/find/findOptionsWidget.ts index 660eed85..72b9bdf9 100644 --- a/src/vs/editor/contrib/find/findOptionsWidget.ts +++ b/src/vs/editor/contrib/find/findOptionsWidget.ts @@ -128,7 +128,7 @@ export class FindOptionsWidget extends Widget implements IOverlayWidget { return ` (${kb.getLabel()})`; } - public dispose(): void { + public override dispose(): void { this._editor.removeOverlayWidget(this); super.dispose(); } diff --git a/src/vs/editor/contrib/find/findWidget.ts b/src/vs/editor/contrib/find/findWidget.ts index 89981ba4..6b0dc6d6 100644 --- a/src/vs/editor/contrib/find/findWidget.ts +++ b/src/vs/editor/contrib/find/findWidget.ts @@ -66,8 +66,8 @@ const NLS_REPLACE_BTN_LABEL = nls.localize('label.replaceButton', "Replace"); const NLS_REPLACE_ALL_BTN_LABEL = nls.localize('label.replaceAllButton', "Replace All"); const NLS_TOGGLE_REPLACE_MODE_BTN_LABEL = nls.localize('label.toggleReplaceButton', "Toggle Replace mode"); const NLS_MATCHES_COUNT_LIMIT_TITLE = nls.localize('title.matchesCountLimit', "Only the first {0} results are highlighted, but all find operations work on the entire text.", MATCHES_LIMIT); -const NLS_MATCHES_LOCATION = nls.localize('label.matchesLocation', "{0} of {1}"); -const NLS_NO_RESULTS = nls.localize('label.noResults', "No results"); +export const NLS_MATCHES_LOCATION = nls.localize('label.matchesLocation', "{0} of {1}"); +export const NLS_NO_RESULTS = nls.localize('label.noResults', "No results"); const FIND_WIDGET_INITIAL_WIDTH = 419; const PART_WIDTH = 275; diff --git a/src/vs/editor/contrib/find/test/findController.test.ts b/src/vs/editor/contrib/find/test/findController.test.ts index 9daca6b1..aade54ae 100644 --- a/src/vs/editor/contrib/find/test/findController.test.ts +++ b/src/vs/editor/contrib/find/test/findController.test.ts @@ -41,7 +41,7 @@ export class TestFindController extends CommonFindController { this.hasFocus = false; } - protected async _start(opts: IFindStartOptions): Promise { + protected override async _start(opts: IFindStartOptions): Promise { await super._start(opts); if (opts.shouldFocus !== FindStartFocusAction.NoFocusChange) { diff --git a/src/vs/editor/contrib/folding/folding.ts b/src/vs/editor/contrib/folding/folding.ts index 139348a4..c6396a55 100644 --- a/src/vs/editor/contrib/folding/folding.ts +++ b/src/vs/editor/contrib/folding/folding.ts @@ -475,7 +475,7 @@ abstract class FoldingAction extends EditorAction { abstract invoke(foldingController: FoldingController, foldingModel: FoldingModel, editor: ICodeEditor, args: T): void; - public runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, args: T): void | Promise { + public override runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, args: T): void | Promise { let foldingController = FoldingController.get(editor); if (!foldingController) { return; diff --git a/src/vs/editor/contrib/folding/test/foldingModel.test.ts b/src/vs/editor/contrib/folding/test/foldingModel.test.ts index f42e1ae3..317f1222 100644 --- a/src/vs/editor/contrib/folding/test/foldingModel.test.ts +++ b/src/vs/editor/contrib/folding/test/foldingModel.test.ts @@ -90,11 +90,11 @@ suite('Folding Model', () => { } function assertRegion(actual: FoldingRegion | null, expected: ExpectedRegion | null, message?: string) { - assert.equal(!!actual, !!expected, message); + assert.strictEqual(!!actual, !!expected, message); if (actual && expected) { - assert.equal(actual.startLineNumber, expected.startLineNumber, message); - assert.equal(actual.endLineNumber, expected.endLineNumber, message); - assert.equal(actual.isCollapsed, expected.isCollapsed, message); + assert.strictEqual(actual.startLineNumber, expected.startLineNumber, message); + assert.strictEqual(actual.endLineNumber, expected.endLineNumber, message); + assert.strictEqual(actual.isCollapsed, expected.isCollapsed, message); } } @@ -106,7 +106,7 @@ suite('Folding Model', () => { actualRanges.push(r(actual.getStartLineNumber(i), actual.getEndLineNumber(i))); } } - assert.deepEqual(actualRanges, expectedRegions, message); + assert.deepStrictEqual(actualRanges, expectedRegions, message); } function assertRanges(foldingModel: FoldingModel, expectedRegions: ExpectedRegion[], message?: string) { @@ -115,16 +115,16 @@ suite('Folding Model', () => { for (let i = 0; i < actual.length; i++) { actualRanges.push(r(actual.getStartLineNumber(i), actual.getEndLineNumber(i), actual.isCollapsed(i))); } - assert.deepEqual(actualRanges, expectedRegions, message); + assert.deepStrictEqual(actualRanges, expectedRegions, message); } function assertDecorations(foldingModel: FoldingModel, expectedDecoration: ExpectedDecoration[], message?: string) { const decorationProvider = foldingModel.decorationProvider as TestDecorationProvider; - assert.deepEqual(decorationProvider.getDecorations(), expectedDecoration, message); + assert.deepStrictEqual(decorationProvider.getDecorations(), expectedDecoration, message); } function assertRegions(actual: FoldingRegion[], expectedRegions: ExpectedRegion[], message?: string) { - assert.deepEqual(actual.map(r => ({ startLineNumber: r.startLineNumber, endLineNumber: r.endLineNumber, isCollapsed: r.isCollapsed })), expectedRegions, message); + assert.deepStrictEqual(actual.map(r => ({ startLineNumber: r.startLineNumber, endLineNumber: r.endLineNumber, isCollapsed: r.isCollapsed })), expectedRegions, message); } test('getRegionAtLine', () => { diff --git a/src/vs/editor/contrib/folding/test/foldingRanges.test.ts b/src/vs/editor/contrib/folding/test/foldingRanges.test.ts index 214135a9..d656202c 100644 --- a/src/vs/editor/contrib/folding/test/foldingRanges.test.ts +++ b/src/vs/editor/contrib/folding/test/foldingRanges.test.ts @@ -28,11 +28,11 @@ suite('FoldingRanges', () => { } let model = createTextModel(lines.join('\n')); let actual = computeRanges(model, false, markers, MAX_FOLDING_REGIONS); - assert.equal(actual.length, nRegions, 'len'); + assert.strictEqual(actual.length, nRegions, 'len'); for (let i = 0; i < nRegions; i++) { - assert.equal(actual.getStartLineNumber(i), i + 1, 'start' + i); - assert.equal(actual.getEndLineNumber(i), nRegions * 2 - i, 'end' + i); - assert.equal(actual.getParentIndex(i), i - 1, 'parent' + i); + assert.strictEqual(actual.getStartLineNumber(i), i + 1, 'start' + i); + assert.strictEqual(actual.getEndLineNumber(i), nRegions * 2 - i, 'end' + i); + assert.strictEqual(actual.getParentIndex(i), i - 1, 'parent' + i); } }); @@ -62,19 +62,19 @@ suite('FoldingRanges', () => { // let r3 = r(5, 6); // let r4 = r(9, 10); - assert.equal(actual.findRange(1), 0, '1'); - assert.equal(actual.findRange(2), 0, '2'); - assert.equal(actual.findRange(3), 1, '3'); - assert.equal(actual.findRange(4), 2, '4'); - assert.equal(actual.findRange(5), 3, '5'); - assert.equal(actual.findRange(6), 3, '6'); - assert.equal(actual.findRange(7), 2, '7'); - assert.equal(actual.findRange(8), 2, '8'); - assert.equal(actual.findRange(9), 4, '9'); - assert.equal(actual.findRange(10), 4, '10'); - assert.equal(actual.findRange(11), 2, '11'); - assert.equal(actual.findRange(12), 1, '12'); - assert.equal(actual.findRange(13), -1, '13'); + assert.strictEqual(actual.findRange(1), 0, '1'); + assert.strictEqual(actual.findRange(2), 0, '2'); + assert.strictEqual(actual.findRange(3), 1, '3'); + assert.strictEqual(actual.findRange(4), 2, '4'); + assert.strictEqual(actual.findRange(5), 3, '5'); + assert.strictEqual(actual.findRange(6), 3, '6'); + assert.strictEqual(actual.findRange(7), 2, '7'); + assert.strictEqual(actual.findRange(8), 2, '8'); + assert.strictEqual(actual.findRange(9), 4, '9'); + assert.strictEqual(actual.findRange(10), 4, '10'); + assert.strictEqual(actual.findRange(11), 2, '11'); + assert.strictEqual(actual.findRange(12), 1, '12'); + assert.strictEqual(actual.findRange(13), -1, '13'); } finally { textModel.dispose(); } @@ -93,12 +93,12 @@ suite('FoldingRanges', () => { } let model = createTextModel(lines.join('\n')); let actual = computeRanges(model, false, markers, MAX_FOLDING_REGIONS); - assert.equal(actual.length, nRegions, 'len'); + assert.strictEqual(actual.length, nRegions, 'len'); for (let i = 0; i < nRegions; i++) { actual.setCollapsed(i, i % 3 === 0); } for (let i = 0; i < nRegions; i++) { - assert.equal(actual.isCollapsed(i), i % 3 === 0, 'line' + i); + assert.strictEqual(actual.isCollapsed(i), i % 3 === 0, 'line' + i); } }); }); diff --git a/src/vs/editor/contrib/folding/test/hiddenRangeModel.test.ts b/src/vs/editor/contrib/folding/test/hiddenRangeModel.test.ts index 9db5f45e..7a3f5247 100644 --- a/src/vs/editor/contrib/folding/test/hiddenRangeModel.test.ts +++ b/src/vs/editor/contrib/folding/test/hiddenRangeModel.test.ts @@ -22,7 +22,7 @@ suite('Hidden Range Model', () => { } function assertRanges(actual: IRange[], expectedRegions: ExpectedRange[], message?: string) { - assert.deepEqual(actual.map(r => ({ startLineNumber: r.startLineNumber, endLineNumber: r.endLineNumber })), expectedRegions, message); + assert.deepStrictEqual(actual.map(r => ({ startLineNumber: r.startLineNumber, endLineNumber: r.endLineNumber })), expectedRegions, message); } test('hasRanges', () => { @@ -42,7 +42,7 @@ suite('Hidden Range Model', () => { let foldingModel = new FoldingModel(textModel, new TestDecorationProvider(textModel)); let hiddenRangeModel = new HiddenRangeModel(foldingModel); - assert.equal(hiddenRangeModel.hasRanges(), false); + assert.strictEqual(hiddenRangeModel.hasRanges(), false); let ranges = computeRanges(textModel, false, undefined); foldingModel.update(ranges); @@ -50,46 +50,46 @@ suite('Hidden Range Model', () => { foldingModel.toggleCollapseState([foldingModel.getRegionAtLine(1)!, foldingModel.getRegionAtLine(6)!]); assertRanges(hiddenRangeModel.hiddenRanges, [r(2, 3), r(7, 7)]); - assert.equal(hiddenRangeModel.hasRanges(), true); - assert.equal(hiddenRangeModel.isHidden(1), false); - assert.equal(hiddenRangeModel.isHidden(2), true); - assert.equal(hiddenRangeModel.isHidden(3), true); - assert.equal(hiddenRangeModel.isHidden(4), false); - assert.equal(hiddenRangeModel.isHidden(5), false); - assert.equal(hiddenRangeModel.isHidden(6), false); - assert.equal(hiddenRangeModel.isHidden(7), true); - assert.equal(hiddenRangeModel.isHidden(8), false); - assert.equal(hiddenRangeModel.isHidden(9), false); - assert.equal(hiddenRangeModel.isHidden(10), false); + assert.strictEqual(hiddenRangeModel.hasRanges(), true); + assert.strictEqual(hiddenRangeModel.isHidden(1), false); + assert.strictEqual(hiddenRangeModel.isHidden(2), true); + assert.strictEqual(hiddenRangeModel.isHidden(3), true); + assert.strictEqual(hiddenRangeModel.isHidden(4), false); + assert.strictEqual(hiddenRangeModel.isHidden(5), false); + assert.strictEqual(hiddenRangeModel.isHidden(6), false); + assert.strictEqual(hiddenRangeModel.isHidden(7), true); + assert.strictEqual(hiddenRangeModel.isHidden(8), false); + assert.strictEqual(hiddenRangeModel.isHidden(9), false); + assert.strictEqual(hiddenRangeModel.isHidden(10), false); foldingModel.toggleCollapseState([foldingModel.getRegionAtLine(4)!]); assertRanges(hiddenRangeModel.hiddenRanges, [r(2, 3), r(5, 9)]); - assert.equal(hiddenRangeModel.hasRanges(), true); - assert.equal(hiddenRangeModel.isHidden(1), false); - assert.equal(hiddenRangeModel.isHidden(2), true); - assert.equal(hiddenRangeModel.isHidden(3), true); - assert.equal(hiddenRangeModel.isHidden(4), false); - assert.equal(hiddenRangeModel.isHidden(5), true); - assert.equal(hiddenRangeModel.isHidden(6), true); - assert.equal(hiddenRangeModel.isHidden(7), true); - assert.equal(hiddenRangeModel.isHidden(8), true); - assert.equal(hiddenRangeModel.isHidden(9), true); - assert.equal(hiddenRangeModel.isHidden(10), false); + assert.strictEqual(hiddenRangeModel.hasRanges(), true); + assert.strictEqual(hiddenRangeModel.isHidden(1), false); + assert.strictEqual(hiddenRangeModel.isHidden(2), true); + assert.strictEqual(hiddenRangeModel.isHidden(3), true); + assert.strictEqual(hiddenRangeModel.isHidden(4), false); + assert.strictEqual(hiddenRangeModel.isHidden(5), true); + assert.strictEqual(hiddenRangeModel.isHidden(6), true); + assert.strictEqual(hiddenRangeModel.isHidden(7), true); + assert.strictEqual(hiddenRangeModel.isHidden(8), true); + assert.strictEqual(hiddenRangeModel.isHidden(9), true); + assert.strictEqual(hiddenRangeModel.isHidden(10), false); foldingModel.toggleCollapseState([foldingModel.getRegionAtLine(1)!, foldingModel.getRegionAtLine(6)!, foldingModel.getRegionAtLine(4)!]); assertRanges(hiddenRangeModel.hiddenRanges, []); - assert.equal(hiddenRangeModel.hasRanges(), false); - assert.equal(hiddenRangeModel.isHidden(1), false); - assert.equal(hiddenRangeModel.isHidden(2), false); - assert.equal(hiddenRangeModel.isHidden(3), false); - assert.equal(hiddenRangeModel.isHidden(4), false); - assert.equal(hiddenRangeModel.isHidden(5), false); - assert.equal(hiddenRangeModel.isHidden(6), false); - assert.equal(hiddenRangeModel.isHidden(7), false); - assert.equal(hiddenRangeModel.isHidden(8), false); - assert.equal(hiddenRangeModel.isHidden(9), false); - assert.equal(hiddenRangeModel.isHidden(10), false); + assert.strictEqual(hiddenRangeModel.hasRanges(), false); + assert.strictEqual(hiddenRangeModel.isHidden(1), false); + assert.strictEqual(hiddenRangeModel.isHidden(2), false); + assert.strictEqual(hiddenRangeModel.isHidden(3), false); + assert.strictEqual(hiddenRangeModel.isHidden(4), false); + assert.strictEqual(hiddenRangeModel.isHidden(5), false); + assert.strictEqual(hiddenRangeModel.isHidden(6), false); + assert.strictEqual(hiddenRangeModel.isHidden(7), false); + assert.strictEqual(hiddenRangeModel.isHidden(8), false); + assert.strictEqual(hiddenRangeModel.isHidden(9), false); + assert.strictEqual(hiddenRangeModel.isHidden(10), false); }); diff --git a/src/vs/editor/contrib/folding/test/indentFold.test.ts b/src/vs/editor/contrib/folding/test/indentFold.test.ts index ccdb26a7..76f33998 100644 --- a/src/vs/editor/contrib/folding/test/indentFold.test.ts +++ b/src/vs/editor/contrib/folding/test/indentFold.test.ts @@ -56,7 +56,7 @@ suite('Indentation Folding', () => { for (let i = 0; i < indentRanges.length; i++) { actual.push({ start: indentRanges.getStartLineNumber(i), end: indentRanges.getEndLineNumber(i) }); } - assert.deepEqual(actual, expectedRanges, message); + assert.deepStrictEqual(actual, expectedRanges, message); } assertLimit(1000, [r1, r2, r3, r4, r5, r6, r7, r8, r9], '1000'); diff --git a/src/vs/editor/contrib/folding/test/indentRangeProvider.test.ts b/src/vs/editor/contrib/folding/test/indentRangeProvider.test.ts index 8a4dbe7f..58fa7ab9 100644 --- a/src/vs/editor/contrib/folding/test/indentRangeProvider.test.ts +++ b/src/vs/editor/contrib/folding/test/indentRangeProvider.test.ts @@ -22,7 +22,7 @@ function assertRanges(lines: string[], expected: ExpectedIndentRange[], offside: for (let i = 0; i < actual.length; i++) { actualRanges[i] = r(actual.getStartLineNumber(i), actual.getEndLineNumber(i), actual.getParentIndex(i)); } - assert.deepEqual(actualRanges, expected); + assert.deepStrictEqual(actualRanges, expected); model.dispose(); } diff --git a/src/vs/editor/contrib/folding/test/syntaxFold.test.ts b/src/vs/editor/contrib/folding/test/syntaxFold.test.ts index 915ba173..c4162109 100644 --- a/src/vs/editor/contrib/folding/test/syntaxFold.test.ts +++ b/src/vs/editor/contrib/folding/test/syntaxFold.test.ts @@ -81,7 +81,7 @@ suite('Syntax folding', () => { actual.push({ start: indentRanges.getStartLineNumber(i), end: indentRanges.getEndLineNumber(i) }); } } - assert.deepEqual(actual, expectedRanges, message); + assert.deepStrictEqual(actual, expectedRanges, message); } await assertLimit(1000, [r1, r2, r3, r4, r5, r6, r7, r8, r9], '1000'); diff --git a/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts b/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts index 8096baa1..95d60395 100644 --- a/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts +++ b/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts @@ -144,7 +144,7 @@ class MessageWidget { this._codeLink.setAttribute('href', `${code.target.toString()}`); this._codeLink.onclick = (e) => { - this._openerService.open(code.target); + this._openerService.open(code.target, { allowCommands: true }); e.preventDefault(); e.stopPropagation(); }; @@ -280,14 +280,14 @@ export class MarkerNavigationWidget extends PeekViewWidget { }); // style() will trigger _applyStyles } - protected _applyStyles(): void { + protected override _applyStyles(): void { if (this._parentContainer) { this._parentContainer.style.backgroundColor = this._backgroundColor ? this._backgroundColor.toString() : ''; } super._applyStyles(); } - dispose(): void { + override dispose(): void { this._callOnDispose.dispose(); super.dispose(); } @@ -296,7 +296,7 @@ export class MarkerNavigationWidget extends PeekViewWidget { this._parentContainer.focus(); } - protected _fillHead(container: HTMLElement): void { + protected override _fillHead(container: HTMLElement): void { super._fillHead(container); this._disposables.add(this._actionbarWidget!.actionRunner.onBeforeRun(e => this.editor.focus())); @@ -308,7 +308,7 @@ export class MarkerNavigationWidget extends PeekViewWidget { menu.dispose(); } - protected _fillTitleIcon(container: HTMLElement): void { + protected override _fillTitleIcon(container: HTMLElement): void { this._icon = dom.append(container, dom.$('')); } @@ -325,7 +325,7 @@ export class MarkerNavigationWidget extends PeekViewWidget { this._disposables.add(this._message); } - show(): void { + override show(): void { throw new Error('call showAtMarker'); } @@ -369,18 +369,18 @@ export class MarkerNavigationWidget extends PeekViewWidget { this._relayout(); } - protected _doLayoutBody(heightInPixel: number, widthInPixel: number): void { + protected override _doLayoutBody(heightInPixel: number, widthInPixel: number): void { super._doLayoutBody(heightInPixel, widthInPixel); this._heightInPixel = heightInPixel; this._message.layout(heightInPixel, widthInPixel); this._container.style.height = `${heightInPixel}px`; } - public _onWidth(widthInPixel: number): void { + public override _onWidth(widthInPixel: number): void { this._message.layout(this._heightInPixel, widthInPixel); } - protected _relayout(): void { + protected override _relayout(): void { super._relayout(this.computeRequiredHeight()); } diff --git a/src/vs/editor/contrib/gotoSymbol/goToCommands.ts b/src/vs/editor/contrib/gotoSymbol/goToCommands.ts index 83503350..2152693b 100644 --- a/src/vs/editor/contrib/gotoSymbol/goToCommands.ts +++ b/src/vs/editor/contrib/gotoSymbol/goToCommands.ts @@ -9,7 +9,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { isWeb } from 'vs/base/common/platform'; import { ICodeEditor, isCodeEditor, IActiveCodeEditor } from 'vs/editor/browser/editorBrowser'; -import { EditorAction, IActionOptions, registerEditorAction, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; +import { EditorAction, IActionOptions, registerInstantiatedEditorAction, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import * as corePosition from 'vs/editor/common/core/position'; import { Range, IRange } from 'vs/editor/common/core/range'; @@ -53,6 +53,16 @@ export interface SymbolNavigationActionConfig { muteMessage: boolean; } + +const _goToActionIds = new Set(); + +function registerGoToAction(ctor: { new(): T; }): T { + const result = new ctor(); + registerInstantiatedEditorAction(result); + _goToActionIds.add(result.id); + return result; +} + abstract class SymbolNavigationAction extends EditorAction { private readonly _configuration: SymbolNavigationActionConfig; @@ -87,7 +97,7 @@ abstract class SymbolNavigationAction extends EditorAction { let altAction: IEditorAction | null | undefined; if (references.referenceAt(model.uri, pos)) { const altActionId = this._getAlternativeCommand(editor); - if (altActionId !== this.id) { + if (altActionId !== this.id && _goToActionIds.has(altActionId)) { altAction = editor.getAction(altActionId); } } @@ -228,7 +238,7 @@ const goToDefinitionKb = isWeb && !isStandalone ? KeyMod.CtrlCmd | KeyCode.F12 : KeyCode.F12; -registerEditorAction(class GoToDefinitionAction extends DefinitionAction { +registerGoToAction(class GoToDefinitionAction extends DefinitionAction { static readonly id = 'editor.action.revealDefinition'; @@ -264,7 +274,7 @@ registerEditorAction(class GoToDefinitionAction extends DefinitionAction { } }); -registerEditorAction(class OpenDefinitionToSideAction extends DefinitionAction { +registerGoToAction(class OpenDefinitionToSideAction extends DefinitionAction { static readonly id = 'editor.action.revealDefinitionAside'; @@ -290,7 +300,7 @@ registerEditorAction(class OpenDefinitionToSideAction extends DefinitionAction { } }); -registerEditorAction(class PeekDefinitionAction extends DefinitionAction { +registerGoToAction(class PeekDefinitionAction extends DefinitionAction { static readonly id = 'editor.action.peekDefinition'; @@ -349,7 +359,7 @@ class DeclarationAction extends SymbolNavigationAction { } } -registerEditorAction(class GoToDeclarationAction extends DeclarationAction { +registerGoToAction(class GoToDeclarationAction extends DeclarationAction { static readonly id = 'editor.action.revealDeclaration'; @@ -379,14 +389,14 @@ registerEditorAction(class GoToDeclarationAction extends DeclarationAction { }); } - protected _getNoResultFoundMessage(info: IWordAtPosition | null): string { + protected override _getNoResultFoundMessage(info: IWordAtPosition | null): string { return info && info.word ? nls.localize('decl.noResultWord', "No declaration found for '{0}'", info.word) : nls.localize('decl.generic.noResults', "No declaration found"); } }); -registerEditorAction(class PeekDeclarationAction extends DeclarationAction { +registerGoToAction(class PeekDeclarationAction extends DeclarationAction { constructor() { super({ openToSide: false, @@ -435,7 +445,7 @@ class TypeDefinitionAction extends SymbolNavigationAction { } } -registerEditorAction(class GoToTypeDefinitionAction extends TypeDefinitionAction { +registerGoToAction(class GoToTypeDefinitionAction extends TypeDefinitionAction { public static readonly ID = 'editor.action.goToTypeDefinition'; @@ -470,7 +480,7 @@ registerEditorAction(class GoToTypeDefinitionAction extends TypeDefinitionAction } }); -registerEditorAction(class PeekTypeDefinitionAction extends TypeDefinitionAction { +registerGoToAction(class PeekTypeDefinitionAction extends TypeDefinitionAction { public static readonly ID = 'editor.action.peekTypeDefinition'; @@ -522,7 +532,7 @@ class ImplementationAction extends SymbolNavigationAction { } } -registerEditorAction(class GoToImplementationAction extends ImplementationAction { +registerGoToAction(class GoToImplementationAction extends ImplementationAction { public static readonly ID = 'editor.action.goToImplementation'; @@ -557,7 +567,7 @@ registerEditorAction(class GoToImplementationAction extends ImplementationAction } }); -registerEditorAction(class PeekImplementationAction extends ImplementationAction { +registerGoToAction(class PeekImplementationAction extends ImplementationAction { public static readonly ID = 'editor.action.peekImplementation'; @@ -610,7 +620,7 @@ abstract class ReferencesAction extends SymbolNavigationAction { } } -registerEditorAction(class GoToReferencesAction extends ReferencesAction { +registerGoToAction(class GoToReferencesAction extends ReferencesAction { constructor() { super({ @@ -649,7 +659,7 @@ registerEditorAction(class GoToReferencesAction extends ReferencesAction { } }); -registerEditorAction(class PeekReferencesAction extends ReferencesAction { +registerGoToAction(class PeekReferencesAction extends ReferencesAction { constructor() { super({ @@ -744,7 +754,7 @@ CommandsRegistry.registerCommand({ return editor.invokeWithinContext(accessor => { const command = new class extends GenericGoToLocationAction { - _getNoResultFoundMessage(info: IWordAtPosition | null) { + override _getNoResultFoundMessage(info: IWordAtPosition | null) { return noResultsMessage || super._getNoResultFoundMessage(info); } }({ diff --git a/src/vs/editor/contrib/gotoSymbol/goToSymbol.ts b/src/vs/editor/contrib/gotoSymbol/goToSymbol.ts index 030aa0ca..9dd9db72 100644 --- a/src/vs/editor/contrib/gotoSymbol/goToSymbol.ts +++ b/src/vs/editor/contrib/gotoSymbol/goToSymbol.ts @@ -5,14 +5,12 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { onUnexpectedExternalError } from 'vs/base/common/errors'; -import { extUri } from 'vs/base/common/resources'; import { registerModelAndPositionCommand } from 'vs/editor/browser/editorExtensions'; import { Position } from 'vs/editor/common/core/position'; import { ITextModel } from 'vs/editor/common/model'; -import { Range } from 'vs/editor/common/core/range'; -import { Location, LocationLink, DefinitionProviderRegistry, ImplementationProviderRegistry, TypeDefinitionProviderRegistry, DeclarationProviderRegistry, ProviderResult, ReferenceProviderRegistry } from 'vs/editor/common/modes'; +import { LocationLink, DefinitionProviderRegistry, ImplementationProviderRegistry, TypeDefinitionProviderRegistry, DeclarationProviderRegistry, ProviderResult, ReferenceProviderRegistry } from 'vs/editor/common/modes'; import { LanguageFeatureRegistry } from 'vs/editor/common/modes/languageFeatureRegistry'; - +import { ReferencesModel } from 'vs/editor/contrib/gotoSymbol/referencesModel'; function getLocationLinks( model: ITextModel, @@ -39,12 +37,10 @@ function getLocationLinks( result.push(value); } } - - return sortAndDeduplicate(result); + return result; }); } - export function getDefinitionsAtPosition(model: ITextModel, position: Position, token: CancellationToken): Promise { return getLocationLinks(model, position, DefinitionProviderRegistry, (provider, model, position) => { return provider.provideDefinition(model, position, token); @@ -83,24 +79,18 @@ export function getReferencesAtPosition(model: ITextModel, position: Position, c }); } -export function sortAndDeduplicate(locations: Location[]): Location[] { - const result: LocationLink[] = []; - let last: LocationLink | undefined; - for (const link of locations.sort(compareLocations)) { - if (last === undefined || compareLocations(last, link) !== 0) { - result.push(link); - last = link; - } - } - return result; +// -- API commands ---- + +async function _sortedAndDeduped(callback: () => Promise): Promise { + const rawLinks = await callback(); + const model = new ReferencesModel(rawLinks, ''); + const modelLinks = model.references.map(ref => ref.link); + model.dispose(); + return modelLinks; } -function compareLocations(a: Location, b: Location): number { - return extUri.compare(a.uri, b.uri) || Range.compareRangesUsingStarts(a.range, b.range); -} - -registerModelAndPositionCommand('_executeDefinitionProvider', (model, position) => getDefinitionsAtPosition(model, position, CancellationToken.None)); -registerModelAndPositionCommand('_executeDeclarationProvider', (model, position) => getDeclarationsAtPosition(model, position, CancellationToken.None)); -registerModelAndPositionCommand('_executeImplementationProvider', (model, position) => getImplementationsAtPosition(model, position, CancellationToken.None)); -registerModelAndPositionCommand('_executeTypeDefinitionProvider', (model, position) => getTypeDefinitionsAtPosition(model, position, CancellationToken.None)); -registerModelAndPositionCommand('_executeReferenceProvider', (model, position) => getReferencesAtPosition(model, position, false, CancellationToken.None)); +registerModelAndPositionCommand('_executeDefinitionProvider', (model, position) => _sortedAndDeduped(() => getDefinitionsAtPosition(model, position, CancellationToken.None))); +registerModelAndPositionCommand('_executeDeclarationProvider', (model, position) => _sortedAndDeduped(() => getDeclarationsAtPosition(model, position, CancellationToken.None))); +registerModelAndPositionCommand('_executeImplementationProvider', (model, position) => _sortedAndDeduped(() => getImplementationsAtPosition(model, position, CancellationToken.None))); +registerModelAndPositionCommand('_executeTypeDefinitionProvider', (model, position) => _sortedAndDeduped(() => getTypeDefinitionsAtPosition(model, position, CancellationToken.None))); +registerModelAndPositionCommand('_executeReferenceProvider', (model, position) => _sortedAndDeduped(() => getReferencesAtPosition(model, position, false, CancellationToken.None))); diff --git a/src/vs/editor/contrib/gotoSymbol/peek/referencesWidget.ts b/src/vs/editor/contrib/gotoSymbol/peek/referencesWidget.ts index 09472b10..9f93856f 100644 --- a/src/vs/editor/contrib/gotoSymbol/peek/referencesWidget.ts +++ b/src/vs/editor/contrib/gotoSymbol/peek/referencesWidget.ts @@ -231,7 +231,7 @@ export class ReferenceWidget extends peekView.PeekViewWidget { this.create(); } - dispose(): void { + override dispose(): void { this.setModel(undefined); this._callOnDispose.dispose(); this._disposeOnNewModel.dispose(); @@ -254,7 +254,7 @@ export class ReferenceWidget extends peekView.PeekViewWidget { }); } - show(where: IRange) { + override show(where: IRange) { this.editor.revealRangeInCenterIfOutsideViewport(where, ScrollType.Smooth); super.show(where, this.layoutData.heightInLines || 18); } @@ -271,7 +271,7 @@ export class ReferenceWidget extends peekView.PeekViewWidget { return this._preview.hasTextFocus(); } - protected _onTitleClick(e: IMouseEvent): void { + protected override _onTitleClick(e: IMouseEvent): void { if (this._preview && this._preview.getModel()) { this._onDidSelectReference.fire({ element: this._getFocusedReference(), @@ -398,13 +398,13 @@ export class ReferenceWidget extends peekView.PeekViewWidget { dom.hide(this._treeContainer); } - protected _onWidth(width: number) { + protected override _onWidth(width: number) { if (this._dim) { this._doLayoutBody(this._dim.height, width); } } - protected _doLayoutBody(heightInPixel: number, widthInPixel: number): void { + protected override _doLayoutBody(heightInPixel: number, widthInPixel: number): void { super._doLayoutBody(heightInPixel, widthInPixel); this._dim = new dom.Dimension(widthInPixel, heightInPixel); this.layoutData.heightInLines = this._viewZone ? this._viewZone.heightInLines : this.layoutData.heightInLines; diff --git a/src/vs/editor/contrib/gotoSymbol/referencesModel.ts b/src/vs/editor/contrib/gotoSymbol/referencesModel.ts index fe0655ba..9a597ef7 100644 --- a/src/vs/editor/contrib/gotoSymbol/referencesModel.ts +++ b/src/vs/editor/contrib/gotoSymbol/referencesModel.ts @@ -11,7 +11,7 @@ import * as strings from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; import { defaultGenerator } from 'vs/base/common/idGenerator'; import { Range, IRange } from 'vs/editor/common/core/range'; -import { LocationLink } from 'vs/editor/common/modes'; +import { Location, LocationLink } from 'vs/editor/common/modes'; import { ITextModelService, ITextEditorModel } from 'vs/editor/common/services/resolverService'; import { Position } from 'vs/editor/common/core/position'; import { IMatch } from 'vs/base/common/filters'; @@ -19,22 +19,25 @@ import { Constants } from 'vs/base/common/uint'; import { ResourceMap } from 'vs/base/common/map'; import { onUnexpectedError } from 'vs/base/common/errors'; -import { sortAndDeduplicate } from './goToSymbol'; - export class OneReference { readonly id: string = defaultGenerator.nextId(); + private _range?: IRange; + constructor( readonly isProviderFirst: boolean, readonly parent: FileReferences, - readonly uri: URI, - private _range: IRange, + readonly link: LocationLink, private _rangeCallback: (ref: OneReference) => void ) { } + get uri() { + return this.link.uri; + } + get range(): IRange { - return this._range; + return this._range ?? this.link.targetSelectionRange ?? this.link.range; } set range(value: IRange) { @@ -156,9 +159,9 @@ export class ReferencesModel implements IDisposable { this._links = links; this._title = title; - // grouping, sorting, and de-duplicating + // grouping and sorting const [providersFirst] = links; - links = sortAndDeduplicate(links); + links.sort(ReferencesModel._compareReferences); let current: FileReferences | undefined; for (let link of links) { @@ -168,15 +171,18 @@ export class ReferencesModel implements IDisposable { this.groups.push(current); } - const oneRef = new OneReference( - providersFirst === link, - current, - link.uri, - link.targetSelectionRange || link.range, - ref => this._onDidChangeReferenceRange.fire(ref) - ); - this.references.push(oneRef); - current.children.push(oneRef); + // append, check for equality first! + if (current.children.length === 0 || ReferencesModel._compareReferences(link, current.children[current.children.length - 1]) !== 0) { + + const oneRef = new OneReference( + providersFirst === link, + current, + link, + ref => this._onDidChangeReferenceRange.fire(ref) + ); + this.references.push(oneRef); + current.children.push(oneRef); + } } } @@ -285,4 +291,8 @@ export class ReferencesModel implements IDisposable { } return this.references[0]; } + + private static _compareReferences(a: Location, b: Location): number { + return extUri.compare(a.uri, b.uri) || Range.compareRangesUsingStarts(a.range, b.range); + } } diff --git a/src/vs/editor/contrib/gotoSymbol/test/referencesModel.test.ts b/src/vs/editor/contrib/gotoSymbol/test/referencesModel.test.ts index c2a1b952..8abf631d 100644 --- a/src/vs/editor/contrib/gotoSymbol/test/referencesModel.test.ts +++ b/src/vs/editor/contrib/gotoSymbol/test/referencesModel.test.ts @@ -24,16 +24,16 @@ suite('references', function () { }], 'FOO'); let ref = model.nearestReference(URI.file('/src/can'), new Position(1, 1)); - assert.equal(ref!.uri.path, '/src/can'); + assert.strictEqual(ref!.uri.path, '/src/can'); ref = model.nearestReference(URI.file('/src/someOtherFileInSrc'), new Position(1, 1)); - assert.equal(ref!.uri.path, '/src/can'); + assert.strictEqual(ref!.uri.path, '/src/can'); ref = model.nearestReference(URI.file('/out/someOtherFile'), new Position(1, 1)); - assert.equal(ref!.uri.path, '/out/obj/can'); + assert.strictEqual(ref!.uri.path, '/out/obj/can'); ref = model.nearestReference(URI.file('/out/obj/can2222'), new Position(1, 1)); - assert.equal(ref!.uri.path, '/out/obj/can2'); + assert.strictEqual(ref!.uri.path, '/out/obj/can2'); }); }); diff --git a/src/vs/editor/contrib/hover/hoverWidgets.ts b/src/vs/editor/contrib/hover/hoverWidgets.ts index ecc374f2..446be6a8 100644 --- a/src/vs/editor/contrib/hover/hoverWidgets.ts +++ b/src/vs/editor/contrib/hover/hoverWidgets.ts @@ -83,7 +83,7 @@ export class GlyphHoverWidget extends Widget implements IOverlayWidget { return null; } - public dispose(): void { + public override dispose(): void { this._editor.removeOverlayWidget(this); super.dispose(); } diff --git a/src/vs/editor/contrib/hover/markerHoverParticipant.ts b/src/vs/editor/contrib/hover/markerHoverParticipant.ts index d13cdc5f..086a7831 100644 --- a/src/vs/editor/contrib/hover/markerHoverParticipant.ts +++ b/src/vs/editor/contrib/hover/markerHoverParticipant.ts @@ -120,7 +120,7 @@ export class MarkerHoverParticipant implements IEditorHoverParticipant { - this._openerService.open(code.target); + this._openerService.open(code.target, { allowCommands: true }); e.preventDefault(); e.stopPropagation(); })); diff --git a/src/vs/editor/contrib/hover/modesContentHover.ts b/src/vs/editor/contrib/hover/modesContentHover.ts index 7b401019..ade94474 100644 --- a/src/vs/editor/contrib/hover/modesContentHover.ts +++ b/src/vs/editor/contrib/hover/modesContentHover.ts @@ -292,7 +292,7 @@ export class ModesContentHoverWidget extends Widget implements IContentWidget, I })); } - public dispose(): void { + public override dispose(): void { this._hoverOperation.cancel(); this._editor.removeContentWidget(this); super.dispose(); diff --git a/src/vs/editor/contrib/hover/modesGlyphHover.ts b/src/vs/editor/contrib/hover/modesGlyphHover.ts index 54d0c58c..db0337c0 100644 --- a/src/vs/editor/contrib/hover/modesGlyphHover.ts +++ b/src/vs/editor/contrib/hover/modesGlyphHover.ts @@ -117,7 +117,7 @@ export class ModesGlyphHoverWidget extends GlyphHoverWidget { } - public dispose(): void { + public override dispose(): void { this._hoverOperation.cancel(); super.dispose(); } @@ -147,7 +147,7 @@ export class ModesGlyphHoverWidget extends GlyphHoverWidget { this._hoverOperation.start(HoverStartMode.Delayed); } - public hide(): void { + public override hide(): void { this._lastLineNumber = -1; this._hoverOperation.cancel(); super.hide(); diff --git a/src/vs/editor/contrib/inlineHints/inlineHintsController.ts b/src/vs/editor/contrib/inlineHints/inlineHintsController.ts index 43750f98..db9477d2 100644 --- a/src/vs/editor/contrib/inlineHints/inlineHintsController.ts +++ b/src/vs/editor/contrib/inlineHints/inlineHintsController.ts @@ -146,6 +146,9 @@ export class InlineHintsController implements IEditorContribution { const newDecorationsTypeIds: string[] = []; const newDecorationsData: IModelDeltaDecoration[] = []; + const fontFamilyVar = '--inlineHintsFontFamily'; + this._editor.getContainerDomNode().style.setProperty(fontFamilyVar, fontFamily); + for (const { list: hints } of hintsData) { for (let j = 0; j < hints.length && newDecorationsData.length < MAX_DECORATORS; j++) { @@ -159,7 +162,7 @@ export class InlineHintsController implements IEditorContribution { color: `${fontColor}`, margin: `0px ${marginAfter}px 0px ${marginBefore}px`, fontSize: `${fontSize}px`, - fontFamily: fontFamily, + fontFamily: `var(${fontFamilyVar})`, padding: `0px ${(fontSize / 4) | 0}px`, borderRadius: `${(fontSize / 4) | 0}px`, }; diff --git a/src/vs/editor/contrib/linkedEditing/linkedEditing.ts b/src/vs/editor/contrib/linkedEditing/linkedEditing.ts index 44146081..8a586c1b 100644 --- a/src/vs/editor/contrib/linkedEditing/linkedEditing.ts +++ b/src/vs/editor/contrib/linkedEditing/linkedEditing.ts @@ -230,7 +230,7 @@ export class LinkedEditingContribution extends Disposable implements IEditorCont } } - public dispose(): void { + public override dispose(): void { this.clearRanges(); super.dispose(); } @@ -378,7 +378,7 @@ export class LinkedEditingAction extends EditorAction { }); } - runCommand(accessor: ServicesAccessor, args: [URI, IPosition]): void | Promise { + override runCommand(accessor: ServicesAccessor, args: [URI, IPosition]): void | Promise { const editorService = accessor.get(ICodeEditorService); const [uri, pos] = Array.isArray(args) && args || [undefined, undefined]; diff --git a/src/vs/editor/contrib/linkedEditing/test/linkedEditing.test..ts b/src/vs/editor/contrib/linkedEditing/test/linkedEditing.test..ts index 9b00cb4b..34c50fff 100644 --- a/src/vs/editor/contrib/linkedEditing/test/linkedEditing.test..ts +++ b/src/vs/editor/contrib/linkedEditing/test/linkedEditing.test..ts @@ -111,9 +111,9 @@ suite('linked editing', () => { return new Promise((resolve) => { setTimeout(() => { if (typeof expectedEndText === 'string') { - assert.equal(editor.getModel()!.getValue(), expectedEndText); + assert.strictEqual(editor.getModel()!.getValue(), expectedEndText); } else { - assert.equal(editor.getModel()!.getValue(), expectedEndText.join('\n')); + assert.strictEqual(editor.getModel()!.getValue(), expectedEndText.join('\n')); } resolve(); }, timeout); diff --git a/src/vs/editor/contrib/links/links.ts b/src/vs/editor/contrib/links/links.ts index 8997154c..5cfe6912 100644 --- a/src/vs/editor/contrib/links/links.ts +++ b/src/vs/editor/contrib/links/links.ts @@ -327,7 +327,7 @@ export class LinkDetector implements IEditorContribution { } } - return this.openerService.open(uri, { openToSide, fromUserGesture, allowContributedOpeners: true }); + return this.openerService.open(uri, { openToSide, fromUserGesture, allowContributedOpeners: true, allowCommands: true }); }, err => { const messageOrError = diff --git a/src/vs/editor/contrib/multicursor/multicursor.ts b/src/vs/editor/contrib/multicursor/multicursor.ts index 250a689c..cfbd4b2d 100644 --- a/src/vs/editor/contrib/multicursor/multicursor.ts +++ b/src/vs/editor/contrib/multicursor/multicursor.ts @@ -463,7 +463,7 @@ export class MultiCursorSelectionController extends Disposable implements IEdito this._session = null; } - public dispose(): void { + public override dispose(): void { this._endSession(); super.dispose(); } @@ -1060,7 +1060,7 @@ export class SelectionHighlighter extends Disposable implements IEditorContribut className: 'selectionHighlight', }); - public dispose(): void { + public override dispose(): void { this._setState(null); super.dispose(); } diff --git a/src/vs/editor/contrib/parameterHints/parameterHintsModel.ts b/src/vs/editor/contrib/parameterHints/parameterHintsModel.ts index 2c01d063..8c4d7d6f 100644 --- a/src/vs/editor/contrib/parameterHints/parameterHintsModel.ts +++ b/src/vs/editor/contrib/parameterHints/parameterHintsModel.ts @@ -305,7 +305,7 @@ export class ParameterHintsModel extends Disposable { } } - dispose(): void { + override dispose(): void { this.cancel(true); super.dispose(); } diff --git a/src/vs/editor/contrib/peekView/media/peekViewWidget.css b/src/vs/editor/contrib/peekView/media/peekViewWidget.css index e6b5ccb0..ed8f673a 100644 --- a/src/vs/editor/contrib/peekView/media/peekViewWidget.css +++ b/src/vs/editor/contrib/peekView/media/peekViewWidget.css @@ -55,23 +55,6 @@ height: 100%; } -.monaco-editor .peekview-widget .head .peekview-actions > .monaco-action-bar .action-item { - margin-left: 4px; -} - -.monaco-editor .peekview-widget .head .peekview-actions > .monaco-action-bar .action-label { - width: 16px; - height: 100%; - margin: 0; - line-height: inherit; - background-repeat: no-repeat; - background-position: center center; -} - -.monaco-editor .peekview-widget .head .peekview-actions > .monaco-action-bar .action-label.codicon { - margin: 0; -} - .monaco-editor .peekview-widget > .body { border-top: 1px solid; position: relative; diff --git a/src/vs/editor/contrib/peekView/peekView.ts b/src/vs/editor/contrib/peekView/peekView.ts index 91e8bc41..6dbcde25 100644 --- a/src/vs/editor/contrib/peekView/peekView.ts +++ b/src/vs/editor/contrib/peekView/peekView.ts @@ -124,7 +124,7 @@ export abstract class PeekViewWidget extends ZoneWidget { objects.mixin(this.options, defaultOptions, false); } - dispose(): void { + override dispose(): void { if (!this.disposed) { this.disposed = true; // prevent consumers who dispose on onDidClose from looping super.dispose(); @@ -132,7 +132,7 @@ export abstract class PeekViewWidget extends ZoneWidget { } } - style(styles: IPeekViewStyles): void { + override style(styles: IPeekViewStyles): void { let options = this.options; if (styles.headerBackgroundColor) { options.headerBackgroundColor = styles.headerBackgroundColor; @@ -146,7 +146,7 @@ export abstract class PeekViewWidget extends ZoneWidget { super.style(styles); } - protected _applyStyles(): void { + protected override _applyStyles(): void { super._applyStyles(); let options = this.options; if (this._headElement && options.headerBackgroundColor) { @@ -241,7 +241,7 @@ export abstract class PeekViewWidget extends ZoneWidget { protected abstract _fillBody(container: HTMLElement): void; - protected _doLayout(heightInPixel: number, widthInPixel: number): void { + protected override _doLayout(heightInPixel: number, widthInPixel: number): void { if (!this._isShowing && heightInPixel < 0) { // Looks like the view zone got folded away! diff --git a/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts b/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts index 96c0a9be..c2c0e0d4 100644 --- a/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts +++ b/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts @@ -35,10 +35,13 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit static SCOPE_PREFIX = ':'; static PREFIX_BY_CATEGORY = `${AbstractGotoSymbolQuickAccessProvider.PREFIX}${AbstractGotoSymbolQuickAccessProvider.SCOPE_PREFIX}`; - constructor(protected options: IGotoSymbolQuickAccessProviderOptions = Object.create(null)) { + protected override readonly options: IGotoSymbolQuickAccessProviderOptions; + + constructor(options: IGotoSymbolQuickAccessProviderOptions = Object.create(null)) { super(options); - options.canAcceptInBackground = true; + this.options = options; + this.options.canAcceptInBackground = true; } protected provideWithoutTextEditor(picker: IQuickPick): IDisposable { diff --git a/src/vs/editor/contrib/rename/rename.ts b/src/vs/editor/contrib/rename/rename.ts index 41547308..3eafd33c 100644 --- a/src/vs/editor/contrib/rename/rename.ts +++ b/src/vs/editor/contrib/rename/rename.ts @@ -281,7 +281,7 @@ export class RenameAction extends EditorAction { }); } - runCommand(accessor: ServicesAccessor, args: [URI, IPosition]): void | Promise { + override runCommand(accessor: ServicesAccessor, args: [URI, IPosition]): void | Promise { const editorService = accessor.get(ICodeEditorService); const [uri, pos] = Array.isArray(args) && args || [undefined, undefined]; diff --git a/src/vs/editor/contrib/smartSelect/test/smartSelect.test.ts b/src/vs/editor/contrib/smartSelect/test/smartSelect.test.ts index 862a0feb..c49cc295 100644 --- a/src/vs/editor/contrib/smartSelect/test/smartSelect.test.ts +++ b/src/vs/editor/contrib/smartSelect/test/smartSelect.test.ts @@ -77,7 +77,7 @@ suite('SmartSelect', () => { let actualStr = actual!.map(r => new Range(r.startLineNumber, r.startColumn, r.endLineNumber, r.endColumn).toString()); let desiredStr = ranges.reverse().map(r => String(r)); - assert.deepEqual(actualStr, desiredStr, `\nA: ${actualStr} VS \nE: ${desiredStr}`); + assert.deepStrictEqual(actualStr, desiredStr, `\nA: ${actualStr} VS \nE: ${desiredStr}`); modelService.destroyModel(uri); } @@ -226,7 +226,7 @@ suite('SmartSelect', () => { modelService.destroyModel(model.uri); - assert.equal(expected.length, ranges!.length); + assert.strictEqual(expected.length, ranges!.length); for (const range of ranges!) { let exp = expected.shift() || null; assert.ok(Range.equalsRange(range.range, exp), `A=${range.range} <> E=${exp}`); diff --git a/src/vs/editor/contrib/snippet/snippetParser.ts b/src/vs/editor/contrib/snippet/snippetParser.ts index b6bd2eb3..8986345e 100644 --- a/src/vs/editor/contrib/snippet/snippetParser.ts +++ b/src/vs/editor/contrib/snippet/snippetParser.ts @@ -200,13 +200,13 @@ export class Text extends Marker { constructor(public value: string) { super(); } - toString() { + override toString() { return this.value; } toTextmateString(): string { return Text.escape(this.value); } - len(): number { + override len(): number { return this.value.length; } clone(): Text { @@ -279,7 +279,7 @@ export class Choice extends Marker { readonly options: Text[] = []; - appendChild(marker: Marker): this { + override appendChild(marker: Marker): this { if (marker instanceof Text) { marker.parent = this; this.options.push(marker); @@ -287,7 +287,7 @@ export class Choice extends Marker { return this; } - toString() { + override toString() { return this.options[0].value; } @@ -297,7 +297,7 @@ export class Choice extends Marker { .join(','); } - len(): number { + override len(): number { return this.options[0].len(); } @@ -341,7 +341,7 @@ export class Transform extends Marker { return ret; } - toString(): string { + override toString(): string { return ''; } @@ -555,12 +555,12 @@ export class TextmateSnippet extends Marker { return this; } - appendChild(child: Marker) { + override appendChild(child: Marker) { this._placeholders = undefined; return super.appendChild(child); } - replace(child: Marker, others: Marker[]): void { + override replace(child: Marker, others: Marker[]): void { this._placeholders = undefined; return super.replace(child, others); } diff --git a/src/vs/editor/contrib/snippet/test/snippetParser.test.ts b/src/vs/editor/contrib/snippet/test/snippetParser.test.ts index ed5dcc44..a9d41faf 100644 --- a/src/vs/editor/contrib/snippet/test/snippetParser.test.ts +++ b/src/vs/editor/contrib/snippet/test/snippetParser.test.ts @@ -10,88 +10,88 @@ suite('SnippetParser', () => { test('Scanner', () => { const scanner = new Scanner(); - assert.equal(scanner.next().type, TokenType.EOF); + assert.strictEqual(scanner.next().type, TokenType.EOF); scanner.text('abc'); - assert.equal(scanner.next().type, TokenType.VariableName); - assert.equal(scanner.next().type, TokenType.EOF); + assert.strictEqual(scanner.next().type, TokenType.VariableName); + assert.strictEqual(scanner.next().type, TokenType.EOF); scanner.text('{{abc}}'); - assert.equal(scanner.next().type, TokenType.CurlyOpen); - assert.equal(scanner.next().type, TokenType.CurlyOpen); - assert.equal(scanner.next().type, TokenType.VariableName); - assert.equal(scanner.next().type, TokenType.CurlyClose); - assert.equal(scanner.next().type, TokenType.CurlyClose); - assert.equal(scanner.next().type, TokenType.EOF); + assert.strictEqual(scanner.next().type, TokenType.CurlyOpen); + assert.strictEqual(scanner.next().type, TokenType.CurlyOpen); + assert.strictEqual(scanner.next().type, TokenType.VariableName); + assert.strictEqual(scanner.next().type, TokenType.CurlyClose); + assert.strictEqual(scanner.next().type, TokenType.CurlyClose); + assert.strictEqual(scanner.next().type, TokenType.EOF); scanner.text('abc() '); - assert.equal(scanner.next().type, TokenType.VariableName); - assert.equal(scanner.next().type, TokenType.Format); - assert.equal(scanner.next().type, TokenType.EOF); + assert.strictEqual(scanner.next().type, TokenType.VariableName); + assert.strictEqual(scanner.next().type, TokenType.Format); + assert.strictEqual(scanner.next().type, TokenType.EOF); scanner.text('abc 123'); - assert.equal(scanner.next().type, TokenType.VariableName); - assert.equal(scanner.next().type, TokenType.Format); - assert.equal(scanner.next().type, TokenType.Int); - assert.equal(scanner.next().type, TokenType.EOF); + assert.strictEqual(scanner.next().type, TokenType.VariableName); + assert.strictEqual(scanner.next().type, TokenType.Format); + assert.strictEqual(scanner.next().type, TokenType.Int); + assert.strictEqual(scanner.next().type, TokenType.EOF); scanner.text('$foo'); - assert.equal(scanner.next().type, TokenType.Dollar); - assert.equal(scanner.next().type, TokenType.VariableName); - assert.equal(scanner.next().type, TokenType.EOF); + assert.strictEqual(scanner.next().type, TokenType.Dollar); + assert.strictEqual(scanner.next().type, TokenType.VariableName); + assert.strictEqual(scanner.next().type, TokenType.EOF); scanner.text('$foo_bar'); - assert.equal(scanner.next().type, TokenType.Dollar); - assert.equal(scanner.next().type, TokenType.VariableName); - assert.equal(scanner.next().type, TokenType.EOF); + assert.strictEqual(scanner.next().type, TokenType.Dollar); + assert.strictEqual(scanner.next().type, TokenType.VariableName); + assert.strictEqual(scanner.next().type, TokenType.EOF); scanner.text('$foo-bar'); - assert.equal(scanner.next().type, TokenType.Dollar); - assert.equal(scanner.next().type, TokenType.VariableName); - assert.equal(scanner.next().type, TokenType.Dash); - assert.equal(scanner.next().type, TokenType.VariableName); - assert.equal(scanner.next().type, TokenType.EOF); + assert.strictEqual(scanner.next().type, TokenType.Dollar); + assert.strictEqual(scanner.next().type, TokenType.VariableName); + assert.strictEqual(scanner.next().type, TokenType.Dash); + assert.strictEqual(scanner.next().type, TokenType.VariableName); + assert.strictEqual(scanner.next().type, TokenType.EOF); scanner.text('${foo}'); - assert.equal(scanner.next().type, TokenType.Dollar); - assert.equal(scanner.next().type, TokenType.CurlyOpen); - assert.equal(scanner.next().type, TokenType.VariableName); - assert.equal(scanner.next().type, TokenType.CurlyClose); - assert.equal(scanner.next().type, TokenType.EOF); + assert.strictEqual(scanner.next().type, TokenType.Dollar); + assert.strictEqual(scanner.next().type, TokenType.CurlyOpen); + assert.strictEqual(scanner.next().type, TokenType.VariableName); + assert.strictEqual(scanner.next().type, TokenType.CurlyClose); + assert.strictEqual(scanner.next().type, TokenType.EOF); scanner.text('${1223:foo}'); - assert.equal(scanner.next().type, TokenType.Dollar); - assert.equal(scanner.next().type, TokenType.CurlyOpen); - assert.equal(scanner.next().type, TokenType.Int); - assert.equal(scanner.next().type, TokenType.Colon); - assert.equal(scanner.next().type, TokenType.VariableName); - assert.equal(scanner.next().type, TokenType.CurlyClose); - assert.equal(scanner.next().type, TokenType.EOF); + assert.strictEqual(scanner.next().type, TokenType.Dollar); + assert.strictEqual(scanner.next().type, TokenType.CurlyOpen); + assert.strictEqual(scanner.next().type, TokenType.Int); + assert.strictEqual(scanner.next().type, TokenType.Colon); + assert.strictEqual(scanner.next().type, TokenType.VariableName); + assert.strictEqual(scanner.next().type, TokenType.CurlyClose); + assert.strictEqual(scanner.next().type, TokenType.EOF); scanner.text('\\${}'); - assert.equal(scanner.next().type, TokenType.Backslash); - assert.equal(scanner.next().type, TokenType.Dollar); - assert.equal(scanner.next().type, TokenType.CurlyOpen); - assert.equal(scanner.next().type, TokenType.CurlyClose); + assert.strictEqual(scanner.next().type, TokenType.Backslash); + assert.strictEqual(scanner.next().type, TokenType.Dollar); + assert.strictEqual(scanner.next().type, TokenType.CurlyOpen); + assert.strictEqual(scanner.next().type, TokenType.CurlyClose); scanner.text('${foo/regex/format/option}'); - assert.equal(scanner.next().type, TokenType.Dollar); - assert.equal(scanner.next().type, TokenType.CurlyOpen); - assert.equal(scanner.next().type, TokenType.VariableName); - assert.equal(scanner.next().type, TokenType.Forwardslash); - assert.equal(scanner.next().type, TokenType.VariableName); - assert.equal(scanner.next().type, TokenType.Forwardslash); - assert.equal(scanner.next().type, TokenType.VariableName); - assert.equal(scanner.next().type, TokenType.Forwardslash); - assert.equal(scanner.next().type, TokenType.VariableName); - assert.equal(scanner.next().type, TokenType.CurlyClose); - assert.equal(scanner.next().type, TokenType.EOF); + assert.strictEqual(scanner.next().type, TokenType.Dollar); + assert.strictEqual(scanner.next().type, TokenType.CurlyOpen); + assert.strictEqual(scanner.next().type, TokenType.VariableName); + assert.strictEqual(scanner.next().type, TokenType.Forwardslash); + assert.strictEqual(scanner.next().type, TokenType.VariableName); + assert.strictEqual(scanner.next().type, TokenType.Forwardslash); + assert.strictEqual(scanner.next().type, TokenType.VariableName); + assert.strictEqual(scanner.next().type, TokenType.Forwardslash); + assert.strictEqual(scanner.next().type, TokenType.VariableName); + assert.strictEqual(scanner.next().type, TokenType.CurlyClose); + assert.strictEqual(scanner.next().type, TokenType.EOF); }); function assertText(value: string, expected: string) { const p = new SnippetParser(); const actual = p.text(value); - assert.equal(actual, expected); + assert.strictEqual(actual, expected); } function assertMarker(input: TextmateSnippet | Marker[] | string, ...ctors: Function[]) { @@ -109,8 +109,8 @@ suite('SnippetParser', () => { let ctor = ctors.pop()!; assert.ok(m instanceof ctor); } - assert.equal(marker.length, ctors.length); - assert.equal(marker.length, 0); + assert.strictEqual(marker.length, ctors.length); + assert.strictEqual(marker.length, 0); } function assertTextAndMarker(value: string, escaped: string, ...ctors: Function[]) { @@ -120,7 +120,7 @@ suite('SnippetParser', () => { function assertEscaped(value: string, expected: string) { const actual = SnippetParser.escape(value); - assert.equal(actual, expected); + assert.strictEqual(actual, expected); } test('Parser, escaped', function () { @@ -167,11 +167,11 @@ suite('SnippetParser', () => { let [, placeholder] = new SnippetParser().parse('foo${1:bar\\}${2:foo}}').children; let { children } = (placeholder); - assert.equal((placeholder).index, '1'); + assert.strictEqual((placeholder).index, 1); assert.ok(children[0] instanceof Text); - assert.equal(children[0].toString(), 'bar}'); + assert.strictEqual(children[0].toString(), 'bar}'); assert.ok(children[1] instanceof Placeholder); - assert.equal(children[1].toString(), 'foo'); + assert.strictEqual(children[1].toString(), 'foo'); }); test('Parser, placeholder', () => { @@ -278,7 +278,7 @@ suite('SnippetParser', () => { assertMarker(snippet, Placeholder); const expected = [Placeholder, Text, Text, Text]; snippet.walk(marker => { - assert.equal(marker, expected.shift()); + assert.strictEqual(marker, expected.shift()); return true; }); }); @@ -292,7 +292,7 @@ suite('SnippetParser', () => { function assertTextsnippetString(input: string, expected: string): void { const snippet = new SnippetParser().parse(input); const actual = snippet.toTextmateString(); - assert.equal(actual, expected); + assert.strictEqual(actual, expected); } assertTextsnippetString('$1', '$1'); @@ -316,8 +316,8 @@ suite('SnippetParser', () => { assert.ok(marker1 instanceof Object.getPrototypeOf(marker2).constructor); assert.ok(marker2 instanceof Object.getPrototypeOf(marker1).constructor); - assert.equal(marker1.children.length, marker2.children.length); - assert.equal(marker1.toString(), marker2.toString()); + assert.strictEqual(marker1.children.length, marker2.children.length); + assert.strictEqual(marker1.toString(), marker2.toString()); for (let i = 0; i < marker1.children.length; i++) { checkCheckChildren(marker1.children[i], marker2.children[i]); @@ -340,10 +340,10 @@ suite('SnippetParser', () => { test('Parser, choise marker', () => { const { placeholders } = new SnippetParser().parse('${1|one,two,three|}'); - assert.equal(placeholders.length, 1); + assert.strictEqual(placeholders.length, 1); assert.ok(placeholders[0].choice instanceof Choice); assert.ok(placeholders[0].children[0] instanceof Choice); - assert.equal((placeholders[0].children[0]).options.length, 3); + assert.strictEqual((placeholders[0].children[0]).options.length, 3); assertText('${1|one,two,three|}', 'one'); assertText('\\${1|one,two,three|}', '${1|one,two,three|}'); @@ -354,7 +354,7 @@ suite('SnippetParser', () => { test('Backslash character escape in choice tabstop doesn\'t work #58494', function () { const { placeholders } = new SnippetParser().parse('${1|\\,,},$,\\|,\\\\|}'); - assert.equal(placeholders.length, 1); + assert.strictEqual(placeholders.length, 1); assert.ok(placeholders[0].choice instanceof Choice); }); @@ -372,27 +372,26 @@ suite('SnippetParser', () => { test('Parser, real world', () => { let marker = new SnippetParser().parse('console.warn(${1: $TM_SELECTED_TEXT })').children; - assert.equal(marker[0].toString(), 'console.warn('); + assert.strictEqual(marker[0].toString(), 'console.warn('); assert.ok(marker[1] instanceof Placeholder); - assert.equal(marker[2].toString(), ')'); + assert.strictEqual(marker[2].toString(), ')'); const placeholder = marker[1]; - assert.equal(placeholder, false); - assert.equal(placeholder.index, '1'); - assert.equal(placeholder.children.length, 3); + assert.strictEqual(placeholder.index, 1); + assert.strictEqual(placeholder.children.length, 3); assert.ok(placeholder.children[0] instanceof Text); assert.ok(placeholder.children[1] instanceof Variable); assert.ok(placeholder.children[2] instanceof Text); - assert.equal(placeholder.children[0].toString(), ' '); - assert.equal(placeholder.children[1].toString(), ''); - assert.equal(placeholder.children[2].toString(), ' '); + assert.strictEqual(placeholder.children[0].toString(), ' '); + assert.strictEqual(placeholder.children[1].toString(), ''); + assert.strictEqual(placeholder.children[2].toString(), ' '); const nestedVariable = placeholder.children[1]; - assert.equal(nestedVariable.name, 'TM_SELECTED_TEXT'); - assert.equal(nestedVariable.children.length, 0); + assert.strictEqual(nestedVariable.name, 'TM_SELECTED_TEXT'); + assert.strictEqual(nestedVariable.children.length, 0); marker = new SnippetParser().parse('$TM_SELECTED_TEXT').children; - assert.equal(marker.length, 1); + assert.strictEqual(marker.length, 1); assert.ok(marker[0] instanceof Variable); }); @@ -401,66 +400,68 @@ suite('SnippetParser', () => { //${1:name} assert.ok(children[0] instanceof Placeholder); - assert.equal(children[0].children.length, 1); - assert.equal(children[0].children[0].toString(), 'name'); - assert.equal((children[0]).transform, undefined); + assert.strictEqual(children[0].children.length, 1); + assert.strictEqual(children[0].children[0].toString(), 'name'); + assert.strictEqual((children[0]).transform, undefined); // : assert.ok(children[1] instanceof Text); - assert.equal(children[1].toString(), ' : '); + assert.strictEqual(children[1].toString(), ' : '); //${2:type} assert.ok(children[2] instanceof Placeholder); - assert.equal(children[2].children.length, 1); - assert.equal(children[2].children[0].toString(), 'type'); + assert.strictEqual(children[2].children.length, 1); + assert.strictEqual(children[2].children[0].toString(), 'type'); //${3/\\s:=(.*)/${1:+ :=}${1}/} assert.ok(children[3] instanceof Placeholder); - assert.equal(children[3].children.length, 0); - assert.notEqual((children[3]).transform, undefined); + assert.strictEqual(children[3].children.length, 0); + assert.notStrictEqual((children[3]).transform, undefined); let transform = (children[3]).transform!; - assert.equal(transform.regexp, '/\\s:=(.*)/'); - assert.equal(transform.children.length, 2); + assert.deepStrictEqual(transform.regexp, /\s:=(.*)/); + assert.strictEqual(transform.children.length, 2); assert.ok(transform.children[0] instanceof FormatString); - assert.equal((transform.children[0]).index, 1); - assert.equal((transform.children[0]).ifValue, ' :='); + assert.strictEqual((transform.children[0]).index, 1); + assert.strictEqual((transform.children[0]).ifValue, ' :='); assert.ok(transform.children[1] instanceof FormatString); - assert.equal((transform.children[1]).index, 1); + assert.strictEqual((transform.children[1]).index, 1); assert.ok(children[4] instanceof Text); - assert.equal(children[4].toString(), ';\n'); + assert.strictEqual(children[4].toString(), ';\n'); }); + // TODO @jrieken making this strictEqul causes circular json conversion errors test('Parser, default placeholder values', () => { assertMarker('errorContext: `${1:err}`, error: $1', Text, Placeholder, Text, Placeholder); const [, p1, , p2] = new SnippetParser().parse('errorContext: `${1:err}`, error:$1').children; - assert.equal((p1).index, '1'); - assert.equal((p1).children.length, '1'); - assert.equal(((p1).children[0]), 'err'); + assert.strictEqual((p1).index, 1); + assert.strictEqual((p1).children.length, 1); + assert.strictEqual(((p1).children[0]).toString(), 'err'); - assert.equal((p2).index, '1'); - assert.equal((p2).children.length, '1'); - assert.equal(((p2).children[0]), 'err'); + assert.strictEqual((p2).index, 1); + assert.strictEqual((p2).children.length, 1); + assert.strictEqual(((p2).children[0]).toString(), 'err'); }); + // TODO @jrieken making this strictEqul causes circular json conversion errors test('Parser, default placeholder values and one transform', () => { assertMarker('errorContext: `${1:err}`, error: ${1/err/ok/}', Text, Placeholder, Text, Placeholder); const [, p3, , p4] = new SnippetParser().parse('errorContext: `${1:err}`, error:${1/err/ok/}').children; - assert.equal((p3).index, '1'); - assert.equal((p3).children.length, '1'); - assert.equal(((p3).children[0]), 'err'); - assert.equal((p3).transform, undefined); + assert.strictEqual((p3).index, 1); + assert.strictEqual((p3).children.length, 1); + assert.strictEqual(((p3).children[0]).toString(), 'err'); + assert.strictEqual((p3).transform, undefined); - assert.equal((p4).index, '1'); - assert.equal((p4).children.length, '1'); - assert.equal(((p4).children[0]), 'err'); - assert.notEqual((p4).transform, undefined); + assert.strictEqual((p4).index, 1); + assert.strictEqual((p4).children.length, 1); + assert.strictEqual(((p4).children[0]).toString(), 'err'); + assert.notStrictEqual((p4).transform, undefined); }); test('Repeated snippet placeholder should always inherit, #31040', function () { @@ -472,15 +473,15 @@ suite('SnippetParser', () => { test('backspace esapce in TM only, #16212', () => { const actual = new SnippetParser().text('Foo \\\\${abc}bar'); - assert.equal(actual, 'Foo \\bar'); + assert.strictEqual(actual, 'Foo \\bar'); }); test('colon as variable/placeholder value, #16717', () => { let actual = new SnippetParser().text('${TM_SELECTED_TEXT:foo:bar}'); - assert.equal(actual, 'foo:bar'); + assert.strictEqual(actual, 'foo:bar'); actual = new SnippetParser().text('${1:foo:bar}'); - assert.equal(actual, 'foo:bar'); + assert.strictEqual(actual, 'foo:bar'); }); test('incomplete placeholder', () => { @@ -493,10 +494,10 @@ suite('SnippetParser', () => { const snippet = new SnippetParser().parse(template, true); snippet.walk(m => { const expected = lengths.shift(); - assert.equal(m.len(), expected); + assert.strictEqual(m.len(), expected); return true; }); - assert.equal(lengths.length, 0); + assert.strictEqual(lengths.length, 0); } assertLen('text$0', 4, 0); @@ -511,17 +512,17 @@ suite('SnippetParser', () => { test('parser, parent node', function () { let snippet = new SnippetParser().parse('This ${1:is ${2:nested}}$0', true); - assert.equal(snippet.placeholders.length, 3); + assert.strictEqual(snippet.placeholders.length, 3); let [first, second] = snippet.placeholders; - assert.equal(first.index, '1'); - assert.equal(second.index, '2'); + assert.strictEqual(first.index, 1); + assert.strictEqual(second.index, 2); assert.ok(second.parent === first); assert.ok(first.parent === snippet); snippet = new SnippetParser().parse('${VAR:default${1:value}}$0', true); - assert.equal(snippet.placeholders.length, 2); + assert.strictEqual(snippet.placeholders.length, 2); [first] = snippet.placeholders; - assert.equal(first.index, '1'); + assert.strictEqual(first.index, 1); assert.ok(snippet.children[0] instanceof Variable); assert.ok(first.parent === snippet.children[0]); @@ -537,76 +538,76 @@ suite('SnippetParser', () => { test('TextmateSnippet#offset', () => { let snippet = new SnippetParser().parse('te$1xt', true); - assert.equal(snippet.offset(snippet.children[0]), 0); - assert.equal(snippet.offset(snippet.children[1]), 2); - assert.equal(snippet.offset(snippet.children[2]), 2); + assert.strictEqual(snippet.offset(snippet.children[0]), 0); + assert.strictEqual(snippet.offset(snippet.children[1]), 2); + assert.strictEqual(snippet.offset(snippet.children[2]), 2); snippet = new SnippetParser().parse('${TM_SELECTED_TEXT:def}', true); - assert.equal(snippet.offset(snippet.children[0]), 0); - assert.equal(snippet.offset((snippet.children[0]).children[0]), 0); + assert.strictEqual(snippet.offset(snippet.children[0]), 0); + assert.strictEqual(snippet.offset((snippet.children[0]).children[0]), 0); // forgein marker - assert.equal(snippet.offset(new Text('foo')), -1); + assert.strictEqual(snippet.offset(new Text('foo')), -1); }); test('TextmateSnippet#placeholder', () => { let snippet = new SnippetParser().parse('te$1xt$0', true); let placeholders = snippet.placeholders; - assert.equal(placeholders.length, 2); + assert.strictEqual(placeholders.length, 2); snippet = new SnippetParser().parse('te$1xt$1$0', true); placeholders = snippet.placeholders; - assert.equal(placeholders.length, 3); + assert.strictEqual(placeholders.length, 3); snippet = new SnippetParser().parse('te$1xt$2$0', true); placeholders = snippet.placeholders; - assert.equal(placeholders.length, 3); + assert.strictEqual(placeholders.length, 3); snippet = new SnippetParser().parse('${1:bar${2:foo}bar}$0', true); placeholders = snippet.placeholders; - assert.equal(placeholders.length, 3); + assert.strictEqual(placeholders.length, 3); }); test('TextmateSnippet#replace 1/2', function () { let snippet = new SnippetParser().parse('aaa${1:bbb${2:ccc}}$0', true); - assert.equal(snippet.placeholders.length, 3); + assert.strictEqual(snippet.placeholders.length, 3); const [, second] = snippet.placeholders; - assert.equal(second.index, '2'); + assert.strictEqual(second.index, 2); const enclosing = snippet.enclosingPlaceholders(second); - assert.equal(enclosing.length, 1); - assert.equal(enclosing[0].index, '1'); + assert.strictEqual(enclosing.length, 1); + assert.strictEqual(enclosing[0].index, 1); let nested = new SnippetParser().parse('ddd$1eee$0', true); snippet.replace(second, nested.children); - assert.equal(snippet.toString(), 'aaabbbdddeee'); - assert.equal(snippet.placeholders.length, 4); - assert.equal(snippet.placeholders[0].index, '1'); - assert.equal(snippet.placeholders[1].index, '1'); - assert.equal(snippet.placeholders[2].index, '0'); - assert.equal(snippet.placeholders[3].index, '0'); + assert.strictEqual(snippet.toString(), 'aaabbbdddeee'); + assert.strictEqual(snippet.placeholders.length, 4); + assert.strictEqual(snippet.placeholders[0].index, 1); + assert.strictEqual(snippet.placeholders[1].index, 1); + assert.strictEqual(snippet.placeholders[2].index, 0); + assert.strictEqual(snippet.placeholders[3].index, 0); const newEnclosing = snippet.enclosingPlaceholders(snippet.placeholders[1]); assert.ok(newEnclosing[0] === snippet.placeholders[0]); - assert.equal(newEnclosing.length, 1); - assert.equal(newEnclosing[0].index, '1'); + assert.strictEqual(newEnclosing.length, 1); + assert.strictEqual(newEnclosing[0].index, 1); }); test('TextmateSnippet#replace 2/2', function () { let snippet = new SnippetParser().parse('aaa${1:bbb${2:ccc}}$0', true); - assert.equal(snippet.placeholders.length, 3); + assert.strictEqual(snippet.placeholders.length, 3); const [, second] = snippet.placeholders; - assert.equal(second.index, '2'); + assert.strictEqual(second.index, 2); let nested = new SnippetParser().parse('dddeee$0', true); snippet.replace(second, nested.children); - assert.equal(snippet.toString(), 'aaabbbdddeee'); - assert.equal(snippet.placeholders.length, 3); + assert.strictEqual(snippet.toString(), 'aaabbbdddeee'); + assert.strictEqual(snippet.placeholders.length, 3); }); test('Snippet order for placeholders, #28185', function () { @@ -614,7 +615,7 @@ suite('SnippetParser', () => { const _10 = new Placeholder(10); const _2 = new Placeholder(2); - assert.equal(Placeholder.compareByIndex(_10, _2), 1); + assert.strictEqual(Placeholder.compareByIndex(_10, _2), 1); }); test('Maximum call stack size exceeded, #28983', function () { @@ -649,32 +650,32 @@ suite('SnippetParser', () => { test('Transform -> FormatString#resolve', function () { // shorthand functions - assert.equal(new FormatString(1, 'upcase').resolve('foo'), 'FOO'); - assert.equal(new FormatString(1, 'downcase').resolve('FOO'), 'foo'); - assert.equal(new FormatString(1, 'capitalize').resolve('bar'), 'Bar'); - assert.equal(new FormatString(1, 'capitalize').resolve('bar no repeat'), 'Bar no repeat'); - assert.equal(new FormatString(1, 'pascalcase').resolve('bar-foo'), 'BarFoo'); - assert.equal(new FormatString(1, 'notKnown').resolve('input'), 'input'); + assert.strictEqual(new FormatString(1, 'upcase').resolve('foo'), 'FOO'); + assert.strictEqual(new FormatString(1, 'downcase').resolve('FOO'), 'foo'); + assert.strictEqual(new FormatString(1, 'capitalize').resolve('bar'), 'Bar'); + assert.strictEqual(new FormatString(1, 'capitalize').resolve('bar no repeat'), 'Bar no repeat'); + assert.strictEqual(new FormatString(1, 'pascalcase').resolve('bar-foo'), 'BarFoo'); + assert.strictEqual(new FormatString(1, 'notKnown').resolve('input'), 'input'); // if - assert.equal(new FormatString(1, undefined, 'foo', undefined).resolve(undefined), ''); - assert.equal(new FormatString(1, undefined, 'foo', undefined).resolve(''), ''); - assert.equal(new FormatString(1, undefined, 'foo', undefined).resolve('bar'), 'foo'); + assert.strictEqual(new FormatString(1, undefined, 'foo', undefined).resolve(undefined), ''); + assert.strictEqual(new FormatString(1, undefined, 'foo', undefined).resolve(''), ''); + assert.strictEqual(new FormatString(1, undefined, 'foo', undefined).resolve('bar'), 'foo'); // else - assert.equal(new FormatString(1, undefined, undefined, 'foo').resolve(undefined), 'foo'); - assert.equal(new FormatString(1, undefined, undefined, 'foo').resolve(''), 'foo'); - assert.equal(new FormatString(1, undefined, undefined, 'foo').resolve('bar'), 'bar'); + assert.strictEqual(new FormatString(1, undefined, undefined, 'foo').resolve(undefined), 'foo'); + assert.strictEqual(new FormatString(1, undefined, undefined, 'foo').resolve(''), 'foo'); + assert.strictEqual(new FormatString(1, undefined, undefined, 'foo').resolve('bar'), 'bar'); // if-else - assert.equal(new FormatString(1, undefined, 'bar', 'foo').resolve(undefined), 'foo'); - assert.equal(new FormatString(1, undefined, 'bar', 'foo').resolve(''), 'foo'); - assert.equal(new FormatString(1, undefined, 'bar', 'foo').resolve('baz'), 'bar'); + assert.strictEqual(new FormatString(1, undefined, 'bar', 'foo').resolve(undefined), 'foo'); + assert.strictEqual(new FormatString(1, undefined, 'bar', 'foo').resolve(''), 'foo'); + assert.strictEqual(new FormatString(1, undefined, 'bar', 'foo').resolve('baz'), 'bar'); }); test('Snippet variable transformation doesn\'t work if regex is complicated and snippet body contains \'$$\' #55627', function () { const snippet = new SnippetParser().parse('const fileName = "${TM_FILENAME/(.*)\\..+$/$1/}"'); - assert.equal(snippet.toTextmateString(), 'const fileName = "${TM_FILENAME/(.*)\\..+$/${1}/}"'); + assert.strictEqual(snippet.toTextmateString(), 'const fileName = "${TM_FILENAME/(.*)\\..+$/${1}/}"'); }); test('[BUG] HTML attribute suggestions: Snippet session does not have end-position set, #33147', function () { @@ -682,9 +683,9 @@ suite('SnippetParser', () => { const { placeholders } = new SnippetParser().parse('src="$1"', true); const [first, second] = placeholders; - assert.equal(placeholders.length, 2); - assert.equal(first.index, 1); - assert.equal(second.index, 0); + assert.strictEqual(placeholders.length, 2); + assert.strictEqual(first.index, 1); + assert.strictEqual(second.index, 0); }); @@ -695,10 +696,10 @@ suite('SnippetParser', () => { transform.appendChild(new FormatString(2, 'upcase')); transform.regexp = /^(.)|-(.)/g; - assert.equal(transform.resolve('my-file-name'), 'MyFileName'); + assert.strictEqual(transform.resolve('my-file-name'), 'MyFileName'); const clone = transform.clone(); - assert.equal(clone.resolve('my-file-name'), 'MyFileName'); + assert.strictEqual(clone.resolve('my-file-name'), 'MyFileName'); }); test('problem with snippets regex #40570', function () { @@ -711,7 +712,7 @@ suite('SnippetParser', () => { let transform = new Transform(); transform.appendChild(new Text('bar')); transform.regexp = new RegExp('foo', 'gi'); - assert.equal(transform.toTextmateString(), '/foo/bar/ig'); + assert.strictEqual(transform.toTextmateString(), '/foo/bar/ig'); }); test('Snippet parser freeze #53144', function () { @@ -725,7 +726,7 @@ suite('SnippetParser', () => { test('Mirroring sequence of nested placeholders not selected properly on backjumping #58736', function () { let snippet = new SnippetParser().parse('${3:nest1 ${1:nest2 ${2:nest3}}} $3'); - assert.equal(snippet.children.length, 3); + assert.strictEqual(snippet.children.length, 3); assert.ok(snippet.children[0] instanceof Placeholder); assert.ok(snippet.children[1] instanceof Text); assert.ok(snippet.children[2] instanceof Placeholder); @@ -759,25 +760,25 @@ suite('SnippetParser', () => { let snippet = new SnippetParser().parse('${TM_DIRECTORY/(.+)/${1:+import { hello \\} from world}/}'); let variable = snippet.children[0]; - assert.equal(snippet.children.length, 1); + assert.strictEqual(snippet.children.length, 1); assert.ok(variable instanceof Variable); assert.ok(variable.transform); - assert.equal(variable.transform!.children.length, 1); + assert.strictEqual(variable.transform!.children.length, 1); assert.ok(variable.transform!.children[0] instanceof FormatString); - assert.equal((variable.transform!.children[0]).ifValue, 'import { hello } from world'); - assert.equal((variable.transform!.children[0]).elseValue, undefined); + assert.strictEqual((variable.transform!.children[0]).ifValue, 'import { hello } from world'); + assert.strictEqual((variable.transform!.children[0]).elseValue, undefined); }); test('Snippet escape backslashes inside conditional insertion variable replacement #80394', function () { let snippet = new SnippetParser().parse('${CURRENT_YEAR/(.+)/${1:+\\\\}/}'); let variable = snippet.children[0]; - assert.equal(snippet.children.length, 1); + assert.strictEqual(snippet.children.length, 1); assert.ok(variable instanceof Variable); assert.ok(variable.transform); - assert.equal(variable.transform!.children.length, 1); + assert.strictEqual(variable.transform!.children.length, 1); assert.ok(variable.transform!.children[0] instanceof FormatString); - assert.equal((variable.transform!.children[0]).ifValue, '\\'); - assert.equal((variable.transform!.children[0]).elseValue, undefined); + assert.strictEqual((variable.transform!.children[0]).ifValue, '\\'); + assert.strictEqual((variable.transform!.children[0]).elseValue, undefined); }); }); diff --git a/src/vs/editor/contrib/snippet/test/snippetVariables.test.ts b/src/vs/editor/contrib/snippet/test/snippetVariables.test.ts index dff2a64b..bd687c66 100644 --- a/src/vs/editor/contrib/snippet/test/snippetVariables.test.ts +++ b/src/vs/editor/contrib/snippet/test/snippetVariables.test.ts @@ -21,7 +21,7 @@ import { toWorkspaceFolders } from 'vs/platform/workspaces/common/workspaces'; suite('Snippet Variables Resolver', function () { const labelService = new class extends mock() { - getUriLabel(uri: URI) { + override getUriLabel(uri: URI) { return uri.fsPath; } }; @@ -92,7 +92,7 @@ suite('Snippet Variables Resolver', function () { test('Path delimiters in code snippet variables aren\'t specific to remote OS #76840', function () { const labelService = new class extends mock() { - getUriLabel(uri: URI) { + override getUriLabel(uri: URI) { return uri.fsPath.replace(/\/|\\/g, '|'); } }; @@ -310,6 +310,7 @@ suite('Snippet Variables Resolver', function () { _throw = () => { throw new Error(); }; onDidChangeWorkbenchState = this._throw; onDidChangeWorkspaceName = this._throw; + onWillChangeWorkspaceFolders = this._throw; onDidChangeWorkspaceFolders = this._throw; getCompleteWorkspace = this._throw; getWorkspace(): IWorkspace { return workspace; } @@ -349,7 +350,7 @@ suite('Snippet Variables Resolver', function () { // Mock a label service (only coded for file uris) const workspaceLabelService = ((rootPath: string): ILabelService => { const labelService = new class extends mock() { - getUriLabel(uri: URI, options: { relative?: boolean } = {}) { + override getUriLabel(uri: URI, options: { relative?: boolean } = {}) { const rootFsPath = URI.file(rootPath).fsPath + sep; const fsPath = uri.fsPath; if (options.relative && rootPath && fsPath.startsWith(rootFsPath)) { diff --git a/src/vs/editor/contrib/suggest/resizable.ts b/src/vs/editor/contrib/suggest/resizable.ts index 382f8828..88e1e73e 100644 --- a/src/vs/editor/contrib/suggest/resizable.ts +++ b/src/vs/editor/contrib/suggest/resizable.ts @@ -151,6 +151,13 @@ export class ResizableHTMLElement { } } + clearSashHoverState(): void { + this._eastSash.clearSashHoverState(); + this._westSash.clearSashHoverState(); + this._northSash.clearSashHoverState(); + this._southSash.clearSashHoverState(); + } + get size() { return this._size; } diff --git a/src/vs/editor/contrib/suggest/suggestMemory.ts b/src/vs/editor/contrib/suggest/suggestMemory.ts index dfb29363..639725c3 100644 --- a/src/vs/editor/contrib/suggest/suggestMemory.ts +++ b/src/vs/editor/contrib/suggest/suggestMemory.ts @@ -90,7 +90,7 @@ export class LRUMemory extends Memory { }); } - select(model: ITextModel, pos: IPosition, items: CompletionItem[]): number { + override select(model: ITextModel, pos: IPosition, items: CompletionItem[]): number { if (items.length === 0) { return 0; @@ -166,7 +166,7 @@ export class PrefixMemory extends Memory { }); } - select(model: ITextModel, pos: IPosition, items: CompletionItem[]): number { + override select(model: ITextModel, pos: IPosition, items: CompletionItem[]): number { let { word } = model.getWordUntilPosition(pos); if (!word) { return super.select(model, pos, items); diff --git a/src/vs/editor/contrib/suggest/suggestWidget.ts b/src/vs/editor/contrib/suggest/suggestWidget.ts index 515d11ef..8e0171cf 100644 --- a/src/vs/editor/contrib/suggest/suggestWidget.ts +++ b/src/vs/editor/contrib/suggest/suggestWidget.ts @@ -440,6 +440,7 @@ export class SuggestWidget implements IDisposable { this._contentWidget.hide(); this._ctxSuggestWidgetVisible.reset(); this._ctxSuggestWidgetMultipleSuggestions.reset(); + this._showTimeout.cancel(); this.element.domNode.classList.remove('visible'); this._list.splice(0, this._list.length); this._focusedItem = undefined; @@ -708,6 +709,7 @@ export class SuggestWidget implements IDisposable { this._loadingTimeout?.dispose(); this._setState(State.Hidden); this._onDidHide.fire(this); + this.element.clearSashHoverState(); // ensure that a reasonable widget height is persisted so that // accidential "resize-to-single-items" cases aren't happening diff --git a/src/vs/editor/contrib/suggest/suggestWidgetDetails.ts b/src/vs/editor/contrib/suggest/suggestWidgetDetails.ts index 40327573..2734906e 100644 --- a/src/vs/editor/contrib/suggest/suggestWidgetDetails.ts +++ b/src/vs/editor/contrib/suggest/suggestWidgetDetails.ts @@ -345,6 +345,8 @@ export class SuggestDetailsOverlay implements IOverlayWidget { } hide(sessionEnded: boolean = false): void { + this._resizable.clearSashHoverState(); + if (this._added) { this._editor.removeOverlayWidget(this); this._added = false; diff --git a/src/vs/editor/contrib/suggest/suggestWidgetStatus.ts b/src/vs/editor/contrib/suggest/suggestWidgetStatus.ts index 5739f38f..ad7f97ae 100644 --- a/src/vs/editor/contrib/suggest/suggestWidgetStatus.ts +++ b/src/vs/editor/contrib/suggest/suggestWidgetStatus.ts @@ -17,7 +17,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti class StatusBarViewItem extends MenuEntryActionViewItem { - updateLabel() { + override updateLabel() { const kb = this._keybindingService.lookupKeybinding(this._action.id); if (!kb) { return super.updateLabel(); diff --git a/src/vs/editor/contrib/suggest/test/completionModel.test.ts b/src/vs/editor/contrib/suggest/test/completionModel.test.ts index 5625a5c9..20b56f9b 100644 --- a/src/vs/editor/contrib/suggest/test/completionModel.test.ts +++ b/src/vs/editor/contrib/suggest/test/completionModel.test.ts @@ -101,7 +101,7 @@ suite('CompletionModel', function () { test('complete/incomplete', () => { - assert.equal(model.incomplete.size, 0); + assert.strictEqual(model.incomplete.size, 0); let incompleteModel = new CompletionModel([ createSuggestItem('foo', 3, undefined, true), @@ -110,7 +110,7 @@ suite('CompletionModel', function () { leadingLineContent: 'foo', characterCountDelta: 0 }, WordDistance.None, EditorOptions.suggest.defaultValue, EditorOptions.snippetSuggestions.defaultValue, undefined); - assert.equal(incompleteModel.incomplete.size, 1); + assert.strictEqual(incompleteModel.incomplete.size, 1); }); test('replaceIncomplete', () => { @@ -119,15 +119,15 @@ suite('CompletionModel', function () { const incompleteItem = createSuggestItem('foofoo', 1, undefined, true, { lineNumber: 1, column: 2 }); const model = new CompletionModel([completeItem, incompleteItem], 2, { leadingLineContent: 'f', characterCountDelta: 0 }, WordDistance.None, EditorOptions.suggest.defaultValue, EditorOptions.snippetSuggestions.defaultValue, undefined); - assert.equal(model.incomplete.size, 1); - assert.equal(model.items.length, 2); + assert.strictEqual(model.incomplete.size, 1); + assert.strictEqual(model.items.length, 2); const { incomplete } = model; const complete = model.adopt(incomplete); - assert.equal(incomplete.size, 1); + assert.strictEqual(incomplete.size, 1); assert.ok(incomplete.has(incompleteItem.provider)); - assert.equal(complete.length, 1); + assert.strictEqual(complete.length, 1); assert.ok(complete[0] === completeItem); }); @@ -149,15 +149,15 @@ suite('CompletionModel', function () { incompleteItem1, ], 2, { leadingLineContent: 'f', characterCountDelta: 0 }, WordDistance.None, EditorOptions.suggest.defaultValue, EditorOptions.snippetSuggestions.defaultValue, undefined ); - assert.equal(model.incomplete.size, 1); - assert.equal(model.items.length, 6); + assert.strictEqual(model.incomplete.size, 1); + assert.strictEqual(model.items.length, 6); const { incomplete } = model; const complete = model.adopt(incomplete); - assert.equal(incomplete.size, 1); + assert.strictEqual(incomplete.size, 1); assert.ok(incomplete.has(incompleteItem1.provider)); - assert.equal(complete.length, 5); + assert.strictEqual(complete.length, 5); }); test('proper current word when length=0, #16380', function () { @@ -173,13 +173,13 @@ suite('CompletionModel', function () { characterCountDelta: 0 }, WordDistance.None, EditorOptions.suggest.defaultValue, EditorOptions.snippetSuggestions.defaultValue, undefined); - assert.equal(model.items.length, 4); + assert.strictEqual(model.items.length, 4); const [a, b, c, d] = model.items; - assert.equal(a.completion.label, ' b.score); // snippet really demoted }); @@ -247,13 +247,13 @@ suite('CompletionModel', function () { characterCountDelta: 0 }, WordDistance.None, EditorOptions.suggest.defaultValue, EditorOptions.snippetSuggestions.defaultValue, undefined); - assert.equal(model.items.length, 2); + assert.strictEqual(model.items.length, 2); model.lineContext = { leadingLineContent: 'Map ', characterCountDelta: 3 }; - assert.equal(model.items.length, 1); + assert.strictEqual(model.items.length, 1); }); test('Vscode 1.12 no longer obeys \'sortText\' in completion items (from language server), #26096', function () { @@ -267,11 +267,11 @@ suite('CompletionModel', function () { characterCountDelta: 0 }, WordDistance.None, EditorOptions.suggest.defaultValue, EditorOptions.snippetSuggestions.defaultValue, undefined); - assert.equal(model.items.length, 2); + assert.strictEqual(model.items.length, 2); const [first, second] = model.items; - assert.equal(first.completion.label, 'source'); - assert.equal(second.completion.label, '<- groups'); + assert.strictEqual(first.completion.label, 'source'); + assert.strictEqual(second.completion.label, '<- groups'); }); test('Score only filtered items when typing more, score all when typing less', function () { @@ -286,19 +286,19 @@ suite('CompletionModel', function () { characterCountDelta: 0 }, WordDistance.None, EditorOptions.suggest.defaultValue, EditorOptions.snippetSuggestions.defaultValue, undefined); - assert.equal(model.items.length, 5); + assert.strictEqual(model.items.length, 5); // narrow down once model.lineContext = { leadingLineContent: 'c', characterCountDelta: 1 }; - assert.equal(model.items.length, 3); + assert.strictEqual(model.items.length, 3); // query gets longer, narrow down the narrow-down'ed-set from before model.lineContext = { leadingLineContent: 'cn', characterCountDelta: 2 }; - assert.equal(model.items.length, 2); + assert.strictEqual(model.items.length, 2); // query gets shorter, refilter everything model.lineContext = { leadingLineContent: '', characterCountDelta: 0 }; - assert.equal(model.items.length, 5); + assert.strictEqual(model.items.length, 5); }); test('Have more relaxed suggest matching algorithm #15419', function () { @@ -315,12 +315,12 @@ suite('CompletionModel', function () { // query gets longer, narrow down the narrow-down'ed-set from before model.lineContext = { leadingLineContent: 'rlut', characterCountDelta: 4 }; - assert.equal(model.items.length, 3); + assert.strictEqual(model.items.length, 3); const [first, second, third] = model.items; - assert.equal(first.completion.label, 'result'); // best with `rult` - assert.equal(second.completion.label, 'replyToUser'); // best with `rltu` - assert.equal(third.completion.label, 'randomLolut'); // best with `rlut` + assert.strictEqual(first.completion.label, 'result'); // best with `rult` + assert.strictEqual(second.completion.label, 'replyToUser'); // best with `rltu` + assert.strictEqual(third.completion.label, 'randomLolut'); // best with `rlut` }); test('Emmet suggestion not appearing at the top of the list in jsx files, #39518', function () { @@ -336,10 +336,10 @@ suite('CompletionModel', function () { }, WordDistance.None, EditorOptions.suggest.defaultValue, EditorOptions.snippetSuggestions.defaultValue, undefined); model.lineContext = { leadingLineContent: 'form', characterCountDelta: 4 }; - assert.equal(model.items.length, 5); + assert.strictEqual(model.items.length, 5); const [first, second, third] = model.items; - assert.equal(first.completion.label, 'form'); // best with `form` - assert.equal(second.completion.label, 'form:get'); // best with `form` - assert.equal(third.completion.label, 'from'); // best with `from` + assert.strictEqual(first.completion.label, 'form'); // best with `form` + assert.strictEqual(second.completion.label, 'form:get'); // best with `form` + assert.strictEqual(third.completion.label, 'from'); // best with `from` }); }); diff --git a/src/vs/editor/contrib/suggest/test/suggest.test.ts b/src/vs/editor/contrib/suggest/test/suggest.test.ts index d281a891..84f86cec 100644 --- a/src/vs/editor/contrib/suggest/test/suggest.test.ts +++ b/src/vs/editor/contrib/suggest/test/suggest.test.ts @@ -53,32 +53,32 @@ suite('Suggest', function () { test('sort - snippet inline', async function () { const { items } = await provideSuggestionItems(model, new Position(1, 1), new CompletionOptions(SnippetSortOrder.Inline)); - assert.equal(items.length, 3); - assert.equal(items[0].completion.label, 'aaa'); - assert.equal(items[1].completion.label, 'fff'); - assert.equal(items[2].completion.label, 'zzz'); + assert.strictEqual(items.length, 3); + assert.strictEqual(items[0].completion.label, 'aaa'); + assert.strictEqual(items[1].completion.label, 'fff'); + assert.strictEqual(items[2].completion.label, 'zzz'); }); test('sort - snippet top', async function () { const { items } = await provideSuggestionItems(model, new Position(1, 1), new CompletionOptions(SnippetSortOrder.Top)); - assert.equal(items.length, 3); - assert.equal(items[0].completion.label, 'aaa'); - assert.equal(items[1].completion.label, 'zzz'); - assert.equal(items[2].completion.label, 'fff'); + assert.strictEqual(items.length, 3); + assert.strictEqual(items[0].completion.label, 'aaa'); + assert.strictEqual(items[1].completion.label, 'zzz'); + assert.strictEqual(items[2].completion.label, 'fff'); }); test('sort - snippet bottom', async function () { const { items } = await provideSuggestionItems(model, new Position(1, 1), new CompletionOptions(SnippetSortOrder.Bottom)); - assert.equal(items.length, 3); - assert.equal(items[0].completion.label, 'fff'); - assert.equal(items[1].completion.label, 'aaa'); - assert.equal(items[2].completion.label, 'zzz'); + assert.strictEqual(items.length, 3); + assert.strictEqual(items[0].completion.label, 'fff'); + assert.strictEqual(items[1].completion.label, 'aaa'); + assert.strictEqual(items[2].completion.label, 'zzz'); }); test('sort - snippet none', async function () { const { items } = await provideSuggestionItems(model, new Position(1, 1), new CompletionOptions(undefined, new Set().add(CompletionItemKind.Snippet))); - assert.equal(items.length, 1); - assert.equal(items[0].completion.label, 'fff'); + assert.strictEqual(items.length, 1); + assert.strictEqual(items[0].completion.label, 'fff'); }); test('only from', function () { @@ -102,7 +102,7 @@ suite('Suggest', function () { provideSuggestionItems(model, new Position(1, 1), new CompletionOptions(undefined, undefined, new Set().add(foo))).then(({ items }) => { registration.dispose(); - assert.equal(items.length, 1); + assert.strictEqual(items.length, 1); assert.ok(items[0].provider === foo); }); }); @@ -141,12 +141,12 @@ suite('Suggest', function () { const { items } = await provideSuggestionItems(model, new Position(0, 0), new CompletionOptions(undefined, undefined, new Set().add(foo))); registration.dispose(); - assert.equal(items.length, 2); + assert.strictEqual(items.length, 2); const [a, b] = items; - assert.equal(a.completion.label, 'one'); - assert.equal(a.isInvalid, false); - assert.equal(b.completion.label, 'two'); - assert.equal(b.isInvalid, true); + assert.strictEqual(a.completion.label, 'one'); + assert.strictEqual(a.isInvalid, false); + assert.strictEqual(b.completion.label, 'two'); + assert.strictEqual(b.isInvalid, true); }); }); diff --git a/src/vs/editor/contrib/suggest/test/suggestController.test.ts b/src/vs/editor/contrib/suggest/test/suggestController.test.ts index 491a7573..492e1cd3 100644 --- a/src/vs/editor/contrib/suggest/test/suggestController.test.ts +++ b/src/vs/editor/contrib/suggest/test/suggestController.test.ts @@ -45,19 +45,19 @@ suite('SuggestController', function () { [IStorageService, new InMemoryStorageService()], [IKeybindingService, new MockKeybindingService()], [IEditorWorkerService, new class extends mock() { - computeWordRanges() { + override computeWordRanges() { return Promise.resolve({}); } }], [ISuggestMemoryService, new class extends mock() { - memorize(): void { } - select(): number { return 0; } + override memorize(): void { } + override select(): number { return 0; } }], [IMenuService, new class extends mock() { - createMenu() { + override createMenu() { return new class extends mock() { - onDidChange = Event.None; - dispose() { } + override onDidChange = Event.None; + override dispose() { } }; } }] @@ -105,7 +105,7 @@ suite('SuggestController', function () { controller.acceptSelectedSuggestion(false, false); await p2; - assert.equal(editor.getValue(), ' let name = foo'); + assert.strictEqual(editor.getValue(), ' let name = foo'); }); test('use additionalTextEdits sync when possible', async function () { @@ -144,7 +144,7 @@ suite('SuggestController', function () { await p2; // insertText happens sync! - assert.equal(editor.getValue(), 'I came synchello\nhallohello'); + assert.strictEqual(editor.getValue(), 'I came synchello\nhallohello'); }); test('resolve additionalTextEdits async when needed', async function () { @@ -187,16 +187,16 @@ suite('SuggestController', function () { await p2; // insertText happens sync! - assert.equal(editor.getValue(), 'hello\nhallohello'); - assert.equal(resolveCallCount, 1); + assert.strictEqual(editor.getValue(), 'hello\nhallohello'); + assert.strictEqual(resolveCallCount, 1); // additional edits happened after a litte wait await timeout(20); - assert.equal(editor.getValue(), 'I came latehello\nhallohello'); + assert.strictEqual(editor.getValue(), 'I came latehello\nhallohello'); // single undo stop editor.getModel()?.undo(); - assert.equal(editor.getValue(), 'hello\nhallo'); + assert.strictEqual(editor.getValue(), 'hello\nhallo'); }); test('resolve additionalTextEdits async when needed (typing)', async function () { @@ -239,18 +239,18 @@ suite('SuggestController', function () { await p2; // insertText happens sync! - assert.equal(editor.getValue(), 'hello\nhallohello'); - assert.equal(resolveCallCount, 1); + assert.strictEqual(editor.getValue(), 'hello\nhallohello'); + assert.strictEqual(resolveCallCount, 1); // additional edits happened after a litte wait assert.ok(editor.getSelection()?.equalsSelection(new Selection(2, 11, 2, 11))); editor.trigger('test', 'type', { text: 'TYPING' }); - assert.equal(editor.getValue(), 'hello\nhallohelloTYPING'); + assert.strictEqual(editor.getValue(), 'hello\nhallohelloTYPING'); resolve(); await timeout(10); - assert.equal(editor.getValue(), 'I came latehello\nhallohelloTYPING'); + assert.strictEqual(editor.getValue(), 'I came latehello\nhallohelloTYPING'); assert.ok(editor.getSelection()?.equalsSelection(new Selection(2, 17, 2, 17))); }); @@ -295,12 +295,12 @@ suite('SuggestController', function () { await p2; // insertText happens sync! - assert.equal(editor.getValue(), 'hello'); - assert.equal(resolveCallCount, 1); + assert.strictEqual(editor.getValue(), 'hello'); + assert.strictEqual(resolveCallCount, 1); resolve(); await timeout(10); - assert.equal(editor.getValue(), 'hello'); + assert.strictEqual(editor.getValue(), 'hello'); }); // additional edit come late and are AFTER the position at which the user typed -> cancelled @@ -344,18 +344,18 @@ suite('SuggestController', function () { await p2; // insertText happens sync! - assert.equal(editor.getValue(), 'hello\nhallohello'); - assert.equal(resolveCallCount, 1); + assert.strictEqual(editor.getValue(), 'hello\nhallohello'); + assert.strictEqual(resolveCallCount, 1); // additional edits happened after a litte wait editor.setSelection(new Selection(1, 1, 1, 1)); editor.trigger('test', 'type', { text: 'TYPING' }); - assert.equal(editor.getValue(), 'TYPINGhello\nhallohello'); + assert.strictEqual(editor.getValue(), 'TYPINGhello\nhallohello'); resolve(); await timeout(10); - assert.equal(editor.getValue(), 'TYPINGhello\nhallohello'); + assert.strictEqual(editor.getValue(), 'TYPINGhello\nhallohello'); assert.ok(editor.getSelection()?.equalsSelection(new Selection(1, 7, 1, 7))); }); @@ -402,7 +402,7 @@ suite('SuggestController', function () { await p2; // insertText happens sync! - assert.equal(editor.getValue(), 'helloabc'); + assert.strictEqual(editor.getValue(), 'helloabc'); // next controller.acceptNextSuggestion(); @@ -413,6 +413,6 @@ suite('SuggestController', function () { await timeout(10); // next suggestion used - assert.equal(editor.getValue(), 'halloabc'); + assert.strictEqual(editor.getValue(), 'halloabc'); }); }); diff --git a/src/vs/editor/contrib/suggest/test/suggestMemory.test.ts b/src/vs/editor/contrib/suggest/test/suggestMemory.test.ts index 5554233c..c25cc412 100644 --- a/src/vs/editor/contrib/suggest/test/suggestMemory.test.ts +++ b/src/vs/editor/contrib/suggest/test/suggestMemory.test.ts @@ -50,7 +50,7 @@ suite('SuggestMemories', function () { item2.completion.preselect = true; item3.completion.preselect = true; - assert.equal(mem.select(buffer, pos, [item1, item2, item3, item4]), 1); + assert.strictEqual(mem.select(buffer, pos, [item1, item2, item3, item4]), 1); }); test('[No|Prefix|LRU]Memory honor selection boost', function () { @@ -64,17 +64,17 @@ suite('SuggestMemories', function () { let items = [item1, item2, item3, item4]; - assert.equal(new NoMemory().select(buffer, pos, items), 1); - assert.equal(new LRUMemory().select(buffer, pos, items), 1); - assert.equal(new PrefixMemory().select(buffer, pos, items), 1); + assert.strictEqual(new NoMemory().select(buffer, pos, items), 1); + assert.strictEqual(new LRUMemory().select(buffer, pos, items), 1); + assert.strictEqual(new PrefixMemory().select(buffer, pos, items), 1); }); test('NoMemory', () => { const mem = new NoMemory(); - assert.equal(mem.select(buffer, pos, items), 0); - assert.equal(mem.select(buffer, pos, []), 0); + assert.strictEqual(mem.select(buffer, pos, items), 0); + assert.strictEqual(mem.select(buffer, pos, []), 0); mem.memorize(buffer, pos, items[0]); mem.memorize(buffer, pos, null!); @@ -87,18 +87,18 @@ suite('SuggestMemories', function () { const mem = new LRUMemory(); mem.memorize(buffer, pos, items[1]); - assert.equal(mem.select(buffer, pos, items), 1); - assert.equal(mem.select(buffer, { lineNumber: 1, column: 3 }, items), 0); + assert.strictEqual(mem.select(buffer, pos, items), 1); + assert.strictEqual(mem.select(buffer, { lineNumber: 1, column: 3 }, items), 0); mem.memorize(buffer, pos, items[0]); - assert.equal(mem.select(buffer, pos, items), 0); + assert.strictEqual(mem.select(buffer, pos, items), 0); - assert.equal(mem.select(buffer, pos, [ + assert.strictEqual(mem.select(buffer, pos, [ createSuggestItem('new', 0), createSuggestItem('bar', 0) ]), 1); - assert.equal(mem.select(buffer, pos, [ + assert.strictEqual(mem.select(buffer, pos, [ createSuggestItem('new1', 0), createSuggestItem('new2', 0) ]), 0); @@ -114,16 +114,16 @@ suite('SuggestMemories', function () { buffer.setValue(' foo.'); mem.memorize(buffer, { lineNumber: 1, column: 1 }, item2); - assert.equal(mem.select(buffer, { lineNumber: 1, column: 2 }, items), 0); // leading whitespace -> ignore recent items + assert.strictEqual(mem.select(buffer, { lineNumber: 1, column: 2 }, items), 0); // leading whitespace -> ignore recent items mem.memorize(buffer, { lineNumber: 1, column: 9 }, item2); - assert.equal(mem.select(buffer, { lineNumber: 1, column: 9 }, items), 1); // foo. + assert.strictEqual(mem.select(buffer, { lineNumber: 1, column: 9 }, items), 1); // foo. buffer.setValue(' foo.g'); - assert.equal(mem.select(buffer, { lineNumber: 1, column: 10 }, items), 1); // foo.g, 'gamma' and 'game' have the same score + assert.strictEqual(mem.select(buffer, { lineNumber: 1, column: 10 }, items), 1); // foo.g, 'gamma' and 'game' have the same score item1.score = [10, 0, 0]; - assert.equal(mem.select(buffer, { lineNumber: 1, column: 10 }, items), 0); // foo.g, 'gamma' has higher score + assert.strictEqual(mem.select(buffer, { lineNumber: 1, column: 10 }, items), 0); // foo.g, 'gamma' has higher score }); @@ -134,10 +134,10 @@ suite('SuggestMemories', function () { const mem = new LRUMemory(); mem.memorize(buffer, pos, items[1]); - assert.equal(mem.select(buffer, pos, items), 1); + assert.strictEqual(mem.select(buffer, pos, items), 1); - assert.equal(mem.select(buffer, { lineNumber: 3, column: 5 }, items), 0); // foo: |, - assert.equal(mem.select(buffer, { lineNumber: 3, column: 6 }, items), 1); // foo: ,| + assert.strictEqual(mem.select(buffer, { lineNumber: 3, column: 5 }, items), 0); // foo: |, + assert.strictEqual(mem.select(buffer, { lineNumber: 3, column: 6 }, items), 1); // foo: ,| }); test('PrefixMemory', () => { @@ -154,11 +154,11 @@ suite('SuggestMemories', function () { mem.memorize(buffer, { lineNumber: 1, column: 3 }, item0); // co -> console mem.memorize(buffer, { lineNumber: 1, column: 4 }, item2); // con -> constructor - assert.equal(mem.select(buffer, { lineNumber: 1, column: 1 }, items), 0); - assert.equal(mem.select(buffer, { lineNumber: 1, column: 2 }, items), 1); - assert.equal(mem.select(buffer, { lineNumber: 1, column: 3 }, items), 0); - assert.equal(mem.select(buffer, { lineNumber: 1, column: 4 }, items), 2); - assert.equal(mem.select(buffer, { lineNumber: 1, column: 7 }, items), 2); // find substr + assert.strictEqual(mem.select(buffer, { lineNumber: 1, column: 1 }, items), 0); + assert.strictEqual(mem.select(buffer, { lineNumber: 1, column: 2 }, items), 1); + assert.strictEqual(mem.select(buffer, { lineNumber: 1, column: 3 }, items), 0); + assert.strictEqual(mem.select(buffer, { lineNumber: 1, column: 4 }, items), 2); + assert.strictEqual(mem.select(buffer, { lineNumber: 1, column: 7 }, items), 2); // find substr }); }); diff --git a/src/vs/editor/contrib/suggest/test/suggestModel.test.ts b/src/vs/editor/contrib/suggest/test/suggestModel.test.ts index ce99f975..a17fa488 100644 --- a/src/vs/editor/contrib/suggest/test/suggestModel.test.ts +++ b/src/vs/editor/contrib/suggest/test/suggestModel.test.ts @@ -104,7 +104,7 @@ suite('SuggestModel - Context', function () { const pos = model.getPositionAt(offset); const editor = createMockEditor(model); editor.setPosition(pos); - assert.equal(LineContext.shouldAutoTrigger(editor), expected, message); + assert.strictEqual(LineContext.shouldAutoTrigger(editor), expected, message); editor.dispose(); }; @@ -194,12 +194,12 @@ suite('SuggestModel - TriggerAndCancelOracle', function () { const oracle = new SuggestModel( editor, new class extends mock() { - computeWordRanges() { + override computeWordRanges() { return Promise.resolve({}); } }, new class extends mock() { - readText() { + override readText() { return Promise.resolve('CLIPPY'); } }, @@ -243,25 +243,25 @@ suite('SuggestModel - TriggerAndCancelOracle', function () { assertEvent(model.onDidTrigger, function () { model.trigger({ auto: true, shy: false }); }, function (event) { - assert.equal(event.auto, true); + assert.strictEqual(event.auto, true); return assertEvent(model.onDidCancel, function () { model.cancel(); }, function (event) { - assert.equal(event.retrigger, false); + assert.strictEqual(event.retrigger, false); }); }), assertEvent(model.onDidTrigger, function () { model.trigger({ auto: true, shy: false }); }, function (event) { - assert.equal(event.auto, true); + assert.strictEqual(event.auto, true); }), assertEvent(model.onDidTrigger, function () { model.trigger({ auto: false, shy: false }); }, function (event) { - assert.equal(event.auto, false); + assert.strictEqual(event.auto, false); }) ]); }); @@ -277,14 +277,14 @@ suite('SuggestModel - TriggerAndCancelOracle', function () { assertEvent(model.onDidCancel, function () { model.trigger({ auto: true, shy: false }); }, function (event) { - assert.equal(event.retrigger, false); + assert.strictEqual(event.retrigger, false); }), assertEvent(model.onDidSuggest, function () { model.trigger({ auto: false, shy: false }); }, function (event) { - assert.equal(event.auto, false); - assert.equal(event.isFrozen, false); - assert.equal(event.completionModel.items.length, 0); + assert.strictEqual(event.auto, false); + assert.strictEqual(event.isFrozen, false); + assert.strictEqual(event.completionModel.items.length, 0); }) ]); }); @@ -300,11 +300,11 @@ suite('SuggestModel - TriggerAndCancelOracle', function () { editor.trigger('keyboard', Handler.Type, { text: 'd' }); }, event => { - assert.equal(event.auto, true); - assert.equal(event.completionModel.items.length, 1); + assert.strictEqual(event.auto, true); + assert.strictEqual(event.completionModel.items.length, 1); const [first] = event.completionModel.items; - assert.equal(first.provider, alwaysSomethingSupport); + assert.strictEqual(first.provider, alwaysSomethingSupport); }); }); }); @@ -339,20 +339,20 @@ suite('SuggestModel - TriggerAndCancelOracle', function () { editor.trigger('keyboard', Handler.Type, { text: 'My' }); }, event => { - assert.equal(event.auto, true); - assert.equal(event.completionModel.items.length, 1); + assert.strictEqual(event.auto, true); + assert.strictEqual(event.completionModel.items.length, 1); const [first] = event.completionModel.items; - assert.equal(first.completion.label, 'My Table'); + assert.strictEqual(first.completion.label, 'My Table'); return assertEvent(model.onDidSuggest, () => { editor.setPosition({ lineNumber: 1, column: 3 }); editor.trigger('keyboard', Handler.Type, { text: ' ' }); }, event => { - assert.equal(event.auto, true); - assert.equal(event.completionModel.items.length, 1); + assert.strictEqual(event.auto, true); + assert.strictEqual(event.completionModel.items.length, 1); const [first] = event.completionModel.items; - assert.equal(first.completion.label, 'My Table'); + assert.strictEqual(first.completion.label, 'My Table'); }); }); }); @@ -402,20 +402,20 @@ suite('SuggestModel - TriggerAndCancelOracle', function () { editor.trigger('keyboard', Handler.Type, { text: 'foo' }); }, event => { - assert.equal(event.auto, true); - assert.equal(event.completionModel.items.length, 1); + assert.strictEqual(event.auto, true); + assert.strictEqual(event.completionModel.items.length, 1); const [first] = event.completionModel.items; - assert.equal(first.completion.label, 'foo.bar'); + assert.strictEqual(first.completion.label, 'foo.bar'); return assertEvent(model.onDidSuggest, () => { editor.trigger('keyboard', Handler.Type, { text: '.' }); }, event => { - assert.equal(event.auto, true); - assert.equal(event.completionModel.items.length, 2); + assert.strictEqual(event.auto, true); + assert.strictEqual(event.completionModel.items.length, 2); const [first, second] = event.completionModel.items; - assert.equal(first.completion.label, 'foo.bar'); - assert.equal(second.completion.label, 'boom'); + assert.strictEqual(first.completion.label, 'foo.bar'); + assert.strictEqual(second.completion.label, 'boom'); }); }); }); @@ -433,14 +433,14 @@ suite('SuggestModel - TriggerAndCancelOracle', function () { return assertEvent(model.onDidSuggest, () => { model.trigger({ auto: false, shy: false }); }, event => { - assert.equal(event.auto, false); - assert.equal(event.isFrozen, false); - assert.equal(event.completionModel.items.length, 1); + assert.strictEqual(event.auto, false); + assert.strictEqual(event.isFrozen, false); + assert.strictEqual(event.completionModel.items.length, 1); return assertEvent(model.onDidCancel, () => { editor.trigger('keyboard', Handler.Type, { text: '+' }); }, event => { - assert.equal(event.retrigger, false); + assert.strictEqual(event.retrigger, false); }); }); }); @@ -458,14 +458,14 @@ suite('SuggestModel - TriggerAndCancelOracle', function () { return assertEvent(model.onDidSuggest, () => { model.trigger({ auto: false, shy: false }); }, event => { - assert.equal(event.auto, false); - assert.equal(event.isFrozen, false); - assert.equal(event.completionModel.items.length, 1); + assert.strictEqual(event.auto, false); + assert.strictEqual(event.isFrozen, false); + assert.strictEqual(event.completionModel.items.length, 1); return assertEvent(model.onDidCancel, () => { editor.trigger('keyboard', Handler.Type, { text: ' ' }); }, event => { - assert.equal(event.retrigger, false); + assert.strictEqual(event.retrigger, false); }); }); }); @@ -495,14 +495,14 @@ suite('SuggestModel - TriggerAndCancelOracle', function () { return assertEvent(model.onDidSuggest, () => { model.trigger({ auto: false, shy: false }); }, event => { - assert.equal(event.auto, false); - assert.equal(event.completionModel.incomplete.size, 1); - assert.equal(event.completionModel.items.length, 1); + assert.strictEqual(event.auto, false); + assert.strictEqual(event.completionModel.incomplete.size, 1); + assert.strictEqual(event.completionModel.items.length, 1); return assertEvent(model.onDidCancel, () => { editor.trigger('keyboard', Handler.Type, { text: ';' }); }, event => { - assert.equal(event.retrigger, false); + assert.strictEqual(event.retrigger, false); }); }); }); @@ -532,9 +532,9 @@ suite('SuggestModel - TriggerAndCancelOracle', function () { return assertEvent(model.onDidSuggest, () => { model.trigger({ auto: false, shy: false }); }, event => { - assert.equal(event.auto, false); - assert.equal(event.completionModel.incomplete.size, 1); - assert.equal(event.completionModel.items.length, 1); + assert.strictEqual(event.auto, false); + assert.strictEqual(event.completionModel.incomplete.size, 1); + assert.strictEqual(event.completionModel.items.length, 1); return assertEvent(model.onDidSuggest, () => { // while we cancel incrementally enriching the set of @@ -542,9 +542,9 @@ suite('SuggestModel - TriggerAndCancelOracle', function () { // until now editor.trigger('keyboard', Handler.Type, { text: ';' }); }, event => { - assert.equal(event.auto, false); - assert.equal(event.completionModel.incomplete.size, 1); - assert.equal(event.completionModel.items.length, 1); + assert.strictEqual(event.auto, false); + assert.strictEqual(event.completionModel.incomplete.size, 1); + assert.strictEqual(event.completionModel.items.length, 1); }); }); @@ -556,7 +556,7 @@ suite('SuggestModel - TriggerAndCancelOracle', function () { disposables.push(CompletionProviderRegistry.register({ scheme: 'test' }, { triggerCharacters: ['.'], provideCompletionItems(doc, pos, context): CompletionList { - assert.equal(context.triggerKind, CompletionTriggerKind.TriggerCharacter); + assert.strictEqual(context.triggerKind, CompletionTriggerKind.TriggerCharacter); triggerCharacter = context.triggerCharacter!; return { incomplete: false, @@ -580,7 +580,7 @@ suite('SuggestModel - TriggerAndCancelOracle', function () { editor.setPosition({ lineNumber: 1, column: 1 }); editor.trigger('keyboard', Handler.Type, { text: 'foo.' }); }, event => { - assert.equal(triggerCharacter, '.'); + assert.strictEqual(triggerCharacter, '.'); }); }); }); @@ -612,16 +612,16 @@ suite('SuggestModel - TriggerAndCancelOracle', function () { editor.setPosition({ lineNumber: 1, column: 1 }); editor.trigger('keyboard', Handler.Type, { text: 'a' }); }, event => { - assert.equal(event.completionModel.items.length, 1); - assert.equal(event.completionModel.items[0].completion.label, 'abc'); + assert.strictEqual(event.completionModel.items.length, 1); + assert.strictEqual(event.completionModel.items[0].completion.label, 'abc'); return assertEvent(model.onDidSuggest, () => { editor.executeEdits('test', [EditOperation.replace(new Range(1, 1, 1, 2), 'ä')]); }, event => { // suggest model changed to äbc - assert.equal(event.completionModel.items.length, 1); - assert.equal(event.completionModel.items[0].completion.label, 'äbc'); + assert.strictEqual(event.completionModel.items.length, 1); + assert.strictEqual(event.completionModel.items[0].completion.label, 'äbc'); }); }); @@ -637,22 +637,22 @@ suite('SuggestModel - TriggerAndCancelOracle', function () { editor.trigger('keyboard', Handler.Type, { text: 'd' }); }, event => { - assert.equal(event.auto, true); - assert.equal(event.completionModel.items.length, 1); + assert.strictEqual(event.auto, true); + assert.strictEqual(event.completionModel.items.length, 1); const [first] = event.completionModel.items; - assert.equal(first.provider, alwaysSomethingSupport); + assert.strictEqual(first.provider, alwaysSomethingSupport); }); await assertEvent(model.onDidSuggest, () => { CoreEditingCommands.DeleteLeft.runEditorCommand(null, editor, null); }, event => { - assert.equal(event.auto, true); - assert.equal(event.completionModel.items.length, 1); + assert.strictEqual(event.auto, true); + assert.strictEqual(event.completionModel.items.length, 1); const [first] = event.completionModel.items; - assert.equal(first.provider, alwaysSomethingSupport); + assert.strictEqual(first.provider, alwaysSomethingSupport); }); }); }); @@ -680,7 +680,7 @@ suite('SuggestModel - TriggerAndCancelOracle', function () { return withOracle(async (sugget, editor) => { class TestCtrl extends SuggestController { - _insertSuggestion(item: ISelectedSuggestion, flags: number = 0) { + override _insertSuggestion(item: ISelectedSuggestion, flags: number = 0) { super._insertSuggestion(item, flags); } } @@ -692,14 +692,14 @@ suite('SuggestModel - TriggerAndCancelOracle', function () { sugget.trigger({ auto: false, shy: false }); }, event => { - assert.equal(event.completionModel.items.length, 1); + assert.strictEqual(event.completionModel.items.length, 1); const [first] = event.completionModel.items; - assert.equal(first.completion.label, 'bar'); + assert.strictEqual(first.completion.label, 'bar'); ctrl._insertSuggestion({ item: first, index: 0, model: event.completionModel }); }); - assert.equal( + assert.strictEqual( model.getValue(), 'bar; import { foo, bar } from "./b"' ); @@ -717,11 +717,11 @@ suite('SuggestModel - TriggerAndCancelOracle', function () { editor.trigger('keyboard', Handler.Type, { text: 'e' }); }, event => { - assert.equal(event.auto, true); - assert.equal(event.completionModel.items.length, 1); + assert.strictEqual(event.auto, true); + assert.strictEqual(event.completionModel.items.length, 1); const [first] = event.completionModel.items; - assert.equal(first.provider, alwaysSomethingSupport); + assert.strictEqual(first.provider, alwaysSomethingSupport); }); }); }); @@ -774,22 +774,22 @@ suite('SuggestModel - TriggerAndCancelOracle', function () { editor.trigger('keyboard', Handler.Type, { text: 'c' }); }, event => { - assert.equal(event.auto, true); - assert.equal(event.completionModel.items.length, 2); - assert.equal(disposeA, 0); - assert.equal(disposeB, 0); + assert.strictEqual(event.auto, true); + assert.strictEqual(event.completionModel.items.length, 2); + assert.strictEqual(disposeA, 0); + assert.strictEqual(disposeB, 0); }); await assertEvent(model.onDidSuggest, () => { editor.trigger('keyboard', Handler.Type, { text: 'o' }); }, event => { - assert.equal(event.auto, true); - assert.equal(event.completionModel.items.length, 2); + assert.strictEqual(event.auto, true); + assert.strictEqual(event.completionModel.items.length, 2); // clean up model.clear(); - assert.equal(disposeA, 2); // provide got called two times! - assert.equal(disposeB, 1); + assert.strictEqual(disposeA, 2); // provide got called two times! + assert.strictEqual(disposeB, 1); }); }); @@ -841,9 +841,9 @@ suite('SuggestModel - TriggerAndCancelOracle', function () { editor.trigger('keyboard', Handler.Type, { text: 'Z' }); }, event => { - assert.equal(event.auto, true); - assert.equal(event.completionModel.items.length, 1); - assert.equal(event.completionModel.items[0].textLabel, 'Z aaa'); + assert.strictEqual(event.auto, true); + assert.strictEqual(event.completionModel.items.length, 1); + assert.strictEqual(event.completionModel.items[0].textLabel, 'Z aaa'); }); await assertEvent(model.onDidSuggest, () => { @@ -851,13 +851,13 @@ suite('SuggestModel - TriggerAndCancelOracle', function () { // item should be: Z aaa, aaa editor.trigger('keyboard', Handler.Type, { text: ' a' }); }, event => { - assert.equal(event.auto, true); - assert.equal(event.completionModel.items.length, 2); - assert.equal(event.completionModel.items[0].textLabel, 'Z aaa'); - assert.equal(event.completionModel.items[1].textLabel, 'aaa'); + assert.strictEqual(event.auto, true); + assert.strictEqual(event.completionModel.items.length, 2); + assert.strictEqual(event.completionModel.items[0].textLabel, 'Z aaa'); + assert.strictEqual(event.completionModel.items[1].textLabel, 'aaa'); - assert.equal(countA, 1); // should we keep the suggestions from the "active" provider?, Yes! See: #106573 - assert.equal(countB, 2); + assert.strictEqual(countA, 1); // should we keep the suggestions from the "active" provider?, Yes! See: #106573 + assert.strictEqual(countB, 2); }); }); }); diff --git a/src/vs/editor/contrib/suggest/test/wordDistance.test.ts b/src/vs/editor/contrib/suggest/test/wordDistance.test.ts index 05ccef17..c7f7cbf7 100644 --- a/src/vs/editor/contrib/suggest/test/wordDistance.test.ts +++ b/src/vs/editor/contrib/suggest/test/wordDistance.test.ts @@ -54,8 +54,8 @@ suite('suggest, word distance', function () { editor.setPosition({ lineNumber: 2, column: 2 }); let modelService = new class extends mock() { - onModelRemoved = Event.None; - getModel(uri: URI) { + override onModelRemoved = Event.None; + override getModel(uri: URI) { return uri.toString() === model.uri.toString() ? model : null; } }; @@ -74,7 +74,7 @@ suite('suggest, word distance', function () { }); model.onDidChangeContent(e => this._worker.acceptModelChanged(model.uri.toString(), e)); } - computeWordRanges(resource: URI, range: IRange): Promise<{ [word: string]: IRange[] } | null> { + override computeWordRanges(resource: URI, range: IRange): Promise<{ [word: string]: IRange[] } | null> { return this._worker.computeWordRanges(resource.toString(), range, DEFAULT_WORD_REGEXP.source, DEFAULT_WORD_REGEXP.flags); } }; diff --git a/src/vs/editor/contrib/wordHighlighter/wordHighlighter.ts b/src/vs/editor/contrib/wordHighlighter/wordHighlighter.ts index 35e8b64a..8e2d8a09 100644 --- a/src/vs/editor/contrib/wordHighlighter/wordHighlighter.ts +++ b/src/vs/editor/contrib/wordHighlighter/wordHighlighter.ts @@ -140,7 +140,7 @@ class TextualOccurenceAtPositionRequest extends OccurenceAtPositionRequest { }); } - public isValid(model: ITextModel, selection: Selection, decorationIds: string[]): boolean { + public override isValid(model: ITextModel, selection: Selection, decorationIds: string[]): boolean { const currentSelectionIsEmpty = selection.isEmpty(); if (this._selectionIsEmpty !== currentSelectionIsEmpty) { return false; @@ -528,7 +528,7 @@ class WordHighlighterContribution extends Disposable implements IEditorContribut } } - public dispose(): void { + public override dispose(): void { if (this.wordHighlighter) { this.wordHighlighter.dispose(); this.wordHighlighter = null; diff --git a/src/vs/editor/contrib/wordOperations/wordOperations.ts b/src/vs/editor/contrib/wordOperations/wordOperations.ts index d617c47b..4dd26431 100644 --- a/src/vs/editor/contrib/wordOperations/wordOperations.ts +++ b/src/vs/editor/contrib/wordOperations/wordOperations.ts @@ -187,7 +187,7 @@ export class CursorWordAccessibilityLeft extends WordLeftCommand { }); } - protected _move(_: WordCharacterClassifier, model: ITextModel, position: Position, wordNavigationType: WordNavigationType): Position { + protected override _move(_: WordCharacterClassifier, model: ITextModel, position: Position, wordNavigationType: WordNavigationType): Position { return super._move(getMapForWordSeparators(EditorOptions.wordSeparators.defaultValue), model, position, wordNavigationType); } } @@ -202,7 +202,7 @@ export class CursorWordAccessibilityLeftSelect extends WordLeftCommand { }); } - protected _move(_: WordCharacterClassifier, model: ITextModel, position: Position, wordNavigationType: WordNavigationType): Position { + protected override _move(_: WordCharacterClassifier, model: ITextModel, position: Position, wordNavigationType: WordNavigationType): Position { return super._move(getMapForWordSeparators(EditorOptions.wordSeparators.defaultValue), model, position, wordNavigationType); } } @@ -295,7 +295,7 @@ export class CursorWordAccessibilityRight extends WordRightCommand { }); } - protected _move(_: WordCharacterClassifier, model: ITextModel, position: Position, wordNavigationType: WordNavigationType): Position { + protected override _move(_: WordCharacterClassifier, model: ITextModel, position: Position, wordNavigationType: WordNavigationType): Position { return super._move(getMapForWordSeparators(EditorOptions.wordSeparators.defaultValue), model, position, wordNavigationType); } } @@ -310,7 +310,7 @@ export class CursorWordAccessibilityRightSelect extends WordRightCommand { }); } - protected _move(_: WordCharacterClassifier, model: ITextModel, position: Position, wordNavigationType: WordNavigationType): Position { + protected override _move(_: WordCharacterClassifier, model: ITextModel, position: Position, wordNavigationType: WordNavigationType): Position { return super._move(getMapForWordSeparators(EditorOptions.wordSeparators.defaultValue), model, position, wordNavigationType); } } diff --git a/src/vs/editor/standalone/browser/accessibilityHelp/accessibilityHelp.ts b/src/vs/editor/standalone/browser/accessibilityHelp/accessibilityHelp.ts index 9da4eb8d..cc247cbb 100644 --- a/src/vs/editor/standalone/browser/accessibilityHelp/accessibilityHelp.ts +++ b/src/vs/editor/standalone/browser/accessibilityHelp/accessibilityHelp.ts @@ -177,7 +177,7 @@ class AccessibilityHelpWidget extends Widget implements IOverlayWidget { this._editor.addOverlayWidget(this); } - public dispose(): void { + public override dispose(): void { this._editor.removeOverlayWidget(this); super.dispose(); } diff --git a/src/vs/editor/standalone/browser/iPadShowKeyboard/iPadShowKeyboard.ts b/src/vs/editor/standalone/browser/iPadShowKeyboard/iPadShowKeyboard.ts index e67d6364..39c480d0 100644 --- a/src/vs/editor/standalone/browser/iPadShowKeyboard/iPadShowKeyboard.ts +++ b/src/vs/editor/standalone/browser/iPadShowKeyboard/iPadShowKeyboard.ts @@ -44,7 +44,7 @@ export class IPadShowKeyboard extends Disposable implements IEditorContribution } } - public dispose(): void { + public override dispose(): void { super.dispose(); if (this.widget) { this.widget.dispose(); @@ -77,7 +77,7 @@ class ShowKeyboardWidget extends Disposable implements IOverlayWidget { this.editor.addOverlayWidget(this); } - public dispose(): void { + public override dispose(): void { this.editor.removeOverlayWidget(this); super.dispose(); } diff --git a/src/vs/editor/standalone/browser/inspectTokens/inspectTokens.ts b/src/vs/editor/standalone/browser/inspectTokens/inspectTokens.ts index 88b54c86..df5c8077 100644 --- a/src/vs/editor/standalone/browser/inspectTokens/inspectTokens.ts +++ b/src/vs/editor/standalone/browser/inspectTokens/inspectTokens.ts @@ -53,7 +53,7 @@ class InspectTokensController extends Disposable implements IEditorContribution this._register(this._editor.onKeyUp((e) => e.keyCode === KeyCode.Escape && this.stop())); } - public dispose(): void { + public override dispose(): void { this.stop(); super.dispose(); } @@ -171,7 +171,7 @@ class InspectTokensWidget extends Disposable implements IContentWidget { this._editor.addContentWidget(this); } - public dispose(): void { + public override dispose(): void { this._editor.removeContentWidget(this); super.dispose(); } diff --git a/src/vs/editor/standalone/browser/quickInput/standaloneQuickInput.css b/src/vs/editor/standalone/browser/quickInput/standaloneQuickInput.css index c95fe332..f2d4dbce 100644 --- a/src/vs/editor/standalone/browser/quickInput/standaloneQuickInput.css +++ b/src/vs/editor/standalone/browser/quickInput/standaloneQuickInput.css @@ -21,3 +21,26 @@ .hc-black .quick-input-widget .monaco-highlighted-label .highlight { color: #F38518; } + +.monaco-keybinding > .monaco-keybinding-key { + background-color: rgba(221, 221, 221, 0.4); + border: solid 1px rgba(204, 204, 204, 0.4); + border-bottom-color: rgba(187, 187, 187, 0.4); + box-shadow: inset 0 -1px 0 rgba(187, 187, 187, 0.4); + color: #555; +} + +.hc-black .monaco-keybinding > .monaco-keybinding-key { + background-color: transparent; + border: solid 1px rgb(111, 195, 223); + box-shadow: none; + color: #fff; +} + +.vs-dark .monaco-keybinding > .monaco-keybinding-key { + background-color: rgba(128, 128, 128, 0.17); + border: solid 1px rgba(51, 51, 51, 0.6); + border-bottom-color: rgba(68, 68, 68, 0.6); + box-shadow: inset 0 -1px 0 rgba(68, 68, 68, 0.6); + color: #ccc; +} diff --git a/src/vs/editor/standalone/browser/quickInput/standaloneQuickInputServiceImpl.ts b/src/vs/editor/standalone/browser/quickInput/standaloneQuickInputServiceImpl.ts index ae133a70..4c4bd06b 100644 --- a/src/vs/editor/standalone/browser/quickInput/standaloneQuickInputServiceImpl.ts +++ b/src/vs/editor/standalone/browser/quickInput/standaloneQuickInputServiceImpl.ts @@ -45,7 +45,7 @@ export class EditorScopedQuickInputServiceImpl extends QuickInputService { }; } - protected createController(): QuickInputController { + protected override createController(): QuickInputController { return super.createController(this.host); } } diff --git a/src/vs/editor/standalone/browser/simpleServices.ts b/src/vs/editor/standalone/browser/simpleServices.ts index 3a22ca52..6271b614 100644 --- a/src/vs/editor/standalone/browser/simpleServices.ts +++ b/src/vs/editor/standalone/browser/simpleServices.ts @@ -39,7 +39,7 @@ import { ILabelService, ResourceLabelFormatter, IFormatterChangeEvent } from 'vs import { INotification, INotificationHandle, INotificationService, IPromptChoice, IPromptOptions, NoOpNotification, IStatusMessageOptions, NotificationsFilter } from 'vs/platform/notification/common/notification'; import { IProgressRunner, IEditorProgressService } from 'vs/platform/progress/common/progress'; import { ITelemetryInfo, ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IWorkspace, IWorkspaceContextService, IWorkspaceFolder, IWorkspaceFoldersChangeEvent, WorkbenchState, WorkspaceFolder } from 'vs/platform/workspace/common/workspace'; +import { IWorkspace, IWorkspaceContextService, IWorkspaceFolder, IWorkspaceFoldersChangeEvent, IWorkspaceFoldersWillChangeEvent, WorkbenchState, WorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { SimpleServicesNLS } from 'vs/editor/common/standaloneStrings'; @@ -51,19 +51,19 @@ import { ILogService } from 'vs/platform/log/common/log'; export class SimpleModel implements IResolvedTextEditorModel { private readonly model: ITextModel; - private readonly _onDispose: Emitter; + private readonly _onWillDispose: Emitter; constructor(model: ITextModel) { this.model = model; - this._onDispose = new Emitter(); + this._onWillDispose = new Emitter(); } - public get onDispose(): Event { - return this._onDispose.event; + public get onWillDispose(): Event { + return this._onWillDispose.event; } - public load(): Promise { - return Promise.resolve(this); + public resolve(): Promise { + return Promise.resolve(); } public get textEditorModel(): ITextModel { @@ -82,7 +82,7 @@ export class SimpleModel implements IResolvedTextEditorModel { public dispose(): void { this.disposed = true; - this._onDispose.fire(); + this._onWillDispose.fire(); } public isDisposed(): boolean { @@ -613,6 +613,9 @@ export class SimpleWorkspaceContextService implements IWorkspaceContextService { private readonly _onDidChangeWorkspaceName = new Emitter(); public readonly onDidChangeWorkspaceName: Event = this._onDidChangeWorkspaceName.event; + private readonly _onWillChangeWorkspaceFolders = new Emitter(); + public readonly onWillChangeWorkspaceFolders: Event = this._onWillChangeWorkspaceFolders.event; + private readonly _onDidChangeWorkspaceFolders = new Emitter(); public readonly onDidChangeWorkspaceFolders: Event = this._onDidChangeWorkspaceFolders.event; diff --git a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts index f315ffb0..89dff41d 100644 --- a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts @@ -427,11 +427,11 @@ export class StandaloneEditor extends StandaloneCodeEditor implements IStandalon } } - public dispose(): void { + public override dispose(): void { super.dispose(); } - public updateOptions(newOptions: Readonly): void { + public override updateOptions(newOptions: Readonly): void { updateConfigurationService(this._configurationService, newOptions, false); if (typeof newOptions.theme === 'string') { this._standaloneThemeService.setTheme(newOptions.theme); @@ -442,14 +442,14 @@ export class StandaloneEditor extends StandaloneCodeEditor implements IStandalon super.updateOptions(newOptions); } - _attachModel(model: ITextModel | null): void { + override _attachModel(model: ITextModel | null): void { super._attachModel(model); if (this._modelData) { this._contextViewService.setContainer(this._modelData.view.domNode.domNode); } } - _postDetachModelCleanup(detachedModel: ITextModel): void { + override _postDetachModelCleanup(detachedModel: ITextModel): void { super._postDetachModelCleanup(detachedModel); if (detachedModel && this._ownsModel) { detachedModel.dispose(); @@ -503,11 +503,11 @@ export class StandaloneDiffEditor extends DiffEditorWidget implements IStandalon this._contextViewService.setContainer(this._containerDomElement); } - public dispose(): void { + public override dispose(): void { super.dispose(); } - public updateOptions(newOptions: Readonly): void { + public override updateOptions(newOptions: Readonly): void { updateConfigurationService(this._configurationService, newOptions, true); if (typeof newOptions.theme === 'string') { this._standaloneThemeService.setTheme(newOptions.theme); @@ -518,15 +518,15 @@ export class StandaloneDiffEditor extends DiffEditorWidget implements IStandalon super.updateOptions(newOptions); } - protected _createInnerEditor(instantiationService: IInstantiationService, container: HTMLElement, options: Readonly): CodeEditorWidget { + protected override _createInnerEditor(instantiationService: IInstantiationService, container: HTMLElement, options: Readonly): CodeEditorWidget { return instantiationService.createInstance(StandaloneCodeEditor, container, options); } - public getOriginalEditor(): IStandaloneCodeEditor { + public override getOriginalEditor(): IStandaloneCodeEditor { return super.getOriginalEditor(); } - public getModifiedEditor(): IStandaloneCodeEditor { + public override getModifiedEditor(): IStandaloneCodeEditor { return super.getModifiedEditor(); } diff --git a/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts b/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts index c4755792..bacfb056 100644 --- a/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts +++ b/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts @@ -25,7 +25,7 @@ suite('TokenizationSupport2Adapter', () => { constructor() { super(null!, null!); } - public match(languageId: LanguageId, token: string): number { + public override match(languageId: LanguageId, token: string): number { return ( ((this.counter++) << MetadataConsts.FOREGROUND_OFFSET) | (languageId << MetadataConsts.LANGUAGEID_OFFSET) diff --git a/src/vs/editor/test/browser/controller/cursor.test.ts b/src/vs/editor/test/browser/controller/cursor.test.ts index c9f56987..c5e45cc8 100644 --- a/src/vs/editor/test/browser/controller/cursor.test.ts +++ b/src/vs/editor/test/browser/controller/cursor.test.ts @@ -4299,6 +4299,29 @@ suite('Editor Controller - Indentation Rules', () => { assert.strictEqual(model.getValue(), ' let a,\n\t b,\n\t c;'); }); }); + + test('issue #122714: tabSize=1 prevent typing a string matching decreaseIndentPattern in an empty file', () => { + let latexMode = new IndentRulesMode({ + increaseIndentPattern: new RegExp('\\\\begin{(?!document)([^}]*)}(?!.*\\\\end{\\1})'), + decreaseIndentPattern: new RegExp('^\\s*\\\\end{(?!document)') + }); + let model = createTextModel( + '\\end', + { tabSize: 1 }, + latexMode.getLanguageIdentifier() + ); + + withTestCodeEditor(null, { model: model, autoIndent: 'full' }, (editor, viewModel) => { + moveTo(editor, viewModel, 1, 5, false); + assertCursor(viewModel, new Selection(1, 5, 1, 5)); + + viewModel.type('{', 'keyboard'); + assert.strictEqual(model.getLineContent(1), '\\end{}'); + }); + + latexMode.dispose(); + model.dispose(); + }); }); interface ICursorOpts { diff --git a/src/vs/editor/test/browser/services/decorationRenderOptions.test.ts b/src/vs/editor/test/browser/services/decorationRenderOptions.test.ts index 8f0e568c..55be3148 100644 --- a/src/vs/editor/test/browser/services/decorationRenderOptions.test.ts +++ b/src/vs/editor/test/browser/services/decorationRenderOptions.test.ts @@ -32,11 +32,11 @@ class TestGlobalStyleSheet extends GlobalStyleSheet { super(null!); } - public insertRule(rule: string, index?: number): void { + public override insertRule(rule: string, index?: number): void { this.rules.unshift(rule); } - public removeRulesContainingSelector(ruleName: string): void { + public override removeRulesContainingSelector(ruleName: string): void { for (let i = 0; i < this.rules.length; i++) { if (this.rules[i].indexOf(ruleName) >= 0) { this.rules.splice(i, 1); diff --git a/src/vs/editor/test/browser/services/openerService.test.ts b/src/vs/editor/test/browser/services/openerService.test.ts index 9568f18f..08c904f0 100644 --- a/src/vs/editor/test/browser/services/openerService.test.ts +++ b/src/vs/editor/test/browser/services/openerService.test.ts @@ -32,47 +32,47 @@ suite('OpenerService', function () { test('delegate to editorService, scheme:///fff', async function () { const openerService = new OpenerService(editorService, NullCommandService); await openerService.open(URI.parse('another:///somepath')); - assert.equal(editorService.lastInput!.options!.selection, undefined); + assert.strictEqual(editorService.lastInput!.options!.selection, undefined); }); test('delegate to editorService, scheme:///fff#L123', async function () { const openerService = new OpenerService(editorService, NullCommandService); await openerService.open(URI.parse('file:///somepath#L23')); - assert.equal(editorService.lastInput!.options!.selection!.startLineNumber, 23); - assert.equal(editorService.lastInput!.options!.selection!.startColumn, 1); - assert.equal(editorService.lastInput!.options!.selection!.endLineNumber, undefined); - assert.equal(editorService.lastInput!.options!.selection!.endColumn, undefined); - assert.equal(editorService.lastInput!.resource.fragment, ''); + assert.strictEqual(editorService.lastInput!.options!.selection!.startLineNumber, 23); + assert.strictEqual(editorService.lastInput!.options!.selection!.startColumn, 1); + assert.strictEqual(editorService.lastInput!.options!.selection!.endLineNumber, undefined); + assert.strictEqual(editorService.lastInput!.options!.selection!.endColumn, undefined); + assert.strictEqual(editorService.lastInput!.resource.fragment, ''); await openerService.open(URI.parse('another:///somepath#L23')); - assert.equal(editorService.lastInput!.options!.selection!.startLineNumber, 23); - assert.equal(editorService.lastInput!.options!.selection!.startColumn, 1); + assert.strictEqual(editorService.lastInput!.options!.selection!.startLineNumber, 23); + assert.strictEqual(editorService.lastInput!.options!.selection!.startColumn, 1); await openerService.open(URI.parse('another:///somepath#L23,45')); - assert.equal(editorService.lastInput!.options!.selection!.startLineNumber, 23); - assert.equal(editorService.lastInput!.options!.selection!.startColumn, 45); - assert.equal(editorService.lastInput!.options!.selection!.endLineNumber, undefined); - assert.equal(editorService.lastInput!.options!.selection!.endColumn, undefined); - assert.equal(editorService.lastInput!.resource.fragment, ''); + assert.strictEqual(editorService.lastInput!.options!.selection!.startLineNumber, 23); + assert.strictEqual(editorService.lastInput!.options!.selection!.startColumn, 45); + assert.strictEqual(editorService.lastInput!.options!.selection!.endLineNumber, undefined); + assert.strictEqual(editorService.lastInput!.options!.selection!.endColumn, undefined); + assert.strictEqual(editorService.lastInput!.resource.fragment, ''); }); test('delegate to editorService, scheme:///fff#123,123', async function () { const openerService = new OpenerService(editorService, NullCommandService); await openerService.open(URI.parse('file:///somepath#23')); - assert.equal(editorService.lastInput!.options!.selection!.startLineNumber, 23); - assert.equal(editorService.lastInput!.options!.selection!.startColumn, 1); - assert.equal(editorService.lastInput!.options!.selection!.endLineNumber, undefined); - assert.equal(editorService.lastInput!.options!.selection!.endColumn, undefined); - assert.equal(editorService.lastInput!.resource.fragment, ''); + assert.strictEqual(editorService.lastInput!.options!.selection!.startLineNumber, 23); + assert.strictEqual(editorService.lastInput!.options!.selection!.startColumn, 1); + assert.strictEqual(editorService.lastInput!.options!.selection!.endLineNumber, undefined); + assert.strictEqual(editorService.lastInput!.options!.selection!.endColumn, undefined); + assert.strictEqual(editorService.lastInput!.resource.fragment, ''); await openerService.open(URI.parse('file:///somepath#23,45')); - assert.equal(editorService.lastInput!.options!.selection!.startLineNumber, 23); - assert.equal(editorService.lastInput!.options!.selection!.startColumn, 45); - assert.equal(editorService.lastInput!.options!.selection!.endLineNumber, undefined); - assert.equal(editorService.lastInput!.options!.selection!.endColumn, undefined); - assert.equal(editorService.lastInput!.resource.fragment, ''); + assert.strictEqual(editorService.lastInput!.options!.selection!.startLineNumber, 23); + assert.strictEqual(editorService.lastInput!.options!.selection!.startColumn, 45); + assert.strictEqual(editorService.lastInput!.options!.selection!.endLineNumber, undefined); + assert.strictEqual(editorService.lastInput!.options!.selection!.endColumn, undefined); + assert.strictEqual(editorService.lastInput!.resource.fragment, ''); }); test('delegate to commandsService, command:someid', async function () { @@ -81,20 +81,37 @@ suite('OpenerService', function () { const id = `aCommand${Math.random()}`; CommandsRegistry.registerCommand(id, function () { }); + assert.strictEqual(lastCommand, undefined); await openerService.open(URI.parse('command:' + id)); - assert.equal(lastCommand!.id, id); - assert.equal(lastCommand!.args.length, 0); + assert.strictEqual(lastCommand, undefined); + }); - await openerService.open(URI.parse('command:' + id).with({ query: '123' })); - assert.equal(lastCommand!.id, id); - assert.equal(lastCommand!.args.length, 1); - assert.equal(lastCommand!.args[0], '123'); - await openerService.open(URI.parse('command:' + id).with({ query: JSON.stringify([12, true]) })); - assert.equal(lastCommand!.id, id); - assert.equal(lastCommand!.args.length, 2); - assert.equal(lastCommand!.args[0], 12); - assert.equal(lastCommand!.args[1], true); + test('delegate to commandsService, command:someid', async function () { + const openerService = new OpenerService(editorService, commandService); + + const id = `aCommand${Math.random()}`; + CommandsRegistry.registerCommand(id, function () { }); + + await openerService.open(URI.parse('command:' + id).with({ query: '\"123\"' }), { allowCommands: true }); + assert.strictEqual(lastCommand!.id, id); + assert.strictEqual(lastCommand!.args.length, 1); + assert.strictEqual(lastCommand!.args[0], '123'); + + await openerService.open(URI.parse('command:' + id), { allowCommands: true }); + assert.strictEqual(lastCommand!.id, id); + assert.strictEqual(lastCommand!.args.length, 0); + + await openerService.open(URI.parse('command:' + id).with({ query: '123' }), { allowCommands: true }); + assert.strictEqual(lastCommand!.id, id); + assert.strictEqual(lastCommand!.args.length, 1); + assert.strictEqual(lastCommand!.args[0], 123); + + await openerService.open(URI.parse('command:' + id).with({ query: JSON.stringify([12, true]) }), { allowCommands: true }); + assert.strictEqual(lastCommand!.id, id); + assert.strictEqual(lastCommand!.args.length, 2); + assert.strictEqual(lastCommand!.args[0], 12); + assert.strictEqual(lastCommand!.args[1], true); }); test('links are protected by validators', async function () { @@ -104,8 +121,8 @@ suite('OpenerService', function () { const httpResult = await openerService.open(URI.parse('https://www.microsoft.com')); const httpsResult = await openerService.open(URI.parse('https://www.microsoft.com')); - assert.equal(httpResult, false); - assert.equal(httpsResult, false); + assert.strictEqual(httpResult, false); + assert.strictEqual(httpsResult, false); }); test('links validated by validators go to openers', async function () { @@ -122,9 +139,9 @@ suite('OpenerService', function () { }); await openerService.open(URI.parse('http://microsoft.com')); - assert.equal(openCount, 1); + assert.strictEqual(openCount, 1); await openerService.open(URI.parse('https://microsoft.com')); - assert.equal(openCount, 2); + assert.strictEqual(openCount, 2); }); test('links aren\'t manipulated before being passed to validator: PR #118226', async function () { @@ -169,13 +186,13 @@ suite('OpenerService', function () { }); await openerService.open(URI.parse('http://microsoft.com')); - assert.equal(openCount, 1); - assert.equal(v1, 1); - assert.equal(v2, 1); + assert.strictEqual(openCount, 1); + assert.strictEqual(v1, 1); + assert.strictEqual(v2, 1); await openerService.open(URI.parse('https://microsoft.com')); - assert.equal(openCount, 2); - assert.equal(v1, 2); - assert.equal(v2, 2); + assert.strictEqual(openCount, 2); + assert.strictEqual(v1, 2); + assert.strictEqual(v2, 2); }); test('links invalidated by first validator do not continue validating', async function () { @@ -206,13 +223,13 @@ suite('OpenerService', function () { }); await openerService.open(URI.parse('http://microsoft.com')); - assert.equal(openCount, 0); - assert.equal(v1, 1); - assert.equal(v2, 0); + assert.strictEqual(openCount, 0); + assert.strictEqual(v1, 1); + assert.strictEqual(v2, 0); await openerService.open(URI.parse('https://microsoft.com')); - assert.equal(openCount, 0); - assert.equal(v1, 2); - assert.equal(v2, 0); + assert.strictEqual(openCount, 0); + assert.strictEqual(v1, 2); + assert.strictEqual(v2, 0); }); test('matchesScheme', function () { diff --git a/src/vs/editor/test/browser/testCodeEditor.ts b/src/vs/editor/test/browser/testCodeEditor.ts index 94741e65..a516f3be 100644 --- a/src/vs/editor/test/browser/testCodeEditor.ts +++ b/src/vs/editor/test/browser/testCodeEditor.ts @@ -36,10 +36,10 @@ export interface ITestCodeEditor extends IActiveCodeEditor { export class TestCodeEditor extends CodeEditorWidget implements ICodeEditor { //#region testing overrides - protected _createConfiguration(options: Readonly): IConfiguration { + protected override _createConfiguration(options: Readonly): IConfiguration { return new TestConfiguration(options); } - protected _createView(viewModel: ViewModel): [View, boolean] { + protected override _createView(viewModel: ViewModel): [View, boolean] { // Never create a view return [null! as View, false]; } @@ -47,7 +47,7 @@ export class TestCodeEditor extends CodeEditorWidget implements ICodeEditor { public setHasTextFocus(hasTextFocus: boolean): void { this._hasTextFocus = hasTextFocus; } - public hasTextFocus(): boolean { + public override hasTextFocus(): boolean { return this._hasTextFocus; } //#endregion @@ -64,7 +64,7 @@ export class TestCodeEditor extends CodeEditorWidget implements ICodeEditor { } class TestCodeEditorWithAutoModelDisposal extends TestCodeEditor { - public dispose() { + public override dispose() { super.dispose(); if (this._modelData) { this._modelData.model.dispose(); diff --git a/src/vs/editor/test/common/config/commonEditorConfig.test.ts b/src/vs/editor/test/common/config/commonEditorConfig.test.ts index 1faca7a1..465996a7 100644 --- a/src/vs/editor/test/common/config/commonEditorConfig.test.ts +++ b/src/vs/editor/test/common/config/commonEditorConfig.test.ts @@ -53,7 +53,7 @@ suite('Common Editor Config', () => { }); class TestWrappingConfiguration extends TestConfiguration { - protected _getEnvConfiguration(): IEnvConfiguration { + protected override _getEnvConfiguration(): IEnvConfiguration { return { extraEditorClassName: '', outerWidth: 1000, diff --git a/src/vs/editor/test/common/mocks/mockMode.ts b/src/vs/editor/test/common/mocks/mockMode.ts index 703a760c..f6cfeff1 100644 --- a/src/vs/editor/test/common/mocks/mockMode.ts +++ b/src/vs/editor/test/common/mocks/mockMode.ts @@ -28,5 +28,4 @@ export class MockMode extends Disposable implements IMode { export class StaticLanguageSelector implements ILanguageSelection { readonly onDidChange: Event = Event.None; constructor(public readonly languageIdentifier: LanguageIdentifier) { } - public dispose(): void { } } diff --git a/src/vs/editor/test/common/model/textModelWithTokens.test.ts b/src/vs/editor/test/common/model/textModelWithTokens.test.ts index e4560fd8..efbed2ef 100644 --- a/src/vs/editor/test/common/model/textModelWithTokens.test.ts +++ b/src/vs/editor/test/common/model/textModelWithTokens.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { IDisposable } from 'vs/base/common/lifecycle'; +import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { TokenizationResult2 } from 'vs/editor/common/core/token'; @@ -337,6 +337,97 @@ suite('TextModelWithTokens', () => { registration.dispose(); }); + test('issue #95843: Highlighting of closing braces is indicating wrong brace when cursor is behind opening brace', () => { + const mode1 = new LanguageIdentifier('testMode1', 3); + const mode2 = new LanguageIdentifier('testMode2', 4); + const otherMetadata1 = ( + (mode1.id << MetadataConsts.LANGUAGEID_OFFSET) + | (StandardTokenType.Other << MetadataConsts.TOKEN_TYPE_OFFSET) + ) >>> 0; + const otherMetadata2 = ( + (mode2.id << MetadataConsts.LANGUAGEID_OFFSET) + | (StandardTokenType.Other << MetadataConsts.TOKEN_TYPE_OFFSET) + ) >>> 0; + + const tokenizationSupport: ITokenizationSupport = { + getInitialState: () => NULL_STATE, + tokenize: undefined!, + tokenize2: (line, hasEOL, state) => { + switch (line) { + case 'function f() {': { + const tokens = new Uint32Array([ + 0, otherMetadata1, + 8, otherMetadata1, + 9, otherMetadata1, + 10, otherMetadata1, + 11, otherMetadata1, + 12, otherMetadata1, + 13, otherMetadata1, + ]); + return new TokenizationResult2(tokens, state); + } + case ' return

{true}

;': { + const tokens = new Uint32Array([ + 0, otherMetadata1, + 2, otherMetadata1, + 8, otherMetadata1, + 9, otherMetadata2, + 10, otherMetadata2, + 11, otherMetadata2, + 12, otherMetadata2, + 13, otherMetadata1, + 17, otherMetadata2, + 18, otherMetadata2, + 20, otherMetadata2, + 21, otherMetadata2, + 22, otherMetadata2, + ]); + return new TokenizationResult2(tokens, state); + } + case '}': { + const tokens = new Uint32Array([ + 0, otherMetadata1 + ]); + return new TokenizationResult2(tokens, state); + } + } + throw new Error(`Unexpected`); + } + }; + + const disposableStore = new DisposableStore(); + + disposableStore.add(TokenizationRegistry.register(mode1.language, tokenizationSupport)); + disposableStore.add(LanguageConfigurationRegistry.register(mode1, { + brackets: [ + ['{', '}'], + ['[', ']'], + ['(', ')'] + ], + })); + disposableStore.add(LanguageConfigurationRegistry.register(mode2, { + brackets: [ + ['{', '}'], + ['[', ']'], + ['(', ')'] + ], + })); + + const model = disposableStore.add(createTextModel([ + 'function f() {', + ' return

{true}

;', + '}', + ].join('\n'), undefined, mode1)); + + model.forceTokenization(1); + model.forceTokenization(2); + model.forceTokenization(3); + + assert.deepStrictEqual(model.matchBracket(new Position(2, 14)), [new Range(2, 13, 2, 14), new Range(2, 18, 2, 19)]); + + disposableStore.dispose(); + }); + test('issue #88075: TypeScript brace matching is incorrect in `${}` strings', () => { const mode = new LanguageIdentifier('testMode', 3); const otherMetadata = ( diff --git a/src/vs/editor/test/common/model/tokensStore.test.ts b/src/vs/editor/test/common/model/tokensStore.test.ts index e883d551..8e3a5c26 100644 --- a/src/vs/editor/test/common/model/tokensStore.test.ts +++ b/src/vs/editor/test/common/model/tokensStore.test.ts @@ -8,13 +8,13 @@ import { MultilineTokens2, SparseEncodedTokens, TokensStore2 } from 'vs/editor/c import { Range } from 'vs/editor/common/core/range'; import { TextModel } from 'vs/editor/common/model/textModel'; import { IIdentifiedSingleEditOperation } from 'vs/editor/common/model'; -import { MetadataConsts, TokenMetadata, FontStyle } from 'vs/editor/common/modes'; +import { MetadataConsts, TokenMetadata, FontStyle, ColorId } from 'vs/editor/common/modes'; import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; import { LineTokens } from 'vs/editor/common/core/lineTokens'; suite('TokensStore', () => { - const SEMANTIC_COLOR = 5; + const SEMANTIC_COLOR: ColorId = 5; function parseTokensState(state: string[]): { text: string; tokens: MultilineTokens2; } { let text: string[] = []; diff --git a/src/vs/editor/test/common/modes/supports/tokenization.test.ts b/src/vs/editor/test/common/modes/supports/tokenization.test.ts index a8f90408..435ba778 100644 --- a/src/vs/editor/test/common/modes/supports/tokenization.test.ts +++ b/src/vs/editor/test/common/modes/supports/tokenization.test.ts @@ -243,60 +243,60 @@ suite('Token theme resolving', () => { }); test('defaults are inherited', () => { - let actual = TokenTheme.createFromParsedTokenTheme([ + const actual = TokenTheme.createFromParsedTokenTheme([ new ParsedTokenThemeRule('', -1, FontStyle.NotSet, 'F8F8F2', '272822'), new ParsedTokenThemeRule('var', -1, FontStyle.NotSet, 'ff0000', null) ], []); - let colorMap = new ColorMap(); + const colorMap = new ColorMap(); const _A = colorMap.getId('F8F8F2'); const _B = colorMap.getId('272822'); const _C = colorMap.getId('ff0000'); assert.deepStrictEqual(actual.getColorMap(), colorMap.getColorMap()); - let root = new ExternalThemeTrieElement(new ThemeTrieElementRule(FontStyle.None, _A, _B), { + const root = new ExternalThemeTrieElement(new ThemeTrieElementRule(FontStyle.None, _A, _B), { 'var': new ExternalThemeTrieElement(new ThemeTrieElementRule(FontStyle.None, _C, _B)) }); - assert.deepEqual(actual.getThemeTrieElement(), root); + assert.deepStrictEqual(actual.getThemeTrieElement(), root); }); test('same rules get merged', () => { - let actual = TokenTheme.createFromParsedTokenTheme([ + const actual = TokenTheme.createFromParsedTokenTheme([ new ParsedTokenThemeRule('', -1, FontStyle.NotSet, 'F8F8F2', '272822'), new ParsedTokenThemeRule('var', 1, FontStyle.Bold, null, null), new ParsedTokenThemeRule('var', 0, FontStyle.NotSet, 'ff0000', null), ], []); - let colorMap = new ColorMap(); + const colorMap = new ColorMap(); const _A = colorMap.getId('F8F8F2'); const _B = colorMap.getId('272822'); const _C = colorMap.getId('ff0000'); assert.deepStrictEqual(actual.getColorMap(), colorMap.getColorMap()); - let root = new ExternalThemeTrieElement(new ThemeTrieElementRule(FontStyle.None, _A, _B), { + const root = new ExternalThemeTrieElement(new ThemeTrieElementRule(FontStyle.None, _A, _B), { 'var': new ExternalThemeTrieElement(new ThemeTrieElementRule(FontStyle.Bold, _C, _B)) }); - assert.deepEqual(actual.getThemeTrieElement(), root); + assert.deepStrictEqual(actual.getThemeTrieElement(), root); }); test('rules are inherited 1', () => { - let actual = TokenTheme.createFromParsedTokenTheme([ + const actual = TokenTheme.createFromParsedTokenTheme([ new ParsedTokenThemeRule('', -1, FontStyle.NotSet, 'F8F8F2', '272822'), new ParsedTokenThemeRule('var', -1, FontStyle.Bold, 'ff0000', null), new ParsedTokenThemeRule('var.identifier', -1, FontStyle.NotSet, '00ff00', null), ], []); - let colorMap = new ColorMap(); + const colorMap = new ColorMap(); const _A = colorMap.getId('F8F8F2'); const _B = colorMap.getId('272822'); const _C = colorMap.getId('ff0000'); const _D = colorMap.getId('00ff00'); assert.deepStrictEqual(actual.getColorMap(), colorMap.getColorMap()); - let root = new ExternalThemeTrieElement(new ThemeTrieElementRule(FontStyle.None, _A, _B), { + const root = new ExternalThemeTrieElement(new ThemeTrieElementRule(FontStyle.None, _A, _B), { 'var': new ExternalThemeTrieElement(new ThemeTrieElementRule(FontStyle.Bold, _C, _B), { 'identifier': new ExternalThemeTrieElement(new ThemeTrieElementRule(FontStyle.Bold, _D, _B)) }) }); - assert.deepEqual(actual.getThemeTrieElement(), root); + assert.deepStrictEqual(actual.getThemeTrieElement(), root); }); test('rules are inherited 2', () => { - let actual = TokenTheme.createFromParsedTokenTheme([ + const actual = TokenTheme.createFromParsedTokenTheme([ new ParsedTokenThemeRule('', -1, FontStyle.NotSet, 'F8F8F2', '272822'), new ParsedTokenThemeRule('var', -1, FontStyle.Bold, 'ff0000', null), new ParsedTokenThemeRule('var.identifier', -1, FontStyle.NotSet, '00ff00', null), @@ -306,7 +306,7 @@ suite('Token theme resolving', () => { new ParsedTokenThemeRule('constant.numeric.oct', 7, FontStyle.Bold | FontStyle.Italic | FontStyle.Underline, null, null), new ParsedTokenThemeRule('constant.numeric.dec', 8, FontStyle.None, '300000', null), ], []); - let colorMap = new ColorMap(); + const colorMap = new ColorMap(); const _A = colorMap.getId('F8F8F2'); const _B = colorMap.getId('272822'); const _C = colorMap.getId('100000'); @@ -315,7 +315,7 @@ suite('Token theme resolving', () => { const _F = colorMap.getId('ff0000'); const _G = colorMap.getId('00ff00'); assert.deepStrictEqual(actual.getColorMap(), colorMap.getColorMap()); - let root = new ExternalThemeTrieElement(new ThemeTrieElementRule(FontStyle.None, _A, _B), { + const root = new ExternalThemeTrieElement(new ThemeTrieElementRule(FontStyle.None, _A, _B), { 'var': new ExternalThemeTrieElement(new ThemeTrieElementRule(FontStyle.Bold, _F, _B), { 'identifier': new ExternalThemeTrieElement(new ThemeTrieElementRule(FontStyle.Bold, _G, _B)) }), @@ -327,7 +327,7 @@ suite('Token theme resolving', () => { }) }) }); - assert.deepEqual(actual.getThemeTrieElement(), root); + assert.deepStrictEqual(actual.getThemeTrieElement(), root); }); test('custom colors are first in color map', () => { diff --git a/src/vs/editor/test/common/services/textResourceConfigurationService.test.ts b/src/vs/editor/test/common/services/textResourceConfigurationService.test.ts index 50fc9f1b..dff36f24 100644 --- a/src/vs/editor/test/common/services/textResourceConfigurationService.test.ts +++ b/src/vs/editor/test/common/services/textResourceConfigurationService.test.ts @@ -18,10 +18,10 @@ suite('TextResourceConfigurationService - Update', () => { let configurationValue: IConfigurationValue = {}; let updateArgs: any[]; let configurationService = new class extends TestConfigurationService { - inspect() { + override inspect() { return configurationValue; } - updateValue() { + override updateValue() { updateArgs = [...arguments]; return Promise.resolve(); } @@ -40,13 +40,13 @@ suite('TextResourceConfigurationService - Update', () => { test('updateValue writes without target and overrides when no language is defined', async () => { const resource = URI.file('someFile'); await testObject.updateValue(resource, 'a', 'b'); - assert.deepEqual(updateArgs, ['a', 'b', { resource }, ConfigurationTarget.USER_LOCAL]); + assert.deepStrictEqual(updateArgs, ['a', 'b', { resource }, ConfigurationTarget.USER_LOCAL]); }); test('updateValue writes with target and without overrides when no language is defined', async () => { const resource = URI.file('someFile'); await testObject.updateValue(resource, 'a', 'b', ConfigurationTarget.USER_LOCAL); - assert.deepEqual(updateArgs, ['a', 'b', { resource }, ConfigurationTarget.USER_LOCAL]); + assert.deepStrictEqual(updateArgs, ['a', 'b', { resource }, ConfigurationTarget.USER_LOCAL]); }); test('updateValue writes into given memory target without overrides', async () => { @@ -59,7 +59,7 @@ suite('TextResourceConfigurationService - Update', () => { const resource = URI.file('someFile'); await testObject.updateValue(resource, 'a', 'b', ConfigurationTarget.MEMORY); - assert.deepEqual(updateArgs, ['a', 'b', { resource }, ConfigurationTarget.MEMORY]); + assert.deepStrictEqual(updateArgs, ['a', 'b', { resource }, ConfigurationTarget.MEMORY]); }); test('updateValue writes into given workspace target without overrides', async () => { @@ -72,7 +72,7 @@ suite('TextResourceConfigurationService - Update', () => { const resource = URI.file('someFile'); await testObject.updateValue(resource, 'a', 'b', ConfigurationTarget.WORKSPACE); - assert.deepEqual(updateArgs, ['a', 'b', { resource }, ConfigurationTarget.WORKSPACE]); + assert.deepStrictEqual(updateArgs, ['a', 'b', { resource }, ConfigurationTarget.WORKSPACE]); }); test('updateValue writes into given user target without overrides', async () => { @@ -85,7 +85,7 @@ suite('TextResourceConfigurationService - Update', () => { const resource = URI.file('someFile'); await testObject.updateValue(resource, 'a', 'b', ConfigurationTarget.USER); - assert.deepEqual(updateArgs, ['a', 'b', { resource }, ConfigurationTarget.USER]); + assert.deepStrictEqual(updateArgs, ['a', 'b', { resource }, ConfigurationTarget.USER]); }); test('updateValue writes into given workspace folder target with overrides', async () => { @@ -98,7 +98,7 @@ suite('TextResourceConfigurationService - Update', () => { const resource = URI.file('someFile'); await testObject.updateValue(resource, 'a', 'b', ConfigurationTarget.WORKSPACE_FOLDER); - assert.deepEqual(updateArgs, ['a', 'b', { resource, overrideIdentifier: language }, ConfigurationTarget.WORKSPACE_FOLDER]); + assert.deepStrictEqual(updateArgs, ['a', 'b', { resource, overrideIdentifier: language }, ConfigurationTarget.WORKSPACE_FOLDER]); }); test('updateValue writes into derived workspace folder target without overrides', async () => { @@ -111,7 +111,7 @@ suite('TextResourceConfigurationService - Update', () => { const resource = URI.file('someFile'); await testObject.updateValue(resource, 'a', 'b'); - assert.deepEqual(updateArgs, ['a', 'b', { resource }, ConfigurationTarget.WORKSPACE_FOLDER]); + assert.deepStrictEqual(updateArgs, ['a', 'b', { resource }, ConfigurationTarget.WORKSPACE_FOLDER]); }); test('updateValue writes into derived workspace folder target with overrides', async () => { @@ -125,7 +125,7 @@ suite('TextResourceConfigurationService - Update', () => { const resource = URI.file('someFile'); await testObject.updateValue(resource, 'a', 'b'); - assert.deepEqual(updateArgs, ['a', 'b', { resource, overrideIdentifier: language }, ConfigurationTarget.WORKSPACE_FOLDER]); + assert.deepStrictEqual(updateArgs, ['a', 'b', { resource, overrideIdentifier: language }, ConfigurationTarget.WORKSPACE_FOLDER]); }); test('updateValue writes into derived workspace target without overrides', async () => { @@ -138,7 +138,7 @@ suite('TextResourceConfigurationService - Update', () => { const resource = URI.file('someFile'); await testObject.updateValue(resource, 'a', 'b'); - assert.deepEqual(updateArgs, ['a', 'b', { resource }, ConfigurationTarget.WORKSPACE]); + assert.deepStrictEqual(updateArgs, ['a', 'b', { resource }, ConfigurationTarget.WORKSPACE]); }); test('updateValue writes into derived workspace target with overrides', async () => { @@ -151,7 +151,7 @@ suite('TextResourceConfigurationService - Update', () => { const resource = URI.file('someFile'); await testObject.updateValue(resource, 'a', 'b'); - assert.deepEqual(updateArgs, ['a', 'b', { resource, overrideIdentifier: language }, ConfigurationTarget.WORKSPACE]); + assert.deepStrictEqual(updateArgs, ['a', 'b', { resource, overrideIdentifier: language }, ConfigurationTarget.WORKSPACE]); }); test('updateValue writes into derived workspace target with overrides and value defined in folder', async () => { @@ -165,7 +165,7 @@ suite('TextResourceConfigurationService - Update', () => { const resource = URI.file('someFile'); await testObject.updateValue(resource, 'a', 'b'); - assert.deepEqual(updateArgs, ['a', 'b', { resource, overrideIdentifier: language }, ConfigurationTarget.WORKSPACE]); + assert.deepStrictEqual(updateArgs, ['a', 'b', { resource, overrideIdentifier: language }, ConfigurationTarget.WORKSPACE]); }); test('updateValue writes into derived user remote target without overrides', async () => { @@ -178,7 +178,7 @@ suite('TextResourceConfigurationService - Update', () => { const resource = URI.file('someFile'); await testObject.updateValue(resource, 'a', 'b'); - assert.deepEqual(updateArgs, ['a', 'b', { resource }, ConfigurationTarget.USER_REMOTE]); + assert.deepStrictEqual(updateArgs, ['a', 'b', { resource }, ConfigurationTarget.USER_REMOTE]); }); test('updateValue writes into derived user remote target with overrides', async () => { @@ -191,7 +191,7 @@ suite('TextResourceConfigurationService - Update', () => { const resource = URI.file('someFile'); await testObject.updateValue(resource, 'a', 'b'); - assert.deepEqual(updateArgs, ['a', 'b', { resource, overrideIdentifier: language }, ConfigurationTarget.USER_REMOTE]); + assert.deepStrictEqual(updateArgs, ['a', 'b', { resource, overrideIdentifier: language }, ConfigurationTarget.USER_REMOTE]); }); test('updateValue writes into derived user remote target with overrides and value defined in workspace', async () => { @@ -205,7 +205,7 @@ suite('TextResourceConfigurationService - Update', () => { const resource = URI.file('someFile'); await testObject.updateValue(resource, 'a', 'b'); - assert.deepEqual(updateArgs, ['a', 'b', { resource, overrideIdentifier: language }, ConfigurationTarget.USER_REMOTE]); + assert.deepStrictEqual(updateArgs, ['a', 'b', { resource, overrideIdentifier: language }, ConfigurationTarget.USER_REMOTE]); }); test('updateValue writes into derived user remote target with overrides and value defined in workspace folder', async () => { @@ -220,7 +220,7 @@ suite('TextResourceConfigurationService - Update', () => { const resource = URI.file('someFile'); await testObject.updateValue(resource, 'a', 'b'); - assert.deepEqual(updateArgs, ['a', 'b', { resource, overrideIdentifier: language }, ConfigurationTarget.USER_REMOTE]); + assert.deepStrictEqual(updateArgs, ['a', 'b', { resource, overrideIdentifier: language }, ConfigurationTarget.USER_REMOTE]); }); test('updateValue writes into derived user target without overrides', async () => { @@ -232,7 +232,7 @@ suite('TextResourceConfigurationService - Update', () => { const resource = URI.file('someFile'); await testObject.updateValue(resource, 'a', 'b'); - assert.deepEqual(updateArgs, ['a', 'b', { resource }, ConfigurationTarget.USER_LOCAL]); + assert.deepStrictEqual(updateArgs, ['a', 'b', { resource }, ConfigurationTarget.USER_LOCAL]); }); test('updateValue writes into derived user target with overrides', async () => { @@ -244,7 +244,7 @@ suite('TextResourceConfigurationService - Update', () => { const resource = URI.file('someFile'); await testObject.updateValue(resource, 'a', '2'); - assert.deepEqual(updateArgs, ['a', '2', { resource, overrideIdentifier: language }, ConfigurationTarget.USER_LOCAL]); + assert.deepStrictEqual(updateArgs, ['a', '2', { resource, overrideIdentifier: language }, ConfigurationTarget.USER_LOCAL]); }); test('updateValue writes into derived user target with overrides and value is defined in remote', async () => { @@ -257,7 +257,7 @@ suite('TextResourceConfigurationService - Update', () => { const resource = URI.file('someFile'); await testObject.updateValue(resource, 'a', '2'); - assert.deepEqual(updateArgs, ['a', '2', { resource, overrideIdentifier: language }, ConfigurationTarget.USER_LOCAL]); + assert.deepStrictEqual(updateArgs, ['a', '2', { resource, overrideIdentifier: language }, ConfigurationTarget.USER_LOCAL]); }); test('updateValue writes into derived user target with overrides and value is defined in workspace', async () => { @@ -270,7 +270,7 @@ suite('TextResourceConfigurationService - Update', () => { const resource = URI.file('someFile'); await testObject.updateValue(resource, 'a', '2'); - assert.deepEqual(updateArgs, ['a', '2', { resource, overrideIdentifier: language }, ConfigurationTarget.USER_LOCAL]); + assert.deepStrictEqual(updateArgs, ['a', '2', { resource, overrideIdentifier: language }, ConfigurationTarget.USER_LOCAL]); }); test('updateValue writes into derived user target with overrides and value is defined in workspace folder', async () => { @@ -284,7 +284,7 @@ suite('TextResourceConfigurationService - Update', () => { const resource = URI.file('someFile'); await testObject.updateValue(resource, 'a', '2'); - assert.deepEqual(updateArgs, ['a', '2', { resource, overrideIdentifier: language }, ConfigurationTarget.USER_LOCAL]); + assert.deepStrictEqual(updateArgs, ['a', '2', { resource, overrideIdentifier: language }, ConfigurationTarget.USER_LOCAL]); }); test('updateValue when not changed', async () => { @@ -295,7 +295,7 @@ suite('TextResourceConfigurationService - Update', () => { const resource = URI.file('someFile'); await testObject.updateValue(resource, 'a', 'b'); - assert.deepEqual(updateArgs, ['a', 'b', { resource }, ConfigurationTarget.USER_LOCAL]); + assert.deepStrictEqual(updateArgs, ['a', 'b', { resource }, ConfigurationTarget.USER_LOCAL]); }); }); diff --git a/src/vs/editor/test/common/viewModel/viewModelImpl.test.ts b/src/vs/editor/test/common/viewModel/viewModelImpl.test.ts index d556f3da..0e9f99ab 100644 --- a/src/vs/editor/test/common/viewModel/viewModelImpl.test.ts +++ b/src/vs/editor/test/common/viewModel/viewModelImpl.test.ts @@ -66,7 +66,7 @@ suite('ViewModel', () => { viewLineCount.push(viewModel.getLineCount()); viewModel.addViewEventHandler(new class extends ViewEventHandler { - handleEvents(events: ViewEvent[]): void { + override handleEvents(events: ViewEvent[]): void { // Access the view model viewLineCount.push(viewModel.getLineCount()); } diff --git a/src/vs/loader.js b/src/vs/loader.js index c3bdda28..815516d9 100644 --- a/src/vs/loader.js +++ b/src/vs/loader.js @@ -763,6 +763,9 @@ var AMDLoader; require.resolve = function resolve(request, options) { return Module._resolveFilename(request, mod, false, options); }; + require.resolve.paths = function paths(request) { + return Module._resolveLookupPaths(request, mod); + }; require.main = process.mainModule; require.extensions = Module._extensions; require.cache = Module._cache; diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index c7c68dae..a7b09f89 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -218,6 +218,7 @@ declare namespace monaco { * But these are "more general", as they should work across browsers & OS`s. */ export enum KeyCode { + DependsOnKbLayout = -1, /** * Placed first to cover the 0 value of the enum. */ @@ -6654,7 +6655,11 @@ declare namespace monaco.languages { declare namespace monaco.worker { - export interface IMirrorModel { + export interface IMirrorTextModel { + readonly version: number; + } + + export interface IMirrorModel extends IMirrorTextModel { readonly uri: Uri; readonly version: number; getValue(): string; diff --git a/src/vs/platform/actions/browser/menuEntryActionViewItem.css b/src/vs/platform/actions/browser/menuEntryActionViewItem.css index c74cf3ac..6b4cb93f 100644 --- a/src/vs/platform/actions/browser/menuEntryActionViewItem.css +++ b/src/vs/platform/actions/browser/menuEntryActionViewItem.css @@ -3,6 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +.monaco-action-bar .action-item.menu-entry .action-label.icon { + width: 16px; + height: 16px; + background-repeat: no-repeat; + background-position: 50%; +} + .monaco-action-bar .action-item.menu-entry .action-label { background-image: var(--menu-entry-icon-light); } diff --git a/src/vs/platform/actions/browser/menuEntryActionViewItem.ts b/src/vs/platform/actions/browser/menuEntryActionViewItem.ts index eed8299d..169aece6 100644 --- a/src/vs/platform/actions/browser/menuEntryActionViewItem.ts +++ b/src/vs/platform/actions/browser/menuEntryActionViewItem.ts @@ -117,7 +117,7 @@ export class MenuEntryActionViewItem extends ActionViewItem { private readonly _altKey: ModifierKeyEmitter; constructor( - readonly _action: MenuItemAction, + _action: MenuItemAction, @IKeybindingService protected readonly _keybindingService: IKeybindingService, @INotificationService protected _notificationService: INotificationService ) { @@ -125,11 +125,15 @@ export class MenuEntryActionViewItem extends ActionViewItem { this._altKey = ModifierKeyEmitter.getInstance(); } - protected get _commandAction(): MenuItemAction { - return this._wantsAltCommand && (this._action).alt || this._action; + protected get _menuItemAction(): MenuItemAction { + return this._action; } - onClick(event: MouseEvent): void { + protected get _commandAction(): MenuItemAction { + return this._wantsAltCommand && this._menuItemAction.alt || this._menuItemAction; + } + + override onClick(event: MouseEvent): void { event.preventDefault(); event.stopPropagation(); @@ -138,11 +142,11 @@ export class MenuEntryActionViewItem extends ActionViewItem { .catch(err => this._notificationService.error(err)); } - render(container: HTMLElement): void { + override render(container: HTMLElement): void { super.render(container); container.classList.add('menu-entry'); - this._updateItemClass(this._action.item); + this._updateItemClass(this._menuItemAction.item); let mouseOver = false; @@ -158,7 +162,7 @@ export class MenuEntryActionViewItem extends ActionViewItem { } }; - if (this._action.alt) { + if (this._menuItemAction.alt) { this._register(this._altKey.event(value => { alternativeKeyDown = value.altKey || ((isWindows || isLinux) && value.shiftKey); updateAltState(); @@ -176,13 +180,13 @@ export class MenuEntryActionViewItem extends ActionViewItem { })); } - updateLabel(): void { + override updateLabel(): void { if (this.options.label && this.label) { this.label.textContent = this._commandAction.label; } } - updateTooltip(): void { + override updateTooltip(): void { if (this.label) { const keybinding = this._keybindingService.lookupKeybinding(this._commandAction.id); const keybindingLabel = keybinding && keybinding.getLabel(); @@ -191,9 +195,9 @@ export class MenuEntryActionViewItem extends ActionViewItem { let title = keybindingLabel ? localize('titleAndKb', "{0} ({1})", tooltip, keybindingLabel) : tooltip; - if (!this._wantsAltCommand && this._action.alt) { - const altTooltip = this._action.alt.tooltip || this._action.alt.label; - const altKeybinding = this._keybindingService.lookupKeybinding(this._action.alt.id); + if (!this._wantsAltCommand && this._menuItemAction.alt) { + const altTooltip = this._menuItemAction.alt.tooltip || this._menuItemAction.alt.label; + const altKeybinding = this._keybindingService.lookupKeybinding(this._menuItemAction.alt.id); const altKeybindingLabel = altKeybinding && altKeybinding.getLabel(); const altTitleSection = altKeybindingLabel ? localize('titleAndKb', "{0} ({1})", altTooltip, altKeybindingLabel) @@ -204,14 +208,14 @@ export class MenuEntryActionViewItem extends ActionViewItem { } } - updateClass(): void { + override updateClass(): void { if (this.options.icon) { - if (this._commandAction !== this._action) { - if (this._action.alt) { - this._updateItemClass(this._action.alt.item); + if (this._commandAction !== this._menuItemAction) { + if (this._menuItemAction.alt) { + this._updateItemClass(this._menuItemAction.alt.item); } - } else if ((this._action).alt) { - this._updateItemClass(this._action.item); + } else if (this._menuItemAction.alt) { + this._updateItemClass(this._menuItemAction.item); } } } @@ -268,7 +272,7 @@ export class SubmenuEntryActionViewItem extends DropdownMenuActionViewItem { }); } - render(container: HTMLElement): void { + override render(container: HTMLElement): void { super.render(container); if (this.element) { container.classList.add('menu-entry'); diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index dd0f4963..5f946cf2 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -45,7 +45,7 @@ export interface ICommandAction { tooltip?: string; icon?: Icon; precondition?: ContextKeyExpression; - toggled?: ContextKeyExpression | { condition: ContextKeyExpression, icon?: Icon, tooltip?: string }; + toggled?: ContextKeyExpression | { condition: ContextKeyExpression, icon?: Icon, tooltip?: string, title?: string | ILocalizedString }; } export type ISerializableCommandAction = UriDto; @@ -123,6 +123,7 @@ export class MenuId { static readonly SCMTitle = new MenuId('SCMTitle'); static readonly SearchContext = new MenuId('SearchContext'); static readonly StatusBarWindowIndicatorMenu = new MenuId('StatusBarWindowIndicatorMenu'); + static readonly StatusBarRemoteIndicatorMenu = new MenuId('StatusBarRemoteIndicatorMenu'); static readonly TestItem = new MenuId('TestItem'); static readonly TouchBarContext = new MenuId('TouchBarContext'); static readonly TitleBarContext = new MenuId('TitleBarContext'); @@ -156,7 +157,11 @@ export class MenuId { static readonly TimelineTitleContext = new MenuId('TimelineTitleContext'); static readonly AccountsContext = new MenuId('AccountsContext'); static readonly PanelTitle = new MenuId('PanelTitle'); - static readonly TerminalContext = new MenuId('TerminalContext'); + static readonly TerminalContainerContext = new MenuId('TerminalContainerContext'); + static readonly TerminalToolbarContext = new MenuId('TerminalToolbarContext'); + static readonly TerminalTabsWidgetContext = new MenuId('TerminalTabsWidgetContext'); + static readonly TerminalTabsWidgetEmptyContext = new MenuId('TerminalTabsWidgetEmptyContext'); + static readonly TerminalSingleTabContext = new MenuId('TerminalSingleTabContext'); readonly id: number; readonly _debugName: string; @@ -321,7 +326,7 @@ export class ExecuteCommandAction extends Action { super(id, label); } - run(...args: any[]): Promise { + override run(...args: any[]): Promise { return this._commandService.executeCommand(this.id, ...args); } } @@ -337,7 +342,7 @@ export class SubmenuItemAction extends SubmenuAction { super(`submenuitem.${item.submenu.id}`, typeof item.title === 'string' ? item.title : item.title.value, [], 'submenu'); } - get actions(): readonly IAction[] { + override get actions(): readonly IAction[] { const result: IAction[] = []; const menu = this._menuService.createMenu(this.item.submenu, this._contextKeyService); const groups = menu.getActions(this._options); @@ -386,12 +391,16 @@ export class MenuItemAction implements IAction { if (item.toggled) { const toggled = ((item.toggled as { condition: ContextKeyExpression }).condition ? item.toggled : { condition: item.toggled }) as { - condition: ContextKeyExpression, icon?: Icon, tooltip?: string | ILocalizedString + condition: ContextKeyExpression, icon?: Icon, tooltip?: string | ILocalizedString, title?: string | ILocalizedString }; this.checked = contextKeyService.contextMatchesRules(toggled.condition); if (this.checked && toggled.tooltip) { this.tooltip = typeof toggled.tooltip === 'string' ? toggled.tooltip : toggled.tooltip.value; } + + if (toggled.title) { + this.label = typeof toggled.title === 'string' ? toggled.title : toggled.title.value; + } } this.item = item; diff --git a/src/vs/platform/actions/test/common/menuService.test.ts b/src/vs/platform/actions/test/common/menuService.test.ts index 48b8fdd9..ca7d9eb3 100644 --- a/src/vs/platform/actions/test/common/menuService.test.ts +++ b/src/vs/platform/actions/test/common/menuService.test.ts @@ -13,7 +13,7 @@ import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKe // --- service instances const contextKeyService = new class extends MockContextKeyService { - contextMatchesRules() { + override contextMatchesRules() { return true; } }; @@ -65,14 +65,14 @@ suite('MenuService', function () { const groups = menuService.createMenu(testMenuId, contextKeyService).getActions(); - assert.equal(groups.length, 5); + assert.strictEqual(groups.length, 5); const [one, two, three, four, five] = groups; - assert.equal(one[0], 'navigation'); - assert.equal(two[0], '0_hello'); - assert.equal(three[0], 'hello'); - assert.equal(four[0], 'Hello'); - assert.equal(five[0], ''); + assert.strictEqual(one[0], 'navigation'); + assert.strictEqual(two[0], '0_hello'); + assert.strictEqual(three[0], 'hello'); + assert.strictEqual(four[0], 'Hello'); + assert.strictEqual(five[0], ''); }); test('in group sorting, by title', function () { @@ -94,14 +94,14 @@ suite('MenuService', function () { const groups = menuService.createMenu(testMenuId, contextKeyService).getActions(); - assert.equal(groups.length, 1); + assert.strictEqual(groups.length, 1); const [, actions] = groups[0]; - assert.equal(actions.length, 3); + assert.strictEqual(actions.length, 3); const [one, two, three] = actions; - assert.equal(one.id, 'a'); - assert.equal(two.id, 'b'); - assert.equal(three.id, 'c'); + assert.strictEqual(one.id, 'a'); + assert.strictEqual(two.id, 'b'); + assert.strictEqual(three.id, 'c'); }); test('in group sorting, by title and order', function () { @@ -131,15 +131,15 @@ suite('MenuService', function () { const groups = menuService.createMenu(testMenuId, contextKeyService).getActions(); - assert.equal(groups.length, 1); + assert.strictEqual(groups.length, 1); const [, actions] = groups[0]; - assert.equal(actions.length, 4); + assert.strictEqual(actions.length, 4); const [one, two, three, four] = actions; - assert.equal(one.id, 'd'); - assert.equal(two.id, 'c'); - assert.equal(three.id, 'b'); - assert.equal(four.id, 'a'); + assert.strictEqual(one.id, 'd'); + assert.strictEqual(two.id, 'c'); + assert.strictEqual(three.id, 'b'); + assert.strictEqual(four.id, 'a'); }); @@ -165,14 +165,14 @@ suite('MenuService', function () { const groups = menuService.createMenu(testMenuId, contextKeyService).getActions(); - assert.equal(groups.length, 1); + assert.strictEqual(groups.length, 1); const [[, actions]] = groups; - assert.equal(actions.length, 3); + assert.strictEqual(actions.length, 3); const [one, two, three] = actions; - assert.equal(one.id, 'c'); - assert.equal(two.id, 'b'); - assert.equal(three.id, 'a'); + assert.strictEqual(one.id, 'c'); + assert.strictEqual(two.id, 'b'); + assert.strictEqual(three.id, 'a'); }); test('special MenuId palette', function () { @@ -188,16 +188,16 @@ suite('MenuService', function () { for (const item of MenuRegistry.getMenuItems(MenuId.CommandPalette)) { if (isIMenuItem(item)) { if (item.command.id === 'a') { - assert.equal(item.command.title, 'Explicit'); + assert.strictEqual(item.command.title, 'Explicit'); foundA = true; } if (item.command.id === 'b') { - assert.equal(item.command.title, 'Implicit'); + assert.strictEqual(item.command.title, 'Implicit'); foundB = true; } } } - assert.equal(foundA, true); - assert.equal(foundB, true); + assert.strictEqual(foundA, true); + assert.strictEqual(foundB, true); }); }); 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 561f0069..9968de89 100644 --- a/src/vs/platform/backup/test/electron-main/backupMainService.test.ts +++ b/src/vs/platform/backup/test/electron-main/backupMainService.test.ts @@ -123,7 +123,7 @@ flakySuite('BackupMainService', () => { return path.join(this.backupHome, id); } - getFolderHash(folderUri: URI): string { + override getFolderHash(folderUri: URI): string { return super.getFolderHash(folderUri); } }; diff --git a/src/vs/platform/checksum/test/node/checksumService.test.ts b/src/vs/platform/checksum/test/node/checksumService.test.ts index 065804c4..6cc41c10 100644 --- a/src/vs/platform/checksum/test/node/checksumService.test.ts +++ b/src/vs/platform/checksum/test/node/checksumService.test.ts @@ -15,17 +15,19 @@ import { NullLogService } from 'vs/platform/log/common/log'; suite('Checksum Service', () => { + let diskFileSystemProvider: DiskFileSystemProvider; let fileService: IFileService; setup(() => { const logService = new NullLogService(); fileService = new FileService(logService); - const diskFileSystemProvider = new DiskFileSystemProvider(logService); + diskFileSystemProvider = new DiskFileSystemProvider(logService); fileService.registerProvider(Schemas.file, diskFileSystemProvider); }); teardown(() => { + diskFileSystemProvider.dispose(); fileService.dispose(); }); diff --git a/src/vs/platform/commands/common/commands.ts b/src/vs/platform/commands/common/commands.ts index 314ccc21..deddb451 100644 --- a/src/vs/platform/commands/common/commands.ts +++ b/src/vs/platform/commands/common/commands.ts @@ -148,3 +148,5 @@ export const NullCommandService: ICommandService = { return Promise.resolve(undefined); } }; + +CommandsRegistry.registerCommand('noop', () => { }); diff --git a/src/vs/platform/commands/test/common/commands.test.ts b/src/vs/platform/commands/test/common/commands.test.ts index 33c61393..bc7e0c7b 100644 --- a/src/vs/platform/commands/test/common/commands.test.ts +++ b/src/vs/platform/commands/test/common/commands.test.ts @@ -71,7 +71,7 @@ suite('Command Tests', function () { CommandsRegistry.getCommands().get('test')!.handler.apply(undefined, [undefined!, 'string']); CommandsRegistry.getCommands().get('test2')!.handler.apply(undefined, [undefined!, 'string']); assert.throws(() => CommandsRegistry.getCommands().get('test3')!.handler.apply(undefined, [undefined!, 'string'])); - assert.equal(CommandsRegistry.getCommands().get('test3')!.handler.apply(undefined, [undefined!, 1]), true); + assert.strictEqual(CommandsRegistry.getCommands().get('test3')!.handler.apply(undefined, [undefined!, 1]), true); }); }); diff --git a/src/vs/platform/configuration/common/configurationModels.ts b/src/vs/platform/configuration/common/configurationModels.ts index 5993f0fd..4793089b 100644 --- a/src/vs/platform/configuration/common/configurationModels.ts +++ b/src/vs/platform/configuration/common/configurationModels.ts @@ -214,42 +214,53 @@ export class DefaultConfigurationModel extends ConfigurationModel { } } +export interface ConfigurationParseOptions { + scopes: ConfigurationScope[] | undefined; + skipRestricted?: boolean; +} + export class ConfigurationModelParser { private _raw: any = null; private _configurationModel: ConfigurationModel | null = null; + private _restrictedConfigurations: string[] = []; private _parseErrors: any[] = []; - constructor(protected readonly _name: string, private _scopes?: ConfigurationScope[]) { } + constructor(protected readonly _name: string) { } get configurationModel(): ConfigurationModel { return this._configurationModel || new ConfigurationModel(); } + get restrictedConfigurations(): string[] { + return this._restrictedConfigurations; + } + get errors(): any[] { return this._parseErrors; } - public parseContent(content: string | null | undefined): void { + public parse(content: string | null | undefined, options?: ConfigurationParseOptions): void { if (!types.isUndefinedOrNull(content)) { const raw = this.doParseContent(content); - this.parseRaw(raw); + this.parseRaw(raw, options); } } - public parseRaw(raw: any): void { - this._raw = raw; - const configurationModel = this.doParseRaw(raw); - this._configurationModel = new ConfigurationModel(configurationModel.contents, configurationModel.keys, configurationModel.overrides); - } - - public parse(): void { + public reparse(options: ConfigurationParseOptions): void { if (this._raw) { - this.parseRaw(this._raw); + this.parseRaw(this._raw, options); } } - protected doParseContent(content: string): any { + public parseRaw(raw: any, options?: ConfigurationParseOptions): void { + this._raw = raw; + const { contents, keys, overrides, restricted } = this.doParseRaw(raw, options); + this._configurationModel = new ConfigurationModel(contents, keys, overrides); + this._restrictedConfigurations = restricted || []; + } + + private doParseContent(content: string): any { let raw: any = {}; let currentProperty: string | null = null; let currentParent: any = []; @@ -306,42 +317,50 @@ export class ConfigurationModelParser { return raw; } - protected doParseRaw(raw: any): IConfigurationModel { - if (this._scopes) { - const configurationProperties = Registry.as(Extensions.Configuration).getConfigurationProperties(); - raw = this.filterByScope(raw, configurationProperties, true, this._scopes); - } + protected doParseRaw(raw: any, options?: ConfigurationParseOptions): IConfigurationModel & { restricted?: string[] } { + const configurationProperties = Registry.as(Extensions.Configuration).getConfigurationProperties(); + const filtered = this.filter(raw, configurationProperties, true, options); + raw = filtered.raw; const contents = toValuesTree(raw, message => console.error(`Conflict in settings file ${this._name}: ${message}`)); const keys = Object.keys(raw); const overrides: IOverrides[] = toOverrides(raw, message => console.error(`Conflict in settings file ${this._name}: ${message}`)); - return { contents, keys, overrides }; + return { contents, keys, overrides, restricted: filtered.restricted }; } - private filterByScope(properties: any, configurationProperties: { [qualifiedKey: string]: IConfigurationPropertySchema }, filterOverriddenProperties: boolean, scopes: ConfigurationScope[]): {} { - const result: any = {}; + private filter(properties: any, configurationProperties: { [qualifiedKey: string]: IConfigurationPropertySchema | undefined }, filterOverriddenProperties: boolean, options?: ConfigurationParseOptions): { raw: {}, restricted: string[] } { + if (!options?.scopes && !options?.skipRestricted) { + return { raw: properties, restricted: [] }; + } + const raw: any = {}; + const restricted: string[] = []; for (let key in properties) { if (OVERRIDE_PROPERTY_PATTERN.test(key) && filterOverriddenProperties) { - result[key] = this.filterByScope(properties[key], configurationProperties, false, scopes); + const result = this.filter(properties[key], configurationProperties, false, options); + raw[key] = result.raw; + restricted.push(...result.restricted); } else { - const scope = this.getScope(key, configurationProperties); + const propertySchema = configurationProperties[key]; + const scope = propertySchema ? typeof propertySchema.scope !== 'undefined' ? propertySchema.scope : ConfigurationScope.WINDOW : undefined; + if (propertySchema?.restricted) { + restricted.push(key); + } // Load unregistered configurations always. - if (scope === undefined || scopes.indexOf(scope) !== -1) { - result[key] = properties[key]; + if (scope === undefined || options.scopes === undefined || options.scopes.includes(scope)) { + if (!(options.skipRestricted && propertySchema?.restricted)) { + raw[key] = properties[key]; + } } } } - return result; + return { raw, restricted }; } - private getScope(key: string, configurationProperties: { [qualifiedKey: string]: IConfigurationPropertySchema }): ConfigurationScope | undefined { - const propertySchema = configurationProperties[key]; - return propertySchema ? typeof propertySchema.scope !== 'undefined' ? propertySchema.scope : ConfigurationScope.WINDOW : undefined; - } } export class UserSettings extends Disposable { private readonly parser: ConfigurationModelParser; + private readonly parseOptions: ConfigurationParseOptions; protected readonly _onDidChange: Emitter = this._register(new Emitter()); readonly onDidChange: Event = this._onDidChange.event; @@ -352,7 +371,8 @@ export class UserSettings extends Disposable { private readonly fileService: IFileService ) { super(); - this.parser = new ConfigurationModelParser(this.userSettingsResource.toString(), this.scopes); + this.parser = new ConfigurationModelParser(this.userSettingsResource.toString()); + this.parseOptions = { scopes: this.scopes }; this._register(this.fileService.watch(extUri.dirname(this.userSettingsResource))); // Also listen to the resource incase the resource is a symlink - https://github.com/microsoft/vscode/issues/118134 this._register(this.fileService.watch(this.userSettingsResource)); @@ -362,17 +382,21 @@ export class UserSettings extends Disposable { async loadConfiguration(): Promise { try { const content = await this.fileService.readFile(this.userSettingsResource); - this.parser.parseContent(content.value.toString() || '{}'); + this.parser.parse(content.value.toString() || '{}', this.parseOptions); return this.parser.configurationModel; } catch (e) { return new ConfigurationModel(); } } - reprocess(): ConfigurationModel { - this.parser.parse(); + reparse(): ConfigurationModel { + this.parser.reparse(this.parseOptions); return this.parser.configurationModel; } + + getRestrictedSettings(): string[] { + return this.parser.restrictedConfigurations; + } } @@ -799,8 +823,9 @@ export class ConfigurationChangeEvent implements IConfigurationChangeEvent { } export class AllKeysConfigurationChangeEvent extends ConfigurationChangeEvent { - constructor(configuration: Configuration, workspace: Workspace, public source: ConfigurationTarget, public sourceConfig: any) { + constructor(configuration: Configuration, workspace: Workspace, source: ConfigurationTarget, sourceConfig: any) { super({ keys: configuration.allKeys(), overrides: [] }, undefined, configuration, workspace); + this.source = source; + this.sourceConfig = sourceConfig; } - } diff --git a/src/vs/platform/configuration/common/configurationRegistry.ts b/src/vs/platform/configuration/common/configurationRegistry.ts index 7504cb72..d0114d52 100644 --- a/src/vs/platform/configuration/common/configurationRegistry.ts +++ b/src/vs/platform/configuration/common/configurationRegistry.ts @@ -109,22 +109,35 @@ export const enum ConfigurationScope { } export interface IConfigurationPropertySchema extends IJSONSchema { + scope?: ConfigurationScope; + + /** + * When restricted, value of this configuration will be read only from trusted sources. + * For eg., If the workspace is not trusted, then the value of this configuration is not read from workspace settings file. + */ + restricted?: boolean; + included?: boolean; + tags?: string[]; + /** * When enabled this setting is ignored during sync and user can override this. */ ignoreSync?: boolean; + /** * When enabled this setting is ignored during sync and user cannot override this. */ disallowSyncIgnore?: boolean; + enumItemLabels?: string[]; } export interface IConfigurationExtensionInfo { id: string; + restrictedConfigurations?: string[]; } export interface IConfigurationNode { @@ -139,14 +152,12 @@ export interface IConfigurationNode { extensionInfo?: IConfigurationExtensionInfo; } -type SettingProperties = { [key: string]: any }; - -export const allSettings: { properties: SettingProperties, patternProperties: SettingProperties } = { properties: {}, patternProperties: {} }; -export const applicationSettings: { properties: SettingProperties, patternProperties: SettingProperties } = { properties: {}, patternProperties: {} }; -export const machineSettings: { properties: SettingProperties, patternProperties: SettingProperties } = { properties: {}, patternProperties: {} }; -export const machineOverridableSettings: { properties: SettingProperties, patternProperties: SettingProperties } = { properties: {}, patternProperties: {} }; -export const windowSettings: { properties: SettingProperties, patternProperties: SettingProperties } = { properties: {}, patternProperties: {} }; -export const resourceSettings: { properties: SettingProperties, patternProperties: SettingProperties } = { properties: {}, patternProperties: {} }; +export const allSettings: { properties: IStringDictionary, patternProperties: IStringDictionary } = { properties: {}, patternProperties: {} }; +export const applicationSettings: { properties: IStringDictionary, patternProperties: IStringDictionary } = { properties: {}, patternProperties: {} }; +export const machineSettings: { properties: IStringDictionary, patternProperties: IStringDictionary } = { properties: {}, patternProperties: {} }; +export const machineOverridableSettings: { properties: IStringDictionary, patternProperties: IStringDictionary } = { properties: {}, patternProperties: {} }; +export const windowSettings: { properties: IStringDictionary, patternProperties: IStringDictionary } = { properties: {}, patternProperties: {} }; +export const resourceSettings: { properties: IStringDictionary, patternProperties: IStringDictionary } = { properties: {}, patternProperties: {} }; export const resourceLanguageSettingsSchemaId = 'vscode://schemas/settings/resourceLanguage'; @@ -190,7 +201,7 @@ class ConfigurationRegistry implements IConfigurationRegistry { public registerConfigurations(configurations: IConfigurationNode[], validate: boolean = true): void { const properties: string[] = []; configurations.forEach(configuration => { - properties.push(...this.validateAndRegisterProperties(configuration, validate)); // fills in defaults + properties.push(...this.validateAndRegisterProperties(configuration, validate, configuration.extensionInfo)); // fills in defaults this.configurationContributors.push(configuration); this.registerJSONConfiguration(configuration); }); @@ -297,7 +308,7 @@ class ConfigurationRegistry implements IConfigurationRegistry { this.updateOverridePropertyPatternKey(); } - private validateAndRegisterProperties(configuration: IConfigurationNode, validate: boolean = true, scope: ConfigurationScope = ConfigurationScope.WINDOW): string[] { + private validateAndRegisterProperties(configuration: IConfigurationNode, validate: boolean = true, extensionInfo?: IConfigurationExtensionInfo, scope: ConfigurationScope = ConfigurationScope.WINDOW): string[] { scope = types.isUndefinedOrNull(configuration.scope) ? scope : configuration.scope; let propertyKeys: string[] = []; let properties = configuration.properties; @@ -318,6 +329,7 @@ class ConfigurationRegistry implements IConfigurationRegistry { property.scope = undefined; // No scope for overridable properties `[${identifier}]` } else { property.scope = types.isUndefinedOrNull(property.scope) ? scope : property.scope; + property.restricted = types.isUndefinedOrNull(property.restricted) ? !!extensionInfo?.restrictedConfigurations?.includes(key) : property.restricted; } // Add to properties maps @@ -341,7 +353,7 @@ class ConfigurationRegistry implements IConfigurationRegistry { let subNodes = configuration.allOf; if (subNodes) { for (let node of subNodes) { - propertyKeys.push(...this.validateAndRegisterProperties(node, validate, scope)); + propertyKeys.push(...this.validateAndRegisterProperties(node, validate, extensionInfo, scope)); } } return propertyKeys; diff --git a/src/vs/platform/configuration/test/common/configuration.test.ts b/src/vs/platform/configuration/test/common/configuration.test.ts index 5470fd08..b7b85453 100644 --- a/src/vs/platform/configuration/test/common/configuration.test.ts +++ b/src/vs/platform/configuration/test/common/configuration.test.ts @@ -11,10 +11,10 @@ suite('Configuration', () => { test('simple merge', () => { let base = { 'a': 1, 'b': 2 }; merge(base, { 'a': 3, 'c': 4 }, true); - assert.deepEqual(base, { 'a': 3, 'b': 2, 'c': 4 }); + assert.deepStrictEqual(base, { 'a': 3, 'b': 2, 'c': 4 }); base = { 'a': 1, 'b': 2 }; merge(base, { 'a': 3, 'c': 4 }, false); - assert.deepEqual(base, { 'a': 1, 'b': 2, 'c': 4 }); + assert.deepStrictEqual(base, { 'a': 1, 'b': 2, 'c': 4 }); }); test('removeFromValueTree: remove a non existing key', () => { @@ -22,7 +22,7 @@ suite('Configuration', () => { removeFromValueTree(target, 'c'); - assert.deepEqual(target, { 'a': { 'b': 2 } }); + assert.deepStrictEqual(target, { 'a': { 'b': 2 } }); }); test('removeFromValueTree: remove a multi segmented key from an object that has only sub sections of the key', () => { @@ -30,7 +30,7 @@ suite('Configuration', () => { removeFromValueTree(target, 'a.b.c'); - assert.deepEqual(target, { 'a': { 'b': 2 } }); + assert.deepStrictEqual(target, { 'a': { 'b': 2 } }); }); test('removeFromValueTree: remove a single segmented key', () => { @@ -38,7 +38,7 @@ suite('Configuration', () => { removeFromValueTree(target, 'a'); - assert.deepEqual(target, {}); + assert.deepStrictEqual(target, {}); }); test('removeFromValueTree: remove a single segmented key when its value is undefined', () => { @@ -46,7 +46,7 @@ suite('Configuration', () => { removeFromValueTree(target, 'a'); - assert.deepEqual(target, {}); + assert.deepStrictEqual(target, {}); }); test('removeFromValueTree: remove a multi segmented key when its value is undefined', () => { @@ -54,7 +54,7 @@ suite('Configuration', () => { removeFromValueTree(target, 'a.b'); - assert.deepEqual(target, {}); + assert.deepStrictEqual(target, {}); }); test('removeFromValueTree: remove a multi segmented key when its value is array', () => { @@ -62,7 +62,7 @@ suite('Configuration', () => { removeFromValueTree(target, 'a.b'); - assert.deepEqual(target, {}); + assert.deepStrictEqual(target, {}); }); test('removeFromValueTree: remove a multi segmented key first segment value is array', () => { @@ -70,7 +70,7 @@ suite('Configuration', () => { removeFromValueTree(target, 'a.0'); - assert.deepEqual(target, { 'a': [1] }); + assert.deepStrictEqual(target, { 'a': [1] }); }); test('removeFromValueTree: remove when key is the first segmenet', () => { @@ -78,7 +78,7 @@ suite('Configuration', () => { removeFromValueTree(target, 'a'); - assert.deepEqual(target, {}); + assert.deepStrictEqual(target, {}); }); test('removeFromValueTree: remove a multi segmented key when the first node has more values', () => { @@ -86,7 +86,7 @@ suite('Configuration', () => { removeFromValueTree(target, 'a.b.c'); - assert.deepEqual(target, { 'a': { 'd': 1 } }); + assert.deepStrictEqual(target, { 'a': { 'd': 1 } }); }); test('removeFromValueTree: remove a multi segmented key when in between node has more values', () => { @@ -94,7 +94,7 @@ suite('Configuration', () => { removeFromValueTree(target, 'a.b.c.d'); - assert.deepEqual(target, { 'a': { 'b': { 'd': 1 } } }); + assert.deepStrictEqual(target, { 'a': { 'b': { 'd': 1 } } }); }); test('removeFromValueTree: remove a multi segmented key when the last but one node has more values', () => { @@ -102,7 +102,7 @@ suite('Configuration', () => { removeFromValueTree(target, 'a.b.c'); - assert.deepEqual(target, { 'a': { 'b': { 'd': 1 } } }); + assert.deepStrictEqual(target, { 'a': { 'b': { 'd': 1 } } }); }); }); @@ -111,37 +111,37 @@ suite('Configuration Changes: Merge', () => { test('merge only keys', () => { const actual = mergeChanges({ keys: ['a', 'b'], overrides: [] }, { keys: ['c', 'd'], overrides: [] }); - assert.deepEqual(actual, { keys: ['a', 'b', 'c', 'd'], overrides: [] }); + assert.deepStrictEqual(actual, { keys: ['a', 'b', 'c', 'd'], overrides: [] }); }); test('merge only keys with duplicates', () => { const actual = mergeChanges({ keys: ['a', 'b'], overrides: [] }, { keys: ['c', 'd'], overrides: [] }, { keys: ['a', 'd', 'e'], overrides: [] }); - assert.deepEqual(actual, { keys: ['a', 'b', 'c', 'd', 'e'], overrides: [] }); + assert.deepStrictEqual(actual, { keys: ['a', 'b', 'c', 'd', 'e'], overrides: [] }); }); test('merge only overrides', () => { const actual = mergeChanges({ keys: [], overrides: [['a', ['1', '2']]] }, { keys: [], overrides: [['b', ['3', '4']]] }); - assert.deepEqual(actual, { keys: [], overrides: [['a', ['1', '2']], ['b', ['3', '4']]] }); + assert.deepStrictEqual(actual, { keys: [], overrides: [['a', ['1', '2']], ['b', ['3', '4']]] }); }); test('merge only overrides with duplicates', () => { const actual = mergeChanges({ keys: [], overrides: [['a', ['1', '2']], ['b', ['5', '4']]] }, { keys: [], overrides: [['b', ['3', '4']]] }, { keys: [], overrides: [['c', ['1', '4']], ['a', ['2', '3']]] }); - assert.deepEqual(actual, { keys: [], overrides: [['a', ['1', '2', '3']], ['b', ['5', '4', '3']], ['c', ['1', '4']]] }); + assert.deepStrictEqual(actual, { keys: [], overrides: [['a', ['1', '2', '3']], ['b', ['5', '4', '3']], ['c', ['1', '4']]] }); }); test('merge', () => { const actual = mergeChanges({ keys: ['b', 'b'], overrides: [['a', ['1', '2']], ['b', ['5', '4']]] }, { keys: ['b'], overrides: [['b', ['3', '4']]] }, { keys: ['c', 'a'], overrides: [['c', ['1', '4']], ['a', ['2', '3']]] }); - assert.deepEqual(actual, { keys: ['b', 'c', 'a'], overrides: [['a', ['1', '2', '3']], ['b', ['5', '4', '3']], ['c', ['1', '4']]] }); + assert.deepStrictEqual(actual, { keys: ['b', 'c', 'a'], overrides: [['a', ['1', '2', '3']], ['b', ['5', '4', '3']], ['c', ['1', '4']]] }); }); test('merge single change', () => { const actual = mergeChanges({ keys: ['b', 'b'], overrides: [['a', ['1', '2']], ['b', ['5', '4']]] }); - assert.deepEqual(actual, { keys: ['b', 'b'], overrides: [['a', ['1', '2']], ['b', ['5', '4']]] }); + assert.deepStrictEqual(actual, { keys: ['b', 'b'], overrides: [['a', ['1', '2']], ['b', ['5', '4']]] }); }); test('merge no changes', () => { const actual = mergeChanges(); - assert.deepEqual(actual, { keys: [], overrides: [] }); + assert.deepStrictEqual(actual, { keys: [], overrides: [] }); }); }); diff --git a/src/vs/platform/configuration/test/common/configurationModels.test.ts b/src/vs/platform/configuration/test/common/configurationModels.test.ts index c743e36b..544fd32e 100644 --- a/src/vs/platform/configuration/test/common/configurationModels.test.ts +++ b/src/vs/platform/configuration/test/common/configurationModels.test.ts @@ -19,8 +19,8 @@ suite('ConfigurationModel', () => { testObject.setValue('f', 1); - assert.deepEqual(testObject.contents, { 'a': { 'b': 1 }, 'f': 1 }); - assert.deepEqual(testObject.keys, ['a.b', 'f']); + assert.deepStrictEqual(testObject.contents, { 'a': { 'b': 1 }, 'f': 1 }); + assert.deepStrictEqual(testObject.keys, ['a.b', 'f']); }); test('setValue for a key that has no sections and defined', () => { @@ -28,8 +28,8 @@ suite('ConfigurationModel', () => { testObject.setValue('f', 3); - assert.deepEqual(testObject.contents, { 'a': { 'b': 1 }, 'f': 3 }); - assert.deepEqual(testObject.keys, ['a.b', 'f']); + assert.deepStrictEqual(testObject.contents, { 'a': { 'b': 1 }, 'f': 3 }); + assert.deepStrictEqual(testObject.keys, ['a.b', 'f']); }); test('setValue for a key that has sections and not defined', () => { @@ -37,8 +37,13 @@ suite('ConfigurationModel', () => { testObject.setValue('b.c', 1); - assert.deepEqual(testObject.contents, { 'a': { 'b': 1 }, 'b': { 'c': 1 }, 'f': 1 }); - assert.deepEqual(testObject.keys, ['a.b', 'f', 'b.c']); + const expected: any = {}; + expected['a'] = { 'b': 1 }; + expected['f'] = 1; + expected['b'] = Object.create(null); + expected['b']['c'] = 1; + assert.deepStrictEqual(testObject.contents, expected); + assert.deepStrictEqual(testObject.keys, ['a.b', 'f', 'b.c']); }); test('setValue for a key that has sections and defined', () => { @@ -46,8 +51,8 @@ suite('ConfigurationModel', () => { testObject.setValue('b.c', 3); - assert.deepEqual(testObject.contents, { 'a': { 'b': 1 }, 'b': { 'c': 3 }, 'f': 1 }); - assert.deepEqual(testObject.keys, ['a.b', 'b.c', 'f']); + assert.deepStrictEqual(testObject.contents, { 'a': { 'b': 1 }, 'b': { 'c': 3 }, 'f': 1 }); + assert.deepStrictEqual(testObject.keys, ['a.b', 'b.c', 'f']); }); test('setValue for a key that has sections and sub section not defined', () => { @@ -55,8 +60,8 @@ suite('ConfigurationModel', () => { testObject.setValue('a.c', 1); - assert.deepEqual(testObject.contents, { 'a': { 'b': 1, 'c': 1 }, 'f': 1 }); - assert.deepEqual(testObject.keys, ['a.b', 'f', 'a.c']); + assert.deepStrictEqual(testObject.contents, { 'a': { 'b': 1, 'c': 1 }, 'f': 1 }); + assert.deepStrictEqual(testObject.keys, ['a.b', 'f', 'a.c']); }); test('setValue for a key that has sections and sub section defined', () => { @@ -64,8 +69,8 @@ suite('ConfigurationModel', () => { testObject.setValue('a.c', 3); - assert.deepEqual(testObject.contents, { 'a': { 'b': 1, 'c': 3 }, 'f': 1 }); - assert.deepEqual(testObject.keys, ['a.b', 'a.c', 'f']); + assert.deepStrictEqual(testObject.contents, { 'a': { 'b': 1, 'c': 3 }, 'f': 1 }); + assert.deepStrictEqual(testObject.keys, ['a.b', 'a.c', 'f']); }); test('setValue for a key that has sections and last section is added', () => { @@ -73,8 +78,8 @@ suite('ConfigurationModel', () => { testObject.setValue('a.b.c', 1); - assert.deepEqual(testObject.contents, { 'a': { 'b': { 'c': 1 } }, 'f': 1 }); - assert.deepEqual(testObject.keys, ['a.b.c', 'f']); + assert.deepStrictEqual(testObject.contents, { 'a': { 'b': { 'c': 1 } }, 'f': 1 }); + assert.deepStrictEqual(testObject.keys, ['a.b.c', 'f']); }); test('removeValue: remove a non existing key', () => { @@ -82,8 +87,8 @@ suite('ConfigurationModel', () => { testObject.removeValue('a.b.c'); - assert.deepEqual(testObject.contents, { 'a': { 'b': 2 } }); - assert.deepEqual(testObject.keys, ['a.b']); + assert.deepStrictEqual(testObject.contents, { 'a': { 'b': 2 } }); + assert.deepStrictEqual(testObject.keys, ['a.b']); }); test('removeValue: remove a single segmented key', () => { @@ -91,8 +96,8 @@ suite('ConfigurationModel', () => { testObject.removeValue('a'); - assert.deepEqual(testObject.contents, {}); - assert.deepEqual(testObject.keys, []); + assert.deepStrictEqual(testObject.contents, {}); + assert.deepStrictEqual(testObject.keys, []); }); test('removeValue: remove a multi segmented key', () => { @@ -100,8 +105,8 @@ suite('ConfigurationModel', () => { testObject.removeValue('a.b'); - assert.deepEqual(testObject.contents, {}); - assert.deepEqual(testObject.keys, []); + assert.deepStrictEqual(testObject.contents, {}); + assert.deepStrictEqual(testObject.keys, []); }); test('get overriding configuration model for an existing identifier', () => { @@ -109,7 +114,7 @@ suite('ConfigurationModel', () => { { 'a': { 'b': 1 }, 'f': 1 }, [], [{ identifiers: ['c'], contents: { 'a': { 'd': 1 } }, keys: ['a'] }]); - assert.deepEqual(testObject.override('c').contents, { 'a': { 'b': 1, 'd': 1 }, 'f': 1 }); + assert.deepStrictEqual(testObject.override('c').contents, { 'a': { 'b': 1, 'd': 1 }, 'f': 1 }); }); test('get overriding configuration model for an identifier that does not exist', () => { @@ -117,7 +122,7 @@ suite('ConfigurationModel', () => { { 'a': { 'b': 1 }, 'f': 1 }, [], [{ identifiers: ['c'], contents: { 'a': { 'd': 1 } }, keys: ['a'] }]); - assert.deepEqual(testObject.override('xyz').contents, { 'a': { 'b': 1 }, 'f': 1 }); + assert.deepStrictEqual(testObject.override('xyz').contents, { 'a': { 'b': 1 }, 'f': 1 }); }); test('get overriding configuration when one of the keys does not exist in base', () => { @@ -125,7 +130,7 @@ suite('ConfigurationModel', () => { { 'a': { 'b': 1 }, 'f': 1 }, [], [{ identifiers: ['c'], contents: { 'a': { 'd': 1 }, 'g': 1 }, keys: ['a', 'g'] }]); - assert.deepEqual(testObject.override('c').contents, { 'a': { 'b': 1, 'd': 1 }, 'f': 1, 'g': 1 }); + assert.deepStrictEqual(testObject.override('c').contents, { 'a': { 'b': 1, 'd': 1 }, 'f': 1, 'g': 1 }); }); test('get overriding configuration when one of the key in base is not of object type', () => { @@ -133,7 +138,7 @@ suite('ConfigurationModel', () => { { 'a': { 'b': 1 }, 'f': 1 }, [], [{ identifiers: ['c'], contents: { 'a': { 'd': 1 }, 'f': { 'g': 1 } }, keys: ['a', 'f'] }]); - assert.deepEqual(testObject.override('c').contents, { 'a': { 'b': 1, 'd': 1 }, 'f': { 'g': 1 } }); + assert.deepStrictEqual(testObject.override('c').contents, { 'a': { 'b': 1, 'd': 1 }, 'f': { 'g': 1 } }); }); test('get overriding configuration when one of the key in overriding contents is not of object type', () => { @@ -141,7 +146,7 @@ suite('ConfigurationModel', () => { { 'a': { 'b': 1 }, 'f': { 'g': 1 } }, [], [{ identifiers: ['c'], contents: { 'a': { 'd': 1 }, 'f': 1 }, keys: ['a', 'f'] }]); - assert.deepEqual(testObject.override('c').contents, { 'a': { 'b': 1, 'd': 1 }, 'f': 1 }); + assert.deepStrictEqual(testObject.override('c').contents, { 'a': { 'b': 1, 'd': 1 }, 'f': 1 }); }); test('get overriding configuration if the value of overriding identifier is not object', () => { @@ -149,7 +154,7 @@ suite('ConfigurationModel', () => { { 'a': { 'b': 1 }, 'f': { 'g': 1 } }, [], [{ identifiers: ['c'], contents: 'abc', keys: [] }]); - assert.deepEqual(testObject.override('c').contents, { 'a': { 'b': 1 }, 'f': { 'g': 1 } }); + assert.deepStrictEqual(testObject.override('c').contents, { 'a': { 'b': 1 }, 'f': { 'g': 1 } }); }); test('get overriding configuration if the value of overriding identifier is an empty object', () => { @@ -157,7 +162,7 @@ suite('ConfigurationModel', () => { { 'a': { 'b': 1 }, 'f': { 'g': 1 } }, [], [{ identifiers: ['c'], contents: {}, keys: [] }]); - assert.deepEqual(testObject.override('c').contents, { 'a': { 'b': 1 }, 'f': { 'g': 1 } }); + assert.deepStrictEqual(testObject.override('c').contents, { 'a': { 'b': 1 }, 'f': { 'g': 1 } }); }); test('simple merge', () => { @@ -165,8 +170,8 @@ suite('ConfigurationModel', () => { let add = new ConfigurationModel({ 'a': 3, 'c': 4 }, ['a', 'c']); let result = base.merge(add); - assert.deepEqual(result.contents, { 'a': 3, 'b': 2, 'c': 4 }); - assert.deepEqual(result.keys, ['a', 'b', 'c']); + assert.deepStrictEqual(result.contents, { 'a': 3, 'b': 2, 'c': 4 }); + assert.deepStrictEqual(result.keys, ['a', 'b', 'c']); }); test('recursive merge', () => { @@ -174,9 +179,9 @@ suite('ConfigurationModel', () => { let add = new ConfigurationModel({ 'a': { 'b': 2 } }, ['a.b']); let result = base.merge(add); - assert.deepEqual(result.contents, { 'a': { 'b': 2 } }); - assert.deepEqual(result.getValue('a'), { 'b': 2 }); - assert.deepEqual(result.keys, ['a.b']); + assert.deepStrictEqual(result.contents, { 'a': { 'b': 2 } }); + assert.deepStrictEqual(result.getValue('a'), { 'b': 2 }); + assert.deepStrictEqual(result.keys, ['a.b']); }); test('simple merge overrides', () => { @@ -184,10 +189,10 @@ suite('ConfigurationModel', () => { let add = new ConfigurationModel({ 'a': { 'b': 2 } }, ['a.b'], [{ identifiers: ['c'], contents: { 'b': 2 }, keys: ['b'] }]); let result = base.merge(add); - assert.deepEqual(result.contents, { 'a': { 'b': 2 } }); - assert.deepEqual(result.overrides, [{ identifiers: ['c'], contents: { 'a': 2, 'b': 2 }, keys: ['a'] }]); - assert.deepEqual(result.override('c').contents, { 'a': 2, 'b': 2 }); - assert.deepEqual(result.keys, ['a.b']); + assert.deepStrictEqual(result.contents, { 'a': { 'b': 2 } }); + assert.deepStrictEqual(result.overrides, [{ identifiers: ['c'], contents: { 'a': 2, 'b': 2 }, keys: ['a'] }]); + assert.deepStrictEqual(result.override('c').contents, { 'a': 2, 'b': 2 }); + assert.deepStrictEqual(result.keys, ['a.b']); }); test('recursive merge overrides', () => { @@ -195,10 +200,10 @@ suite('ConfigurationModel', () => { let add = new ConfigurationModel({ 'a': { 'b': 2 } }, ['a.b'], [{ identifiers: ['c'], contents: { 'a': { 'e': 2 } }, keys: ['a'] }]); let result = base.merge(add); - assert.deepEqual(result.contents, { 'a': { 'b': 2 }, 'f': 1 }); - assert.deepEqual(result.overrides, [{ identifiers: ['c'], contents: { 'a': { 'd': 1, 'e': 2 } }, keys: ['a'] }]); - assert.deepEqual(result.override('c').contents, { 'a': { 'b': 2, 'd': 1, 'e': 2 }, 'f': 1 }); - assert.deepEqual(result.keys, ['a.b', 'f']); + assert.deepStrictEqual(result.contents, { 'a': { 'b': 2 }, 'f': 1 }); + assert.deepStrictEqual(result.overrides, [{ identifiers: ['c'], contents: { 'a': { 'd': 1, 'e': 2 } }, keys: ['a'] }]); + assert.deepStrictEqual(result.override('c').contents, { 'a': { 'b': 2, 'd': 1, 'e': 2 }, 'f': 1 }); + assert.deepStrictEqual(result.keys, ['a.b', 'f']); }); test('merge overrides when frozen', () => { @@ -206,30 +211,30 @@ suite('ConfigurationModel', () => { let model2 = new ConfigurationModel({ 'a': { 'b': 2 } }, ['a.b'], [{ identifiers: ['c'], contents: { 'a': { 'e': 2 } }, keys: ['a'] }]).freeze(); let result = new ConfigurationModel().merge(model1, model2); - assert.deepEqual(result.contents, { 'a': { 'b': 2 }, 'f': 1 }); - assert.deepEqual(result.overrides, [{ identifiers: ['c'], contents: { 'a': { 'd': 1, 'e': 2 } }, keys: ['a'] }]); - assert.deepEqual(result.override('c').contents, { 'a': { 'b': 2, 'd': 1, 'e': 2 }, 'f': 1 }); - assert.deepEqual(result.keys, ['a.b', 'f']); + assert.deepStrictEqual(result.contents, { 'a': { 'b': 2 }, 'f': 1 }); + assert.deepStrictEqual(result.overrides, [{ identifiers: ['c'], contents: { 'a': { 'd': 1, 'e': 2 } }, keys: ['a'] }]); + assert.deepStrictEqual(result.override('c').contents, { 'a': { 'b': 2, 'd': 1, 'e': 2 }, 'f': 1 }); + assert.deepStrictEqual(result.keys, ['a.b', 'f']); }); test('Test contents while getting an existing property', () => { let testObject = new ConfigurationModel({ 'a': 1 }); - assert.deepEqual(testObject.getValue('a'), 1); + assert.deepStrictEqual(testObject.getValue('a'), 1); testObject = new ConfigurationModel({ 'a': { 'b': 1 } }); - assert.deepEqual(testObject.getValue('a'), { 'b': 1 }); + assert.deepStrictEqual(testObject.getValue('a'), { 'b': 1 }); }); test('Test contents are undefined for non existing properties', () => { const testObject = new ConfigurationModel({ awesome: true }); - assert.deepEqual(testObject.getValue('unknownproperty'), undefined); + assert.deepStrictEqual(testObject.getValue('unknownproperty'), undefined); }); test('Test override gives all content merged with overrides', () => { const testObject = new ConfigurationModel({ 'a': 1, 'c': 1 }, [], [{ identifiers: ['b'], contents: { 'a': 2 }, keys: ['a'] }]); - assert.deepEqual(testObject.override('b').contents, { 'a': 2, 'c': 1 }); + assert.deepStrictEqual(testObject.override('b').contents, { 'a': 2, 'c': 1 }); }); }); @@ -237,96 +242,96 @@ suite('CustomConfigurationModel', () => { test('simple merge using models', () => { let base = new ConfigurationModelParser('base'); - base.parseContent(JSON.stringify({ 'a': 1, 'b': 2 })); + base.parse(JSON.stringify({ 'a': 1, 'b': 2 })); let add = new ConfigurationModelParser('add'); - add.parseContent(JSON.stringify({ 'a': 3, 'c': 4 })); + add.parse(JSON.stringify({ 'a': 3, 'c': 4 })); let result = base.configurationModel.merge(add.configurationModel); - assert.deepEqual(result.contents, { 'a': 3, 'b': 2, 'c': 4 }); + assert.deepStrictEqual(result.contents, { 'a': 3, 'b': 2, 'c': 4 }); }); test('simple merge with an undefined contents', () => { let base = new ConfigurationModelParser('base'); - base.parseContent(JSON.stringify({ 'a': 1, 'b': 2 })); + base.parse(JSON.stringify({ 'a': 1, 'b': 2 })); let add = new ConfigurationModelParser('add'); let result = base.configurationModel.merge(add.configurationModel); - assert.deepEqual(result.contents, { 'a': 1, 'b': 2 }); + assert.deepStrictEqual(result.contents, { 'a': 1, 'b': 2 }); base = new ConfigurationModelParser('base'); add = new ConfigurationModelParser('add'); - add.parseContent(JSON.stringify({ 'a': 1, 'b': 2 })); + add.parse(JSON.stringify({ 'a': 1, 'b': 2 })); result = base.configurationModel.merge(add.configurationModel); - assert.deepEqual(result.contents, { 'a': 1, 'b': 2 }); + assert.deepStrictEqual(result.contents, { 'a': 1, 'b': 2 }); base = new ConfigurationModelParser('base'); add = new ConfigurationModelParser('add'); result = base.configurationModel.merge(add.configurationModel); - assert.deepEqual(result.contents, {}); + assert.deepStrictEqual(result.contents, {}); }); test('Recursive merge using config models', () => { let base = new ConfigurationModelParser('base'); - base.parseContent(JSON.stringify({ 'a': { 'b': 1 } })); + base.parse(JSON.stringify({ 'a': { 'b': 1 } })); let add = new ConfigurationModelParser('add'); - add.parseContent(JSON.stringify({ 'a': { 'b': 2 } })); + add.parse(JSON.stringify({ 'a': { 'b': 2 } })); let result = base.configurationModel.merge(add.configurationModel); - assert.deepEqual(result.contents, { 'a': { 'b': 2 } }); + assert.deepStrictEqual(result.contents, { 'a': { 'b': 2 } }); }); test('Test contents while getting an existing property', () => { let testObject = new ConfigurationModelParser('test'); - testObject.parseContent(JSON.stringify({ 'a': 1 })); - assert.deepEqual(testObject.configurationModel.getValue('a'), 1); + testObject.parse(JSON.stringify({ 'a': 1 })); + assert.deepStrictEqual(testObject.configurationModel.getValue('a'), 1); - testObject.parseContent(JSON.stringify({ 'a': { 'b': 1 } })); - assert.deepEqual(testObject.configurationModel.getValue('a'), { 'b': 1 }); + testObject.parse(JSON.stringify({ 'a': { 'b': 1 } })); + assert.deepStrictEqual(testObject.configurationModel.getValue('a'), { 'b': 1 }); }); test('Test contents are undefined for non existing properties', () => { const testObject = new ConfigurationModelParser('test'); - testObject.parseContent(JSON.stringify({ + testObject.parse(JSON.stringify({ awesome: true })); - assert.deepEqual(testObject.configurationModel.getValue('unknownproperty'), undefined); + assert.deepStrictEqual(testObject.configurationModel.getValue('unknownproperty'), undefined); }); test('Test contents are undefined for undefined config', () => { const testObject = new ConfigurationModelParser('test'); - assert.deepEqual(testObject.configurationModel.getValue('unknownproperty'), undefined); + assert.deepStrictEqual(testObject.configurationModel.getValue('unknownproperty'), undefined); }); test('Test configWithOverrides gives all content merged with overrides', () => { const testObject = new ConfigurationModelParser('test'); - testObject.parseContent(JSON.stringify({ 'a': 1, 'c': 1, '[b]': { 'a': 2 } })); + testObject.parse(JSON.stringify({ 'a': 1, 'c': 1, '[b]': { 'a': 2 } })); - assert.deepEqual(testObject.configurationModel.override('b').contents, { 'a': 2, 'c': 1, '[b]': { 'a': 2 } }); + assert.deepStrictEqual(testObject.configurationModel.override('b').contents, { 'a': 2, 'c': 1, '[b]': { 'a': 2 } }); }); test('Test configWithOverrides gives empty contents', () => { const testObject = new ConfigurationModelParser('test'); - assert.deepEqual(testObject.configurationModel.override('b').contents, {}); + assert.deepStrictEqual(testObject.configurationModel.override('b').contents, {}); }); test('Test update with empty data', () => { const testObject = new ConfigurationModelParser('test'); - testObject.parseContent(''); + testObject.parse(''); - assert.deepEqual(testObject.configurationModel.contents, {}); - assert.deepEqual(testObject.configurationModel.keys, []); + assert.deepStrictEqual(testObject.configurationModel.contents, Object.create(null)); + assert.deepStrictEqual(testObject.configurationModel.keys, []); - testObject.parseContent(null!); + testObject.parse(null!); - assert.deepEqual(testObject.configurationModel.contents, {}); - assert.deepEqual(testObject.configurationModel.keys, []); + assert.deepStrictEqual(testObject.configurationModel.contents, Object.create(null)); + assert.deepStrictEqual(testObject.configurationModel.keys, []); - testObject.parseContent(undefined!); + testObject.parse(undefined!); - assert.deepEqual(testObject.configurationModel.contents, {}); - assert.deepEqual(testObject.configurationModel.keys, []); + assert.deepStrictEqual(testObject.configurationModel.contents, Object.create(null)); + assert.deepStrictEqual(testObject.configurationModel.keys, []); }); test('Test registering the same property again', () => { @@ -356,7 +361,7 @@ suite('CustomConfigurationModel', () => { } } }); - assert.equal(true, new DefaultConfigurationModel().getValue('a')); + assert.strictEqual(true, new DefaultConfigurationModel().getValue('a')); }); }); @@ -370,28 +375,28 @@ suite('Configuration', () => { const { overrideIdentifiers } = testObject.inspect('a', {}, undefined); - assert.deepEqual(overrideIdentifiers, ['l1', 'l3', 'l4']); + assert.deepStrictEqual(overrideIdentifiers, ['l1', 'l3', 'l4']); }); test('Test update value', () => { const parser = new ConfigurationModelParser('test'); - parser.parseContent(JSON.stringify({ 'a': 1 })); + parser.parse(JSON.stringify({ 'a': 1 })); const testObject: Configuration = new Configuration(parser.configurationModel, new ConfigurationModel()); testObject.updateValue('a', 2); - assert.equal(testObject.getValue('a', {}, undefined), 2); + assert.strictEqual(testObject.getValue('a', {}, undefined), 2); }); test('Test update value after inspect', () => { const parser = new ConfigurationModelParser('test'); - parser.parseContent(JSON.stringify({ 'a': 1 })); + parser.parse(JSON.stringify({ 'a': 1 })); const testObject: Configuration = new Configuration(parser.configurationModel, new ConfigurationModel()); testObject.inspect('a', {}, undefined); testObject.updateValue('a', 2); - assert.equal(testObject.getValue('a', {}, undefined), 2); + assert.strictEqual(testObject.getValue('a', {}, undefined), 2); }); test('Test compare and update default configuration', () => { @@ -407,7 +412,7 @@ suite('Configuration', () => { } }), ['editor.lineNumbers', '[markdown]']); - assert.deepEqual(actual, { keys: ['editor.lineNumbers', '[markdown]'], overrides: [['markdown', ['editor.wordWrap']]] }); + assert.deepStrictEqual(actual, { keys: ['editor.lineNumbers', '[markdown]'], overrides: [['markdown', ['editor.wordWrap']]] }); }); @@ -430,7 +435,7 @@ suite('Configuration', () => { } })); - assert.deepEqual(actual, { keys: ['window.zoomLevel', 'editor.lineNumbers', '[typescript]', 'editor.fontSize'], overrides: [['typescript', ['editor.insertSpaces', 'editor.wordWrap']]] }); + assert.deepStrictEqual(actual, { keys: ['window.zoomLevel', 'editor.lineNumbers', '[typescript]', 'editor.fontSize'], overrides: [['typescript', ['editor.insertSpaces', 'editor.wordWrap']]] }); }); @@ -453,7 +458,7 @@ suite('Configuration', () => { } })); - assert.deepEqual(actual, { keys: ['window.zoomLevel', 'editor.lineNumbers', '[typescript]', 'editor.fontSize'], overrides: [['typescript', ['editor.insertSpaces', 'editor.wordWrap']]] }); + assert.deepStrictEqual(actual, { keys: ['window.zoomLevel', 'editor.lineNumbers', '[typescript]', 'editor.fontSize'], overrides: [['typescript', ['editor.insertSpaces', 'editor.wordWrap']]] }); }); @@ -476,7 +481,7 @@ suite('Configuration', () => { } })); - assert.deepEqual(actual, { keys: ['window.zoomLevel', 'editor.lineNumbers', '[typescript]', 'editor.fontSize'], overrides: [['typescript', ['editor.insertSpaces', 'editor.wordWrap']]] }); + assert.deepStrictEqual(actual, { keys: ['window.zoomLevel', 'editor.lineNumbers', '[typescript]', 'editor.fontSize'], overrides: [['typescript', ['editor.insertSpaces', 'editor.wordWrap']]] }); }); @@ -492,13 +497,13 @@ suite('Configuration', () => { const actual = testObject.compareAndDeleteFolderConfiguration(URI.file('file1')); - assert.deepEqual(actual, { keys: ['editor.lineNumbers', 'editor.fontSize', '[typescript]'], overrides: [['typescript', ['editor.wordWrap']]] }); + assert.deepStrictEqual(actual, { keys: ['editor.lineNumbers', 'editor.fontSize', '[typescript]'], overrides: [['typescript', ['editor.wordWrap']]] }); }); function parseConfigurationModel(content: any): ConfigurationModel { const parser = new ConfigurationModelParser('test'); - parser.parseContent(JSON.stringify(content)); + parser.parse(JSON.stringify(content)); return parser.configurationModel; } @@ -515,7 +520,7 @@ suite('ConfigurationChangeEvent', () => { })); let testObject = new ConfigurationChangeEvent(change, undefined, configuration); - assert.deepEqual(testObject.affectedKeys, ['window.zoomLevel', 'workbench.editor.enablePreview', 'files.autoSave']); + assert.deepStrictEqual(testObject.affectedKeys, ['window.zoomLevel', 'workbench.editor.enablePreview', 'files.autoSave']); assert.ok(testObject.affectsConfiguration('window.zoomLevel')); assert.ok(testObject.affectsConfiguration('window')); @@ -547,7 +552,7 @@ suite('ConfigurationChangeEvent', () => { })); let testObject = new ConfigurationChangeEvent(change, { data }, configuration); - assert.deepEqual(testObject.affectedKeys, ['window.zoomLevel', 'workbench.editor.enablePreview']); + assert.deepStrictEqual(testObject.affectedKeys, ['window.zoomLevel', 'workbench.editor.enablePreview']); assert.ok(testObject.affectsConfiguration('window.zoomLevel')); assert.ok(testObject.affectsConfiguration('window')); @@ -571,7 +576,7 @@ suite('ConfigurationChangeEvent', () => { })); let testObject = new ConfigurationChangeEvent(change, undefined, configuration); - assert.deepEqual(testObject.affectedKeys, ['files.autoSave', '[markdown]', 'editor.wordWrap']); + assert.deepStrictEqual(testObject.affectedKeys, ['files.autoSave', '[markdown]', 'editor.wordWrap']); assert.ok(testObject.affectsConfiguration('files')); assert.ok(testObject.affectsConfiguration('files.autoSave')); @@ -613,7 +618,7 @@ suite('ConfigurationChangeEvent', () => { })); let testObject = new ConfigurationChangeEvent(change, { data }, configuration); - assert.deepEqual(testObject.affectedKeys, ['window.zoomLevel', '[markdown]', 'workbench.editor.enablePreview', 'editor.fontSize']); + assert.deepStrictEqual(testObject.affectedKeys, ['window.zoomLevel', '[markdown]', 'workbench.editor.enablePreview', 'editor.fontSize']); assert.ok(!testObject.affectsConfiguration('files')); @@ -657,7 +662,7 @@ suite('ConfigurationChangeEvent', () => { ); let testObject = new ConfigurationChangeEvent(change, { data, workspace }, configuration, workspace); - assert.deepEqual(testObject.affectedKeys, ['window.title', 'window.zoomLevel', 'window.restoreFullscreen', 'workbench.editor.enablePreview', 'window.restoreWindows']); + assert.deepStrictEqual(testObject.affectedKeys, ['window.title', 'window.zoomLevel', 'window.restoreFullscreen', 'workbench.editor.enablePreview', 'window.restoreWindows']); assert.ok(testObject.affectsConfiguration('window.zoomLevel')); assert.ok(testObject.affectsConfiguration('window.zoomLevel', { resource: URI.file('folder1') })); @@ -755,7 +760,7 @@ suite('ConfigurationChangeEvent', () => { const workspace = new Workspace('a', [new WorkspaceFolder({ index: 0, name: 'a', uri: URI.file('file1') }), new WorkspaceFolder({ index: 1, name: 'b', uri: URI.file('file2') }), new WorkspaceFolder({ index: 2, name: 'c', uri: URI.file('folder3') })]); const testObject = new ConfigurationChangeEvent(change, { data, workspace }, configuration, workspace); - assert.deepEqual(testObject.affectedKeys, ['editor.lineNumbers', '[markdown]', '[json]', 'window.title', 'window.zoomLevel', 'window.restoreFullscreen', 'workbench.editor.enablePreview', 'window.restoreWindows', 'editor.wordWrap']); + assert.deepStrictEqual(testObject.affectedKeys, ['editor.lineNumbers', '[markdown]', '[json]', 'window.title', 'window.zoomLevel', 'window.restoreFullscreen', 'workbench.editor.enablePreview', 'window.restoreWindows', 'editor.wordWrap']); assert.ok(testObject.affectsConfiguration('window.title')); assert.ok(testObject.affectsConfiguration('window.title', { resource: URI.file('file1') })); @@ -841,7 +846,7 @@ suite('ConfigurationChangeEvent', () => { })); let testObject = new ConfigurationChangeEvent(change, undefined, configuration); - assert.deepEqual(testObject.affectedKeys, ['launch', 'launch.version', 'tasks']); + assert.deepStrictEqual(testObject.affectedKeys, ['launch', 'launch.version', 'tasks']); assert.ok(testObject.affectsConfiguration('launch')); assert.ok(testObject.affectsConfiguration('launch.version')); assert.ok(testObject.affectsConfiguration('tasks')); @@ -870,7 +875,7 @@ suite('AllKeysConfigurationChangeEvent', () => { const workspace = new Workspace('a', [new WorkspaceFolder({ index: 0, name: 'a', uri: URI.file('file1') }), new WorkspaceFolder({ index: 1, name: 'b', uri: URI.file('file2') }), new WorkspaceFolder({ index: 2, name: 'c', uri: URI.file('folder3') })]); let testObject = new AllKeysConfigurationChangeEvent(configuration, workspace, ConfigurationTarget.USER, null); - assert.deepEqual(testObject.affectedKeys, ['editor.lineNumbers', '[markdown]', '[json]', 'window.title', 'window.zoomLevel', 'window.restoreFullscreen', 'workbench.editor.enablePreview', 'window.restoreWindows']); + assert.deepStrictEqual(testObject.affectedKeys, ['editor.lineNumbers', '[markdown]', '[json]', 'window.title', 'window.zoomLevel', 'window.restoreFullscreen', 'workbench.editor.enablePreview', 'window.restoreWindows']); assert.ok(testObject.affectsConfiguration('window.title')); assert.ok(testObject.affectsConfiguration('window.title', { resource: URI.file('file1') })); @@ -946,6 +951,6 @@ suite('AllKeysConfigurationChangeEvent', () => { function toConfigurationModel(obj: any): ConfigurationModel { const parser = new ConfigurationModelParser('test'); - parser.parseContent(JSON.stringify(obj)); + parser.parse(JSON.stringify(obj)); return parser.configurationModel; } diff --git a/src/vs/platform/configuration/test/common/configurationRegistry.test.ts b/src/vs/platform/configuration/test/common/configurationRegistry.test.ts index 1c1d8fff..9f52ef24 100644 --- a/src/vs/platform/configuration/test/common/configurationRegistry.test.ts +++ b/src/vs/platform/configuration/test/common/configurationRegistry.test.ts @@ -24,15 +24,15 @@ suite('ConfigurationRegistry', () => { configurationRegistry.registerDefaultConfigurations([{ 'config': { a: 1, b: 2 } }]); configurationRegistry.registerDefaultConfigurations([{ '[lang]': { a: 2, c: 3 } }]); - assert.deepEqual(configurationRegistry.getConfigurationProperties()['config'].default, { a: 1, b: 2 }); - assert.deepEqual(configurationRegistry.getConfigurationProperties()['[lang]'].default, { a: 2, c: 3 }); + assert.deepStrictEqual(configurationRegistry.getConfigurationProperties()['config'].default, { a: 1, b: 2 }); + assert.deepStrictEqual(configurationRegistry.getConfigurationProperties()['[lang]'].default, { a: 2, c: 3 }); }); test('configuration override defaults - merges defaults', async () => { configurationRegistry.registerDefaultConfigurations([{ '[lang]': { a: 1, b: 2 } }]); configurationRegistry.registerDefaultConfigurations([{ '[lang]': { a: 2, c: 3 } }]); - assert.deepEqual(configurationRegistry.getConfigurationProperties()['[lang]'].default, { a: 2, b: 2, c: 3 }); + assert.deepStrictEqual(configurationRegistry.getConfigurationProperties()['[lang]'].default, { a: 2, b: 2, c: 3 }); }); test('configuration defaults - overrides defaults', async () => { @@ -48,6 +48,6 @@ suite('ConfigurationRegistry', () => { configurationRegistry.registerDefaultConfigurations([{ 'config': { a: 1, b: 2 } }]); configurationRegistry.registerDefaultConfigurations([{ 'config': { a: 2, c: 3 } }]); - assert.deepEqual(configurationRegistry.getConfigurationProperties()['config'].default, { a: 2, c: 3 }); + assert.deepStrictEqual(configurationRegistry.getConfigurationProperties()['config'].default, { a: 2, c: 3 }); }); }); diff --git a/src/vs/platform/configuration/test/common/configurationService.test.ts b/src/vs/platform/configuration/test/common/configurationService.test.ts index ee4dcdfa..7e83b2a8 100644 --- a/src/vs/platform/configuration/test/common/configurationService.test.ts +++ b/src/vs/platform/configuration/test/common/configurationService.test.ts @@ -43,7 +43,7 @@ suite('ConfigurationService', () => { }>(); assert.ok(config); - assert.equal(config.foo, 'bar'); + assert.strictEqual(config.foo, 'bar'); }); test('config gets flattened', async () => { @@ -62,7 +62,7 @@ suite('ConfigurationService', () => { assert.ok(config); assert.ok(config.testworkbench); assert.ok(config.testworkbench.editor); - assert.equal(config.testworkbench.editor.tabs, true); + assert.strictEqual(config.testworkbench.editor.tabs, true); }); test('error case does not explode', async () => { @@ -91,7 +91,7 @@ suite('ConfigurationService', () => { await testObject.initialize(); return new Promise(async (c) => { disposables.add(Event.filter(testObject.onDidChangeConfiguration, e => e.source === ConfigurationTarget.USER)(() => { - assert.equal(testObject.getValue('foo'), 'bar'); + assert.strictEqual(testObject.getValue('foo'), 'bar'); c(); })); await fileService.writeFile(settingsResource, VSBuffer.fromString('{ "foo": "bar" }')); @@ -106,7 +106,7 @@ suite('ConfigurationService', () => { return new Promise((c) => { disposables.add(Event.filter(testObject.onDidChangeConfiguration, e => e.source === ConfigurationTarget.USER)(async (e) => { - assert.equal(testObject.getValue('foo'), 'barz'); + assert.strictEqual(testObject.getValue('foo'), 'barz'); c(); })); fileService.writeFile(settingsResource, VSBuffer.fromString('{ "foo": "barz" }')); @@ -122,7 +122,7 @@ suite('ConfigurationService', () => { foo: string; }>(); assert.ok(config); - assert.equal(config.foo, 'bar'); + assert.strictEqual(config.foo, 'bar'); await fileService.writeFile(settingsResource, VSBuffer.fromString('{ "foo": "changed" }')); // force a reload to get latest @@ -131,7 +131,7 @@ suite('ConfigurationService', () => { foo: string; }>(); assert.ok(config); - assert.equal(config.foo, 'changed'); + assert.strictEqual(config.foo, 'changed'); }); test('model defaults', async () => { @@ -160,7 +160,7 @@ suite('ConfigurationService', () => { let setting = testObject.getValue(); assert.ok(setting); - assert.equal(setting.configuration.service.testSetting, 'isSet'); + assert.strictEqual(setting.configuration.service.testSetting, 'isSet'); await fileService.writeFile(settingsResource, VSBuffer.fromString('{ "testworkbench.editor.tabs": true }')); testObject = disposables.add(new ConfigurationService(settingsResource, fileService)); @@ -168,14 +168,14 @@ suite('ConfigurationService', () => { setting = testObject.getValue(); assert.ok(setting); - assert.equal(setting.configuration.service.testSetting, 'isSet'); + assert.strictEqual(setting.configuration.service.testSetting, 'isSet'); await fileService.writeFile(settingsResource, VSBuffer.fromString('{ "configuration.service.testSetting": "isChanged" }')); await testObject.reloadConfiguration(); setting = testObject.getValue(); assert.ok(setting); - assert.equal(setting.configuration.service.testSetting, 'isChanged'); + assert.strictEqual(setting.configuration.service.testSetting, 'isChanged'); }); test('lookup', async () => { diff --git a/src/vs/platform/contextkey/browser/contextKeyService.ts b/src/vs/platform/contextkey/browser/contextKeyService.ts index e4001631..34cf6f57 100644 --- a/src/vs/platform/contextkey/browser/contextKeyService.ts +++ b/src/vs/platform/contextkey/browser/contextKeyService.ts @@ -75,19 +75,19 @@ class NullContext extends Context { super(-1, null); } - public setValue(key: string, value: any): boolean { + public override setValue(key: string, value: any): boolean { return false; } - public removeValue(key: string): boolean { + public override removeValue(key: string): boolean { return false; } - public getValue(key: string): T | undefined { + public override getValue(key: string): T | undefined { return undefined; } - collectAllValues(): { [key: string]: any; } { + override collectAllValues(): { [key: string]: any; } { return Object.create(null); } } @@ -137,7 +137,7 @@ class ConfigAwareContextValuesContainer extends Context { this._listener.dispose(); } - getValue(key: string): any { + override getValue(key: string): any { if (key.indexOf(ConfigAwareContextValuesContainer._keyPrefix) !== 0) { return super.getValue(key); @@ -168,15 +168,15 @@ class ConfigAwareContextValuesContainer extends Context { return value; } - setValue(key: string, value: any): boolean { + override setValue(key: string, value: any): boolean { return super.setValue(key, value); } - removeValue(key: string): boolean { + override removeValue(key: string): boolean { return super.removeValue(key); } - collectAllValues(): { [key: string]: any; } { + override collectAllValues(): { [key: string]: any; } { const result: { [key: string]: any } = Object.create(null); this._values.forEach((value, index) => result[index] = value); return { ...result, ...super.collectAllValues() }; diff --git a/src/vs/platform/contextkey/common/contextkey.ts b/src/vs/platform/contextkey/common/contextkey.ts index af2326a7..749573dc 100644 --- a/src/vs/platform/contextkey/common/contextkey.ts +++ b/src/vs/platform/contextkey/common/contextkey.ts @@ -339,7 +339,7 @@ export class ContextKeyDefinedExpr implements IContextKeyExpression { public readonly type = ContextKeyExprType.Defined; - protected constructor(protected readonly key: string) { + protected constructor(readonly key: string) { } public cmp(other: ContextKeyExpression): number { @@ -1273,7 +1273,7 @@ export class RawContextKey extends ContextKeyDefinedExpr { private readonly _defaultValue: T | undefined; - constructor(readonly key: string, defaultValue: T | undefined, metaOrHide?: string | true | { type: string, description: string }) { + constructor(key: string, defaultValue: T | undefined, metaOrHide?: string | true | { type: string, description: string }) { super(key); this._defaultValue = defaultValue; diff --git a/src/vs/platform/contextview/browser/contextMenuHandler.ts b/src/vs/platform/contextview/browser/contextMenuHandler.ts index 0b515dc5..bf0652d0 100644 --- a/src/vs/platform/contextview/browser/contextMenuHandler.ts +++ b/src/vs/platform/contextview/browser/contextMenuHandler.ts @@ -18,6 +18,7 @@ import { EventType, $, isHTMLElement } from 'vs/base/browser/dom'; import { attachMenuStyler } from 'vs/platform/theme/common/styler'; import { domEvent } from 'vs/base/browser/event'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; +import { isPromiseCanceledError } from 'vs/base/common/errors'; export interface IContextMenuHandlerOptions { blockMouse: boolean; @@ -145,9 +146,7 @@ export class ContextMenuHandler { } private onActionRun(e: IRunEvent): void { - if (this.telemetryService) { - this.telemetryService.publicLog2('workbenchActionExecuted', { id: e.action.id, from: 'contextMenu' }); - } + this.telemetryService.publicLog2('workbenchActionExecuted', { id: e.action.id, from: 'contextMenu' }); this.contextViewService.hideContextView(false); @@ -158,7 +157,7 @@ export class ContextMenuHandler { } private onDidActionRun(e: IRunEvent): void { - if (e.error) { + if (e.error && !isPromiseCanceledError(e.error)) { this.notificationService.error(e.error); } } diff --git a/src/vs/platform/debug/common/extensionHostDebug.ts b/src/vs/platform/debug/common/extensionHostDebug.ts index b30c4e44..6b75bdf2 100644 --- a/src/vs/platform/debug/common/extensionHostDebug.ts +++ b/src/vs/platform/debug/common/extensionHostDebug.ts @@ -5,7 +5,6 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { Event } from 'vs/base/common/event'; -import { IProcessEnvironment } from 'vs/base/common/platform'; export const IExtensionHostDebugService = createDecorator('extensionHostDebugService'); @@ -30,6 +29,14 @@ export interface ICloseSessionEvent { export interface IOpenExtensionWindowResult { rendererDebugPort?: number; + success: boolean; +} + +/** + * Like a IProcessEnvironment, but the value "null" deletes an environment variable + */ +export interface INullableProcessEnvironment { + [key: string]: string | null; } export interface IExtensionHostDebugService { @@ -47,5 +54,5 @@ export interface IExtensionHostDebugService { terminateSession(sessionId: string, subId?: string): void; readonly onTerminateSession: Event; - openExtensionDevelopmentHostWindow(args: string[], env: IProcessEnvironment, debugRenderer: boolean): Promise; + openExtensionDevelopmentHostWindow(args: string[], env: INullableProcessEnvironment | undefined, debugRenderer: boolean): Promise; } diff --git a/src/vs/platform/debug/common/extensionHostDebugIpc.ts b/src/vs/platform/debug/common/extensionHostDebugIpc.ts index 09c2a191..d7bb1194 100644 --- a/src/vs/platform/debug/common/extensionHostDebugIpc.ts +++ b/src/vs/platform/debug/common/extensionHostDebugIpc.ts @@ -4,10 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import { IServerChannel, IChannel } from 'vs/base/parts/ipc/common/ipc'; -import { IReloadSessionEvent, ICloseSessionEvent, IAttachSessionEvent, ITerminateSessionEvent, IExtensionHostDebugService, IOpenExtensionWindowResult } from 'vs/platform/debug/common/extensionHostDebug'; +import { IReloadSessionEvent, ICloseSessionEvent, IAttachSessionEvent, ITerminateSessionEvent, IExtensionHostDebugService, IOpenExtensionWindowResult, INullableProcessEnvironment } from 'vs/platform/debug/common/extensionHostDebug'; import { Event, Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; -import { IProcessEnvironment } from 'vs/base/common/platform'; export class ExtensionHostDebugBroadcastChannel implements IServerChannel { @@ -87,7 +86,7 @@ export class ExtensionHostDebugChannelClient extends Disposable implements IExte return this.channel.listen('terminate'); } - openExtensionDevelopmentHostWindow(args: string[], env: IProcessEnvironment, debugRenderer: boolean): Promise { - return this.channel.call('openExtensionDevelopmentHostWindow', [args, env, debugRenderer]); + openExtensionDevelopmentHostWindow(args: string[], env: INullableProcessEnvironment | undefined, debugRenderer: boolean): Promise { + return this.channel.call('openExtensionDevelopmentHostWindow', [args, env || {}, debugRenderer]); } } diff --git a/src/vs/platform/debug/electron-main/extensionHostDebugIpc.ts b/src/vs/platform/debug/electron-main/extensionHostDebugIpc.ts index 9d37b279..5c814347 100644 --- a/src/vs/platform/debug/electron-main/extensionHostDebugIpc.ts +++ b/src/vs/platform/debug/electron-main/extensionHostDebugIpc.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IOpenExtensionWindowResult } from 'vs/platform/debug/common/extensionHostDebug'; +import { INullableProcessEnvironment, IOpenExtensionWindowResult } from 'vs/platform/debug/common/extensionHostDebug'; import { IProcessEnvironment } from 'vs/base/common/platform'; import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv'; import { createServer, AddressInfo } from 'net'; @@ -16,7 +16,7 @@ export class ElectronExtensionHostDebugBroadcastChannel extends Extens super(); } - call(ctx: TContext, command: string, arg?: any): Promise { + override call(ctx: TContext, command: string, arg?: any): Promise { if (command === 'openExtensionDevelopmentHostWindow') { return this.openExtensionDevelopmentHostWindow(arg[0], arg[1], arg[2]); } else { @@ -24,28 +24,45 @@ export class ElectronExtensionHostDebugBroadcastChannel extends Extens } } - private async openExtensionDevelopmentHostWindow(args: string[], env: IProcessEnvironment, debugRenderer: boolean): Promise { + private async openExtensionDevelopmentHostWindow(args: string[], env: INullableProcessEnvironment, debugRenderer: boolean): Promise { const pargs = parseArgs(args, OPTIONS); pargs.debugRenderer = debugRenderer; const extDevPaths = pargs.extensionDevelopmentPath; if (!extDevPaths) { - return {}; + return { success: false }; + } + + // split INullableProcessEnvironment into a IProcessEnvironment and an array of keys to be deleted + // TODO: support to delete env vars; currently the "deletes" are ignored + let userEnv: IProcessEnvironment | undefined; + //let userEnvDeletes: string[] = []; + const keys = Object.keys(env); + for (let k of keys) { + let value = env[k]; + if (value === null) { + //userEnvDeletes.push(k); + } else { + if (!userEnv) { + userEnv = Object.create(null) as IProcessEnvironment; + } + userEnv[k] = value; + } } const [codeWindow] = this.windowsMainService.openExtensionDevelopmentHostWindow(extDevPaths, { context: OpenContext.API, cli: pargs, - userEnv: Object.keys(env).length > 0 ? env : undefined + userEnv: userEnv }); if (!debugRenderer) { - return {}; + return { success: true }; } const win = codeWindow.win; if (!win) { - return {}; + return { success: true }; } const debug = win.webContents.debugger; @@ -110,6 +127,6 @@ export class ElectronExtensionHostDebugBroadcastChannel extends Extens await new Promise(r => server.listen(0, r)); win.on('close', () => server.close()); - return { rendererDebugPort: (server.address() as AddressInfo).port }; + return { rendererDebugPort: (server.address() as AddressInfo).port, success: true }; } } diff --git a/src/vs/platform/dialogs/common/dialogs.ts b/src/vs/platform/dialogs/common/dialogs.ts index 25e49ef1..9e373840 100644 --- a/src/vs/platform/dialogs/common/dialogs.ts +++ b/src/vs/platform/dialogs/common/dialogs.ts @@ -9,6 +9,8 @@ import { URI } from 'vs/base/common/uri'; import { basename } from 'vs/base/common/resources'; import { localize } from 'vs/nls'; import { ITelemetryData } from 'vs/platform/telemetry/common/telemetry'; +import { Codicon } from 'vs/base/common/codicons'; +import { IMarkdownString } from 'vs/base/common/htmlContent'; export interface FileFilter { extensions: string[]; @@ -100,6 +102,7 @@ export interface IPickAndOpenOptions { defaultUri?: URI; telemetryExtraData?: ITelemetryData; availableFileSystems?: string[]; + remoteAuthority?: string | null; } export interface ISaveDialogOptions { @@ -177,11 +180,24 @@ export interface IOpenDialogOptions { export const IDialogService = createDecorator('dialogService'); +export interface ICustomDialogOptions { + buttonDetails?: string[]; + markdownDetails?: ICustomDialogMarkdown[]; + classes?: string[]; + icon?: Codicon; + disableCloseAction?: boolean; +} + +export interface ICustomDialogMarkdown { + markdown: IMarkdownString, + classes?: string[] +} + export interface IDialogOptions { cancelId?: number; detail?: string; checkbox?: ICheckbox; - useCustom?: boolean; + custom?: boolean | ICustomDialogOptions; } export interface IInput { diff --git a/src/vs/platform/driver/common/driver.ts b/src/vs/platform/driver/common/driver.ts index bae76735..b444cb31 100644 --- a/src/vs/platform/driver/common/driver.ts +++ b/src/vs/platform/driver/common/driver.ts @@ -54,3 +54,12 @@ export interface IWindowDriver { getTerminalBuffer(selector: string): Promise; writeInTerminal(selector: string, text: string): Promise; } + +export interface IDriverOptions { + verbose: boolean; +} + +export interface IWindowDriverRegistry { + registerWindowDriver(windowId: number): Promise; + reloadWindowDriver(windowId: number): Promise; +} diff --git a/src/vs/platform/driver/common/driverIpc.ts b/src/vs/platform/driver/common/driverIpc.ts new file mode 100644 index 00000000..f492817c --- /dev/null +++ b/src/vs/platform/driver/common/driverIpc.ts @@ -0,0 +1,96 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Event } from 'vs/base/common/event'; +import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; +import { IDriverOptions, IElement, IWindowDriver, IWindowDriverRegistry } from 'vs/platform/driver/common/driver'; + +export class WindowDriverChannel implements IServerChannel { + + constructor(private driver: IWindowDriver) { } + + listen(_: unknown, event: string): Event { + throw new Error(`No event found: ${event}`); + } + + call(_: unknown, command: string, arg?: any): Promise { + switch (command) { + case 'click': return this.driver.click(arg[0], arg[1], arg[2]); + case 'doubleClick': return this.driver.doubleClick(arg); + case 'setValue': return this.driver.setValue(arg[0], arg[1]); + case 'getTitle': return this.driver.getTitle(); + case 'isActiveElement': return this.driver.isActiveElement(arg); + case 'getElements': return this.driver.getElements(arg[0], arg[1]); + case 'getElementXY': return this.driver.getElementXY(arg[0], arg[1], arg[2]); + case 'typeInEditor': return this.driver.typeInEditor(arg[0], arg[1]); + case 'getTerminalBuffer': return this.driver.getTerminalBuffer(arg); + case 'writeInTerminal': return this.driver.writeInTerminal(arg[0], arg[1]); + } + + throw new Error(`Call not found: ${command}`); + } +} + +export class WindowDriverChannelClient implements IWindowDriver { + + declare readonly _serviceBrand: undefined; + + constructor(private channel: IChannel) { } + + click(selector: string, xoffset?: number, yoffset?: number): Promise { + return this.channel.call('click', [selector, xoffset, yoffset]); + } + + doubleClick(selector: string): Promise { + return this.channel.call('doubleClick', selector); + } + + setValue(selector: string, text: string): Promise { + return this.channel.call('setValue', [selector, text]); + } + + getTitle(): Promise { + return this.channel.call('getTitle'); + } + + isActiveElement(selector: string): Promise { + return this.channel.call('isActiveElement', selector); + } + + getElements(selector: string, recursive: boolean): Promise { + return this.channel.call('getElements', [selector, recursive]); + } + + getElementXY(selector: string, xoffset?: number, yoffset?: number): Promise<{ x: number, y: number }> { + return this.channel.call('getElementXY', [selector, xoffset, yoffset]); + } + + typeInEditor(selector: string, text: string): Promise { + return this.channel.call('typeInEditor', [selector, text]); + } + + getTerminalBuffer(selector: string): Promise { + return this.channel.call('getTerminalBuffer', selector); + } + + writeInTerminal(selector: string, text: string): Promise { + return this.channel.call('writeInTerminal', [selector, text]); + } +} + +export class WindowDriverRegistryChannelClient implements IWindowDriverRegistry { + + declare readonly _serviceBrand: undefined; + + constructor(private channel: IChannel) { } + + registerWindowDriver(windowId: number): Promise { + return this.channel.call('registerWindowDriver', windowId); + } + + reloadWindowDriver(windowId: number): Promise { + return this.channel.call('reloadWindowDriver', windowId); + } +} diff --git a/src/vs/platform/driver/electron-main/driver.ts b/src/vs/platform/driver/electron-main/driver.ts index f2f76df8..f33718e8 100644 --- a/src/vs/platform/driver/electron-main/driver.ts +++ b/src/vs/platform/driver/electron-main/driver.ts @@ -3,7 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { DriverChannel, WindowDriverChannelClient, IWindowDriverRegistry, WindowDriverRegistryChannel, IDriverOptions } from 'vs/platform/driver/node/driver'; +import { DriverChannel, WindowDriverRegistryChannel } from 'vs/platform/driver/node/driver'; +import { WindowDriverChannelClient } from 'vs/platform/driver/common/driverIpc'; import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows'; import { serve as serveNet } from 'vs/base/parts/ipc/node/ipc.net'; import { combinedDisposable, IDisposable } from 'vs/base/common/lifecycle'; @@ -17,7 +18,7 @@ import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/e import { ScanCodeBinding } from 'vs/base/common/scanCode'; import { KeybindingParser } from 'vs/base/common/keybindingParser'; import { timeout } from 'vs/base/common/async'; -import { IDriver, IElement, IWindowDriver } from 'vs/platform/driver/common/driver'; +import { IDriver, IDriverOptions, IElement, IWindowDriver, IWindowDriverRegistry } from 'vs/platform/driver/common/driver'; import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { INativeHostMainService } from 'vs/platform/native/electron-main/nativeHostMainService'; diff --git a/src/vs/platform/driver/electron-browser/driver.ts b/src/vs/platform/driver/electron-sandbox/driver.ts similarity index 98% rename from src/vs/platform/driver/electron-browser/driver.ts rename to src/vs/platform/driver/electron-sandbox/driver.ts index 169d7365..9bcf61b1 100644 --- a/src/vs/platform/driver/electron-browser/driver.ts +++ b/src/vs/platform/driver/electron-sandbox/driver.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { WindowDriverChannel, WindowDriverRegistryChannelClient } from 'vs/platform/driver/node/driver'; +import { WindowDriverChannel, WindowDriverRegistryChannelClient } from 'vs/platform/driver/common/driverIpc'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/services'; import { timeout } from 'vs/base/common/async'; diff --git a/src/vs/platform/driver/node/driver.ts b/src/vs/platform/driver/node/driver.ts index 185100ef..ba435b01 100644 --- a/src/vs/platform/driver/node/driver.ts +++ b/src/vs/platform/driver/node/driver.ts @@ -7,7 +7,7 @@ import { Client } from 'vs/base/parts/ipc/common/ipc.net'; import { connect as connectNet } from 'vs/base/parts/ipc/node/ipc.net'; import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; import { Event } from 'vs/base/common/event'; -import { IDriver, IElement, IWindowDriver } from 'vs/platform/driver/common/driver'; +import { IDriver, IElement, IWindowDriverRegistry } from 'vs/platform/driver/common/driver'; export class DriverChannel implements IServerChannel { @@ -107,15 +107,6 @@ export class DriverChannelClient implements IDriver { } } -export interface IDriverOptions { - verbose: boolean; -} - -export interface IWindowDriverRegistry { - registerWindowDriver(windowId: number): Promise; - reloadWindowDriver(windowId: number): Promise; -} - export class WindowDriverRegistryChannel implements IServerChannel { constructor(private registry: IWindowDriverRegistry) { } @@ -134,94 +125,6 @@ export class WindowDriverRegistryChannel implements IServerChannel { } } -export class WindowDriverRegistryChannelClient implements IWindowDriverRegistry { - - declare readonly _serviceBrand: undefined; - - constructor(private channel: IChannel) { } - - registerWindowDriver(windowId: number): Promise { - return this.channel.call('registerWindowDriver', windowId); - } - - reloadWindowDriver(windowId: number): Promise { - return this.channel.call('reloadWindowDriver', windowId); - } -} - -export class WindowDriverChannel implements IServerChannel { - - constructor(private driver: IWindowDriver) { } - - listen(_: unknown, event: string): Event { - throw new Error(`No event found: ${event}`); - } - - call(_: unknown, command: string, arg?: any): Promise { - switch (command) { - case 'click': return this.driver.click(arg[0], arg[1], arg[2]); - case 'doubleClick': return this.driver.doubleClick(arg); - case 'setValue': return this.driver.setValue(arg[0], arg[1]); - case 'getTitle': return this.driver.getTitle(); - case 'isActiveElement': return this.driver.isActiveElement(arg); - case 'getElements': return this.driver.getElements(arg[0], arg[1]); - case 'getElementXY': return this.driver.getElementXY(arg[0], arg[1], arg[2]); - case 'typeInEditor': return this.driver.typeInEditor(arg[0], arg[1]); - case 'getTerminalBuffer': return this.driver.getTerminalBuffer(arg); - case 'writeInTerminal': return this.driver.writeInTerminal(arg[0], arg[1]); - } - - throw new Error(`Call not found: ${command}`); - } -} - -export class WindowDriverChannelClient implements IWindowDriver { - - declare readonly _serviceBrand: undefined; - - constructor(private channel: IChannel) { } - - click(selector: string, xoffset?: number, yoffset?: number): Promise { - return this.channel.call('click', [selector, xoffset, yoffset]); - } - - doubleClick(selector: string): Promise { - return this.channel.call('doubleClick', selector); - } - - setValue(selector: string, text: string): Promise { - return this.channel.call('setValue', [selector, text]); - } - - getTitle(): Promise { - return this.channel.call('getTitle'); - } - - isActiveElement(selector: string): Promise { - return this.channel.call('isActiveElement', selector); - } - - getElements(selector: string, recursive: boolean): Promise { - return this.channel.call('getElements', [selector, recursive]); - } - - getElementXY(selector: string, xoffset?: number, yoffset?: number): Promise<{ x: number, y: number }> { - return this.channel.call('getElementXY', [selector, xoffset, yoffset]); - } - - typeInEditor(selector: string, text: string): Promise { - return this.channel.call('typeInEditor', [selector, text]); - } - - getTerminalBuffer(selector: string): Promise { - return this.channel.call('getTerminalBuffer', selector); - } - - writeInTerminal(selector: string, text: string): Promise { - return this.channel.call('writeInTerminal', [selector, text]); - } -} - export async function connect(handle: string): Promise<{ client: Client, driver: IDriver }> { const client = await connectNet(handle, 'driverClient'); const channel = client.getChannel('driver'); diff --git a/src/vs/platform/editor/common/editor.ts b/src/vs/platform/editor/common/editor.ts index d1903158..c5611b87 100644 --- a/src/vs/platform/editor/common/editor.ts +++ b/src/vs/platform/editor/common/editor.ts @@ -9,14 +9,19 @@ import { Event } from 'vs/base/common/event'; export interface IEditorModel { /** - * Emitted when the model is disposed. + * Emitted when the model is about to be disposed. */ - readonly onDispose: Event; + readonly onWillDispose: Event; /** - * Loads the model. + * Resolves the model. */ - load(): Promise; + resolve(): Promise; + + /** + * Find out if the editor model was resolved or not. + */ + isResolved(): boolean; /** * Find out if this model has been disposed. @@ -65,6 +70,23 @@ export interface IBaseResourceEditorInput { readonly forceUntitled?: boolean; } +/** + * This identifier allows to uniquely identify an editor with a + * resource and type identifier. + */ +export interface IResourceEditorInputIdentifier { + + /** + * The resource URI of the editor. + */ + readonly resource: URI; + + /** + * The type of the editor. + */ + readonly typeId: string; +} + export interface IResourceEditorInput extends IBaseResourceEditorInput { /** diff --git a/src/vs/platform/environment/common/argv.ts b/src/vs/platform/environment/common/argv.ts index 48941c08..2628fa72 100644 --- a/src/vs/platform/environment/common/argv.ts +++ b/src/vs/platform/environment/common/argv.ts @@ -39,7 +39,7 @@ export interface NativeParsedArgs { 'extensions-dir'?: string; 'extensions-download-dir'?: string; 'builtin-extensions-dir'?: string; - extensionDevelopmentPath?: string[]; // // undefined or array of 1 or more local paths or URIs + extensionDevelopmentPath?: string[]; // undefined or array of 1 or more local paths or URIs extensionTestsPath?: string; // either a local path or a URI extensionDevelopmentKind?: string[]; 'inspect-extensions'?: string; @@ -98,4 +98,5 @@ export interface NativeParsedArgs { 'ignore-certificate-errors'?: boolean; 'allow-insecure-localhost'?: boolean; 'log-net-log'?: string; + 'vmodule'?: string; } diff --git a/src/vs/platform/environment/electron-main/environmentMainService.ts b/src/vs/platform/environment/electron-main/environmentMainService.ts index 912cf8e9..56705313 100644 --- a/src/vs/platform/environment/electron-main/environmentMainService.ts +++ b/src/vs/platform/environment/electron-main/environmentMainService.ts @@ -25,9 +25,12 @@ export interface IEnvironmentMainService extends INativeEnvironmentService { backupHome: string; backupWorkspacesPath: string; - // --- V8 script cache path + // --- V8 script cache path (ours) nodeCachedDataDir?: string; + // --- V8 script cache path (chrome) + chromeCachedDataDir: string; + // --- IPC mainIPCHandle: string; @@ -66,4 +69,7 @@ export class EnvironmentMainService extends NativeEnvironmentService implements @memoize get nodeCachedDataDir(): string | undefined { return process.env['VSCODE_NODE_CACHED_DATA_DIR'] || undefined; } + + @memoize + get chromeCachedDataDir(): string { return join(this.userDataPath, 'Code Cache'); } } diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index 8b655f51..1b5db388 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -132,6 +132,7 @@ export const OPTIONS: OptionDescriptions> = { 'ignore-certificate-errors': { type: 'boolean' }, 'allow-insecure-localhost': { type: 'boolean' }, 'log-net-log': { type: 'string' }, + 'vmodule': { type: 'string' }, '_urls': { type: 'string[]' }, _: { type: 'string[]' } // main arguments diff --git a/src/vs/platform/environment/node/argvHelper.ts b/src/vs/platform/environment/node/argvHelper.ts index 3ace20cf..4fb88eb7 100644 --- a/src/vs/platform/environment/node/argvHelper.ts +++ b/src/vs/platform/environment/node/argvHelper.ts @@ -8,6 +8,7 @@ import { localize } from 'vs/nls'; import { MIN_MAX_MEMORY_SIZE_MB } from 'vs/platform/files/common/files'; import { parseArgs, ErrorReporter, OPTIONS } from 'vs/platform/environment/node/argv'; import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; +import { IProcessEnvironment } from 'vs/base/common/platform'; function parseAndValidate(cmdLineArgs: string[], reportWarnings: boolean): NativeParsedArgs { const errorReporter: ErrorReporter = { @@ -79,6 +80,6 @@ export function addArg(argv: string[], ...args: string[]): string[] { return argv; } -export function isLaunchedFromCli(env: NodeJS.ProcessEnv): boolean { +export function isLaunchedFromCli(env: IProcessEnvironment): boolean { return env['VSCODE_CLI'] === '1'; } diff --git a/src/vs/platform/environment/node/shellEnv.ts b/src/vs/platform/environment/node/shellEnv.ts index df5ad7eb..091bb7e5 100644 --- a/src/vs/platform/environment/node/shellEnv.ts +++ b/src/vs/platform/environment/node/shellEnv.ts @@ -3,9 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as path from 'path'; import { spawn } from 'child_process'; import { generateUuid } from 'vs/base/common/uuid'; -import { isWindows, platform } from 'vs/base/common/platform'; +import { IProcessEnvironment, isWindows, OS } from 'vs/base/common/platform'; import { ILogService } from 'vs/platform/log/common/log'; import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; import { isLaunchedFromCli } from 'vs/platform/environment/node/argvHelper'; @@ -17,7 +18,7 @@ import { getSystemShell } from 'vs/base/node/shell'; * This should only be done when Code itself is not launched * from within a shell. */ -export async function resolveShellEnv(logService: ILogService, args: NativeParsedArgs, env: NodeJS.ProcessEnv): Promise { +export async function resolveShellEnv(logService: ILogService, args: NativeParsedArgs, env: IProcessEnvironment): Promise { // Skip if --force-disable-user-env if (args['force-disable-user-env']) { @@ -75,31 +76,55 @@ async function doResolveUnixShellEnv(logService: ILogService): Promise; + if (/^pwsh(-preview)?$/.test(name)) { + // Older versions of PowerShell removes double quotes sometimes so we use "double single quotes" which is how + // you escape single quotes inside of a single quoted string. + command = `& '${process.execPath}' -p '''${mark}'' + JSON.stringify(process.env) + ''${mark}'''`; + shellArgs = ['-Login', '-Command']; + } else { + command = `'${process.execPath}' -p '"${mark}" + JSON.stringify(process.env) + "${mark}"'`; + shellArgs = ['-ilc']; + } + + logService.trace('getUnixShellEnvironment#spawn', JSON.stringify(shellArgs), command); + + const child = spawn(systemShellUnix, [...shellArgs, command], { detached: true, - stdio: ['ignore', 'pipe', process.stderr], + stdio: ['ignore', 'pipe', 'pipe'], env }); + child.on('error', err => { + logService.error('getUnixShellEnvironment#errorChildProcess', toErrorMessage(err)); + resolve({}); + }); + const buffers: Buffer[] = []; - child.on('error', () => resolve({})); child.stdout.on('data', b => buffers.push(b)); - child.on('close', code => { - if (code !== 0) { - return reject(new Error('Failed to get environment')); - } + const stderr: Buffer[] = []; + child.stderr.on('data', b => stderr.push(b)); + child.on('close', (code, signal) => { const raw = Buffer.concat(buffers).toString('utf8'); logService.trace('getUnixShellEnvironment#raw', raw); + const stderrStr = Buffer.concat(stderr).toString('utf8'); + if (stderrStr.trim()) { + logService.trace('getUnixShellEnvironment#stderr', stderrStr); + } + + if (code || signal) { + return reject(new Error(`Failed to get environment (code ${code}, signal ${signal})`)); + } + const match = regex.exec(raw); const rawStripped = match ? match[1] : '{}'; @@ -124,7 +149,7 @@ async function doResolveUnixShellEnv(logService: ILogService): Promise { } test('Text should display small columns correctly', () => { - assert.deepEqual( + assert.deepStrictEqual( formatOptions({ 'add': o('bar') }, 80), [' --add bar'] ); - assert.deepEqual( + assert.deepStrictEqual( formatOptions({ 'add': o('bar'), 'wait': o('ba'), @@ -36,7 +36,7 @@ suite('formatOptions', () => { }); test('Text should wrap', () => { - assert.deepEqual( + assert.deepStrictEqual( formatOptions({ 'add': o(('bar ').repeat(9)) }, 40), @@ -47,7 +47,7 @@ suite('formatOptions', () => { }); test('Text should revert to the condensed view when the terminal is too narrow', () => { - assert.deepEqual( + assert.deepStrictEqual( formatOptions({ 'add': o(('bar ').repeat(9)) }, 30), @@ -58,11 +58,11 @@ suite('formatOptions', () => { }); test('addArg', () => { - assert.deepEqual(addArg([], 'foo'), ['foo']); - assert.deepEqual(addArg([], 'foo', 'bar'), ['foo', 'bar']); - assert.deepEqual(addArg(['foo'], 'bar'), ['foo', 'bar']); - assert.deepEqual(addArg(['--wait'], 'bar'), ['--wait', 'bar']); - assert.deepEqual(addArg(['--wait', '--', '--foo'], 'bar'), ['--wait', 'bar', '--', '--foo']); - assert.deepEqual(addArg(['--', '--foo'], 'bar'), ['bar', '--', '--foo']); + assert.deepStrictEqual(addArg([], 'foo'), ['foo']); + assert.deepStrictEqual(addArg([], 'foo', 'bar'), ['foo', 'bar']); + assert.deepStrictEqual(addArg(['foo'], 'bar'), ['foo', 'bar']); + assert.deepStrictEqual(addArg(['--wait'], 'bar'), ['--wait', 'bar']); + assert.deepStrictEqual(addArg(['--wait', '--', '--foo'], 'bar'), ['--wait', 'bar', '--', '--foo']); + assert.deepStrictEqual(addArg(['--', '--foo'], 'bar'), ['bar', '--', '--foo']); }); }); diff --git a/src/vs/platform/extensionManagement/common/extensionManagementCLIService.ts b/src/vs/platform/extensionManagement/common/extensionManagementCLIService.ts index acfde86c..c5fa6e33 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagementCLIService.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagementCLIService.ts @@ -133,7 +133,7 @@ export class ExtensionManagementCLIService implements IExtensionManagementCLISer if (vsixs.length) { await Promise.all(vsixs.map(async vsix => { try { - const manifest = await this.installVSIX(vsix, force, output); + const manifest = await this.installVSIX(vsix, { isBuiltin: false, isMachineScoped }, force, output); if (manifest) { installedExtensionsManifests.push(manifest); } @@ -173,7 +173,7 @@ export class ExtensionManagementCLIService implements IExtensionManagementCLISer } } - private async installVSIX(vsix: URI, force: boolean, output: CLIOutput): Promise { + private async installVSIX(vsix: URI, installOptions: InstallOptions, force: boolean, output: CLIOutput): Promise { const manifest = await this.extensionManagementService.getManifest(vsix); if (!manifest) { @@ -183,7 +183,7 @@ export class ExtensionManagementCLIService implements IExtensionManagementCLISer const valid = await this.validateVSIX(manifest, force, output); if (valid) { try { - await this.extensionManagementService.install(vsix); + await this.extensionManagementService.install(vsix, installOptions); output.log(localize('successVsixInstall', "Extension '{0}' was successfully installed.", getBaseLabel(vsix))); return manifest; } catch (error) { diff --git a/src/vs/platform/extensionManagement/common/extensionTipsService.ts b/src/vs/platform/extensionManagement/common/extensionTipsService.ts index 9f5712ef..410604bd 100644 --- a/src/vs/platform/extensionManagement/common/extensionTipsService.ts +++ b/src/vs/platform/extensionManagement/common/extensionTipsService.ts @@ -4,7 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { URI } from 'vs/base/common/uri'; -import { IProductService, IConfigBasedExtensionTip as IRawConfigBasedExtensionTip } from 'vs/platform/product/common/productService'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { IConfigBasedExtensionTip as IRawConfigBasedExtensionTip } from 'vs/base/common/product'; import { IFileService } from 'vs/platform/files/common/files'; import { isNonEmptyArray } from 'vs/base/common/arrays'; import { IExtensionTipsService, IExecutableBasedExtensionTip, IWorkspaceTips, IConfigBasedExtensionTip } from 'vs/platform/extensionManagement/common/extensionManagement'; diff --git a/src/vs/platform/extensionManagement/electron-sandbox/extensionTipsService.ts b/src/vs/platform/extensionManagement/electron-sandbox/extensionTipsService.ts index 51066d5b..b2d2caa2 100644 --- a/src/vs/platform/extensionManagement/electron-sandbox/extensionTipsService.ts +++ b/src/vs/platform/extensionManagement/electron-sandbox/extensionTipsService.ts @@ -40,7 +40,7 @@ const lastPromptedMediumImpExeTimeStorageKey = 'extensionTips/lastPromptedMedium export class ExtensionTipsService extends BaseExtensionTipsService { - _serviceBrand: any; + override _serviceBrand: any; private readonly highImportanceExecutableTips: Map = new Map(); private readonly mediumImportanceExecutableTips: Map = new Map(); @@ -101,13 +101,13 @@ export class ExtensionTipsService extends BaseExtensionTipsService { }); } - async getImportantExecutableBasedTips(): Promise { + override async getImportantExecutableBasedTips(): Promise { const highImportanceExeTips = await this.getValidExecutableBasedExtensionTips(this.highImportanceExecutableTips); const mediumImportanceExeTips = await this.getValidExecutableBasedExtensionTips(this.mediumImportanceExecutableTips); return [...highImportanceExeTips, ...mediumImportanceExeTips]; } - getOtherExecutableBasedTips(): Promise { + override getOtherExecutableBasedTips(): Promise { return this.getValidExecutableBasedExtensionTips(this.allOtherExecutableTips); } diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index 4a412105..ed907661 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -388,12 +388,11 @@ export class ExtensionManagementService extends Disposable implements IExtension publisherDisplayName: extension.publisherDisplayName, }; - let zipPath; + let zipPath: string | undefined; try { this.logService.trace('Started downloading extension:', extension.identifier.id); - const zip = await this.extensionsDownloader.downloadExtension(extension, operation); + zipPath = (await this.extensionsDownloader.downloadExtension(extension, operation)).fsPath; this.logService.info('Downloaded extension:', extension.identifier.id, zipPath); - zipPath = zip.fsPath; } catch (error) { throw new ExtensionManagementError(this.joinErrors(error).message, INSTALL_ERROR_DOWNLOADING); } diff --git a/src/vs/platform/extensionManagement/test/common/extensionGalleryService.test.ts b/src/vs/platform/extensionManagement/test/common/extensionGalleryService.test.ts index c1085576..b7a319ad 100644 --- a/src/vs/platform/extensionManagement/test/common/extensionGalleryService.test.ts +++ b/src/vs/platform/extensionManagement/test/common/extensionGalleryService.test.ts @@ -19,8 +19,10 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' import { mock } from 'vs/base/test/common/mock'; class EnvironmentServiceMock extends mock() { - constructor(readonly serviceMachineIdResource: URI) { + override readonly serviceMachineIdResource: URI; + constructor(serviceMachineIdResource: URI) { super(); + this.serviceMachineIdResource = serviceMachineIdResource; } } @@ -43,6 +45,6 @@ suite('Extension Gallery Service', () => { const headers = await resolveMarketplaceHeaders(product.version, environmentService, fileService, storageService); assert.ok(isUUID(headers['X-Market-User-Id'])); const headers2 = await resolveMarketplaceHeaders(product.version, environmentService, fileService, storageService); - assert.equal(headers['X-Market-User-Id'], headers2['X-Market-User-Id']); + assert.strictEqual(headers['X-Market-User-Id'], headers2['X-Market-User-Id']); }); }); diff --git a/src/vs/platform/extensionManagement/test/common/extensionManagement.test.ts b/src/vs/platform/extensionManagement/test/common/extensionManagement.test.ts index a9652639..82ae6dd9 100644 --- a/src/vs/platform/extensionManagement/test/common/extensionManagement.test.ts +++ b/src/vs/platform/extensionManagement/test/common/extensionManagement.test.ts @@ -9,21 +9,21 @@ suite('Extension Identifier Pattern', () => { test('extension identifier pattern', () => { const regEx = new RegExp(EXTENSION_IDENTIFIER_PATTERN); - assert.equal(true, regEx.test('publisher.name')); - assert.equal(true, regEx.test('publiSher.name')); - assert.equal(true, regEx.test('publisher.Name')); - assert.equal(true, regEx.test('PUBLISHER.NAME')); - assert.equal(true, regEx.test('PUBLISHEr.NAMe')); - assert.equal(true, regEx.test('PUBLISHEr.N-AMe')); - assert.equal(true, regEx.test('PUB-LISHEr.NAMe')); - assert.equal(true, regEx.test('PUB-LISHEr.N-AMe')); - assert.equal(true, regEx.test('PUBLISH12Er90.N-A54Me123')); - assert.equal(true, regEx.test('111PUBLISH12Er90.N-1111A54Me123')); - assert.equal(false, regEx.test('publishername')); - assert.equal(false, regEx.test('-publisher.name')); - assert.equal(false, regEx.test('publisher.-name')); - assert.equal(false, regEx.test('-publisher.-name')); - assert.equal(false, regEx.test('publ_isher.name')); - assert.equal(false, regEx.test('publisher._name')); + assert.strictEqual(true, regEx.test('publisher.name')); + assert.strictEqual(true, regEx.test('publiSher.name')); + assert.strictEqual(true, regEx.test('publisher.Name')); + assert.strictEqual(true, regEx.test('PUBLISHER.NAME')); + assert.strictEqual(true, regEx.test('PUBLISHEr.NAMe')); + assert.strictEqual(true, regEx.test('PUBLISHEr.N-AMe')); + assert.strictEqual(true, regEx.test('PUB-LISHEr.NAMe')); + assert.strictEqual(true, regEx.test('PUB-LISHEr.N-AMe')); + assert.strictEqual(true, regEx.test('PUBLISH12Er90.N-A54Me123')); + assert.strictEqual(true, regEx.test('111PUBLISH12Er90.N-1111A54Me123')); + assert.strictEqual(false, regEx.test('publishername')); + assert.strictEqual(false, regEx.test('-publisher.name')); + assert.strictEqual(false, regEx.test('publisher.-name')); + assert.strictEqual(false, regEx.test('-publisher.-name')); + assert.strictEqual(false, regEx.test('publ_isher.name')); + assert.strictEqual(false, regEx.test('publisher._name')); }); }); diff --git a/src/vs/platform/extensions/common/extensions.ts b/src/vs/platform/extensions/common/extensions.ts index dccd114c..dde7a4c4 100644 --- a/src/vs/platform/extensions/common/extensions.ts +++ b/src/vs/platform/extensions/common/extensions.ts @@ -113,14 +113,13 @@ export interface IAuthenticationContribution { readonly label: string; } -export interface IWalkthroughTask { +export interface IWalkthroughStep { readonly id: string; readonly title: string; readonly description: string; - readonly button: - | { title: string, link: string, command?: never } - | { title: string, command: string, link?: never }, - readonly media: { path: string, altText: string }, + readonly media: + | { path: string | { dark: string, light: string, hc: string }, altText: string } + | { path: string, }, readonly doneOn?: { command: string }; readonly when?: string; } @@ -129,11 +128,19 @@ export interface IWalkthrough { readonly id: string, readonly title: string; readonly description: string; - readonly tasks: IWalkthroughTask[]; + readonly steps: IWalkthroughStep[]; readonly primary?: boolean; readonly when?: string; } +export interface IStartEntry { + readonly title: string; + readonly description: string; + readonly command: string; + readonly type?: 'sample-folder' | 'sample-notebook' | string; + readonly when?: string; +} + export interface IExtensionContributions { commands?: ICommand[]; configuration?: IConfiguration | IConfiguration[]; @@ -155,11 +162,17 @@ export interface IExtensionContributions { readonly codeActions?: readonly ICodeActionContribution[]; authentication?: IAuthenticationContribution[]; walkthroughs?: IWalkthrough[]; + startEntries?: IStartEntry[]; +} + +export interface IExtensionCapabilities { + readonly virtualWorkspaces?: boolean; + readonly untrustedWorkspaces?: ExtensionUntrustedWorkspaceSupport; } export type ExtensionKind = 'ui' | 'workspace' | 'web'; -export type ExtensionWorkspaceTrustRequirement = false | 'onStart' | 'onDemand'; -export type ExtensionWorkspaceTrust = { required: ExtensionWorkspaceTrustRequirement, description?: string }; +export type ExtensionUntrustedWorkpaceSupportType = boolean | 'limited'; +export type ExtensionUntrustedWorkspaceSupport = { supported: true; } | { supported: false, description: string } | { supported: 'limited', description: string, restrictedConfigurations?: string[] }; export function isIExtensionIdentifier(thing: any): thing is IExtensionIdentifier { return thing @@ -216,7 +229,7 @@ export interface IExtensionManifest { readonly enableProposedApi?: boolean; readonly api?: string; readonly scripts?: { [key: string]: string; }; - readonly workspaceTrust?: ExtensionWorkspaceTrust; + readonly capabilities?: IExtensionCapabilities; } export const enum ExtensionType { @@ -316,6 +329,7 @@ export interface IScannedExtension { readonly packageNLSUrl?: URI; readonly readmeUrl?: URI; readonly changelogUrl?: URI; + readonly isUnderDevelopment: boolean; } export interface ITranslatedScannedExtension { @@ -325,6 +339,7 @@ export interface ITranslatedScannedExtension { readonly packageJSON: IExtensionManifest; readonly readmeUrl?: URI; readonly changelogUrl?: URI; + readonly isUnderDevelopment: boolean; } export const IBuiltinExtensionsScannerService = createDecorator('IBuiltinExtensionsScannerService'); diff --git a/src/vs/platform/files/browser/htmlFileSystemProvider.ts b/src/vs/platform/files/browser/htmlFileSystemProvider.ts new file mode 100644 index 00000000..435ec9ac --- /dev/null +++ b/src/vs/platform/files/browser/htmlFileSystemProvider.ts @@ -0,0 +1,210 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { IFileSystemProviderWithFileReadWriteCapability, FileSystemProviderCapabilities, IFileChange, IWatchOptions, IStat, FileOverwriteOptions, FileType, FileDeleteOptions, FileWriteOptions } from 'vs/platform/files/common/files'; +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { Event, Emitter } from 'vs/base/common/event'; +import { extUri } from 'vs/base/common/resources'; + +function split(path: string): [string, string] | undefined { + const match = /^(.*)\/([^/]+)$/.exec(path); + + if (!match) { + return undefined; + } + + const [, parentPath, name] = match; + return [parentPath, name]; +} + +function getRootUUID(uri: URI): string | undefined { + const match = /^\/([^/]+)\/[^/]+\/?$/.exec(uri.path); + + if (!match) { + return undefined; + } + + return match[1]; +} + +export class HTMLFileSystemProvider implements IFileSystemProviderWithFileReadWriteCapability { + + private readonly files = new Map(); + private readonly directories = new Map(); + + readonly capabilities: FileSystemProviderCapabilities = + FileSystemProviderCapabilities.FileReadWrite + | FileSystemProviderCapabilities.PathCaseSensitive; + + readonly onDidChangeCapabilities = Event.None; + + private readonly _onDidChangeFile = new Emitter(); + readonly onDidChangeFile = this._onDidChangeFile.event; + + private readonly _onDidErrorOccur = new Emitter(); + readonly onDidErrorOccur = this._onDidErrorOccur.event; + + async readFile(resource: URI): Promise { + const handle = await this.getFileHandle(resource); + + if (!handle) { + throw new Error('File not found.'); + } + + const file = await handle.getFile(); + return new Uint8Array(await file.arrayBuffer()); + } + + async writeFile(resource: URI, content: Uint8Array, opts: FileWriteOptions): Promise { + const handle = await this.getFileHandle(resource); + + if (!handle) { + throw new Error('File not found.'); + } + + const writable = await handle.createWritable(); + await writable.write(content); + await writable.close(); + } + + watch(resource: URI, opts: IWatchOptions): IDisposable { + return Disposable.None; + } + + async stat(resource: URI): Promise { + const rootUUID = getRootUUID(resource); + + if (rootUUID) { + const fileHandle = this.files.get(rootUUID); + + if (fileHandle) { + const file = await fileHandle.getFile(); + + return { + type: FileType.File, + mtime: file.lastModified, + ctime: 0, + size: file.size + }; + } + + const directoryHandle = this.directories.get(rootUUID); + + if (directoryHandle) { + return { + type: FileType.Directory, + mtime: 0, + ctime: 0, + size: 0 + }; + } + } + + const parent = await this.getParentDirectoryHandle(resource); + + if (!parent) { + throw new Error('Stat error: no parent found'); + } + + const name = extUri.basename(resource); + for await (const [childName, child] of parent) { + if (childName === name) { + if (child.kind === 'file') { + const file = await child.getFile(); + + return { + type: FileType.File, + mtime: file.lastModified, + ctime: 0, + size: file.size + }; + } else { + return { + type: FileType.Directory, + mtime: 0, + ctime: 0, + size: 0 + }; + } + } + } + + throw new Error('Stat error: entry not found'); + } + + mkdir(resource: URI): Promise { + throw new Error('Method not implemented.'); + } + + async readdir(resource: URI): Promise<[string, FileType][]> { + const parent = await this.getDirectoryHandle(resource); + + if (!parent) { + throw new Error('Stat error: no parent found'); + } + + const result: [string, FileType][] = []; + + for await (const [name, child] of parent) { + result.push([name, child.kind === 'file' ? FileType.File : FileType.Directory]); + } + + return result; + } + + delete(resource: URI, opts: FileDeleteOptions): Promise { + throw new Error('Method not implemented: delete'); + } + + rename(from: URI, to: URI, opts: FileOverwriteOptions): Promise { + throw new Error('Method not implemented: rename'); + } + + private async getDirectoryHandle(uri: URI): Promise { + const rootUUID = getRootUUID(uri); + + if (rootUUID) { + return this.directories.get(rootUUID); + } + + const splitResult = split(uri.path); + + if (!splitResult) { + return undefined; + } + + const parent = await this.getDirectoryHandle(URI.from({ ...uri, path: splitResult[0] })); + return await parent?.getDirectoryHandle(extUri.basename(uri)); + } + + private async getParentDirectoryHandle(uri: URI): Promise { + return this.getDirectoryHandle(URI.from({ ...uri, path: extUri.dirname(uri).path })); + } + + private async getFileHandle(uri: URI): Promise { + const rootUUID = getRootUUID(uri); + + if (rootUUID) { + return this.files.get(rootUUID); + } + + const parent = await this.getParentDirectoryHandle(uri); + const name = extUri.basename(uri); + return await parent?.getFileHandle(name); + } + + registerFileHandle(uuid: string, handle: FileSystemFileHandle): void { + this.files.set(uuid, handle); + } + + registerDirectoryHandle(uuid: string, handle: FileSystemDirectoryHandle): void { + this.directories.set(uuid, handle); + } + + dispose(): void { + this._onDidChangeFile.dispose(); + } +} diff --git a/src/vs/platform/files/common/fileService.ts b/src/vs/platform/files/common/fileService.ts index 6bdbbe9c..0453e6f4 100644 --- a/src/vs/platform/files/common/fileService.ts +++ b/src/vs/platform/files/common/fileService.ts @@ -71,6 +71,10 @@ export class FileService extends Disposable implements IFileService { }); } + getProvider(scheme: string): IFileSystemProvider | undefined { + return this.provider.get(scheme); + } + async activateProvider(scheme: string): Promise { // Emit an event that we are about to activate a provider with the given scheme. @@ -1000,7 +1004,7 @@ export class FileService extends Disposable implements IFileService { ].join(); } - dispose(): void { + override dispose(): void { super.dispose(); this.activeWatchers.forEach(watcher => dispose(watcher.disposable)); diff --git a/src/vs/platform/files/common/files.ts b/src/vs/platform/files/common/files.ts index f99d94f0..f2543a85 100644 --- a/src/vs/platform/files/common/files.ts +++ b/src/vs/platform/files/common/files.ts @@ -46,6 +46,11 @@ export interface IFileService { */ registerProvider(scheme: string, provider: IFileSystemProvider): IDisposable; + /** + * Returns a file system provider for a certain scheme. + */ + getProvider(scheme: string): IFileSystemProvider | undefined; + /** * Tries to activate a provider with the given scheme. */ @@ -199,7 +204,7 @@ export interface FileOverwriteOptions { * Set to `true` to overwrite a file if it exists. Will * throw an error otherwise if the file does exist. */ - overwrite: boolean; + readonly overwrite: boolean; } export interface FileUnlockOptions { @@ -209,7 +214,7 @@ export interface FileUnlockOptions { * have. A file that is write locked will throw an error for any * attempt to write to unless `unlock: true` is provided. */ - unlock: boolean; + readonly unlock: boolean; } export interface FileReadStreamOptions { @@ -241,7 +246,7 @@ export interface FileWriteOptions extends FileOverwriteOptions, FileUnlockOption * Set to `true` to create a file when it does not exist. Will * throw an error otherwise if the file does not exist. */ - create: boolean; + readonly create: boolean; } export type FileOpenOptions = FileOpenForReadOptions | FileOpenForWriteOptions; @@ -255,7 +260,7 @@ export interface FileOpenForReadOptions { /** * A hint that the file should be opened for reading only. */ - create: false; + readonly create: false; } export interface FileOpenForWriteOptions extends FileUnlockOptions { @@ -263,7 +268,7 @@ export interface FileOpenForWriteOptions extends FileUnlockOptions { /** * A hint that the file should be opened for reading and writing. */ - create: true; + readonly create: true; } export interface FileDeleteOptions { @@ -273,14 +278,14 @@ export interface FileDeleteOptions { * only applies to folders and can lead to an error unless provided * if the folder is not empty. */ - recursive: boolean; + readonly recursive: boolean; /** * Set to `true` to attempt to move the file to trash * instead of deleting it permanently from disk. This * option maybe not be supported on all providers. */ - useTrash: boolean; + readonly useTrash: boolean; } export enum FileType { @@ -315,17 +320,17 @@ export interface IStat { /** * The file type. */ - type: FileType; + readonly type: FileType; /** * The last modification date represented as millis from unix epoch. */ - mtime: number; + readonly mtime: number; /** * The creation date represented as millis from unix epoch. */ - ctime: number; + readonly ctime: number; /** * The size of the file in bytes. @@ -339,7 +344,7 @@ export interface IWatchOptions { * Set to `true` to watch for changes recursively in a folder * and all of its children. */ - recursive: boolean; + readonly recursive: boolean; /** * A set of paths to exclude from watching. @@ -561,18 +566,18 @@ export function toFileOperationResult(error: Error): FileOperationResult { } export interface IFileSystemProviderRegistrationEvent { - added: boolean; - scheme: string; - provider?: IFileSystemProvider; + readonly added: boolean; + readonly scheme: string; + readonly provider?: IFileSystemProvider; } export interface IFileSystemProviderCapabilitiesChangeEvent { - provider: IFileSystemProvider; - scheme: string; + readonly provider: IFileSystemProvider; + readonly scheme: string; } export interface IFileSystemProviderActivationEvent { - scheme: string; + readonly scheme: string; join(promise: Promise): void; } @@ -823,13 +828,13 @@ interface IBaseStat { /** * The unified resource identifier of this file or folder. */ - resource: URI; + readonly resource: URI; /** * The name which is the last segment * of the {{path}}. */ - name: string; + readonly name: string; /** * The size of the file. @@ -837,7 +842,7 @@ interface IBaseStat { * The value may or may not be resolved as * it is optional. */ - size?: number; + readonly size?: number; /** * The last modification date represented as millis from unix epoch. @@ -845,7 +850,7 @@ interface IBaseStat { * The value may or may not be resolved as * it is optional. */ - mtime?: number; + readonly mtime?: number; /** * The creation date represented as millis from unix epoch. @@ -853,7 +858,7 @@ interface IBaseStat { * The value may or may not be resolved as * it is optional. */ - ctime?: number; + readonly ctime?: number; /** * A unique identifier thet represents the @@ -862,7 +867,7 @@ interface IBaseStat { * The value may or may not be resolved as * it is optional. */ - etag?: string; + readonly etag?: string; } export interface IBaseStatWithMetadata extends Required { } @@ -875,12 +880,12 @@ export interface IFileStat extends IBaseStat { /** * The resource is a file. */ - isFile: boolean; + readonly isFile: boolean; /** * The resource is a directory. */ - isDirectory: boolean; + readonly isDirectory: boolean; /** * The resource is a symbolic link. Note: even when the @@ -888,7 +893,7 @@ export interface IFileStat extends IBaseStat { * and `FileType.Directory` to know the type of the target * the link points to. */ - isSymbolicLink: boolean; + readonly isSymbolicLink: boolean; /** * The children of the file stat or undefined if none. @@ -897,20 +902,20 @@ export interface IFileStat extends IBaseStat { } export interface IFileStatWithMetadata extends IFileStat, IBaseStatWithMetadata { - mtime: number; - ctime: number; - etag: string; - size: number; - children?: IFileStatWithMetadata[]; + readonly mtime: number; + readonly ctime: number; + readonly etag: string; + readonly size: number; + readonly children?: IFileStatWithMetadata[]; } export interface IResolveFileResult { - stat?: IFileStat; - success: boolean; + readonly stat?: IFileStat; + readonly success: boolean; } export interface IResolveFileResultWithMetadata extends IResolveFileResult { - stat?: IFileStatWithMetadata; + readonly stat?: IFileStatWithMetadata; } export interface IFileContent extends IBaseStatWithMetadata { @@ -918,7 +923,7 @@ export interface IFileContent extends IBaseStatWithMetadata { /** * The content of a file as buffer. */ - value: VSBuffer; + readonly value: VSBuffer; } export interface IFileStreamContent extends IBaseStatWithMetadata { @@ -926,7 +931,7 @@ export interface IFileStreamContent extends IBaseStatWithMetadata { /** * The content of a file as stream. */ - value: VSBufferReadableStream; + readonly value: VSBufferReadableStream; } export interface IBaseReadFileOptions extends FileReadStreamOptions { @@ -1126,6 +1131,7 @@ export const FALLBACK_MAX_MEMORY_SIZE_MB = 4096; * Helper to format a raw byte size into a human readable label. */ export class ByteSize { + static readonly KB = 1024; static readonly MB = ByteSize.KB * ByteSize.KB; static readonly GB = ByteSize.MB * ByteSize.KB; @@ -1159,8 +1165,8 @@ export class ByteSize { // Native only: Arch limits export interface IArchLimits { - maxFileSize: number; - maxHeapSize: number; + readonly maxFileSize: number; + readonly maxHeapSize: number; } export const enum Arch { diff --git a/src/vs/platform/files/common/io.ts b/src/vs/platform/files/common/io.ts index dab87ae0..31ccfcac 100644 --- a/src/vs/platform/files/common/io.ts +++ b/src/vs/platform/files/common/io.ts @@ -10,6 +10,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { IFileSystemProviderWithOpenReadWriteCloseCapability, FileReadStreamOptions, createFileSystemProviderError, FileSystemProviderErrorCode, ensureFileSystemProviderError } from 'vs/platform/files/common/files'; import { canceled } from 'vs/base/common/errors'; import { IErrorTransformer, IDataTransformer, WriteableStream } from 'vs/base/common/stream'; +import product from 'vs/platform/product/common/product'; export interface ICreateReadStreamOptions extends FileReadStreamOptions { @@ -127,7 +128,7 @@ function throwIfTooLarge(totalBytesRead: number, options: ICreateReadStreamOptio // Return early if file is too large to load and we have configured limits if (options?.limits) { if (typeof options.limits.memory === 'number' && totalBytesRead > options.limits.memory) { - throw createFileSystemProviderError(localize('fileTooLargeForHeapError', "To open a file of this size, you need to restart and allow it to use more memory"), FileSystemProviderErrorCode.FileExceedsMemoryLimit); + throw createFileSystemProviderError(localize('fileTooLargeForHeapError', "To open a file of this size, you need to restart and allow {0} to use more memory", product.nameShort), FileSystemProviderErrorCode.FileExceedsMemoryLimit); } if (typeof options.limits.size === 'number' && totalBytesRead > options.limits.size) { diff --git a/src/vs/platform/files/electron-browser/diskFileSystemProvider.ts b/src/vs/platform/files/electron-browser/diskFileSystemProvider.ts index a2db35c4..9cdde03e 100644 --- a/src/vs/platform/files/electron-browser/diskFileSystemProvider.ts +++ b/src/vs/platform/files/electron-browser/diskFileSystemProvider.ts @@ -21,7 +21,7 @@ export class DiskFileSystemProvider extends NodeDiskFileSystemProvider { super(logService, options); } - get capabilities(): FileSystemProviderCapabilities { + override get capabilities(): FileSystemProviderCapabilities { if (!this._capabilities) { this._capabilities = super.capabilities | FileSystemProviderCapabilities.Trash; } @@ -29,7 +29,7 @@ export class DiskFileSystemProvider extends NodeDiskFileSystemProvider { return this._capabilities; } - protected async doDelete(filePath: string, opts: FileDeleteOptions): Promise { + protected override async doDelete(filePath: string, opts: FileDeleteOptions): Promise { if (!opts.useTrash) { return super.doDelete(filePath, opts); } diff --git a/src/vs/platform/files/node/diskFileSystemProvider.ts b/src/vs/platform/files/node/diskFileSystemProvider.ts index ce598c20..ff111367 100644 --- a/src/vs/platform/files/node/diskFileSystemProvider.ts +++ b/src/vs/platform/files/node/diskFileSystemProvider.ts @@ -730,7 +730,7 @@ export class DiskFileSystemProvider extends Disposable implements //#endregion - dispose(): void { + override dispose(): void { super.dispose(); dispose(this.recursiveWatcher); diff --git a/src/vs/platform/files/node/watcher/nodejs/watcherService.ts b/src/vs/platform/files/node/watcher/nodejs/watcherService.ts index b645715d..0d03d317 100644 --- a/src/vs/platform/files/node/watcher/nodejs/watcherService.ts +++ b/src/vs/platform/files/node/watcher/nodejs/watcherService.ts @@ -124,7 +124,7 @@ export class FileWatcher extends Disposable { } } - dispose(): void { + override dispose(): void { this.isDisposed = true; super.dispose(); diff --git a/src/vs/platform/files/node/watcher/nsfw/watcherService.ts b/src/vs/platform/files/node/watcher/nsfw/watcherService.ts index 32dead9e..eebfe67f 100644 --- a/src/vs/platform/files/node/watcher/nsfw/watcherService.ts +++ b/src/vs/platform/files/node/watcher/nsfw/watcherService.ts @@ -91,7 +91,7 @@ export class FileWatcher extends Disposable { } } - dispose(): void { + override dispose(): void { this.isDisposed = true; super.dispose(); diff --git a/src/vs/platform/files/node/watcher/unix/watcherService.ts b/src/vs/platform/files/node/watcher/unix/watcherService.ts index 1ee26f96..da00c8a1 100644 --- a/src/vs/platform/files/node/watcher/unix/watcherService.ts +++ b/src/vs/platform/files/node/watcher/unix/watcherService.ts @@ -92,7 +92,7 @@ export class FileWatcher extends Disposable { } } - dispose(): void { + override dispose(): void { this.isDisposed = true; super.dispose(); diff --git a/src/vs/platform/files/test/browser/fileService.test.ts b/src/vs/platform/files/test/browser/fileService.test.ts index 1aad371d..093b5e0a 100644 --- a/src/vs/platform/files/test/browser/fileService.test.ts +++ b/src/vs/platform/files/test/browser/fileService.test.ts @@ -22,6 +22,7 @@ suite('File Service', () => { const provider = new NullFileSystemProvider(); assert.strictEqual(service.canHandleResource(resource), false); + assert.strictEqual(service.getProvider(resource.scheme), undefined); const registrations: IFileSystemProviderRegistrationEvent[] = []; service.onDidChangeFileSystemProviderRegistrations(e => { @@ -33,7 +34,7 @@ suite('File Service', () => { capabilityChanges.push(e); }); - let registrationDisposable: IDisposable | undefined = undefined; + let registrationDisposable: IDisposable | undefined; let callCount = 0; service.onWillActivateFileSystemProvider(e => { callCount++; @@ -50,6 +51,7 @@ suite('File Service', () => { await service.activateProvider('test'); assert.strictEqual(service.canHandleResource(resource), true); + assert.strictEqual(service.getProvider(resource.scheme), provider); assert.strictEqual(registrations.length, 1); assert.strictEqual(registrations[0].scheme, 'test'); @@ -141,7 +143,7 @@ suite('File Service', () => { const service = new FileService(new NullLogService()); const provider = new class extends NullFileSystemProvider { - async stat(resource: URI): Promise { + override async stat(resource: URI): Promise { return { mtime: Date.now(), ctime: Date.now(), @@ -150,7 +152,7 @@ suite('File Service', () => { }; } - readFile(resource: URI): Promise { + override readFile(resource: URI): Promise { if (async) { return timeout(5).then(() => { throw new Error('failed'); }); } @@ -158,7 +160,7 @@ suite('File Service', () => { throw new Error('failed'); } - open(resource: URI, opts: FileOpenOptions): Promise { + override open(resource: URI, opts: FileOpenOptions): Promise { if (async) { return timeout(5).then(() => { throw new Error('failed'); }); } 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 58574258..ebdd45d8 100644 --- a/src/vs/platform/files/test/electron-browser/diskFileService.test.ts +++ b/src/vs/platform/files/test/electron-browser/diskFileService.test.ts @@ -58,7 +58,7 @@ export class TestDiskFileSystemProvider extends DiskFileSystemProvider { private smallStatSize: boolean = false; private _testCapabilities!: FileSystemProviderCapabilities; - get capabilities(): FileSystemProviderCapabilities { + override get capabilities(): FileSystemProviderCapabilities { if (!this._testCapabilities) { this._testCapabilities = FileSystemProviderCapabilities.FileReadWrite | @@ -76,7 +76,7 @@ export class TestDiskFileSystemProvider extends DiskFileSystemProvider { return this._testCapabilities; } - set capabilities(capabilities: FileSystemProviderCapabilities) { + override set capabilities(capabilities: FileSystemProviderCapabilities) { this._testCapabilities = capabilities; } @@ -88,7 +88,7 @@ export class TestDiskFileSystemProvider extends DiskFileSystemProvider { this.smallStatSize = enabled; } - async stat(resource: URI): Promise { + override async stat(resource: URI): Promise { const res = await super.stat(resource); if (this.invalidStatSize) { @@ -100,7 +100,7 @@ export class TestDiskFileSystemProvider extends DiskFileSystemProvider { return res; } - async read(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise { + override async read(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise { const bytesRead = await super.read(fd, pos, data, offset, length); this.totalBytesRead += bytesRead; @@ -108,7 +108,7 @@ export class TestDiskFileSystemProvider extends DiskFileSystemProvider { return bytesRead; } - async readFile(resource: URI): Promise { + override async readFile(resource: URI): Promise { const res = await super.readFile(resource); this.totalBytesRead += res.byteLength; diff --git a/src/vs/platform/instantiation/common/graph.ts b/src/vs/platform/instantiation/common/graph.ts index 9c55ae35..d6e3aef0 100644 --- a/src/vs/platform/instantiation/common/graph.ts +++ b/src/vs/platform/instantiation/common/graph.ts @@ -77,4 +77,34 @@ export class Graph { } return data.join('\n'); } + + /** + * This is brute force and slow and **only** be used + * to trouble shoot. + */ + findCycleSlow() { + for (let [id, node] of this._nodes) { + const seen = new Set([id]); + const res = this._findCycle(node, seen); + if (res) { + return res; + } + } + return undefined; + } + + private _findCycle(node: Node, seen: Set): string | undefined { + for (let [id, outgoing] of node.outgoing) { + if (seen.has(id)) { + return [...seen, id].join(' -> '); + } + seen.add(id); + const value = this._findCycle(outgoing, seen); + if (value) { + return value; + } + seen.delete(id); + } + return undefined; + } } diff --git a/src/vs/platform/instantiation/common/instantiationService.ts b/src/vs/platform/instantiation/common/instantiationService.ts index 00f0cb9d..4005f89b 100644 --- a/src/vs/platform/instantiation/common/instantiationService.ts +++ b/src/vs/platform/instantiation/common/instantiationService.ts @@ -16,7 +16,7 @@ const _enableTracing = false; class CyclicDependencyError extends Error { constructor(graph: Graph) { super('cyclic dependency between services'); - this.message = graph.toString(); + this.message = graph.findCycleSlow() ?? `UNABLE to detect cycle, dumping graph: \n${graph.toString()}`; } } @@ -267,8 +267,8 @@ class Trace { private static readonly _None = new class extends Trace { constructor() { super(-1, null); } - stop() { } - branch() { return this; } + override stop() { } + override branch() { return this; } }; static traceInvocation(ctor: any): Trace { diff --git a/src/vs/platform/instantiation/test/common/graph.test.ts b/src/vs/platform/instantiation/test/common/graph.test.ts index 58002e0d..a441a417 100644 --- a/src/vs/platform/instantiation/test/common/graph.test.ts +++ b/src/vs/platform/instantiation/test/common/graph.test.ts @@ -13,34 +13,34 @@ suite('Graph', () => { }); test('is possible to lookup nodes that don\'t exist', function () { - assert.deepEqual(graph.lookup('ddd'), null); + assert.strictEqual(graph.lookup('ddd'), undefined); }); test('inserts nodes when not there yet', function () { - assert.deepEqual(graph.lookup('ddd'), null); - assert.deepEqual(graph.lookupOrInsertNode('ddd').data, 'ddd'); - assert.deepEqual(graph.lookup('ddd')!.data, 'ddd'); + assert.strictEqual(graph.lookup('ddd'), undefined); + assert.strictEqual(graph.lookupOrInsertNode('ddd').data, 'ddd'); + assert.strictEqual(graph.lookup('ddd')!.data, 'ddd'); }); test('can remove nodes and get length', function () { assert.ok(graph.isEmpty()); - assert.deepEqual(graph.lookup('ddd'), null); - assert.deepEqual(graph.lookupOrInsertNode('ddd').data, 'ddd'); + assert.strictEqual(graph.lookup('ddd'), undefined); + assert.strictEqual(graph.lookupOrInsertNode('ddd').data, 'ddd'); assert.ok(!graph.isEmpty()); graph.removeNode('ddd'); - assert.deepEqual(graph.lookup('ddd'), null); + assert.strictEqual(graph.lookup('ddd'), undefined); assert.ok(graph.isEmpty()); }); test('root', () => { graph.insertEdge('1', '2'); let roots = graph.roots(); - assert.equal(roots.length, 1); - assert.equal(roots[0].data, '2'); + assert.strictEqual(roots.length, 1); + assert.strictEqual(roots[0].data, '2'); graph.insertEdge('2', '1'); roots = graph.roots(); - assert.equal(roots.length, 0); + assert.strictEqual(roots.length, 0); }); test('root complex', function () { @@ -49,7 +49,7 @@ suite('Graph', () => { graph.insertEdge('3', '4'); let roots = graph.roots(); - assert.equal(roots.length, 2); + assert.strictEqual(roots.length, 2); assert(['2', '4'].every(n => roots.some(node => node.data === n))); }); }); diff --git a/src/vs/platform/instantiation/test/common/instantiationService.test.ts b/src/vs/platform/instantiation/test/common/instantiationService.test.ts index c604f2a0..8b1849ea 100644 --- a/src/vs/platform/instantiation/test/common/instantiationService.test.ts +++ b/src/vs/platform/instantiation/test/common/instantiationService.test.ts @@ -55,7 +55,7 @@ interface IDependentService { class DependentService implements IDependentService { declare readonly _serviceBrand: undefined; constructor(@IService1 service: IService1) { - assert.equal(service.c, 1); + assert.strictEqual(service.c, 1); } name = 'farboo'; @@ -65,7 +65,7 @@ class Service1Consumer { constructor(@IService1 service1: IService1) { assert.ok(service1); - assert.equal(service1.c, 1); + assert.strictEqual(service1.c, 1); } } @@ -81,7 +81,7 @@ class TargetWithStaticParam { constructor(v: boolean, @IService1 service1: IService1) { assert.ok(v); assert.ok(service1); - assert.equal(service1.c, 1); + assert.strictEqual(service1.c, 1); } } @@ -93,7 +93,7 @@ class TargetNotOptional { class TargetOptional { constructor(@IService1 service1: IService1, @optional(IService2) service2: IService2) { assert.ok(service1); - assert.equal(service1.c, 1); + assert.strictEqual(service1.c, 1); assert.ok(service2 === undefined); } } @@ -101,16 +101,16 @@ class TargetOptional { class DependentServiceTarget { constructor(@IDependentService d: IDependentService) { assert.ok(d); - assert.equal(d.name, 'farboo'); + assert.strictEqual(d.name, 'farboo'); } } class DependentServiceTarget2 { constructor(@IDependentService d: IDependentService, @IService1 s: IService1) { assert.ok(d); - assert.equal(d.name, 'farboo'); + assert.strictEqual(d.name, 'farboo'); assert.ok(s); - assert.equal(s.c, 1); + assert.strictEqual(s.c, 1); } } @@ -138,9 +138,9 @@ suite('Instantiation Service', () => { test('service collection, cannot overwrite', function () { let collection = new ServiceCollection(); let result = collection.set(IService1, null!); - assert.equal(result, undefined); + assert.strictEqual(result, undefined); result = collection.set(IService1, new Service1()); - assert.equal(result, null); + assert.strictEqual(result, null); }); test('service collection, add/has', function () { @@ -237,7 +237,7 @@ suite('Instantiation Service', () => { let service1 = accessor.get(IService1); assert.ok(service1); - assert.equal(service1.c, 1); + assert.strictEqual(service1.c, 1); let service2 = accessor.get(IService1); assert.ok(service1 === service2); @@ -253,7 +253,7 @@ suite('Instantiation Service', () => { service.invokeFunction(accessor => { let d = accessor.get(IDependentService); assert.ok(d); - assert.equal(d.name, 'farboo'); + assert.strictEqual(d.name, 'farboo'); }); }); @@ -305,12 +305,12 @@ suite('Instantiation Service', () => { function test(accessor: ServicesAccessor) { assert.ok(accessor.get(IService1) instanceof Service1); - assert.equal(accessor.get(IService1).c, 1); + assert.strictEqual(accessor.get(IService1).c, 1); return true; } - assert.equal(service.invokeFunction(test), true); + assert.strictEqual(service.invokeFunction(test), true); }); test('Invoke - get service, optional', function () { @@ -320,10 +320,10 @@ suite('Instantiation Service', () => { function test(accessor: ServicesAccessor) { assert.ok(accessor.get(IService1) instanceof Service1); assert.throws(() => accessor.get(IService2)); - assert.equal(accessor.get(IService2, optional), undefined); + assert.strictEqual(accessor.get(IService2, optional), undefined); return true; } - assert.equal(service.invokeFunction(test), true); + assert.strictEqual(service.invokeFunction(test), true); }); test('Invoke - keeping accessor NOT allowed', function () { @@ -336,12 +336,12 @@ suite('Instantiation Service', () => { function test(accessor: ServicesAccessor) { assert.ok(accessor.get(IService1) instanceof Service1); - assert.equal(accessor.get(IService1).c, 1); + assert.strictEqual(accessor.get(IService1).c, 1); cached = accessor; return true; } - assert.equal(service.invokeFunction(test), true); + assert.strictEqual(service.invokeFunction(test), true); assert.throws(() => cached.get(IService2)); }); @@ -379,7 +379,7 @@ suite('Instantiation Service', () => { let child = service.createChild(new ServiceCollection([IService2, new Service2()])); child.createInstance(Service1Consumer); - assert.equal(serviceInstanceCount, 1); + assert.strictEqual(serviceInstanceCount, 1); // creating the service instance AFTER the child service serviceInstanceCount = 0; @@ -390,7 +390,7 @@ suite('Instantiation Service', () => { service.createInstance(Service1Consumer); child.createInstance(Service1Consumer); - assert.equal(serviceInstanceCount, 1); + assert.strictEqual(serviceInstanceCount, 1); }); test('Remote window / integration tests is broken #105562', function () { diff --git a/src/vs/platform/issue/common/issue.ts b/src/vs/platform/issue/common/issue.ts index dedce29b..58e1883d 100644 --- a/src/vs/platform/issue/common/issue.ts +++ b/src/vs/platform/issue/common/issue.ts @@ -3,6 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { ISandboxConfiguration } from 'vs/base/parts/sandbox/common/sandboxTypes'; + // Since data sent through the service is serialized to JSON, functions will be lost, so Color objects // should not be sent as their 'toString' method will be stripped. Instead convert to strings before sending. export interface WindowStyles { @@ -67,9 +69,6 @@ export interface ISettingSearchResult { score: number; } -export interface IssueReporterFeatures { -} - export interface ProcessExplorerStyles extends WindowStyles { hoverBackground?: string; hoverForeground?: string; @@ -88,3 +87,17 @@ export interface ICommonIssueService { openProcessExplorer(data: ProcessExplorerData): Promise; getSystemStatus(): Promise; } + +export interface IssueReporterWindowConfiguration extends ISandboxConfiguration { + disableExtensions: boolean; + data: IssueReporterData; + os: { + type: string; + arch: string; + release: string; + } +} + +export interface ProcessExplorerWindowConfiguration extends ISandboxConfiguration { + data: ProcessExplorerData; +} diff --git a/src/vs/platform/issue/electron-main/issueMainService.ts b/src/vs/platform/issue/electron-main/issueMainService.ts index c9ed978e..e6be4645 100644 --- a/src/vs/platform/issue/electron-main/issueMainService.ts +++ b/src/vs/platform/issue/electron-main/issueMainService.ts @@ -4,15 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; -import * as os from 'os'; -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 { arch, release, type } from 'os'; +import product from 'vs/platform/product/common/product'; +import { ICommonIssueService, IssueReporterWindowConfiguration, IssueReporterData, ProcessExplorerData, ProcessExplorerWindowConfiguration } from 'vs/platform/issue/common/issue'; import { BrowserWindow, ipcMain, screen, IpcMainEvent, Display } from 'electron'; import { ILaunchMainService } from 'vs/platform/launch/electron-main/launchMainService'; import { IDiagnosticsService, PerformanceInfo, isRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnostics'; import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService'; -import { isMacintosh, IProcessEnvironment } from 'vs/base/common/platform'; +import { isMacintosh, IProcessEnvironment, browserCodeLoadingCacheStrategy } from 'vs/base/common/platform'; import { ILogService } from 'vs/platform/log/common/log'; import { IWindowState } from 'vs/platform/windows/electron-main/windows'; import { listProcesses } from 'vs/base/node/ps'; @@ -21,22 +20,26 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' import { zoomLevelToZoomFactor } from 'vs/platform/windows/common/windows'; import { FileAccess } from 'vs/base/common/network'; import { INativeHostMainService } from 'vs/platform/native/electron-main/nativeHostMainService'; - -const DEFAULT_BACKGROUND_COLOR = '#1E1E1E'; +import { IIPCObjectUrl, IProtocolMainService } from 'vs/platform/protocol/electron-main/protocol'; +import { DisposableStore } from 'vs/base/common/lifecycle'; export const IIssueMainService = createDecorator('issueMainService'); export interface IIssueMainService extends ICommonIssueService { } export class IssueMainService implements ICommonIssueService { + declare readonly _serviceBrand: undefined; - _issueWindow: BrowserWindow | null = null; - _issueParentWindow: BrowserWindow | null = null; - _processExplorerWindow: BrowserWindow | null = null; - _processExplorerParentWindow: BrowserWindow | null = null; + + private static readonly DEFAULT_BACKGROUND_COLOR = '#1E1E1E'; + + private issueReporterWindow: BrowserWindow | null = null; + private issueReporterParentWindow: BrowserWindow | null = null; + + private processExplorerWindow: BrowserWindow | null = null; + private processExplorerParentWindow: BrowserWindow | null = null; constructor( - private machineId: string, private userEnv: IProcessEnvironment, @IEnvironmentMainService private readonly environmentMainService: IEnvironmentMainService, @ILaunchMainService private readonly launchMainService: ILaunchMainService, @@ -44,44 +47,42 @@ export class IssueMainService implements ICommonIssueService { @IDiagnosticsService private readonly diagnosticsService: IDiagnosticsService, @IDialogMainService private readonly dialogMainService: IDialogMainService, @INativeHostMainService private readonly nativeHostMainService: INativeHostMainService, - @IProductService private readonly productService: IProductService + @IProtocolMainService private readonly protocolMainService: IProtocolMainService ) { this.registerListeners(); } private registerListeners(): void { - ipcMain.on('vscode:issueSystemInfoRequest', async (event: IpcMainEvent) => { - Promise.all([this.launchMainService.getMainProcessInfo(), this.launchMainService.getRemoteDiagnostics({ includeProcesses: false, includeWorkspaceMetadata: false })]) - .then(result => { - const [info, remoteData] = result; - this.diagnosticsService.getSystemInfo(info, remoteData).then(msg => { - this.safeSend(event, 'vscode:issueSystemInfoResponse', msg); - }); - }); + ipcMain.on('vscode:issueSystemInfoRequest', async event => { + const [info, remoteData] = await Promise.all([this.launchMainService.getMainProcessInfo(), this.launchMainService.getRemoteDiagnostics({ includeProcesses: false, includeWorkspaceMetadata: false })]); + const msg = await this.diagnosticsService.getSystemInfo(info, remoteData); + + this.safeSend(event, 'vscode:issueSystemInfoResponse', msg); }); - ipcMain.on('vscode:listProcesses', async (event: IpcMainEvent) => { + ipcMain.on('vscode:listProcesses', async event => { const processes = []; try { const mainPid = await this.launchMainService.getMainProcessId(); processes.push({ name: localize('local', "Local"), rootProcess: await listProcesses(mainPid) }); - (await this.launchMainService.getRemoteDiagnostics({ includeProcesses: true })) - .forEach(data => { - if (isRemoteDiagnosticError(data)) { + + const remoteDiagnostics = await this.launchMainService.getRemoteDiagnostics({ includeProcesses: true }); + remoteDiagnostics.forEach(data => { + if (isRemoteDiagnosticError(data)) { + processes.push({ + name: data.hostName, + rootProcess: data + }); + } else { + if (data.processes) { processes.push({ name: data.hostName, - rootProcess: data + rootProcess: data.processes }); - } else { - if (data.processes) { - processes.push({ - name: data.hostName, - rootProcess: data.processes - }); - } } - }); + } + }); } catch (e) { this.logService.error(`Listing processes failed: ${e}`); } @@ -89,7 +90,7 @@ export class IssueMainService implements ICommonIssueService { this.safeSend(event, 'vscode:listProcessesResponse', processes); }); - ipcMain.on('vscode:issueReporterClipboard', (event: IpcMainEvent) => { + ipcMain.on('vscode:issueReporterClipboard', async event => { const messageOptions = { message: localize('issueReporterWriteToClipboard', "There is too much data to send to GitHub directly. The data will be copied to the clipboard, please paste it into the GitHub issue page that is opened."), type: 'warning', @@ -99,21 +100,18 @@ export class IssueMainService implements ICommonIssueService { ] }; - if (this._issueWindow) { - this.dialogMainService.showMessageBox(messageOptions, this._issueWindow) - .then(result => { - this.safeSend(event, 'vscode:issueReporterClipboardResponse', result.response === 0); - }); + if (this.issueReporterWindow) { + const result = await this.dialogMainService.showMessageBox(messageOptions, this.issueReporterWindow); + this.safeSend(event, 'vscode:issueReporterClipboardResponse', result.response === 0); } }); - ipcMain.on('vscode:issuePerformanceInfoRequest', (event: IpcMainEvent) => { - this.getPerformanceInfo().then(msg => { - this.safeSend(event, 'vscode:issuePerformanceInfoResponse', msg); - }); + ipcMain.on('vscode:issuePerformanceInfoRequest', async event => { + const performanceInfo = await this.getPerformanceInfo(); + this.safeSend(event, 'vscode:issuePerformanceInfoResponse', performanceInfo); }); - ipcMain.on('vscode:issueReporterConfirmClose', () => { + ipcMain.on('vscode:issueReporterConfirmClose', async () => { const messageOptions = { message: localize('confirmCloseIssueReporter', "Your input will not be saved. Are you sure you want to close this window?"), type: 'warning', @@ -123,16 +121,14 @@ export class IssueMainService implements ICommonIssueService { ] }; - if (this._issueWindow) { - this.dialogMainService.showMessageBox(messageOptions, this._issueWindow) - .then(result => { - if (result.response === 0) { - if (this._issueWindow) { - this._issueWindow.destroy(); - this._issueWindow = null; - } - } - }); + if (this.issueReporterWindow) { + const result = await this.dialogMainService.showMessageBox(messageOptions, this.issueReporterWindow); + if (result.response === 0) { + if (this.issueReporterWindow) { + this.issueReporterWindow.destroy(); + this.issueReporterWindow = null; + } + } } }); @@ -142,10 +138,10 @@ export class IssueMainService implements ICommonIssueService { let parentWindow: BrowserWindow | null; switch (from) { case 'issueReporter': - parentWindow = this._issueParentWindow; + parentWindow = this.issueReporterParentWindow; break; case 'processExplorer': - parentWindow = this._processExplorerParentWindow; + parentWindow = this.processExplorerParentWindow; break; default: throw new Error(`Unexpected command source: ${from}`); @@ -160,22 +156,21 @@ export class IssueMainService implements ICommonIssueService { this.nativeHostMainService.openExternal(undefined, arg); }); - ipcMain.on('vscode:closeIssueReporter', (event: IpcMainEvent) => { - if (this._issueWindow) { - this._issueWindow.close(); + ipcMain.on('vscode:closeIssueReporter', event => { + if (this.issueReporterWindow) { + this.issueReporterWindow.close(); } }); - ipcMain.on('vscode:closeProcessExplorer', (event: IpcMainEvent) => { - if (this._processExplorerWindow) { - this._processExplorerWindow.close(); + ipcMain.on('vscode:closeProcessExplorer', event => { + if (this.processExplorerWindow) { + this.processExplorerWindow.close(); } }); - ipcMain.on('vscode:windowsInfoRequest', (event: IpcMainEvent) => { - this.launchMainService.getMainProcessInfo().then(info => { - this.safeSend(event, 'vscode:windowsInfoResponse', info.windows); - }); + ipcMain.on('vscode:windowsInfoRequest', async event => { + const mainProcessInfo = await this.launchMainService.getMainProcessInfo(); + this.safeSend(event, 'vscode:windowsInfoResponse', mainProcessInfo.windows); }); } @@ -186,128 +181,138 @@ export class IssueMainService implements ICommonIssueService { } async openReporter(data: IssueReporterData): Promise { - if (!this._issueWindow) { - this._issueParentWindow = BrowserWindow.getFocusedWindow(); - if (this._issueParentWindow) { - const position = this.getWindowPosition(this._issueParentWindow, 700, 800); + if (!this.issueReporterWindow) { + this.issueReporterParentWindow = BrowserWindow.getFocusedWindow(); + if (this.issueReporterParentWindow) { + const issueReporterDisposables = new DisposableStore(); - this._issueWindow = new BrowserWindow({ - fullscreen: false, - width: position.width, - height: position.height, - minWidth: 300, - minHeight: 200, - x: position.x, - y: position.y, - title: localize('issueReporter', "Issue Reporter"), - backgroundColor: data.styles.backgroundColor || DEFAULT_BACKGROUND_COLOR, - webPreferences: { - preload: FileAccess.asFileUri('vs/base/parts/sandbox/electron-browser/preload.js', require).fsPath, - v8CacheOptions: 'bypassHeatCheck', - enableWebSQL: false, - enableRemoteModule: false, - spellcheck: false, - nativeWindowOpen: true, - zoomFactor: zoomLevelToZoomFactor(data.zoomLevel), - sandbox: true, - contextIsolation: true - } + const issueReporterWindowConfigUrl = issueReporterDisposables.add(this.protocolMainService.createIPCObjectUrl()); + const position = this.getWindowPosition(this.issueReporterParentWindow, 700, 800); + + this.issueReporterWindow = this.createBrowserWindow(position, issueReporterWindowConfigUrl, data.styles.backgroundColor, localize('issueReporter', "Issue Reporter"), data.zoomLevel); + + // Store into config object URL + issueReporterWindowConfigUrl.update({ + appRoot: this.environmentMainService.appRoot, + windowId: this.issueReporterWindow.id, + userEnv: this.userEnv, + data, + disableExtensions: !!this.environmentMainService.disableExtensions, + os: { + type: type(), + arch: arch(), + release: release(), + }, + product }); - this._issueWindow.setMenuBarVisibility(false); // workaround for now, until a menu is implemented + this.issueReporterWindow.loadURL( + FileAccess.asBrowserUri('vs/code/electron-sandbox/issue/issueReporter.html', require, true).toString(true) + ); - // Modified when testing UI - const features: IssueReporterFeatures = {}; + this.issueReporterWindow.on('close', () => { + this.issueReporterWindow = null; - this.logService.trace('issueService#openReporter: opening issue reporter'); - this._issueWindow.loadURL(this.getIssueReporterPath(data, features)); + issueReporterDisposables.dispose(); + }); - this._issueWindow.on('close', () => this._issueWindow = null); + this.issueReporterParentWindow.on('closed', () => { + if (this.issueReporterWindow) { + this.issueReporterWindow.close(); + this.issueReporterWindow = null; - this._issueParentWindow.on('closed', () => { - if (this._issueWindow) { - this._issueWindow.close(); - this._issueWindow = null; + issueReporterDisposables.dispose(); } }); } } - if (this._issueWindow) { - this._issueWindow.focus(); - } + this.issueReporterWindow?.focus(); } async openProcessExplorer(data: ProcessExplorerData): Promise { - // Create as singleton - if (!this._processExplorerWindow) { - this._processExplorerParentWindow = BrowserWindow.getFocusedWindow(); - if (this._processExplorerParentWindow) { - const position = this.getWindowPosition(this._processExplorerParentWindow, 800, 500); - this._processExplorerWindow = new BrowserWindow({ - skipTaskbar: true, - resizable: true, - fullscreen: false, - width: position.width, - height: position.height, - minWidth: 300, - minHeight: 200, - x: position.x, - y: position.y, - backgroundColor: data.styles.backgroundColor, - title: localize('processExplorer', "Process Explorer"), - webPreferences: { - preload: FileAccess.asFileUri('vs/base/parts/sandbox/electron-browser/preload.js', require).fsPath, - v8CacheOptions: 'bypassHeatCheck', - enableWebSQL: false, - enableRemoteModule: false, - spellcheck: false, - nativeWindowOpen: true, - zoomFactor: zoomLevelToZoomFactor(data.zoomLevel), - sandbox: true, - contextIsolation: true - } + if (!this.processExplorerWindow) { + this.processExplorerParentWindow = BrowserWindow.getFocusedWindow(); + if (this.processExplorerParentWindow) { + const processExplorerDisposables = new DisposableStore(); + + const processExplorerWindowConfigUrl = processExplorerDisposables.add(this.protocolMainService.createIPCObjectUrl()); + const position = this.getWindowPosition(this.processExplorerParentWindow, 800, 500); + + this.processExplorerWindow = this.createBrowserWindow(position, processExplorerWindowConfigUrl, data.styles.backgroundColor, localize('processExplorer', "Process Explorer"), data.zoomLevel); + + // Store into config object URL + processExplorerWindowConfigUrl.update({ + appRoot: this.environmentMainService.appRoot, + windowId: this.processExplorerWindow.id, + userEnv: this.userEnv, + data, + product }); - this._processExplorerWindow.setMenuBarVisibility(false); + this.processExplorerWindow.loadURL( + FileAccess.asBrowserUri('vs/code/electron-sandbox/processExplorer/processExplorer.html', require, true).toString(true) + ); - const windowConfiguration = { - appRoot: this.environmentMainService.appRoot, - windowId: this._processExplorerWindow.id, - userEnv: this.userEnv, - machineId: this.machineId, - data - }; + this.processExplorerWindow.on('close', () => { + this.processExplorerWindow = null; + processExplorerDisposables.dispose(); + }); - this._processExplorerWindow.loadURL( - toWindowUrl('vs/code/electron-sandbox/processExplorer/processExplorer.html', windowConfiguration)); + this.processExplorerParentWindow.on('close', () => { + if (this.processExplorerWindow) { + this.processExplorerWindow.close(); + this.processExplorerWindow = null; - this._processExplorerWindow.on('close', () => this._processExplorerWindow = null); - - this._processExplorerParentWindow.on('close', () => { - if (this._processExplorerWindow) { - this._processExplorerWindow.close(); - this._processExplorerWindow = null; + processExplorerDisposables.dispose(); } }); } } - // Focus - if (this._processExplorerWindow) { - this._processExplorerWindow.focus(); - } + this.processExplorerWindow?.focus(); } - public async getSystemStatus(): Promise { - return Promise.all([this.launchMainService.getMainProcessInfo(), this.launchMainService.getRemoteDiagnostics({ includeProcesses: false, includeWorkspaceMetadata: false })]) - .then(result => { - const [info, remoteData] = result; - return this.diagnosticsService.getDiagnostics(info, remoteData); - }); + private createBrowserWindow(position: IWindowState, ipcObjectUrl: IIPCObjectUrl, backgroundColor: string | undefined, title: string, zoomLevel: number): BrowserWindow { + const window = new BrowserWindow({ + fullscreen: false, + skipTaskbar: true, + resizable: true, + width: position.width, + height: position.height, + minWidth: 300, + minHeight: 200, + x: position.x, + y: position.y, + title, + backgroundColor: backgroundColor || IssueMainService.DEFAULT_BACKGROUND_COLOR, + webPreferences: { + preload: FileAccess.asFileUri('vs/base/parts/sandbox/electron-browser/preload.js', require).fsPath, + additionalArguments: [`--vscode-window-config=${ipcObjectUrl.resource.toString()}`, '--context-isolation' /* TODO@bpasero: Use process.contextIsolateed when 13-x-y is adopted (https://github.com/electron/electron/pull/28030) */], + v8CacheOptions: browserCodeLoadingCacheStrategy, + enableWebSQL: false, + enableRemoteModule: false, + spellcheck: false, + nativeWindowOpen: true, + zoomFactor: zoomLevelToZoomFactor(zoomLevel), + sandbox: true, + contextIsolation: true + } + }); + + window.setMenuBarVisibility(false); + + return window; + } + + async getSystemStatus(): Promise { + const [info, remoteData] = await Promise.all([this.launchMainService.getMainProcessInfo(), this.launchMainService.getRemoteDiagnostics({ includeProcesses: false, includeWorkspaceMetadata: false })]); + + return this.diagnosticsService.getDiagnostics(info, remoteData); } private getWindowPosition(parentWindow: BrowserWindow, defaultWidth: number, defaultHeight: number): IWindowState { + // We want the new window to open on the same display that the parent is in let displayToUse: Display | undefined; const displays = screen.getAllDisplays(); @@ -375,67 +380,14 @@ export class IssueMainService implements ICommonIssueService { return state; } - private getPerformanceInfo(): Promise { - return new Promise(async (resolve, reject) => { - Promise.all([this.launchMainService.getMainProcessInfo(), this.launchMainService.getRemoteDiagnostics({ includeProcesses: true, includeWorkspaceMetadata: true })]) - .then(result => { - const [info, remoteData] = result; - this.diagnosticsService.getPerformanceInfo(info, remoteData) - .then(diagnosticInfo => { - resolve(diagnosticInfo); - }) - .catch(err => { - this.logService.warn('issueService#getPerformanceInfo ', err.message); - reject(err); - }); - }); - }); - } + private async getPerformanceInfo(): Promise { + try { + const [info, remoteData] = await Promise.all([this.launchMainService.getMainProcessInfo(), this.launchMainService.getRemoteDiagnostics({ includeProcesses: true, includeWorkspaceMetadata: true })]); + return await this.diagnosticsService.getPerformanceInfo(info, remoteData); + } catch (error) { + this.logService.warn('issueService#getPerformanceInfo ', error.message); - private getIssueReporterPath(data: IssueReporterData, features: IssueReporterFeatures): string { - if (!this._issueWindow) { - throw new Error('Issue window has been disposed'); - } - - const windowConfiguration = { - appRoot: this.environmentMainService.appRoot, - windowId: this._issueWindow.id, - machineId: this.machineId, - userEnv: this.userEnv, - data, - features, - disableExtensions: this.environmentMainService.disableExtensions, - os: { - type: os.type(), - arch: os.arch(), - release: os.release(), - }, - product: { - 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 - } - }; - - return toWindowUrl('vs/code/electron-sandbox/issue/issueReporter.html', windowConfiguration); - } -} - -function toWindowUrl(modulePathToHtml: string, windowConfiguration: T): string { - const environment = parseArgs(process.argv, OPTIONS); - const config = Object.assign(environment, windowConfiguration); - for (const keyValue of Object.keys(config)) { - const key = keyValue as keyof typeof config; - if (config[key] === undefined || config[key] === null || config[key] === '') { - delete config[key]; // only send over properties that have a true value + throw error; } } - - return FileAccess - .asBrowserUri(modulePathToHtml, require, true) - .with({ query: `config=${encodeURIComponent(JSON.stringify(config))}` }) - .toString(true); } diff --git a/src/vs/platform/keybinding/common/abstractKeybindingService.ts b/src/vs/platform/keybinding/common/abstractKeybindingService.ts index 23716ee9..689431ec 100644 --- a/src/vs/platform/keybinding/common/abstractKeybindingService.ts +++ b/src/vs/platform/keybinding/common/abstractKeybindingService.ts @@ -61,7 +61,7 @@ export abstract class AbstractKeybindingService extends Disposable implements IK this._logging = false; } - public dispose(): void { + public override dispose(): void { super.dispose(); } diff --git a/src/vs/platform/keybinding/test/common/mockKeybindingService.ts b/src/vs/platform/keybinding/test/common/mockKeybindingService.ts index 2fe7bbab..4f022e9d 100644 --- a/src/vs/platform/keybinding/test/common/mockKeybindingService.ts +++ b/src/vs/platform/keybinding/test/common/mockKeybindingService.ts @@ -78,7 +78,7 @@ export class MockScopableContextKeyService extends MockContextKeyService { /** * Don't implement this for all tests since we rarely depend on this behavior and it isn't implemented fully */ - public createScoped(domNote: HTMLElement): IContextKeyService { + public override createScoped(domNote: HTMLElement): IContextKeyService { return new MockContextKeyService(); } } diff --git a/src/vs/platform/list/browser/listService.ts b/src/vs/platform/list/browser/listService.ts index d1b0fa73..6b2f6b35 100644 --- a/src/vs/platform/list/browser/listService.ts +++ b/src/vs/platform/list/browser/listService.ts @@ -298,7 +298,7 @@ export class WorkbenchList extends List { this.disposables.add(this.navigator); } - updateOptions(options: IWorkbenchListOptionsUpdate): void { + override updateOptions(options: IWorkbenchListOptionsUpdate): void { super.updateOptions(options); if (options.overrideStyles) { @@ -315,7 +315,7 @@ export class WorkbenchList extends List { return this._useAltAsMultipleSelectionModifier; } - dispose(): void { + override dispose(): void { this._styler?.dispose(); super.dispose(); } @@ -410,7 +410,7 @@ export class WorkbenchPagedList extends PagedList { this.disposables.add(this.navigator); } - updateOptions(options: IWorkbenchListOptionsUpdate): void { + override updateOptions(options: IWorkbenchListOptionsUpdate): void { super.updateOptions(options); if (options.overrideStyles) { @@ -427,7 +427,7 @@ export class WorkbenchPagedList extends PagedList { return this._useAltAsMultipleSelectionModifier; } - dispose(): void { + override dispose(): void { this._styler?.dispose(); this.disposables.dispose(); super.dispose(); @@ -547,7 +547,7 @@ export class WorkbenchTable extends Table { this.disposables.add(this.navigator); } - updateOptions(options: IWorkbenchTableOptionsUpdate): void { + override updateOptions(options: IWorkbenchTableOptionsUpdate): void { super.updateOptions(options); if (options.overrideStyles) { @@ -564,7 +564,7 @@ export class WorkbenchTable extends Table { return this._useAltAsMultipleSelectionModifier; } - dispose(): void { + override dispose(): void { this._styler?.dispose(); this.disposables.dispose(); super.dispose(); @@ -669,6 +669,15 @@ abstract class ResourceNavigator extends Disposable { return; } + // copied from AbstractTree + const target = browserEvent.target as HTMLElement; + const onTwistie = target.classList.contains('monaco-tl-twistie') + || (target.classList.contains('monaco-icon-label') && target.classList.contains('folder-icon') && browserEvent.offsetX < 16); + + if (onTwistie) { + return; + } + const preserveFocus = false; const pinned = true; const sideBySide = (browserEvent.ctrlKey || browserEvent.metaKey || browserEvent.altKey); @@ -698,11 +707,14 @@ abstract class ResourceNavigator extends Disposable { class ListResourceNavigator extends ResourceNavigator { + protected override readonly widget: List | PagedList; + constructor( - protected readonly widget: List | PagedList, + widget: List | PagedList, options: IResourceNavigatorOptions ) { super(widget, options); + this.widget = widget; } getSelectedElement(): T | undefined { @@ -712,8 +724,10 @@ class ListResourceNavigator extends ResourceNavigator { class TableResourceNavigator extends ResourceNavigator { + protected override readonly widget!: Table; + constructor( - protected readonly widget: Table, + widget: Table, options: IResourceNavigatorOptions ) { super(widget, options); @@ -726,8 +740,10 @@ class TableResourceNavigator extends ResourceNavigator { class TreeResourceNavigator extends ResourceNavigator { + protected override readonly widget!: ObjectTree | CompressibleObjectTree | DataTree | AsyncDataTree | CompressibleAsyncDataTree; + constructor( - protected readonly widget: ObjectTree | CompressibleObjectTree | DataTree | AsyncDataTree | CompressibleAsyncDataTree, + widget: ObjectTree | CompressibleObjectTree | DataTree | AsyncDataTree | CompressibleAsyncDataTree, options: IResourceNavigatorOptions ) { super(widget, options); @@ -829,7 +845,7 @@ export class WorkbenchCompressibleObjectTree, TFilter this.disposables.add(this.internals); } - updateOptions(options: IWorkbenchCompressibleObjectTreeOptionsUpdate = {}): void { + override updateOptions(options: IWorkbenchCompressibleObjectTreeOptionsUpdate = {}): void { super.updateOptions(options); if (options.overrideStyles) { @@ -875,7 +891,7 @@ export class WorkbenchDataTree extends DataTree extends Async this.disposables.add(this.internals); } - updateOptions(options: IWorkbenchAsyncDataTreeOptionsUpdate = {}): void { + override updateOptions(options: IWorkbenchAsyncDataTreeOptionsUpdate = {}): void { super.updateOptions(options); if (options.overrideStyles) { diff --git a/src/vs/platform/log/common/bufferLog.ts b/src/vs/platform/log/common/bufferLog.ts index ea9020d9..6cedce2d 100644 --- a/src/vs/platform/log/common/bufferLog.ts +++ b/src/vs/platform/log/common/bufferLog.ts @@ -82,7 +82,7 @@ export class BufferLogService extends AbstractLogger implements ILogService { this._log(LogLevel.Critical, message, ...args); } - dispose(): void { + override dispose(): void { if (this._logger) { this._logger.dispose(); } diff --git a/src/vs/platform/log/common/fileLog.ts b/src/vs/platform/log/common/fileLog.ts index 75e227c2..97daf813 100644 --- a/src/vs/platform/log/common/fileLog.ts +++ b/src/vs/platform/log/common/fileLog.ts @@ -3,13 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ILogService, LogLevel, AbstractLogger, ILoggerService, ILogger } from 'vs/platform/log/common/log'; +import { ILogService, LogLevel, AbstractLogger, ILoggerService, ILogger, AbstractLoggerService } from 'vs/platform/log/common/log'; import { URI } from 'vs/base/common/uri'; import { ByteSize, FileOperationError, FileOperationResult, IFileService, whenProviderRegistered } from 'vs/platform/files/common/files'; import { Queue } from 'vs/base/common/async'; import { VSBuffer } from 'vs/base/common/buffer'; import { dirname, joinPath, basename } from 'vs/base/common/resources'; -import { Disposable } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { BufferLogService } from 'vs/platform/log/common/bufferLog'; @@ -159,34 +158,19 @@ export class FileLogger extends AbstractLogger implements ILogger { } } -export class FileLoggerService extends Disposable implements ILoggerService { - - declare readonly _serviceBrand: undefined; - - private readonly loggers = new Map(); +export class FileLoggerService extends AbstractLoggerService implements ILoggerService { constructor( - @ILogService private logService: ILogService, - @IInstantiationService private instantiationService: IInstantiationService, - @IFileService private fileService: IFileService, + @ILogService logService: ILogService, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IFileService private readonly fileService: IFileService, ) { - super(); - this._register(logService.onDidChangeLogLevel(level => this.loggers.forEach(logger => logger.setLevel(level)))); + super(logService.getLevel(), logService.onDidChangeLogLevel); } - createLogger(resource: URI): ILogger { - let logger = this.loggers.get(resource.toString()); - if (!logger) { - logger = new BufferLogService(this.logService.getLevel()); - this.loggers.set(resource.toString(), logger); - whenProviderRegistered(resource, this.fileService).then(() => (logger).logger = this.instantiationService.createInstance(FileLogger, basename(resource), resource, this.logService.getLevel())); - } + protected doCreateLogger(resource: URI, logLevel: LogLevel): ILogger { + const logger = new BufferLogService(logLevel); + whenProviderRegistered(resource, this.fileService).then(() => (logger).logger = this.instantiationService.createInstance(FileLogger, basename(resource), resource, logger.getLevel())); return logger; } - - dispose(): void { - this.loggers.forEach(logger => logger.dispose()); - this.loggers.clear(); - super.dispose(); - } } diff --git a/src/vs/platform/log/common/log.ts b/src/vs/platform/log/common/log.ts index 8a2764bc..1e3b7497 100644 --- a/src/vs/platform/log/common/log.ts +++ b/src/vs/platform/log/common/log.ts @@ -250,7 +250,7 @@ export class ConsoleMainLogger extends AbstractLogger implements ILogger { } } - dispose(): void { + override dispose(): void { // noop } @@ -303,7 +303,7 @@ export class ConsoleLogger extends AbstractLogger implements ILogger { } } - dispose(): void { + override dispose(): void { // noop } @@ -363,7 +363,7 @@ export class AdapterLogger extends AbstractLogger implements ILogger { return toErrorMessage(msg, this.getLevel() <= LogLevel.Trace); } - dispose(): void { + override dispose(): void { // noop } @@ -382,7 +382,7 @@ export class MultiplexLogService extends AbstractLogger implements ILogService { } } - setLevel(level: LogLevel): void { + override setLevel(level: LogLevel): void { for (const logService of this.logServices) { logService.setLevel(level); } @@ -431,7 +431,7 @@ export class MultiplexLogService extends AbstractLogger implements ILogService { } } - dispose(): void { + override dispose(): void { for (const logService of this.logServices) { logService.dispose(); } @@ -487,6 +487,46 @@ export class LogService extends Disposable implements ILogService { } } +export abstract class AbstractLoggerService extends Disposable implements ILoggerService { + + declare readonly _serviceBrand: undefined; + + private readonly loggers = new Map(); + private readonly logLevelChangeableLoggers: ILogger[] = []; + + constructor( + private logLevel: LogLevel, + onDidChangeLogLevel: Event, + ) { + super(); + this._register(onDidChangeLogLevel(logLevel => { + this.logLevel = logLevel; + this.logLevelChangeableLoggers.forEach(logger => logger.setLevel(logLevel)); + })); + } + + createLogger(resource: URI, options?: ILoggerOptions): ILogger { + let logger = this.loggers.get(resource.toString()); + if (!logger) { + logger = this.doCreateLogger(resource, options?.always ? LogLevel.Trace : this.logLevel, options); + this.loggers.set(resource.toString(), logger); + if (!options?.always) { + this.logLevelChangeableLoggers.push(logger); + } + } + return logger; + } + + override dispose(): void { + this.logLevelChangeableLoggers.splice(0, this.logLevelChangeableLoggers.length); + this.loggers.forEach(logger => logger.dispose()); + this.loggers.clear(); + super.dispose(); + } + + protected abstract doCreateLogger(resource: URI, logLevel: LogLevel, options?: ILoggerOptions): ILogger; +} + export class NullLogService implements ILogService { declare readonly _serviceBrand: undefined; readonly onDidChangeLogLevel: Event = new Emitter().event; diff --git a/src/vs/platform/log/common/logIpc.ts b/src/vs/platform/log/common/logIpc.ts index 4b8ac5fa..789b3e02 100644 --- a/src/vs/platform/log/common/logIpc.ts +++ b/src/vs/platform/log/common/logIpc.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; -import { LogLevel, ILogService, LogService, ILoggerService, ILogger, AbstractMessageLogger, ILoggerOptions, AdapterLogger } from 'vs/platform/log/common/log'; +import { LogLevel, ILogService, LogService, ILoggerService, ILogger, AbstractMessageLogger, ILoggerOptions, AdapterLogger, AbstractLoggerService } from 'vs/platform/log/common/log'; import { Event } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; @@ -113,11 +113,11 @@ export class LoggerChannel implements IServerChannel { } } -export class LoggerChannelClient implements ILoggerService { +export class LoggerChannelClient extends AbstractLoggerService implements ILoggerService { - declare readonly _serviceBrand: undefined; - - constructor(private readonly channel: IChannel) { } + constructor(logLevel: LogLevel, onDidChangeLogLevel: Event, private readonly channel: IChannel) { + super(logLevel, onDidChangeLogLevel); + } createConsoleMainLogger(): ILogger { return new AdapterLogger({ @@ -127,8 +127,8 @@ export class LoggerChannelClient implements ILoggerService { }); } - createLogger(file: URI, options?: ILoggerOptions): ILogger { - return new Logger(this.channel, file, options); + protected doCreateLogger(file: URI, logLevel: LogLevel, options?: ILoggerOptions): ILogger { + return new Logger(this.channel, file, logLevel, options); } } @@ -141,38 +141,40 @@ class Logger extends AbstractMessageLogger { constructor( private readonly channel: IChannel, private readonly file: URI, + logLevel: LogLevel, loggerOptions?: ILoggerOptions, ) { super(loggerOptions?.always); + this.setLevel(logLevel); this.channel.call('createLogger', [file, loggerOptions]) .then(() => { - this._log(this.buffer); + this.doLog(this.buffer); this.isLoggerCreated = true; }); } protected log(level: LogLevel, message: string) { - this._log([[level, message]]); - } - - private _log(messages: [LogLevel, string][]) { + const messages: [LogLevel, string][] = [[level, message]]; if (this.isLoggerCreated) { - this.channel.call('log', [this.file, messages]); + this.doLog(messages); } else { this.buffer.push(...messages); } } + + private doLog(messages: [LogLevel, string][]) { + this.channel.call('log', [this.file, messages]); + } } export class FollowerLogService extends LogService implements ILogService { - declare readonly _serviceBrand: undefined; constructor(private parent: LogLevelChannelClient, logService: ILogService) { super(logService); this._register(parent.onDidChangeLogLevel(level => logService.setLevel(level))); } - setLevel(level: LogLevel): void { + override setLevel(level: LogLevel): void { super.setLevel(level); this.parent.setLevel(level); diff --git a/src/vs/platform/log/node/loggerService.ts b/src/vs/platform/log/node/loggerService.ts index 893025ae..5c9e15ee 100644 --- a/src/vs/platform/log/node/loggerService.ts +++ b/src/vs/platform/log/node/loggerService.ts @@ -3,8 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ILogService, ILoggerService, ILogger, ILoggerOptions, LogLevel } from 'vs/platform/log/common/log'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { ILogService, ILoggerService, ILogger, ILoggerOptions, AbstractLoggerService, LogLevel } from 'vs/platform/log/common/log'; import { URI } from 'vs/base/common/uri'; import { basename } from 'vs/base/common/resources'; import { Schemas } from 'vs/base/common/network'; @@ -13,47 +12,25 @@ import { SpdLogLogger } from 'vs/platform/log/node/spdlogLog'; import { IFileService } from 'vs/platform/files/common/files'; import { generateUuid } from 'vs/base/common/uuid'; -export class LoggerService extends Disposable implements ILoggerService { - - declare readonly _serviceBrand: undefined; - - private readonly loggers = new Map(); - private readonly logLevelChangeableLoggers: ILogger[] = []; +export class LoggerService extends AbstractLoggerService implements ILoggerService { constructor( - @ILogService private logService: ILogService, - @IFileService private fileService: IFileService + @ILogService logService: ILogService, + @IFileService private readonly fileService: IFileService ) { - super(); - this._register(logService.onDidChangeLogLevel(level => this.logLevelChangeableLoggers.forEach(logger => logger.setLevel(level)))); + super(logService.getLevel(), logService.onDidChangeLogLevel); } - createLogger(resource: URI, options?: ILoggerOptions): ILogger { - let logger = this.loggers.get(resource.toString()); - if (!logger) { - if (resource.scheme === Schemas.file) { - logger = new SpdLogLogger(options?.name || generateUuid(), resource.fsPath, !options?.donotRotate, this.logService.getLevel()); - if (options?.donotUseFormatters) { - (logger).clearFormatters(); - } - } else { - logger = new FileLogger(options?.name ?? basename(resource), resource, this.logService.getLevel(), this.fileService); - } - this.loggers.set(resource.toString(), logger); - if (options?.always) { - logger.setLevel(LogLevel.Trace); - } else { - this.logLevelChangeableLoggers.push(logger); + protected doCreateLogger(resource: URI, logLevel: LogLevel, options?: ILoggerOptions): ILogger { + if (resource.scheme === Schemas.file) { + const logger = new SpdLogLogger(options?.name || generateUuid(), resource.fsPath, !options?.donotRotate, logLevel); + if (options?.donotUseFormatters) { + (logger).clearFormatters(); } + return logger; + } else { + return new FileLogger(options?.name ?? basename(resource), resource, logLevel, this.fileService); } - return logger; - } - - dispose(): void { - this.logLevelChangeableLoggers.splice(0, this.logLevelChangeableLoggers.length); - this.loggers.forEach(logger => logger.dispose()); - this.loggers.clear(); - super.dispose(); } } diff --git a/src/vs/platform/log/node/spdlogLog.ts b/src/vs/platform/log/node/spdlogLog.ts index 5b036c47..b97064e2 100644 --- a/src/vs/platform/log/node/spdlogLog.ts +++ b/src/vs/platform/log/node/spdlogLog.ts @@ -95,7 +95,7 @@ export class SpdLogLogger extends AbstractMessageLogger implements ILogger { } } - flush(): void { + override flush(): void { if (this._logger) { this._logger.flush(); } else { @@ -103,7 +103,7 @@ export class SpdLogLogger extends AbstractMessageLogger implements ILogger { } } - dispose(): void { + override dispose(): void { if (this._logger) { this.disposeLogger(); } else { diff --git a/src/vs/platform/markers/test/common/markerService.test.ts b/src/vs/platform/markers/test/common/markerService.test.ts index ad6265fd..3869d3f6 100644 --- a/src/vs/platform/markers/test/common/markerService.test.ts +++ b/src/vs/platform/markers/test/common/markerService.test.ts @@ -30,10 +30,10 @@ suite('Marker Service', () => { marker: randomMarkerData(MarkerSeverity.Error) }]); - assert.equal(service.read().length, 1); - assert.equal(service.read({ owner: 'far' }).length, 1); - assert.equal(service.read({ resource: URI.parse('file:///c/test/file.cs') }).length, 1); - assert.equal(service.read({ owner: 'far', resource: URI.parse('file:///c/test/file.cs') }).length, 1); + assert.strictEqual(service.read().length, 1); + assert.strictEqual(service.read({ owner: 'far' }).length, 1); + assert.strictEqual(service.read({ resource: URI.parse('file:///c/test/file.cs') }).length, 1); + assert.strictEqual(service.read({ owner: 'far', resource: URI.parse('file:///c/test/file.cs') }).length, 1); service.changeAll('boo', [{ @@ -41,14 +41,14 @@ suite('Marker Service', () => { marker: randomMarkerData(MarkerSeverity.Warning) }]); - assert.equal(service.read().length, 2); - assert.equal(service.read({ owner: 'far' }).length, 1); - assert.equal(service.read({ owner: 'boo' }).length, 1); + assert.strictEqual(service.read().length, 2); + assert.strictEqual(service.read({ owner: 'far' }).length, 1); + assert.strictEqual(service.read({ owner: 'boo' }).length, 1); - assert.equal(service.read({ severities: MarkerSeverity.Error }).length, 1); - assert.equal(service.read({ severities: MarkerSeverity.Warning }).length, 1); - assert.equal(service.read({ severities: MarkerSeverity.Hint }).length, 0); - assert.equal(service.read({ severities: MarkerSeverity.Error | MarkerSeverity.Warning }).length, 2); + assert.strictEqual(service.read({ severities: MarkerSeverity.Error }).length, 1); + assert.strictEqual(service.read({ severities: MarkerSeverity.Warning }).length, 1); + assert.strictEqual(service.read({ severities: MarkerSeverity.Hint }).length, 0); + assert.strictEqual(service.read({ severities: MarkerSeverity.Error | MarkerSeverity.Warning }).length, 2); }); @@ -57,17 +57,17 @@ suite('Marker Service', () => { let service = new markerService.MarkerService(); service.changeOne('far', URI.parse('file:///path/only.cs'), [randomMarkerData()]); - assert.equal(service.read().length, 1); - assert.equal(service.read({ owner: 'far' }).length, 1); + assert.strictEqual(service.read().length, 1); + assert.strictEqual(service.read({ owner: 'far' }).length, 1); service.changeOne('boo', URI.parse('file:///path/only.cs'), [randomMarkerData()]); - assert.equal(service.read().length, 2); - assert.equal(service.read({ owner: 'far' }).length, 1); - assert.equal(service.read({ owner: 'boo' }).length, 1); + assert.strictEqual(service.read().length, 2); + assert.strictEqual(service.read({ owner: 'far' }).length, 1); + assert.strictEqual(service.read({ owner: 'boo' }).length, 1); service.changeOne('far', URI.parse('file:///path/only.cs'), [randomMarkerData(), randomMarkerData()]); - assert.equal(service.read({ owner: 'far' }).length, 2); - assert.equal(service.read({ owner: 'boo' }).length, 1); + assert.strictEqual(service.read({ owner: 'far' }).length, 2); + assert.strictEqual(service.read({ owner: 'boo' }).length, 1); }); @@ -76,19 +76,19 @@ suite('Marker Service', () => { let service = new markerService.MarkerService(); service.changeOne('far', URI.parse('file:///path/only.cs'), [randomMarkerData()]); service.changeOne('boo', URI.parse('file:///path/only.cs'), [randomMarkerData()]); - assert.equal(service.read({ owner: 'far' }).length, 1); - assert.equal(service.read({ owner: 'boo' }).length, 1); - assert.equal(service.read().length, 2); + assert.strictEqual(service.read({ owner: 'far' }).length, 1); + assert.strictEqual(service.read({ owner: 'boo' }).length, 1); + assert.strictEqual(service.read().length, 2); service.changeOne('far', URI.parse('file:///path/only.cs'), []); - assert.equal(service.read({ owner: 'far' }).length, 0); - assert.equal(service.read({ owner: 'boo' }).length, 1); - assert.equal(service.read().length, 1); + assert.strictEqual(service.read({ owner: 'far' }).length, 0); + assert.strictEqual(service.read({ owner: 'boo' }).length, 1); + assert.strictEqual(service.read().length, 1); service.changeAll('boo', []); - assert.equal(service.read({ owner: 'far' }).length, 0); - assert.equal(service.read({ owner: 'boo' }).length, 0); - assert.equal(service.read().length, 0); + assert.strictEqual(service.read({ owner: 'far' }).length, 0); + assert.strictEqual(service.read({ owner: 'boo' }).length, 0); + assert.strictEqual(service.read().length, 0); }); test('changeAll sends event for cleared', () => { @@ -102,12 +102,12 @@ suite('Marker Service', () => { marker: randomMarkerData() }]); - assert.equal(service.read({ owner: 'far' }).length, 2); + assert.strictEqual(service.read({ owner: 'far' }).length, 2); service.onMarkerChanged(changedResources => { - assert.equal(changedResources.length, 1); - changedResources.forEach(u => assert.equal(u.toString(), 'file:///d/path')); - assert.equal(service.read({ owner: 'far' }).length, 0); + assert.strictEqual(changedResources.length, 1); + changedResources.forEach(u => assert.strictEqual(u.toString(), 'file:///d/path')); + assert.strictEqual(service.read({ owner: 'far' }).length, 0); }); service.changeAll('far', []); @@ -124,7 +124,7 @@ suite('Marker Service', () => { marker: randomMarkerData() }]); - assert.equal(service.read({ owner: 'far' }).length, 2); + assert.strictEqual(service.read({ owner: 'far' }).length, 2); }); test('changeAll must not break integrety, issue #12635', () => { @@ -151,8 +151,8 @@ suite('Marker Service', () => { marker: randomMarkerData() }]); - assert.equal(service.read({ owner: 'far' }).length, 2); - assert.equal(service.read({ resource: URI.parse('scheme:path1') }).length, 2); + assert.strictEqual(service.read({ owner: 'far' }).length, 2); + assert.strictEqual(service.read({ resource: URI.parse('scheme:path1') }).length, 2); }); test('invalid marker data', () => { @@ -162,15 +162,15 @@ suite('Marker Service', () => { data.message = undefined!; service.changeOne('far', URI.parse('some:uri/path'), [data]); - assert.equal(service.read({ owner: 'far' }).length, 0); + assert.strictEqual(service.read({ owner: 'far' }).length, 0); data.message = null!; service.changeOne('far', URI.parse('some:uri/path'), [data]); - assert.equal(service.read({ owner: 'far' }).length, 0); + assert.strictEqual(service.read({ owner: 'far' }).length, 0); data.message = 'null'; service.changeOne('far', URI.parse('some:uri/path'), [data]); - assert.equal(service.read({ owner: 'far' }).length, 1); + assert.strictEqual(service.read({ owner: 'far' }).length, 1); }); test('MapMap#remove returns bad values, https://github.com/microsoft/vscode/issues/13548', () => { @@ -189,7 +189,7 @@ suite('Marker Service', () => { endLineNumber: 1, endColumn: 5, message: 'test', - severity: 0, + severity: 0 as MarkerSeverity, source: 'me' }; let service = new markerService.MarkerService(); @@ -197,7 +197,7 @@ suite('Marker Service', () => { service.changeOne('far', URI.parse('some:thing'), [data]); let marker = service.read({ resource: URI.parse('some:thing') }); - assert.equal(marker.length, 1); - assert.equal(marker[0].code, '0'); + assert.strictEqual(marker.length, 1); + assert.strictEqual(marker[0].code, '0'); }); }); diff --git a/src/vs/platform/menubar/electron-main/menubar.ts b/src/vs/platform/menubar/electron-main/menubar.ts index d4f42330..f9ab5a1e 100644 --- a/src/vs/platform/menubar/electron-main/menubar.ts +++ b/src/vs/platform/menubar/electron-main/menubar.ts @@ -168,7 +168,7 @@ export class Menubar { // Keep flag when app quits this.lifecycleMainService.onWillShutdown(() => this.willShutdown = true); - // // Listen to some events from window service to update menu + // Listen to some events from window service to update menu this.windowsMainService.onDidChangeWindowsCount(e => this.onDidChangeWindowsCount(e)); this.nativeHostMainService.onDidBlurWindow(() => this.onDidChangeWindowFocus()); this.nativeHostMainService.onDidFocusWindow(() => this.onDidChangeWindowFocus()); @@ -569,10 +569,7 @@ export class Menubar { return [new MenuItem({ label: this.mnemonicLabel(nls.localize('miCheckForUpdates', "Check for &&Updates...")), click: () => setTimeout(() => { this.reportMenuActionTelemetry('CheckForUpdate'); - - const window = this.windowsMainService.getLastActiveWindow(); - const context = window && `window:${window.id}`; // sessionId - this.updateService.checkForUpdates(context); + this.updateService.checkForUpdates(true); }, 0) })]; diff --git a/src/vs/platform/native/electron-main/nativeHostMainService.ts b/src/vs/platform/native/electron-main/nativeHostMainService.ts index eb071741..a0b1a3bd 100644 --- a/src/vs/platform/native/electron-main/nativeHostMainService.ts +++ b/src/vs/platform/native/electron-main/nativeHostMainService.ts @@ -407,7 +407,7 @@ export class NativeHostMainService extends Disposable implements INativeHostMain 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) => { + sudoPrompt.exec(sudoCommand.join(' '), promptOptions, (error?, stdout?, stderr?) => { if (stdout) { this.logService.trace(`[sudo-prompt] received stdout: ${stdout}`); } diff --git a/src/vs/platform/notification/common/notification.ts b/src/vs/platform/notification/common/notification.ts index 130e513d..1e3bf4e0 100644 --- a/src/vs/platform/notification/common/notification.ts +++ b/src/vs/platform/notification/common/notification.ts @@ -72,6 +72,13 @@ export interface INeverShowAgainOptions { export interface INotification extends INotificationProperties { + /** + * The id of the notification. If provided, will be used to compare + * notifications with others to decide whether a notification is + * duplicate or not. + */ + readonly id?: string; + /** * The severity of the notification. Either `Info`, `Warning` or `Error`. */ @@ -117,14 +124,14 @@ export interface INotificationActions { * * Pass `ActionWithMenuAction` for an action that has additional menu actions. */ - readonly primary?: ReadonlyArray; + readonly primary?: readonly IAction[]; /** * Secondary actions are meant to provide additional configuration or context * for the notification and will show up less prominent. A notification does not * close automatically when invoking a secondary action. */ - readonly secondary?: ReadonlyArray; + readonly secondary?: readonly IAction[]; } export interface INotificationProgressProperties { diff --git a/src/vs/platform/opener/browser/link.ts b/src/vs/platform/opener/browser/link.ts index ca887940..c3d3d7d0 100644 --- a/src/vs/platform/opener/browser/link.ts +++ b/src/vs/platform/opener/browser/link.ts @@ -53,7 +53,7 @@ export class Link extends Disposable { this._register(onOpen(e => { EventHelper.stop(e, true); if (!this.disabled) { - openerService.open(link.href); + openerService.open(link.href, { allowCommands: true }); } })); diff --git a/src/vs/platform/opener/common/opener.ts b/src/vs/platform/opener/common/opener.ts index ff6740cf..33c46573 100644 --- a/src/vs/platform/opener/common/opener.ts +++ b/src/vs/platform/opener/common/opener.ts @@ -30,6 +30,11 @@ export type OpenInternalOptions = { * action, such as keyboard or mouse usage. */ readonly fromUserGesture?: boolean; + + /** + * Allow command links to be handled. + */ + readonly allowCommands?: boolean; }; export type OpenExternalOptions = { diff --git a/src/vs/platform/product/common/product.ts b/src/vs/platform/product/common/product.ts index 251ed36d..d79c8a32 100644 --- a/src/vs/platform/product/common/product.ts +++ b/src/vs/platform/product/common/product.ts @@ -4,44 +4,26 @@ *--------------------------------------------------------------------------------------------*/ import { FileAccess } from 'vs/base/common/network'; -import { isWeb } from 'vs/base/common/platform'; +import { isWeb, globals } from 'vs/base/common/platform'; import { env } from 'vs/base/common/process'; import { dirname, joinPath } from 'vs/base/common/resources'; -import { IProductConfiguration } from 'vs/platform/product/common/productService'; +import { IProductConfiguration } from 'vs/base/common/product'; +import { ISandboxConfiguration } from 'vs/base/parts/sandbox/common/sandboxTypes'; let product: IProductConfiguration; -// Web or Native (sandbox TODO@sandbox need to add all properties of product.json) -if (isWeb || typeof require === 'undefined' || typeof require.__$__nodeRequire !== 'function') { - - // Built time configuration (do NOT modify) - product = { /*BUILD->INSERT_PRODUCT_CONFIGURATION*/ } as IProductConfiguration; - - // Running out of sources - if (Object.keys(product).length === 0) { - Object.assign(product, { - version: '1.55.0-dev', - nameShort: isWeb ? 'Code Web - OSS Dev' : 'Code - OSS Dev', - nameLong: isWeb ? 'Code Web - OSS Dev' : 'Code - OSS Dev', - applicationName: 'code-oss', - dataFolderName: '.vscode-oss', - urlProtocol: 'code-oss', - reportIssueUrl: 'https://github.com/microsoft/vscode/issues/new', - licenseName: 'MIT', - licenseUrl: 'https://github.com/microsoft/vscode/blob/main/LICENSE.txt', - extensionAllowedProposedApi: [ - 'ms-vscode.vscode-js-profile-flame', - 'ms-vscode.vscode-js-profile-table', - 'ms-vscode.github-browser', - 'ms-vscode.remotehub', - 'ms-vscode.remotehub-insiders' - ], - }); +// Native sandbox environment +if (typeof globals.vscode !== 'undefined') { + const configuration: ISandboxConfiguration | undefined = globals.vscode.context.configuration(); + if (configuration) { + product = configuration.product; + } else { + throw new Error('Sandbox: unable to resolve product configuration from preload script.'); } } -// Native (non-sandboxed) -else { +// Native node.js environment +else if (typeof require?.__$__nodeRequire === 'function') { // Obtain values from product.json and package.json const rootPath = dirname(FileAccess.asFileUri('', require)); @@ -63,4 +45,34 @@ else { }); } +// Web environment or unknown +else { + + // Built time configuration (do NOT modify) + product = { /*BUILD->INSERT_PRODUCT_CONFIGURATION*/ } as IProductConfiguration; + + // Running out of sources + if (Object.keys(product).length === 0) { + Object.assign(product, { + version: '1.56.0-dev', + nameShort: isWeb ? 'Code Web - OSS Dev' : 'Code - OSS Dev', + nameLong: isWeb ? 'Code Web - OSS Dev' : 'Code - OSS Dev', + applicationName: 'code-oss', + dataFolderName: '.vscode-oss', + urlProtocol: 'code-oss', + reportIssueUrl: 'https://github.com/microsoft/vscode/issues/new', + licenseName: 'MIT', + licenseUrl: 'https://github.com/microsoft/vscode/blob/main/LICENSE.txt', + extensionAllowedProposedApi: [ + 'ms-vscode.vscode-js-profile-flame', + 'ms-vscode.vscode-js-profile-table', + 'ms-vscode.github-browser', + 'ms-vscode.github-richnav', + 'ms-vscode.remotehub', + 'ms-vscode.remotehub-insiders' + ], + }); + } +} + export default product; diff --git a/src/vs/platform/product/common/productService.ts b/src/vs/platform/product/common/productService.ts index 34acc149..857189f8 100644 --- a/src/vs/platform/product/common/productService.ts +++ b/src/vs/platform/product/common/productService.ts @@ -4,8 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { ExtensionKind } from 'vs/platform/extensions/common/extensions'; -import { IStringDictionary } from 'vs/base/common/collections'; +import { IProductConfiguration } from 'vs/base/common/product'; export const IProductService = createDecorator('productService'); @@ -14,158 +13,3 @@ export interface IProductService extends Readonly { readonly _serviceBrand: undefined; } - -export interface IBuiltInExtension { - readonly name: string; - readonly version: string; - readonly repo: string; - readonly metadata: any; -} - -export type ConfigurationSyncStore = { - url: string, - insidersUrl: string, - stableUrl: string, - canSwitch: boolean, - authenticationProviders: IStringDictionary<{ scopes: string[] }> -}; - -export interface IProductConfiguration { - readonly version: string; - readonly date?: string; - readonly quality?: string; - readonly commit?: string; - - readonly nameShort: string; - readonly nameLong: string; - - readonly win32AppUserModelId?: string; - readonly win32MutexName?: string; - readonly applicationName: string; - - readonly urlProtocol: string; - readonly dataFolderName: string; // location for extensions (e.g. ~/.vscode-insiders) - - readonly builtInExtensions?: IBuiltInExtension[]; - - readonly downloadUrl?: string; - readonly updateUrl?: string; - readonly webEndpointUrl?: string; - readonly target?: string; - - readonly settingsSearchBuildId?: number; - readonly settingsSearchUrl?: string; - - readonly tasConfig?: { - endpoint: string; - telemetryEventName: string; - featuresTelemetryPropertyName: string; - assignmentContextTelemetryPropertyName: string; - }; - - readonly experimentsUrl?: string; - - readonly extensionsGallery?: { - readonly serviceUrl: string; - readonly itemUrl: string; - readonly controlUrl: string; - readonly recommendationsUrl: string; - }; - - readonly extensionTips?: { [id: string]: string; }; - readonly extensionImportantTips?: IStringDictionary; - readonly configBasedExtensionTips?: { [id: string]: IConfigBasedExtensionTip; }; - readonly exeBasedExtensionTips?: { [id: string]: IExeBasedExtensionTip; }; - readonly remoteExtensionTips?: { [remoteName: string]: IRemoteExtensionTip; }; - readonly extensionKeywords?: { [extension: string]: readonly string[]; }; - readonly keymapExtensionTips?: readonly string[]; - readonly trustedExtensionUrlPublicKeys?: { [id: string]: string[]; }; - - readonly crashReporter?: { - readonly companyName: string; - readonly productName: string; - }; - - readonly enableTelemetry?: boolean; - readonly aiConfig?: { - readonly asimovKey: string; - }; - - readonly sendASmile?: { - readonly reportIssueUrl: string, - readonly requestFeatureUrl: string - }; - - readonly documentationUrl?: string; - readonly releaseNotesUrl?: string; - readonly keyboardShortcutsUrlMac?: string; - readonly keyboardShortcutsUrlLinux?: string; - readonly keyboardShortcutsUrlWin?: string; - readonly introductoryVideosUrl?: string; - readonly tipsAndTricksUrl?: string; - readonly newsletterSignupUrl?: string; - readonly twitterUrl?: string; - readonly requestFeatureUrl?: string; - readonly reportIssueUrl?: string; - readonly reportMarketplaceIssueUrl?: string; - readonly licenseUrl?: string; - readonly privacyStatementUrl?: string; - readonly telemetryOptOutUrl?: string; - - readonly npsSurveyUrl?: string; - readonly cesSurveyUrl?: string; - readonly surveys?: readonly ISurveyData[]; - - readonly checksums?: { [path: string]: string; }; - readonly checksumFailMoreInfoUrl?: string; - - readonly appCenter?: IAppCenterConfiguration; - - readonly portable?: string; - - readonly extensionKind?: { readonly [extensionId: string]: ExtensionKind[]; }; - readonly extensionSyncedKeys?: { readonly [extensionId: string]: string[]; }; - readonly extensionAllowedProposedApi?: readonly string[]; - - readonly msftInternalDomains?: string[]; - readonly linkProtectionTrustedDomains?: readonly string[]; - - readonly 'configurationSync.store'?: ConfigurationSyncStore; - - readonly darwinUniversalAssetId?: string; -} - -export type ImportantExtensionTip = { name: string; languages?: string[]; pattern?: string; isExtensionPack?: boolean }; - -export interface IAppCenterConfiguration { - readonly 'win32-ia32': string; - readonly 'win32-x64': string; - readonly 'linux-x64': string; - readonly 'darwin': string; -} - -export interface IConfigBasedExtensionTip { - configPath: string; - configName: string; - recommendations: IStringDictionary<{ name: string, remotes?: string[], important?: boolean, isExtensionPack?: boolean }>; -} - -export interface IExeBasedExtensionTip { - friendlyName: string; - windowsPath?: string; - important?: boolean; - recommendations: IStringDictionary<{ name: string, important?: boolean, isExtensionPack?: boolean }>; -} - -export interface IRemoteExtensionTip { - friendlyName: string; - extensionId: string; -} - -export interface ISurveyData { - surveyId: string; - surveyUrl: string; - languageId: string; - editCount: number; - userProbability: number; -} diff --git a/src/vs/platform/progress/common/progress.ts b/src/vs/platform/progress/common/progress.ts index e777dc2b..9c784f36 100644 --- a/src/vs/platform/progress/common/progress.ts +++ b/src/vs/platform/progress/common/progress.ts @@ -60,8 +60,8 @@ export interface IProgressOptions { export interface IProgressNotificationOptions extends IProgressOptions { readonly location: ProgressLocation.Notification; - readonly primaryActions?: ReadonlyArray; - readonly secondaryActions?: ReadonlyArray; + readonly primaryActions?: readonly IAction[]; + readonly secondaryActions?: readonly IAction[]; readonly delay?: number; readonly silent?: boolean; } diff --git a/src/vs/platform/protocol/electron-main/protocol.ts b/src/vs/platform/protocol/electron-main/protocol.ts new file mode 100644 index 00000000..e224973a --- /dev/null +++ b/src/vs/platform/protocol/electron-main/protocol.ts @@ -0,0 +1,51 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IDisposable } from 'vs/base/common/lifecycle'; +import { URI } from 'vs/base/common/uri'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; + +export const IProtocolMainService = createDecorator('protocolMainService'); + +export interface IIPCObjectUrl extends IDisposable { + + /** + * A `URI` that a renderer can use to retrieve the + * object via `ipcRenderer.invoke(resource.toString())` + */ + resource: URI; + + /** + * Allows to update the value of the object after it + * has been created. + * + * @param obj the object to make accessible to the + * renderer. + */ + update(obj: T): void; +} + +export interface IProtocolMainService { + + readonly _serviceBrand: undefined; + + /** + * Allows to make an object accessible to a renderer + * via `ipcRenderer.invoke(resource.toString())`. + * + * @param obj the (optional) object to make accessible to the + * renderer. Can be updated later via the `IObjectUrl#update` + * method too. + */ + createIPCObjectUrl(obj?: T): IIPCObjectUrl; + + /** + * Adds a `URI` as root to the list of allowed + * resources for file access. + * + * @param root the URI to allow for file access + */ + addValidFileRoot(root: URI): IDisposable; +} diff --git a/src/vs/code/electron-main/protocol.ts b/src/vs/platform/protocol/electron-main/protocolMainService.ts similarity index 69% rename from src/vs/code/electron-main/protocol.ts rename to src/vs/platform/protocol/electron-main/protocolMainService.ts index 05998534..9f93a6b7 100644 --- a/src/vs/code/electron-main/protocol.ts +++ b/src/vs/platform/protocol/electron-main/protocolMainService.ts @@ -3,21 +3,23 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Event } from 'vs/base/common/event'; -import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { FileAccess, Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; import { INativeEnvironmentService } from 'vs/platform/environment/common/environment'; -import { session } from 'electron'; +import { ipcMain, session } from 'electron'; import { ILogService } from 'vs/platform/log/common/log'; import { TernarySearchTree } from 'vs/base/common/map'; import { isLinux, isPreferringBrowserCodeLoad } from 'vs/base/common/platform'; -import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows'; import { extname } from 'vs/base/common/resources'; +import { IIPCObjectUrl, IProtocolMainService } from 'vs/platform/protocol/electron-main/protocol'; +import { generateUuid } from 'vs/base/common/uuid'; type ProtocolCallback = { (result: string | Electron.FilePathWithHeaders | { error: number }): void }; -export class FileProtocolHandler extends Disposable { +export class ProtocolMainService extends Disposable implements IProtocolMainService { + + declare readonly _serviceBrand: undefined; private readonly validRoots = TernarySearchTree.forUris(() => !isLinux); private readonly validExtensions = new Set(['.png', '.jpg', '.jpeg', '.gif', '.bmp']); // https://github.com/microsoft/vscode/issues/119384 @@ -28,16 +30,21 @@ export class FileProtocolHandler extends Disposable { ) { super(); - const { defaultSession } = session; - // Define an initial set of roots we allow loading from // - appRoot : all files installed as part of the app // - extensions : all files shipped from extensions // - storage : all files in global and workspace storage (https://github.com/microsoft/vscode/issues/116735) - this.validRoots.set(URI.file(environmentService.appRoot), true); - this.validRoots.set(URI.file(environmentService.extensionsPath), true); - this.validRoots.set(environmentService.globalStorageHome, true); - this.validRoots.set(environmentService.workspaceStorageHome, true); + this.addValidFileRoot(URI.file(environmentService.appRoot)); + this.addValidFileRoot(URI.file(environmentService.extensionsPath)); + this.addValidFileRoot(environmentService.globalStorageHome); + this.addValidFileRoot(environmentService.workspaceStorageHome); + + // Handle protocols + this.handleProtocols(); + } + + private handleProtocols(): void { + const { defaultSession } = session; // Register vscode-file:// handler defaultSession.protocol.registerFileProtocol(Schemas.vscodeFileResource, (request, callback) => this.handleResourceRequest(request, callback as unknown as ProtocolCallback)); @@ -52,28 +59,7 @@ export class FileProtocolHandler extends Disposable { })); } - injectWindowsMainService(windowsMainService: IWindowsMainService): void { - this._register(windowsMainService.onDidSignalReadyWindow(window => { - if (window.config?.extensionDevelopmentPath || window.config?.extensionTestsPath) { - const disposables = new DisposableStore(); - disposables.add(Event.any(window.onDidClose, window.onDidDestroy)(() => disposables.dispose())); - - // Allow access to extension development path - if (window.config.extensionDevelopmentPath) { - for (const extensionDevelopmentPath of window.config.extensionDevelopmentPath) { - disposables.add(this.addValidRoot(URI.file(extensionDevelopmentPath))); - } - } - - // Allow access to extension tests path - if (window.config.extensionTestsPath) { - disposables.add(this.addValidRoot(URI.file(window.config.extensionTestsPath))); - } - } - })); - } - - private addValidRoot(root: URI): IDisposable { + addValidFileRoot(root: URI): IDisposable { if (!this.validRoots.get(root)) { this.validRoots.set(root, true); @@ -83,7 +69,9 @@ export class FileProtocolHandler extends Disposable { return Disposable.None; } - private async handleFileRequest(request: Electron.ProtocolRequest, callback: ProtocolCallback) { + //#region file:// + + private handleFileRequest(request: Electron.ProtocolRequest, callback: ProtocolCallback): void { const fileUri = URI.parse(request.url); // isPreferringBrowserCodeLoad: false @@ -118,7 +106,11 @@ export class FileProtocolHandler extends Disposable { } } - private async handleResourceRequest(request: Electron.ProtocolRequest, callback: ProtocolCallback) { + //#endregion + + //#region vscode-file:// + + private handleResourceRequest(request: Electron.ProtocolRequest, callback: ProtocolCallback): void { const uri = URI.parse(request.url); // Restore the `vscode-file` URI to a `file` URI so that we can @@ -145,4 +137,36 @@ export class FileProtocolHandler extends Disposable { return callback({ error: -3 /* ABORTED */ }); } + + //#endregion + + //#region IPC Object URLs + + createIPCObjectUrl(obj: T): IIPCObjectUrl { + + // Create unique URI + const resource = URI.from({ + scheme: 'vscode', // used for all our IPC communication (vscode:) + path: generateUuid() + }); + + // Install IPC handler + const channel = resource.toString(); + const handler = async (): Promise => obj; + ipcMain.handle(channel, handler); + + this.logService.trace(`IPC Object URL: Registered new channel ${channel}.`); + + return { + resource, + update: updatedObj => obj = updatedObj, + dispose: () => { + this.logService.trace(`IPC Object URL: Removed channel ${channel}.`); + + ipcMain.removeHandler(channel); + } + }; + } + + //#endregion } diff --git a/src/vs/platform/quickinput/browser/commandsQuickAccess.ts b/src/vs/platform/quickinput/browser/commandsQuickAccess.ts index a1d27c5a..96210a56 100644 --- a/src/vs/platform/quickinput/browser/commandsQuickAccess.ts +++ b/src/vs/platform/quickinput/browser/commandsQuickAccess.ts @@ -39,8 +39,10 @@ export abstract class AbstractCommandsQuickAccessProvider extends PickerQuickAcc private readonly commandsHistory = this._register(this.instantiationService.createInstance(CommandsHistory)); + protected override readonly options: ICommandsQuickAccessOptions; + constructor( - protected options: ICommandsQuickAccessOptions, + options: ICommandsQuickAccessOptions, @IInstantiationService private readonly instantiationService: IInstantiationService, @IKeybindingService private readonly keybindingService: IKeybindingService, @ICommandService private readonly commandService: ICommandService, @@ -48,6 +50,8 @@ export abstract class AbstractCommandsQuickAccessProvider extends PickerQuickAcc @INotificationService private readonly notificationService: INotificationService ) { super(AbstractCommandsQuickAccessProvider.PREFIX, options); + + this.options = options; } protected async getPicks(filter: string, disposables: DisposableStore, token: CancellationToken): Promise> { diff --git a/src/vs/platform/quickinput/browser/pickerQuickAccess.ts b/src/vs/platform/quickinput/browser/pickerQuickAccess.ts index 8286520c..4d8dafdb 100644 --- a/src/vs/platform/quickinput/browser/pickerQuickAccess.ts +++ b/src/vs/platform/quickinput/browser/pickerQuickAccess.ts @@ -73,8 +73,8 @@ export interface IPickerQuickAccessProviderOptions = T | IQuickPickSeparator; -export type PicksWithActive = { items: ReadonlyArray>, active?: T }; -export type Picks = ReadonlyArray> | PicksWithActive; +export type PicksWithActive = { items: readonly Pick[], active?: T }; +export type Picks = readonly Pick[] | PicksWithActive; export type FastAndSlowPicks = { picks: Picks, additionalPicks: Promise> }; function isPicksWithActive(obj: unknown): obj is PicksWithActive { @@ -125,7 +125,7 @@ export abstract class PickerQuickAccessProvider, skipEmpty?: boolean): boolean => { - let items: ReadonlyArray>; + let items: readonly Pick[]; let activeItem: T | undefined = undefined; if (isPicksWithActive(picks)) { @@ -191,7 +191,7 @@ export abstract class PickerQuickAccessProvider>; + let picks: readonly Pick[]; let activePick: Pick | undefined = undefined; if (isPicksWithActive(providedPicks.picks)) { picks = providedPicks.picks.items; @@ -200,7 +200,7 @@ export abstract class PickerQuickAccessProvider>; + let additionalPicks: readonly Pick[]; let additionalActivePick: Pick | undefined = undefined; if (isPicksWithActive(awaitedAdditionalPicks)) { additionalPicks = awaitedAdditionalPicks.items; diff --git a/src/vs/platform/quickinput/browser/quickInput.ts b/src/vs/platform/quickinput/browser/quickInput.ts index d696697f..e0884b98 100644 --- a/src/vs/platform/quickinput/browser/quickInput.ts +++ b/src/vs/platform/quickinput/browser/quickInput.ts @@ -7,7 +7,7 @@ import { IQuickInputService, IQuickPickItem, IPickOptions, IInputOptions, IQuick import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IThemeService, Themable } from 'vs/platform/theme/common/themeService'; -import { inputBackground, inputForeground, inputBorder, inputValidationInfoBackground, inputValidationInfoForeground, inputValidationInfoBorder, inputValidationWarningBackground, inputValidationWarningForeground, inputValidationWarningBorder, inputValidationErrorBackground, inputValidationErrorForeground, inputValidationErrorBorder, badgeBackground, badgeForeground, contrastBorder, buttonForeground, buttonBackground, buttonHoverBackground, progressBarBackground, widgetShadow, listFocusForeground, activeContrastBorder, pickerGroupBorder, pickerGroupForeground, quickInputForeground, quickInputBackground, quickInputTitleBackground, quickInputListFocusBackground } from 'vs/platform/theme/common/colorRegistry'; +import { inputBackground, inputForeground, inputBorder, inputValidationInfoBackground, inputValidationInfoForeground, inputValidationInfoBorder, inputValidationWarningBackground, inputValidationWarningForeground, inputValidationWarningBorder, inputValidationErrorBackground, inputValidationErrorForeground, inputValidationErrorBorder, badgeBackground, badgeForeground, contrastBorder, buttonForeground, buttonBackground, buttonHoverBackground, progressBarBackground, widgetShadow, listFocusForeground, activeContrastBorder, pickerGroupBorder, pickerGroupForeground, quickInputForeground, quickInputBackground, quickInputTitleBackground, quickInputListFocusBackground, keybindingLabelBackground, keybindingLabelForeground, keybindingLabelBorder, keybindingLabelBottomBorder } from 'vs/platform/theme/common/colorRegistry'; import { CancellationToken } from 'vs/base/common/cancellation'; import { computeStyles } from 'vs/platform/theme/common/styler'; import { IContextKeyService, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey'; @@ -166,7 +166,7 @@ export class QuickInputService extends Themable implements IQuickInputService { return this.controller.cancel(); } - protected updateStyles() { + protected override updateStyles() { this.controller.applyStyles(this.computeStyles()); } @@ -209,6 +209,13 @@ export class QuickInputService extends Themable implements IQuickInputService { progressBar: computeStyles(this.theme, { progressBarBackground }), + keybindingLabel: computeStyles(this.theme, { + keybindingLabelBackground, + keybindingLabelForeground, + keybindingLabelBorder, + keybindingLabelBottomBorder, + keybindingLabelShadow: widgetShadow + }), list: computeStyles(this.theme, { listBackground: quickInputBackground, // Look like focused when inactive. diff --git a/src/vs/platform/registry/test/common/platform.test.ts b/src/vs/platform/registry/test/common/platform.test.ts index a3d06dff..47cf1aff 100644 --- a/src/vs/platform/registry/test/common/platform.test.ts +++ b/src/vs/platform/registry/test/common/platform.test.ts @@ -21,7 +21,7 @@ suite('Platform / Registry', () => { assert.ok(Registry.knows('foo')); assert.ok(Registry.as('foo').bar); - assert.equal(Registry.as('foo').bar, true); + assert.strictEqual(Registry.as('foo').bar, true); }); test('registry - knows, as', function () { diff --git a/src/vs/platform/remote/common/remoteAgentEnvironment.ts b/src/vs/platform/remote/common/remoteAgentEnvironment.ts index fd275280..bf2cc0f3 100644 --- a/src/vs/platform/remote/common/remoteAgentEnvironment.ts +++ b/src/vs/platform/remote/common/remoteAgentEnvironment.ts @@ -20,6 +20,7 @@ export interface IRemoteAgentEnvironment { userHome: URI; os: OperatingSystem; marks: performance.PerformanceMark[]; + useHostProxy: boolean; } export interface RemoteAgentConnectionContext { diff --git a/src/vs/platform/remote/common/remoteAuthorityResolver.ts b/src/vs/platform/remote/common/remoteAuthorityResolver.ts index d2f056af..52902edf 100644 --- a/src/vs/platform/remote/common/remoteAuthorityResolver.ts +++ b/src/vs/platform/remote/common/remoteAuthorityResolver.ts @@ -15,8 +15,15 @@ export interface ResolvedAuthority { readonly connectionToken: string | undefined; } +export enum RemoteTrustOption { + Unknown = 0, + DisableTrust = 1, + MachineTrusted = 2 +} + export interface ResolvedOptions { readonly extensionHostEnv?: { [key: string]: string | null }; + readonly trust?: RemoteTrustOption; } export interface TunnelDescription { diff --git a/src/vs/platform/remote/common/remoteHosts.ts b/src/vs/platform/remote/common/remoteHosts.ts index 86f2c4f2..6894d782 100644 --- a/src/vs/platform/remote/common/remoteHosts.ts +++ b/src/vs/platform/remote/common/remoteHosts.ts @@ -5,6 +5,7 @@ import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; +import { IWorkspace } from 'vs/platform/workspace/common/workspace'; export function getRemoteAuthority(uri: URI): string | undefined { return uri.scheme === Schemas.vscodeRemote ? uri.authority : undefined; @@ -24,3 +25,20 @@ export function getRemoteName(authority: string | undefined): string | undefined } return authority.substr(0, pos); } + +function isVirtualResource(resource: URI) { + return resource.scheme !== Schemas.file && resource.scheme !== Schemas.vscodeRemote; +} + +export function getVirtualWorkspaceLocation(workspace: IWorkspace): { scheme: string, authority: string } | undefined { + if (workspace.folders.length) { + return workspace.folders.every(f => isVirtualResource(f.uri)) ? workspace.folders[0].uri : undefined; + } else if (workspace.configuration && isVirtualResource(workspace.configuration)) { + return workspace.configuration; + } + return undefined; +} + +export function getVirtualWorkspaceScheme(workspace: IWorkspace): string | undefined { + return getVirtualWorkspaceLocation(workspace)?.scheme; +} diff --git a/src/vs/platform/remote/common/tunnel.ts b/src/vs/platform/remote/common/tunnel.ts index 080b647f..06de888d 100644 --- a/src/vs/platform/remote/common/tunnel.ts +++ b/src/vs/platform/remote/common/tunnel.ts @@ -237,7 +237,7 @@ export abstract class AbstractTunnelService implements ITunnelService { localAddress: tunnel.localAddress, public: tunnel.public, dispose: async () => { - this.logService.trace(`ForwardedPorts: (TunnelService) dispose request for ${tunnel.tunnelRemotePort} `); + this.logService.trace(`ForwardedPorts: (TunnelService) dispose request for ${tunnel.tunnelRemoteHost}:${tunnel.tunnelRemotePort} `); const existingHost = this._tunnels.get(tunnel.tunnelRemoteHost); if (existingHost) { const existing = existingHost.get(tunnel.tunnelRemotePort); @@ -267,7 +267,7 @@ export abstract class AbstractTunnelService implements ITunnelService { } async closeTunnel(remoteHost: string, remotePort: number): Promise { - this.logService.trace(`ForwardedPorts: (TunnelService) close request for ${remotePort} `); + this.logService.trace(`ForwardedPorts: (TunnelService) close request for ${remoteHost}:${remotePort} `); const portMap = this._tunnels.get(remoteHost); if (portMap && portMap.has(remotePort)) { const value = portMap.get(remotePort)!; diff --git a/src/vs/platform/remote/node/tunnelService.ts b/src/vs/platform/remote/node/tunnelService.ts index 0f0bb251..6483000c 100644 --- a/src/vs/platform/remote/node/tunnelService.ts +++ b/src/vs/platform/remote/node/tunnelService.ts @@ -58,7 +58,7 @@ class NodeRemoteTunnel extends Disposable implements RemoteTunnel { this.tunnelRemoteHost = tunnelRemoteHost; } - public async dispose(): Promise { + public override async dispose(): Promise { super.dispose(); this._server.removeListener('listening', this._listeningListener); this._server.removeListener('connection', this._connectionListener); diff --git a/src/vs/platform/request/common/request.ts b/src/vs/platform/request/common/request.ts index 0ab494b5..3b42f381 100644 --- a/src/vs/platform/request/common/request.ts +++ b/src/vs/platform/request/common/request.ts @@ -6,7 +6,7 @@ import { localize } from 'vs/nls'; import { CancellationToken } from 'vs/base/common/cancellation'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry'; +import { IConfigurationRegistry, Extensions, ConfigurationScope, IConfigurationNode } from 'vs/platform/configuration/common/configurationRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; import { streamToBuffer } from 'vs/base/common/buffer'; import { IRequestOptions, IRequestContext } from 'vs/base/parts/request/common/request'; @@ -66,43 +66,63 @@ export interface IHTTPConfiguration { }; } -Registry.as(Extensions.Configuration) - .registerConfiguration({ +export function updateProxyConfigurationsScope(scope: ConfigurationScope): void { + registerProxyConfigurations(scope); +} + +let proxyConfiguration: IConfigurationNode | undefined; +function registerProxyConfigurations(scope: ConfigurationScope): void { + const configurationRegistry = Registry.as(Extensions.Configuration); + if (proxyConfiguration) { + configurationRegistry.deregisterConfigurations([proxyConfiguration]); + } + proxyConfiguration = { id: 'http', order: 15, title: localize('httpConfigurationTitle', "HTTP"), type: 'object', + scope, properties: { 'http.proxy': { type: 'string', pattern: '^https?://([^:]*(:[^@]*)?@)?([^:]+|\\[[:0-9a-fA-F]+\\])(:\\d+)?/?$|^$', - markdownDescription: localize('proxy', "The proxy setting to use. If not set, will be inherited from the `http_proxy` and `https_proxy` environment variables.") + markdownDescription: localize('proxy', "The proxy setting to use. If not set, will be inherited from the `http_proxy` and `https_proxy` environment variables."), + restricted: true }, 'http.proxyStrictSSL': { type: 'boolean', default: true, - description: localize('strictSSL', "Controls whether the proxy server certificate should be verified against the list of supplied CAs.") + description: localize('strictSSL', "Controls whether the proxy server certificate should be verified against the list of supplied CAs."), + restricted: true }, 'http.proxyAuthorization': { type: ['null', 'string'], default: null, - markdownDescription: localize('proxyAuthorization', "The value to send as the `Proxy-Authorization` header for every network request.") + markdownDescription: localize('proxyAuthorization', "The value to send as the `Proxy-Authorization` header for every network request."), + restricted: true }, 'http.proxySupport': { type: 'string', - enum: ['off', 'on', 'override'], + enum: ['off', 'on', 'fallback', 'override'], enumDescriptions: [ localize('proxySupportOff', "Disable proxy support for extensions."), localize('proxySupportOn', "Enable proxy support for extensions."), + localize('proxySupportFallback', "Enable proxy support for extensions, fall back to request options, when no proxy found."), localize('proxySupportOverride', "Enable proxy support for extensions, override request options."), ], default: 'override', - description: localize('proxySupport', "Use the proxy support for extensions.") + description: localize('proxySupport', "Use the proxy support for extensions."), + restricted: true }, 'http.systemCertificates': { type: 'boolean', default: true, - description: localize('systemCertificates', "Controls whether CA certificates should be loaded from the OS. (On Windows and macOS, a reload of the window is required after turning this off.)") + description: localize('systemCertificates', "Controls whether CA certificates should be loaded from the OS. (On Windows and macOS, a reload of the window is required after turning this off.)"), + restricted: true } } - }); + }; + configurationRegistry.registerConfiguration(proxyConfiguration); +} + +registerProxyConfigurations(ConfigurationScope.MACHINE); diff --git a/src/vs/platform/request/electron-main/requestMainService.ts b/src/vs/platform/request/electron-main/requestMainService.ts index d08f82f4..ea709437 100644 --- a/src/vs/platform/request/electron-main/requestMainService.ts +++ b/src/vs/platform/request/electron-main/requestMainService.ts @@ -14,7 +14,7 @@ function getRawRequest(options: IRequestOptions): IRawRequestFunction { export class RequestMainService extends NodeRequestService { - request(options: IRequestOptions, token: CancellationToken): Promise { + override request(options: IRequestOptions, token: CancellationToken): Promise { return super.request({ ...(options || {}), getRawRequest }, token); } } diff --git a/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts b/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts index dcc27345..64168de4 100644 --- a/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts +++ b/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts @@ -3,6 +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 { BrowserWindow, ipcMain, Event as ElectronEvent, MessagePortMain, IpcMainEvent, RenderProcessGoneDetails } from 'electron'; import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService'; import { Barrier } from 'vs/base/common/async'; @@ -10,7 +11,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { IThemeMainService } from 'vs/platform/theme/electron-main/themeMainService'; import { FileAccess } from 'vs/base/common/network'; -import { browserCodeLoadingCacheStrategy } from 'vs/base/common/platform'; +import { browserCodeLoadingCacheStrategy, IProcessEnvironment } from 'vs/base/common/platform'; import { ISharedProcess, ISharedProcessConfiguration } from 'vs/platform/sharedProcess/node/sharedProcess'; import { Disposable } from 'vs/base/common/lifecycle'; import { connect as connectMessagePort } from 'vs/base/parts/ipc/electron-main/ipc.mp'; @@ -18,6 +19,7 @@ import { assertIsDefined } from 'vs/base/common/types'; import { Emitter, Event } from 'vs/base/common/event'; import { WindowError } from 'vs/platform/windows/electron-main/windows'; import { resolveShellEnv } from 'vs/platform/environment/node/shellEnv'; +import { IProtocolMainService } from 'vs/platform/protocol/electron-main/protocol'; export class SharedProcess extends Disposable implements ISharedProcess { @@ -31,11 +33,12 @@ export class SharedProcess extends Disposable implements ISharedProcess { constructor( private readonly machineId: string, - private userEnv: NodeJS.ProcessEnv, + private userEnv: IProcessEnvironment, @IEnvironmentMainService private readonly environmentMainService: IEnvironmentMainService, @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, @ILogService private readonly logService: ILogService, - @IThemeMainService private readonly themeMainService: IThemeMainService + @IThemeMainService private readonly themeMainService: IThemeMainService, + @IProtocolMainService private readonly protocolMainService: IProtocolMainService ) { super(); @@ -158,6 +161,7 @@ export class SharedProcess extends Disposable implements ISharedProcess { } private createWindow(): void { + const configObjectUrl = this._register(this.protocolMainService.createIPCObjectUrl()); // shared process is a hidden window by default this.window = new BrowserWindow({ @@ -165,8 +169,10 @@ export class SharedProcess extends Disposable implements ISharedProcess { backgroundColor: this.themeMainService.getBackgroundColor(), webPreferences: { preload: FileAccess.asFileUri('vs/base/parts/sandbox/electron-browser/preload.js', require).fsPath, + additionalArguments: [`--vscode-window-config=${configObjectUrl.resource.toString()}`], v8CacheOptions: browserCodeLoadingCacheStrategy, nodeIntegration: true, + contextIsolation: false, enableWebSQL: false, enableRemoteModule: false, spellcheck: false, @@ -177,7 +183,8 @@ export class SharedProcess extends Disposable implements ISharedProcess { } }); - const config: ISharedProcessConfiguration = { + // Store into config object URL + configObjectUrl.update({ machineId: this.machineId, windowId: this.window.id, appRoot: this.environmentMainService.appRoot, @@ -185,15 +192,12 @@ export class SharedProcess extends Disposable implements ISharedProcess { backupWorkspacesPath: this.environmentMainService.backupWorkspacesPath, userEnv: this.userEnv, args: this.environmentMainService.args, - logLevel: this.logService.getLevel() - }; + logLevel: this.logService.getLevel(), + product + }); // Load with config - this.window.loadURL(FileAccess - .asBrowserUri('vs/code/electron-browser/sharedProcess/sharedProcess.html', require) - .with({ query: `config=${encodeURIComponent(JSON.stringify(config))}` }) - .toString(true) - ); + this.window.loadURL(FileAccess.asBrowserUri('vs/code/electron-browser/sharedProcess/sharedProcess.html', require).toString(true)); } private registerWindowListeners(): void { @@ -216,7 +220,7 @@ export class SharedProcess extends Disposable implements ISharedProcess { this.window.on('close', this.windowCloseListener); - // Crashes & Unrsponsive & Failed to load + // Crashes & Unresponsive & Failed to load // We use `onUnexpectedError` explicitly because the error handler // will send the error to the active window to log in devtools too this.window.webContents.on('render-process-gone', (event, details) => this._onDidError.fire({ type: WindowError.CRASHED, details })); diff --git a/src/vs/platform/sharedProcess/node/sharedProcess.ts b/src/vs/platform/sharedProcess/node/sharedProcess.ts index ebd1f517..70c028b7 100644 --- a/src/vs/platform/sharedProcess/node/sharedProcess.ts +++ b/src/vs/platform/sharedProcess/node/sharedProcess.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { ISandboxConfiguration } from 'vs/base/parts/sandbox/common/sandboxTypes'; import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; import { LogLevel } from 'vs/platform/log/common/log'; @@ -15,18 +16,12 @@ export interface ISharedProcess { toggle(): Promise; } -export interface ISharedProcessConfiguration { +export interface ISharedProcessConfiguration extends ISandboxConfiguration { readonly machineId: string; - readonly windowId: number; - - readonly appRoot: string; - - readonly userEnv: NodeJS.ProcessEnv; readonly args: NativeParsedArgs; readonly logLevel: LogLevel; - readonly nodeCachedDataDir?: string; readonly backupWorkspacesPath: string; } diff --git a/src/vs/platform/storage/browser/storageService.ts b/src/vs/platform/storage/browser/storageService.ts index 5cfb8996..c1aae2e7 100644 --- a/src/vs/platform/storage/browser/storageService.ts +++ b/src/vs/platform/storage/browser/storageService.ts @@ -94,7 +94,7 @@ export class BrowserStorageService extends AbstractStorageService { throw new Error('Migrating storage is currently unsupported in Web'); } - protected shouldFlushWhenIdle(): boolean { + protected override shouldFlushWhenIdle(): boolean { // this flush() will potentially cause new state to be stored // since new state will only be created while the document // has focus, one optimization is to not run this when the diff --git a/src/vs/platform/storage/electron-main/storageMain.ts b/src/vs/platform/storage/electron-main/storageMain.ts index c9a430dc..3cd50515 100644 --- a/src/vs/platform/storage/electron-main/storageMain.ts +++ b/src/vs/platform/storage/electron-main/storageMain.ts @@ -206,7 +206,7 @@ export class GlobalStorageMain extends BaseStorageMain implements IStorageMain { })); } - protected async doInit(storage: IStorage): Promise { + protected override async doInit(storage: IStorage): Promise { await super.doInit(storage); // Apply global telemetry values as part of the initialization 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 897a2367..0f55ca5c 100644 --- a/src/vs/platform/storage/test/electron-main/storageMainService.test.ts +++ b/src/vs/platform/storage/test/electron-main/storageMainService.test.ts @@ -26,7 +26,7 @@ suite('StorageMainService', function () { class TestStorageMainService extends StorageMainService { - protected getStorageOptions(): IStorageMainOptions { + protected override getStorageOptions(): IStorageMainOptions { return { useInMemoryStorage: true }; diff --git a/src/vs/platform/telemetry/browser/errorTelemetry.ts b/src/vs/platform/telemetry/browser/errorTelemetry.ts index 6ef338fd..48bb1247 100644 --- a/src/vs/platform/telemetry/browser/errorTelemetry.ts +++ b/src/vs/platform/telemetry/browser/errorTelemetry.ts @@ -8,7 +8,7 @@ import { globals } from 'vs/base/common/platform'; import BaseErrorTelemetry, { ErrorEvent } from 'vs/platform/telemetry/common/errorTelemetry'; export default class ErrorTelemetry extends BaseErrorTelemetry { - protected installErrorListeners(): void { + protected override installErrorListeners(): void { let oldOnError: Function; let that = this; if (typeof globals.onerror === 'function') { diff --git a/src/vs/platform/telemetry/common/commonProperties.ts b/src/vs/platform/telemetry/common/commonProperties.ts index 46f08b5d..454b873f 100644 --- a/src/vs/platform/telemetry/common/commonProperties.ts +++ b/src/vs/platform/telemetry/common/commonProperties.ts @@ -4,14 +4,23 @@ *--------------------------------------------------------------------------------------------*/ import { IFileService } from 'vs/platform/files/common/files'; -import { isLinuxSnap, PlatformToString, platform } from 'vs/base/common/platform'; +import { isLinuxSnap, PlatformToString, platform, Platform } from 'vs/base/common/platform'; import { platform as nodePlatform, env } from 'vs/base/common/process'; import { generateUuid } from 'vs/base/common/uuid'; import { URI } from 'vs/base/common/uri'; +function getPlatformDetail(hostname: string): string | undefined { + if (platform === Platform.Linux && /^penguin(\.|$)/i.test(hostname)) { + return 'chromebook'; + } + + return undefined; +} + export async function resolveCommonProperties( fileService: IFileService, release: string, + hostname: string, arch: string, commit: string | undefined, version: string | undefined, @@ -73,6 +82,13 @@ export async function resolveCommonProperties( result['common.snap'] = 'true'; } + const platformDetail = getPlatformDetail(hostname); + + if (platformDetail) { + // __GDPR__COMMON__ "common.platformDetail" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + result['common.platformDetail'] = platformDetail; + } + try { const contents = await fileService.readFile(URI.file(installSourcePath)); diff --git a/src/vs/platform/telemetry/common/telemetryService.ts b/src/vs/platform/telemetry/common/telemetryService.ts index f30cde48..d2170c42 100644 --- a/src/vs/platform/telemetry/common/telemetryService.ts +++ b/src/vs/platform/telemetry/common/telemetryService.ts @@ -10,7 +10,7 @@ import { ITelemetryService, ITelemetryInfo, ITelemetryData } from 'vs/platform/t import { ITelemetryAppender } from 'vs/platform/telemetry/common/telemetryUtils'; import { optional } from 'vs/platform/instantiation/common/instantiation'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry'; +import { IConfigurationRegistry, Extensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { cloneAndChange, mixin } from 'vs/base/common/objects'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -223,6 +223,8 @@ Registry.as(Extensions.Configuration).registerConfigurat localize('telemetry.enableTelemetry', "Enable usage data and errors to be sent to a Microsoft online service.") : localize('telemetry.enableTelemetryMd', "Enable usage data and errors to be sent to a Microsoft online service. Read our privacy statement [here]({0}).", product.privacyStatementUrl), 'default': true, + 'restricted': true, + 'scope': ConfigurationScope.APPLICATION, 'tags': ['usesOnlineServices'] } } diff --git a/src/vs/platform/telemetry/node/errorTelemetry.ts b/src/vs/platform/telemetry/node/errorTelemetry.ts index 562eb44b..ba1c2a18 100644 --- a/src/vs/platform/telemetry/node/errorTelemetry.ts +++ b/src/vs/platform/telemetry/node/errorTelemetry.ts @@ -7,7 +7,7 @@ import { onUnexpectedError, setUnexpectedErrorHandler } from 'vs/base/common/err import BaseErrorTelemetry from 'vs/platform/telemetry/common/errorTelemetry'; export default class ErrorTelemetry extends BaseErrorTelemetry { - protected installErrorListeners(): void { + protected override installErrorListeners(): void { setUnexpectedErrorHandler(err => console.error(err)); // Print a console message when rejection isn't handled within N seconds. For details: diff --git a/src/vs/platform/telemetry/test/browser/telemetryService.test.ts b/src/vs/platform/telemetry/test/browser/telemetryService.test.ts index 905b8fc3..9b8969ff 100644 --- a/src/vs/platform/telemetry/test/browser/telemetryService.test.ts +++ b/src/vs/platform/telemetry/test/browser/telemetryService.test.ts @@ -90,10 +90,10 @@ suite('TelemetryService', () => { let service = new TelemetryService({ appender: testAppender }, undefined!); return service.publicLog('testPrivateEvent').then(() => { - assert.equal(testAppender.getEventsCount(), 1); + assert.strictEqual(testAppender.getEventsCount(), 1); service.dispose(); - assert.equal(!testAppender.isDisposed, true); + assert.strictEqual(!testAppender.isDisposed, true); }); })); @@ -103,9 +103,9 @@ suite('TelemetryService', () => { let service = new TelemetryService({ appender: testAppender }, undefined!); return service.publicLog('testEvent').then(_ => { - assert.equal(testAppender.getEventsCount(), 1); - assert.equal(testAppender.events[0].eventName, 'testEvent'); - assert.notEqual(testAppender.events[0].data, null); + assert.strictEqual(testAppender.getEventsCount(), 1); + assert.strictEqual(testAppender.events[0].eventName, 'testEvent'); + assert.notStrictEqual(testAppender.events[0].data, null); service.dispose(); }); @@ -123,13 +123,13 @@ suite('TelemetryService', () => { 'value': 0 } }).then(() => { - assert.equal(testAppender.getEventsCount(), 1); - assert.equal(testAppender.events[0].eventName, 'testEvent'); - assert.notEqual(testAppender.events[0].data, null); - assert.equal(testAppender.events[0].data['stringProp'], 'property'); - assert.equal(testAppender.events[0].data['numberProp'], 1); - assert.equal(testAppender.events[0].data['booleanProp'], true); - assert.equal(testAppender.events[0].data['complexProp'].value, 0); + assert.strictEqual(testAppender.getEventsCount(), 1); + assert.strictEqual(testAppender.events[0].eventName, 'testEvent'); + assert.notStrictEqual(testAppender.events[0].data, null); + assert.strictEqual(testAppender.events[0].data['stringProp'], 'property'); + assert.strictEqual(testAppender.events[0].data['numberProp'], 1); + assert.strictEqual(testAppender.events[0].data['booleanProp'], true); + assert.strictEqual(testAppender.events[0].data['complexProp'].value, 0); service.dispose(); }); @@ -146,9 +146,9 @@ suite('TelemetryService', () => { return service.publicLog('testEvent').then(_ => { let [first] = testAppender.events; - assert.equal(Object.keys(first.data).length, 2); - assert.equal(typeof first.data['foo'], 'string'); - assert.equal(typeof first.data['bar'], 'number'); + assert.strictEqual(Object.keys(first.data).length, 2); + assert.strictEqual(typeof first.data['foo'], 'string'); + assert.strictEqual(typeof first.data['bar'], 'number'); service.dispose(); }); @@ -164,11 +164,11 @@ suite('TelemetryService', () => { return service.publicLog('testEvent', { hightower: 'xl', price: 8000 }).then(_ => { let [first] = testAppender.events; - assert.equal(Object.keys(first.data).length, 4); - assert.equal(typeof first.data['foo'], 'string'); - assert.equal(typeof first.data['bar'], 'number'); - assert.equal(typeof first.data['hightower'], 'string'); - assert.equal(typeof first.data['price'], 'number'); + assert.strictEqual(Object.keys(first.data).length, 4); + assert.strictEqual(typeof first.data['foo'], 'string'); + assert.strictEqual(typeof first.data['bar'], 'number'); + assert.strictEqual(typeof first.data['hightower'], 'string'); + assert.strictEqual(typeof first.data['price'], 'number'); service.dispose(); }); @@ -185,9 +185,9 @@ suite('TelemetryService', () => { }, undefined!); return service.getTelemetryInfo().then(info => { - assert.equal(info.sessionId, 'one'); - assert.equal(info.instanceId, 'two'); - assert.equal(info.machineId, 'three'); + assert.strictEqual(info.sessionId, 'one'); + assert.strictEqual(info.instanceId, 'two'); + assert.strictEqual(info.machineId, 'three'); service.dispose(); }); @@ -198,8 +198,8 @@ suite('TelemetryService', () => { let service = new TelemetryService({ appender: testAppender }, undefined!); return service.publicLog('testEvent').then(() => { - assert.equal(testAppender.getEventsCount(), 1); - assert.equal(testAppender.events[0].eventName, 'testEvent'); + assert.strictEqual(testAppender.getEventsCount(), 1); + assert.strictEqual(testAppender.events[0].eventName, 'testEvent'); service.dispose(); }); @@ -217,7 +217,7 @@ suite('TelemetryService', () => { return Promise.all(this.promises); } - publicLog(eventName: string, data?: ITelemetryData, anonymizeFilePaths?: boolean): Promise { + override publicLog(eventName: string, data?: ITelemetryData, anonymizeFilePaths?: boolean): Promise { let p = super.publicLog(eventName, data, anonymizeFilePaths); this.promises.push(p); return p; @@ -245,9 +245,9 @@ suite('TelemetryService', () => { this.clock.tick(ErrorTelemetry.ERROR_FLUSH_TIMEOUT); await service.join(); - assert.equal(testAppender.getEventsCount(), 1); - assert.equal(testAppender.events[0].eventName, 'UnhandledError'); - assert.equal(testAppender.events[0].data.msg, 'This is a test.'); + assert.strictEqual(testAppender.getEventsCount(), 1); + assert.strictEqual(testAppender.events[0].eventName, 'UnhandledError'); + assert.strictEqual(testAppender.events[0].data.msg, 'This is a test.'); errorTelemetry.dispose(); service.dispose(); @@ -275,9 +275,9 @@ suite('TelemetryService', () => { // // allow for the promise to finish // this.clock.tick(MainErrorTelemetry.ERROR_FLUSH_TIMEOUT); // - // assert.equal(testAppender.getEventsCount(), 1); - // assert.equal(testAppender.events[0].eventName, 'UnhandledError'); - // assert.equal(testAppender.events[0].data.msg, 'This should get logged'); + // assert.strictEqual(testAppender.getEventsCount(), 1); + // assert.strictEqual(testAppender.events[0].eventName, 'UnhandledError'); + // assert.strictEqual(testAppender.events[0].data.msg, 'This should get logged'); // // service.dispose(); // } finally { @@ -298,16 +298,16 @@ suite('TelemetryService', () => { this.clock.tick(ErrorTelemetry.ERROR_FLUSH_TIMEOUT); await service.join(); - assert.equal(errorStub.alwaysCalledWithExactly('Error Message', 'file.js', 2, 42, testError), true); - assert.equal(errorStub.callCount, 1); + assert.strictEqual(errorStub.alwaysCalledWithExactly('Error Message', 'file.js', 2, 42, testError), true); + assert.strictEqual(errorStub.callCount, 1); - assert.equal(testAppender.getEventsCount(), 1); - assert.equal(testAppender.events[0].eventName, 'UnhandledError'); - assert.equal(testAppender.events[0].data.msg, 'Error Message'); - assert.equal(testAppender.events[0].data.file, 'file.js'); - assert.equal(testAppender.events[0].data.line, 2); - assert.equal(testAppender.events[0].data.column, 42); - assert.equal(testAppender.events[0].data.uncaught_error_msg, 'test'); + assert.strictEqual(testAppender.getEventsCount(), 1); + assert.strictEqual(testAppender.events[0].eventName, 'UnhandledError'); + assert.strictEqual(testAppender.events[0].data.msg, 'Error Message'); + assert.strictEqual(testAppender.events[0].data.file, 'file.js'); + assert.strictEqual(testAppender.events[0].data.line, 2); + assert.strictEqual(testAppender.events[0].data.column, 42); + assert.strictEqual(testAppender.events[0].data.uncaught_error_msg, 'test'); errorTelemetry.dispose(); service.dispose(); @@ -328,9 +328,9 @@ suite('TelemetryService', () => { this.clock.tick(ErrorTelemetry.ERROR_FLUSH_TIMEOUT); await service.join(); - assert.equal(errorStub.callCount, 1); - assert.equal(testAppender.events[0].data.file.indexOf(settings.dangerousPathWithImportantInfo.replace(settings.personalInfo, personInfoWithSpaces)), -1); - assert.equal(testAppender.events[0].data.file, settings.importantInfo + '/test.js'); + assert.strictEqual(errorStub.callCount, 1); + assert.strictEqual(testAppender.events[0].data.file.indexOf(settings.dangerousPathWithImportantInfo.replace(settings.personalInfo, personInfoWithSpaces)), -1); + assert.strictEqual(testAppender.events[0].data.file, settings.importantInfo + '/test.js'); errorTelemetry.dispose(); service.dispose(); @@ -350,8 +350,8 @@ suite('TelemetryService', () => { (window.onerror)('dangerousFilename', settings.dangerousPathWithImportantInfo + '/test.js', 2, 42, dangerousFilenameError); clock.tick(ErrorTelemetry.ERROR_FLUSH_TIMEOUT); return service.join().then(() => { - assert.equal(errorStub.callCount, 1); - assert.equal(testAppender.events[0].data.file.indexOf(settings.dangerousPathWithImportantInfo), -1); + assert.strictEqual(errorStub.callCount, 1); + assert.strictEqual(testAppender.events[0].data.file.indexOf(settings.dangerousPathWithImportantInfo), -1); dangerousFilenameError = new Error('dangerousFilename'); dangerousFilenameError.stack = settings.stack; @@ -359,9 +359,9 @@ suite('TelemetryService', () => { clock.tick(ErrorTelemetry.ERROR_FLUSH_TIMEOUT); return service.join(); }).then(() => { - assert.equal(errorStub.callCount, 2); - assert.equal(testAppender.events[0].data.file.indexOf(settings.dangerousPathWithImportantInfo), -1); - assert.equal(testAppender.events[0].data.file, settings.importantInfo + '/test.js'); + assert.strictEqual(errorStub.callCount, 2); + assert.strictEqual(testAppender.events[0].data.file.indexOf(settings.dangerousPathWithImportantInfo), -1); + assert.strictEqual(testAppender.events[0].data.file, settings.importantInfo + '/test.js'); errorTelemetry.dispose(); service.dispose(); @@ -383,13 +383,13 @@ suite('TelemetryService', () => { this.clock.tick(ErrorTelemetry.ERROR_FLUSH_TIMEOUT); await service.join(); - assert.equal(testAppender.events[0].data.msg.indexOf(settings.personalInfo), -1); - assert.equal(testAppender.events[0].data.msg.indexOf(settings.filePrefix), -1); + assert.strictEqual(testAppender.events[0].data.msg.indexOf(settings.personalInfo), -1); + assert.strictEqual(testAppender.events[0].data.msg.indexOf(settings.filePrefix), -1); - assert.equal(testAppender.events[0].data.callstack.indexOf(settings.personalInfo), -1); - assert.equal(testAppender.events[0].data.callstack.indexOf(settings.filePrefix), -1); - assert.notEqual(testAppender.events[0].data.callstack.indexOf(settings.stack[4].replace(settings.randomUserFile, settings.anonymizedRandomUserFile)), -1); - assert.equal(testAppender.events[0].data.callstack.split('\n').length, settings.stack.length); + assert.strictEqual(testAppender.events[0].data.callstack.indexOf(settings.personalInfo), -1); + assert.strictEqual(testAppender.events[0].data.callstack.indexOf(settings.filePrefix), -1); + assert.notStrictEqual(testAppender.events[0].data.callstack.indexOf(settings.stack[4].replace(settings.randomUserFile, settings.anonymizedRandomUserFile)), -1); + assert.strictEqual(testAppender.events[0].data.callstack.split('\n').length, settings.stack.length); errorTelemetry.dispose(); service.dispose(); @@ -413,14 +413,14 @@ suite('TelemetryService', () => { this.clock.tick(ErrorTelemetry.ERROR_FLUSH_TIMEOUT); await service.join(); - assert.equal(errorStub.callCount, 1); + assert.strictEqual(errorStub.callCount, 1); // Test that no file information remains, esp. personal info - assert.equal(testAppender.events[0].data.msg.indexOf(settings.personalInfo), -1); - assert.equal(testAppender.events[0].data.msg.indexOf(settings.filePrefix), -1); - assert.equal(testAppender.events[0].data.callstack.indexOf(settings.personalInfo), -1); - assert.equal(testAppender.events[0].data.callstack.indexOf(settings.filePrefix), -1); - assert.notEqual(testAppender.events[0].data.callstack.indexOf(settings.stack[4].replace(settings.randomUserFile, settings.anonymizedRandomUserFile)), -1); - assert.equal(testAppender.events[0].data.callstack.split('\n').length, settings.stack.length); + assert.strictEqual(testAppender.events[0].data.msg.indexOf(settings.personalInfo), -1); + assert.strictEqual(testAppender.events[0].data.msg.indexOf(settings.filePrefix), -1); + assert.strictEqual(testAppender.events[0].data.callstack.indexOf(settings.personalInfo), -1); + assert.strictEqual(testAppender.events[0].data.callstack.indexOf(settings.filePrefix), -1); + assert.notStrictEqual(testAppender.events[0].data.callstack.indexOf(settings.stack[4].replace(settings.randomUserFile, settings.anonymizedRandomUserFile)), -1); + assert.strictEqual(testAppender.events[0].data.callstack.split('\n').length, settings.stack.length); errorTelemetry.dispose(); service.dispose(); @@ -445,14 +445,14 @@ suite('TelemetryService', () => { this.clock.tick(ErrorTelemetry.ERROR_FLUSH_TIMEOUT); await service.join(); - assert.notEqual(testAppender.events[0].data.msg.indexOf(settings.importantInfo), -1); - assert.equal(testAppender.events[0].data.msg.indexOf(settings.personalInfo), -1); - assert.equal(testAppender.events[0].data.msg.indexOf(settings.filePrefix), -1); - assert.notEqual(testAppender.events[0].data.callstack.indexOf(settings.importantInfo), -1); - assert.equal(testAppender.events[0].data.callstack.indexOf(settings.personalInfo), -1); - assert.equal(testAppender.events[0].data.callstack.indexOf(settings.filePrefix), -1); - assert.notEqual(testAppender.events[0].data.callstack.indexOf(settings.stack[4].replace(settings.randomUserFile, settings.anonymizedRandomUserFile)), -1); - assert.equal(testAppender.events[0].data.callstack.split('\n').length, settings.stack.length); + assert.notStrictEqual(testAppender.events[0].data.msg.indexOf(settings.importantInfo), -1); + assert.strictEqual(testAppender.events[0].data.msg.indexOf(settings.personalInfo), -1); + assert.strictEqual(testAppender.events[0].data.msg.indexOf(settings.filePrefix), -1); + assert.notStrictEqual(testAppender.events[0].data.callstack.indexOf(settings.importantInfo), -1); + assert.strictEqual(testAppender.events[0].data.callstack.indexOf(settings.personalInfo), -1); + assert.strictEqual(testAppender.events[0].data.callstack.indexOf(settings.filePrefix), -1); + assert.notStrictEqual(testAppender.events[0].data.callstack.indexOf(settings.stack[4].replace(settings.randomUserFile, settings.anonymizedRandomUserFile)), -1); + assert.strictEqual(testAppender.events[0].data.callstack.split('\n').length, settings.stack.length); errorTelemetry.dispose(); service.dispose(); @@ -476,16 +476,16 @@ suite('TelemetryService', () => { this.clock.tick(ErrorTelemetry.ERROR_FLUSH_TIMEOUT); await service.join(); - assert.equal(errorStub.callCount, 1); + assert.strictEqual(errorStub.callCount, 1); // Test that important information remains but personal info does not - assert.notEqual(testAppender.events[0].data.msg.indexOf(settings.importantInfo), -1); - assert.equal(testAppender.events[0].data.msg.indexOf(settings.personalInfo), -1); - assert.equal(testAppender.events[0].data.msg.indexOf(settings.filePrefix), -1); - assert.notEqual(testAppender.events[0].data.callstack.indexOf(settings.importantInfo), -1); - assert.equal(testAppender.events[0].data.callstack.indexOf(settings.personalInfo), -1); - assert.equal(testAppender.events[0].data.callstack.indexOf(settings.filePrefix), -1); - assert.notEqual(testAppender.events[0].data.callstack.indexOf(settings.stack[4].replace(settings.randomUserFile, settings.anonymizedRandomUserFile)), -1); - assert.equal(testAppender.events[0].data.callstack.split('\n').length, settings.stack.length); + assert.notStrictEqual(testAppender.events[0].data.msg.indexOf(settings.importantInfo), -1); + assert.strictEqual(testAppender.events[0].data.msg.indexOf(settings.personalInfo), -1); + assert.strictEqual(testAppender.events[0].data.msg.indexOf(settings.filePrefix), -1); + assert.notStrictEqual(testAppender.events[0].data.callstack.indexOf(settings.importantInfo), -1); + assert.strictEqual(testAppender.events[0].data.callstack.indexOf(settings.personalInfo), -1); + assert.strictEqual(testAppender.events[0].data.callstack.indexOf(settings.filePrefix), -1); + assert.notStrictEqual(testAppender.events[0].data.callstack.indexOf(settings.stack[4].replace(settings.randomUserFile, settings.anonymizedRandomUserFile)), -1); + assert.strictEqual(testAppender.events[0].data.callstack.split('\n').length, settings.stack.length); errorTelemetry.dispose(); service.dispose(); @@ -510,10 +510,10 @@ suite('TelemetryService', () => { this.clock.tick(ErrorTelemetry.ERROR_FLUSH_TIMEOUT); await service.join(); - assert.notEqual(testAppender.events[0].data.callstack.indexOf('(' + settings.nodeModuleAsarPathToRetain), -1); - assert.notEqual(testAppender.events[0].data.callstack.indexOf('(' + settings.nodeModulePathToRetain), -1); - assert.notEqual(testAppender.events[0].data.callstack.indexOf('(/' + settings.nodeModuleAsarPathToRetain), -1); - assert.notEqual(testAppender.events[0].data.callstack.indexOf('(/' + settings.nodeModulePathToRetain), -1); + assert.notStrictEqual(testAppender.events[0].data.callstack.indexOf('(' + settings.nodeModuleAsarPathToRetain), -1); + assert.notStrictEqual(testAppender.events[0].data.callstack.indexOf('(' + settings.nodeModulePathToRetain), -1); + assert.notStrictEqual(testAppender.events[0].data.callstack.indexOf('(/' + settings.nodeModuleAsarPathToRetain), -1); + assert.notStrictEqual(testAppender.events[0].data.callstack.indexOf('(/' + settings.nodeModulePathToRetain), -1); errorTelemetry.dispose(); service.dispose(); @@ -537,12 +537,12 @@ suite('TelemetryService', () => { this.clock.tick(ErrorTelemetry.ERROR_FLUSH_TIMEOUT); await service.join(); - assert.equal(errorStub.callCount, 1); + assert.strictEqual(errorStub.callCount, 1); - assert.notEqual(testAppender.events[0].data.callstack.indexOf('(' + settings.nodeModuleAsarPathToRetain), -1); - assert.notEqual(testAppender.events[0].data.callstack.indexOf('(' + settings.nodeModulePathToRetain), -1); - assert.notEqual(testAppender.events[0].data.callstack.indexOf('(/' + settings.nodeModuleAsarPathToRetain), -1); - assert.notEqual(testAppender.events[0].data.callstack.indexOf('(/' + settings.nodeModulePathToRetain), -1); + assert.notStrictEqual(testAppender.events[0].data.callstack.indexOf('(' + settings.nodeModuleAsarPathToRetain), -1); + assert.notStrictEqual(testAppender.events[0].data.callstack.indexOf('(' + settings.nodeModulePathToRetain), -1); + assert.notStrictEqual(testAppender.events[0].data.callstack.indexOf('(/' + settings.nodeModuleAsarPathToRetain), -1); + assert.notStrictEqual(testAppender.events[0].data.callstack.indexOf('(/' + settings.nodeModulePathToRetain), -1); errorTelemetry.dispose(); service.dispose(); @@ -568,14 +568,14 @@ suite('TelemetryService', () => { this.clock.tick(ErrorTelemetry.ERROR_FLUSH_TIMEOUT); await service.join(); - assert.notEqual(testAppender.events[0].data.msg.indexOf(settings.importantInfo), -1); - assert.equal(testAppender.events[0].data.msg.indexOf(settings.personalInfo), -1); - assert.equal(testAppender.events[0].data.msg.indexOf(settings.filePrefix), -1); - assert.notEqual(testAppender.events[0].data.callstack.indexOf(settings.importantInfo), -1); - assert.equal(testAppender.events[0].data.callstack.indexOf(settings.personalInfo), -1); - assert.equal(testAppender.events[0].data.callstack.indexOf(settings.filePrefix), -1); - assert.notEqual(testAppender.events[0].data.callstack.indexOf(settings.stack[4].replace(settings.randomUserFile, settings.anonymizedRandomUserFile)), -1); - assert.equal(testAppender.events[0].data.callstack.split('\n').length, settings.stack.length); + assert.notStrictEqual(testAppender.events[0].data.msg.indexOf(settings.importantInfo), -1); + assert.strictEqual(testAppender.events[0].data.msg.indexOf(settings.personalInfo), -1); + assert.strictEqual(testAppender.events[0].data.msg.indexOf(settings.filePrefix), -1); + assert.notStrictEqual(testAppender.events[0].data.callstack.indexOf(settings.importantInfo), -1); + assert.strictEqual(testAppender.events[0].data.callstack.indexOf(settings.personalInfo), -1); + assert.strictEqual(testAppender.events[0].data.callstack.indexOf(settings.filePrefix), -1); + assert.notStrictEqual(testAppender.events[0].data.callstack.indexOf(settings.stack[4].replace(settings.randomUserFile, settings.anonymizedRandomUserFile)), -1); + assert.strictEqual(testAppender.events[0].data.callstack.split('\n').length, settings.stack.length); errorTelemetry.dispose(); service.dispose(); @@ -599,16 +599,16 @@ suite('TelemetryService', () => { this.clock.tick(ErrorTelemetry.ERROR_FLUSH_TIMEOUT); await service.join(); - assert.equal(errorStub.callCount, 1); + assert.strictEqual(errorStub.callCount, 1); // Test that important information remains but personal info does not - assert.notEqual(testAppender.events[0].data.msg.indexOf(settings.importantInfo), -1); - assert.equal(testAppender.events[0].data.msg.indexOf(settings.personalInfo), -1); - assert.equal(testAppender.events[0].data.msg.indexOf(settings.filePrefix), -1); - assert.notEqual(testAppender.events[0].data.callstack.indexOf(settings.importantInfo), -1); - assert.equal(testAppender.events[0].data.callstack.indexOf(settings.personalInfo), -1); - assert.equal(testAppender.events[0].data.callstack.indexOf(settings.filePrefix), -1); - assert.notEqual(testAppender.events[0].data.callstack.indexOf(settings.stack[4].replace(settings.randomUserFile, settings.anonymizedRandomUserFile)), -1); - assert.equal(testAppender.events[0].data.callstack.split('\n').length, settings.stack.length); + assert.notStrictEqual(testAppender.events[0].data.msg.indexOf(settings.importantInfo), -1); + assert.strictEqual(testAppender.events[0].data.msg.indexOf(settings.personalInfo), -1); + assert.strictEqual(testAppender.events[0].data.msg.indexOf(settings.filePrefix), -1); + assert.notStrictEqual(testAppender.events[0].data.callstack.indexOf(settings.importantInfo), -1); + assert.strictEqual(testAppender.events[0].data.callstack.indexOf(settings.personalInfo), -1); + assert.strictEqual(testAppender.events[0].data.callstack.indexOf(settings.filePrefix), -1); + assert.notStrictEqual(testAppender.events[0].data.callstack.indexOf(settings.stack[4].replace(settings.randomUserFile, settings.anonymizedRandomUserFile)), -1); + assert.strictEqual(testAppender.events[0].data.callstack.split('\n').length, settings.stack.length); errorTelemetry.dispose(); service.dispose(); @@ -634,14 +634,14 @@ suite('TelemetryService', () => { this.clock.tick(ErrorTelemetry.ERROR_FLUSH_TIMEOUT); await service.join(); - assert.notEqual(testAppender.events[0].data.msg.indexOf(settings.missingModelPrefix), -1); - assert.equal(testAppender.events[0].data.msg.indexOf(settings.personalInfo), -1); - assert.equal(testAppender.events[0].data.msg.indexOf(settings.filePrefix), -1); - assert.notEqual(testAppender.events[0].data.callstack.indexOf(settings.missingModelPrefix), -1); - assert.equal(testAppender.events[0].data.callstack.indexOf(settings.personalInfo), -1); - assert.equal(testAppender.events[0].data.callstack.indexOf(settings.filePrefix), -1); - assert.notEqual(testAppender.events[0].data.callstack.indexOf(settings.stack[4].replace(settings.randomUserFile, settings.anonymizedRandomUserFile)), -1); - assert.equal(testAppender.events[0].data.callstack.split('\n').length, settings.stack.length); + assert.notStrictEqual(testAppender.events[0].data.msg.indexOf(settings.missingModelPrefix), -1); + assert.strictEqual(testAppender.events[0].data.msg.indexOf(settings.personalInfo), -1); + assert.strictEqual(testAppender.events[0].data.msg.indexOf(settings.filePrefix), -1); + assert.notStrictEqual(testAppender.events[0].data.callstack.indexOf(settings.missingModelPrefix), -1); + assert.strictEqual(testAppender.events[0].data.callstack.indexOf(settings.personalInfo), -1); + assert.strictEqual(testAppender.events[0].data.callstack.indexOf(settings.filePrefix), -1); + assert.notStrictEqual(testAppender.events[0].data.callstack.indexOf(settings.stack[4].replace(settings.randomUserFile, settings.anonymizedRandomUserFile)), -1); + assert.strictEqual(testAppender.events[0].data.callstack.split('\n').length, settings.stack.length); errorTelemetry.dispose(); service.dispose(); @@ -664,17 +664,17 @@ suite('TelemetryService', () => { this.clock.tick(ErrorTelemetry.ERROR_FLUSH_TIMEOUT); await service.join(); - assert.equal(errorStub.callCount, 1); + assert.strictEqual(errorStub.callCount, 1); // Test that no file information remains, but this particular // error message does (Received model events for missing model) - assert.notEqual(testAppender.events[0].data.msg.indexOf(settings.missingModelPrefix), -1); - assert.equal(testAppender.events[0].data.msg.indexOf(settings.personalInfo), -1); - assert.equal(testAppender.events[0].data.msg.indexOf(settings.filePrefix), -1); - assert.notEqual(testAppender.events[0].data.callstack.indexOf(settings.missingModelPrefix), -1); - assert.equal(testAppender.events[0].data.callstack.indexOf(settings.personalInfo), -1); - assert.equal(testAppender.events[0].data.callstack.indexOf(settings.filePrefix), -1); - assert.notEqual(testAppender.events[0].data.callstack.indexOf(settings.stack[4].replace(settings.randomUserFile, settings.anonymizedRandomUserFile)), -1); - assert.equal(testAppender.events[0].data.callstack.split('\n').length, settings.stack.length); + assert.notStrictEqual(testAppender.events[0].data.msg.indexOf(settings.missingModelPrefix), -1); + assert.strictEqual(testAppender.events[0].data.msg.indexOf(settings.personalInfo), -1); + assert.strictEqual(testAppender.events[0].data.msg.indexOf(settings.filePrefix), -1); + assert.notStrictEqual(testAppender.events[0].data.callstack.indexOf(settings.missingModelPrefix), -1); + assert.strictEqual(testAppender.events[0].data.callstack.indexOf(settings.personalInfo), -1); + assert.strictEqual(testAppender.events[0].data.callstack.indexOf(settings.filePrefix), -1); + assert.notStrictEqual(testAppender.events[0].data.callstack.indexOf(settings.stack[4].replace(settings.randomUserFile, settings.anonymizedRandomUserFile)), -1); + assert.strictEqual(testAppender.events[0].data.callstack.split('\n').length, settings.stack.length); errorTelemetry.dispose(); service.dispose(); @@ -700,14 +700,14 @@ suite('TelemetryService', () => { this.clock.tick(ErrorTelemetry.ERROR_FLUSH_TIMEOUT); await service.join(); - assert.notEqual(testAppender.events[0].data.msg.indexOf(settings.noSuchFilePrefix), -1); - assert.equal(testAppender.events[0].data.msg.indexOf(settings.personalInfo), -1); - assert.equal(testAppender.events[0].data.msg.indexOf(settings.filePrefix), -1); - assert.notEqual(testAppender.events[0].data.callstack.indexOf(settings.noSuchFilePrefix), -1); - assert.equal(testAppender.events[0].data.callstack.indexOf(settings.personalInfo), -1); - assert.equal(testAppender.events[0].data.callstack.indexOf(settings.filePrefix), -1); - assert.notEqual(testAppender.events[0].data.callstack.indexOf(settings.stack[4].replace(settings.randomUserFile, settings.anonymizedRandomUserFile)), -1); - assert.equal(testAppender.events[0].data.callstack.split('\n').length, settings.stack.length); + assert.notStrictEqual(testAppender.events[0].data.msg.indexOf(settings.noSuchFilePrefix), -1); + assert.strictEqual(testAppender.events[0].data.msg.indexOf(settings.personalInfo), -1); + assert.strictEqual(testAppender.events[0].data.msg.indexOf(settings.filePrefix), -1); + assert.notStrictEqual(testAppender.events[0].data.callstack.indexOf(settings.noSuchFilePrefix), -1); + assert.strictEqual(testAppender.events[0].data.callstack.indexOf(settings.personalInfo), -1); + assert.strictEqual(testAppender.events[0].data.callstack.indexOf(settings.filePrefix), -1); + assert.notStrictEqual(testAppender.events[0].data.callstack.indexOf(settings.stack[4].replace(settings.randomUserFile, settings.anonymizedRandomUserFile)), -1); + assert.strictEqual(testAppender.events[0].data.callstack.split('\n').length, settings.stack.length); errorTelemetry.dispose(); service.dispose(); @@ -734,18 +734,18 @@ suite('TelemetryService', () => { this.clock.tick(ErrorTelemetry.ERROR_FLUSH_TIMEOUT); await service.join(); - assert.equal(errorStub.callCount, 1); + assert.strictEqual(errorStub.callCount, 1); // Test that no file information remains, but this particular // error message does (ENOENT: no such file or directory) Errors.onUnexpectedError(noSuchFileError); - assert.notEqual(testAppender.events[0].data.msg.indexOf(settings.noSuchFilePrefix), -1); - assert.equal(testAppender.events[0].data.msg.indexOf(settings.personalInfo), -1); - assert.equal(testAppender.events[0].data.msg.indexOf(settings.filePrefix), -1); - assert.notEqual(testAppender.events[0].data.callstack.indexOf(settings.noSuchFilePrefix), -1); - assert.equal(testAppender.events[0].data.callstack.indexOf(settings.personalInfo), -1); - assert.equal(testAppender.events[0].data.callstack.indexOf(settings.filePrefix), -1); - assert.notEqual(testAppender.events[0].data.callstack.indexOf(settings.stack[4].replace(settings.randomUserFile, settings.anonymizedRandomUserFile)), -1); - assert.equal(testAppender.events[0].data.callstack.split('\n').length, settings.stack.length); + assert.notStrictEqual(testAppender.events[0].data.msg.indexOf(settings.noSuchFilePrefix), -1); + assert.strictEqual(testAppender.events[0].data.msg.indexOf(settings.personalInfo), -1); + assert.strictEqual(testAppender.events[0].data.msg.indexOf(settings.filePrefix), -1); + assert.notStrictEqual(testAppender.events[0].data.callstack.indexOf(settings.noSuchFilePrefix), -1); + assert.strictEqual(testAppender.events[0].data.callstack.indexOf(settings.personalInfo), -1); + assert.strictEqual(testAppender.events[0].data.callstack.indexOf(settings.filePrefix), -1); + assert.notStrictEqual(testAppender.events[0].data.callstack.indexOf(settings.stack[4].replace(settings.randomUserFile, settings.anonymizedRandomUserFile)), -1); + assert.strictEqual(testAppender.events[0].data.callstack.split('\n').length, settings.stack.length); errorTelemetry.dispose(); service.dispose(); @@ -759,7 +759,7 @@ suite('TelemetryService', () => { let service = new TelemetryService({ appender: testAppender }, undefined!); return service.publicLog('testEvent').then(() => { - assert.equal(testAppender.getEventsCount(), 1); + assert.strictEqual(testAppender.getEventsCount(), 1); service.dispose(); }); })); @@ -773,23 +773,23 @@ suite('TelemetryService', () => { let service = new TelemetryService({ appender: testAppender }, new class extends TestConfigurationService { - onDidChangeConfiguration = emitter.event; - getValue() { + override onDidChangeConfiguration = emitter.event; + override getValue() { return { enableTelemetry: enableTelemetry } as any; } }()); - assert.equal(service.isOptedIn, false); + assert.strictEqual(service.isOptedIn, false); enableTelemetry = true; emitter.fire({}); - assert.equal(service.isOptedIn, true); + assert.strictEqual(service.isOptedIn, true); enableTelemetry = false; emitter.fire({}); - assert.equal(service.isOptedIn, false); + assert.strictEqual(service.isOptedIn, false); service.dispose(); }); diff --git a/src/vs/platform/telemetry/test/common/telemetryLogAppender.test.ts b/src/vs/platform/telemetry/test/common/telemetryLogAppender.test.ts index 5d528983..5ed128e0 100644 --- a/src/vs/platform/telemetry/test/common/telemetryLogAppender.test.ts +++ b/src/vs/platform/telemetry/test/common/telemetryLogAppender.test.ts @@ -53,7 +53,7 @@ class TestTelemetryLogger extends AbstractLogger implements ILogger { } } - dispose(): void { } + override dispose(): void { } flush(): void { } } @@ -77,14 +77,14 @@ suite('TelemetryLogAdapter', () => { const testLoggerService = new TestTelemetryLoggerService(DEFAULT_LOG_LEVEL); const testObject = new TelemetryLogAppender(testLoggerService, new TestInstantiationService().stub(IEnvironmentService, {})); testObject.log('testEvent', { hello: 'world', isTrue: true, numberBetween1And3: 2 }); - assert.equal(testLoggerService.logger.logs.length, 2); + assert.strictEqual(testLoggerService.logger.logs.length, 2); }); test('Log Telemetry if log level is trace', async () => { const testLoggerService = new TestTelemetryLoggerService(LogLevel.Trace); const testObject = new TelemetryLogAppender(testLoggerService, new TestInstantiationService().stub(IEnvironmentService, {})); testObject.log('testEvent', { hello: 'world', isTrue: true, numberBetween1And3: 2 }); - assert.equal(testLoggerService.logger.logs[2], 'telemetry/testEvent' + JSON.stringify([{ + assert.strictEqual(testLoggerService.logger.logs[2], 'telemetry/testEvent' + JSON.stringify([{ properties: { hello: 'world', }, diff --git a/src/vs/platform/telemetry/test/electron-browser/appInsightsAppender.test.ts b/src/vs/platform/telemetry/test/electron-browser/appInsightsAppender.test.ts index b17ece4d..77c201e4 100644 --- a/src/vs/platform/telemetry/test/electron-browser/appInsightsAppender.test.ts +++ b/src/vs/platform/telemetry/test/electron-browser/appInsightsAppender.test.ts @@ -7,8 +7,8 @@ import { AppInsightsAppender } from 'vs/platform/telemetry/node/appInsightsAppen import { TelemetryClient, Contracts } from 'applicationinsights'; class AppInsightsMock extends TelemetryClient { - public config: any; - public channel: any; + public override config: any; + public override channel: any; public events: Contracts.EventTelemetry[] = []; public IsTrackingPageView: boolean = false; public exceptions: any[] = []; @@ -17,11 +17,11 @@ class AppInsightsMock extends TelemetryClient { super('testKey'); } - public trackEvent(event: any) { + public override trackEvent(event: any) { this.events.push(event); } - public flush(options: any): void { + public override flush(options: any): void { // called on dispose } } @@ -44,20 +44,20 @@ suite('AIAdapter', () => { test('Simple event', () => { adapter.log('testEvent'); - assert.equal(appInsightsMock.events.length, 1); - assert.equal(appInsightsMock.events[0].name, `${prefix}/testEvent`); + assert.strictEqual(appInsightsMock.events.length, 1); + assert.strictEqual(appInsightsMock.events[0].name, `${prefix}/testEvent`); }); test('addional data', () => { adapter = new AppInsightsAppender(prefix, { first: '1st', second: 2, third: true }, () => appInsightsMock); adapter.log('testEvent'); - assert.equal(appInsightsMock.events.length, 1); + assert.strictEqual(appInsightsMock.events.length, 1); let [first] = appInsightsMock.events; - assert.equal(first.name, `${prefix}/testEvent`); - assert.equal(first.properties!['first'], '1st'); - assert.equal(first.measurements!['second'], '2'); - assert.equal(first.measurements!['third'], 1); + assert.strictEqual(first.name, `${prefix}/testEvent`); + assert.strictEqual(first.properties!['first'], '1st'); + assert.strictEqual(first.measurements!['second'], 2); + assert.strictEqual(first.measurements!['third'], 1); }); test('property limits', () => { @@ -78,7 +78,7 @@ suite('AIAdapter', () => { data['reallyLongPropertyValue'] = reallyLongPropertyValue; adapter.log('testEvent', data); - assert.equal(appInsightsMock.events.length, 1); + assert.strictEqual(appInsightsMock.events.length, 1); for (let prop in appInsightsMock.events[0].properties!) { assert(prop.length < 150); @@ -90,14 +90,14 @@ suite('AIAdapter', () => { let date = new Date(); adapter.log('testEvent', { favoriteDate: date, likeRed: false, likeBlue: true, favoriteNumber: 1, favoriteColor: 'blue', favoriteCars: ['bmw', 'audi', 'ford'] }); - assert.equal(appInsightsMock.events.length, 1); - assert.equal(appInsightsMock.events[0].name, `${prefix}/testEvent`); - assert.equal(appInsightsMock.events[0].properties!['favoriteColor'], 'blue'); - assert.equal(appInsightsMock.events[0].measurements!['likeRed'], 0); - assert.equal(appInsightsMock.events[0].measurements!['likeBlue'], 1); - assert.equal(appInsightsMock.events[0].properties!['favoriteDate'], date.toISOString()); - assert.equal(appInsightsMock.events[0].properties!['favoriteCars'], JSON.stringify(['bmw', 'audi', 'ford'])); - assert.equal(appInsightsMock.events[0].measurements!['favoriteNumber'], 1); + assert.strictEqual(appInsightsMock.events.length, 1); + assert.strictEqual(appInsightsMock.events[0].name, `${prefix}/testEvent`); + assert.strictEqual(appInsightsMock.events[0].properties!['favoriteColor'], 'blue'); + assert.strictEqual(appInsightsMock.events[0].measurements!['likeRed'], 0); + assert.strictEqual(appInsightsMock.events[0].measurements!['likeBlue'], 1); + assert.strictEqual(appInsightsMock.events[0].properties!['favoriteDate'], date.toISOString()); + assert.strictEqual(appInsightsMock.events[0].properties!['favoriteCars'], JSON.stringify(['bmw', 'audi', 'ford'])); + assert.strictEqual(appInsightsMock.events[0].measurements!['favoriteNumber'], 1); }); test('Nested data', () => { @@ -119,15 +119,15 @@ suite('AIAdapter', () => { } }); - assert.equal(appInsightsMock.events.length, 1); - assert.equal(appInsightsMock.events[0].name, `${prefix}/testEvent`); + assert.strictEqual(appInsightsMock.events.length, 1); + assert.strictEqual(appInsightsMock.events[0].name, `${prefix}/testEvent`); - assert.equal(appInsightsMock.events[0].properties!['window.title'], 'some title'); - assert.equal(appInsightsMock.events[0].measurements!['window.measurements.width'], 100); - assert.equal(appInsightsMock.events[0].measurements!['window.measurements.height'], 200); + assert.strictEqual(appInsightsMock.events[0].properties!['window.title'], 'some title'); + assert.strictEqual(appInsightsMock.events[0].measurements!['window.measurements.width'], 100); + assert.strictEqual(appInsightsMock.events[0].measurements!['window.measurements.height'], 200); - assert.equal(appInsightsMock.events[0].properties!['nestedObj.nestedObj2.nestedObj3'], JSON.stringify({ 'testProperty': 'test' })); - assert.equal(appInsightsMock.events[0].measurements!['nestedObj.testMeasurement'], 1); + assert.strictEqual(appInsightsMock.events[0].properties!['nestedObj.nestedObj2.nestedObj3'], JSON.stringify({ 'testProperty': 'test' })); + assert.strictEqual(appInsightsMock.events[0].measurements!['nestedObj.testMeasurement'], 1); }); }); diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts index 9802b3ac..28a4c9b6 100644 --- a/src/vs/platform/terminal/common/terminal.ts +++ b/src/vs/platform/terminal/common/terminal.ts @@ -5,8 +5,8 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { Event } from 'vs/base/common/event'; -import { IProcessEnvironment } from 'vs/base/common/platform'; -import { URI } from 'vs/base/common/uri'; +import { IProcessEnvironment, OperatingSystem } from 'vs/base/common/platform'; +import { URI, UriComponents } from 'vs/base/common/uri'; import { IGetTerminalLayoutInfoArgs, IProcessDetails, IPtyHostProcessReplayEvent, ISetTerminalLayoutInfoArgs } from 'vs/platform/terminal/common/terminalProcess'; export enum WindowsShellType { @@ -44,6 +44,7 @@ export interface IPtyHostAttachTarget { workspaceId: string; workspaceName: string; isOrphan: boolean; + icon: string | undefined; } export type ITerminalsLayoutInfo = IRawTerminalsLayoutInfo; @@ -93,9 +94,12 @@ export interface IOffProcessTerminalService { onPtyHostRestart: Event; attachToProcess(id: number): Promise; - listProcesses(reduceGraceTime?: boolean): Promise; + listProcesses(): Promise; + getDefaultSystemShell(osOverride?: OperatingSystem): Promise; + getShellEnvironment(): Promise; setTerminalLayoutInfo(layoutInfo?: ITerminalsLayoutInfoById): Promise; getTerminalLayoutInfo(): Promise; + reduceConnectionGraceTime(): Promise; } export const ILocalTerminalService = createDecorator('localTerminalService'); @@ -111,7 +115,6 @@ export interface IPtyService { readonly onPtyHostStart?: Event; readonly onPtyHostUnresponsive?: Event; readonly onPtyHostResponsive?: Event; - readonly onProcessData: Event<{ id: number, event: IProcessDataEvent | string }>; readonly onProcessExit: Event<{ id: number, event: number | undefined }>; readonly onProcessReady: Event<{ id: number, event: { pid: number, cwd: string } }>; @@ -142,10 +145,8 @@ export interface IPtyService { /** * Lists all orphaned processes, ie. those without a connected frontend. - * @param reduceGraceTime Whether to reduce the reconnection grace time for all orphaned - * terminals. */ - listProcesses(reduceGraceTime: boolean): Promise; + listProcesses(): Promise; start(id: number): Promise; shutdown(id: number, immediate: boolean): Promise; @@ -155,11 +156,15 @@ export interface IPtyService { getCwd(id: number): Promise; getLatency(id: number): Promise; acknowledgeDataEvent(id: number, charCount: number): Promise; + processBinary(id: number, data: string): Promise; /** Confirm the process is _not_ an orphan. */ orphanQuestionReply(id: number): Promise; + getDefaultSystemShell(osOverride?: OperatingSystem): Promise; + getShellEnvironment(): Promise; setTerminalLayoutInfo(args: ISetTerminalLayoutInfoArgs): Promise; getTerminalLayoutInfo(args: IGetTerminalLayoutInfoArgs): Promise; + reduceConnectionGraceTime(): Promise; } export enum HeartbeatConstants { @@ -196,6 +201,11 @@ export interface IShellLaunchConfig { */ name?: string; + /** + * An string to follow the name of the terminal with, indicating a special kind of terminal + */ + description?: string; + /** * The shell executable (bash, cmd, etc.). */ @@ -238,9 +248,9 @@ export interface IShellLaunchConfig { initialText?: string; /** - * Whether an extension is controlling the terminal via a `vscode.Pseudoterminal`. + * Custom PTY/pseudoterminal process to use. */ - isExtensionCustomPtyTerminal?: boolean; + customPtyImplementation?: (terminalId: number, cols: number, rows: number) => ITerminalChildProcess; /** * A UUID generated by the extension host process for terminals created on the extension host process. @@ -250,7 +260,7 @@ export interface IShellLaunchConfig { /** * This is a terminal that attaches to an already running terminal. */ - attachPersistentProcess?: { id: number; pid: number; title: string; cwd: string; }; + attachPersistentProcess?: { id: number; pid: number; title: string; cwd: string; icon?: string; }; /** * Whether the terminal process environment should be exactly as provided in @@ -280,10 +290,25 @@ export interface IShellLaunchConfig { * Whether this terminal was created by an extension. */ isExtensionOwnedTerminal?: boolean; + + /** + * The codicon ID to use for this terminal. If not specified it will use the default fallback + * icon. + */ + icon?: string; +} + +export interface IShellLaunchConfigDto { + name?: string; + executable?: string; + args?: string[] | string; + cwd?: string | UriComponents; + env?: ITerminalEnvironment; + hideFromUser?: boolean; } export interface ITerminalEnvironment { - [key: string]: string | null; + [key: string]: string | null | undefined; } export interface ITerminalLaunchError { @@ -337,6 +362,7 @@ export interface ITerminalChildProcess { */ shutdown(immediate: boolean): void; input(data: string): void; + processBinary(data: string): Promise; resize(cols: number, rows: number): void; /** @@ -388,7 +414,11 @@ export const enum FlowControlConstants { export interface IProcessDataEvent { data: string; - sync: boolean; + trackCommit: boolean; + /** + * When trackCommit is set, this will be set to a promise that resolves when the data is parsed. + */ + writePromise?: Promise; } export interface ITerminalDimensions { @@ -409,3 +439,5 @@ export interface ITerminalDimensionsOverride extends Readonly(key: string) => T | undefined; diff --git a/src/vs/platform/terminal/common/terminalProcess.ts b/src/vs/platform/terminal/common/terminalProcess.ts index 3358e918..6cdf3b88 100644 --- a/src/vs/platform/terminal/common/terminalProcess.ts +++ b/src/vs/platform/terminal/common/terminalProcess.ts @@ -7,15 +7,6 @@ import { UriComponents } from 'vs/base/common/uri'; import { IRawTerminalTabLayoutInfo, ITerminalEnvironment, ITerminalTabLayoutInfoById } from 'vs/platform/terminal/common/terminal'; import { ISerializableEnvironmentVariableCollection } from 'vs/platform/terminal/common/environmentVariable'; -export interface IShellLaunchConfigDto { - name?: string; - executable?: string; - args?: string[] | string; - cwd?: string | UriComponents; - env?: { [key: string]: string | null; }; - hideFromUser?: boolean; -} - export interface ISingleTerminalConfiguration { userValue: T | undefined; value: T | undefined; @@ -65,6 +56,7 @@ export interface IProcessDetails { workspaceId: string; workspaceName: string; isOrphan: boolean; + icon: string | undefined; } export type ITerminalTabLayoutInfoDto = IRawTerminalTabLayoutInfo; diff --git a/src/vs/platform/terminal/node/ptyHostService.ts b/src/vs/platform/terminal/node/ptyHostService.ts index 6d56aad1..fba6308c 100644 --- a/src/vs/platform/terminal/node/ptyHostService.ts +++ b/src/vs/platform/terminal/node/ptyHostService.ts @@ -9,10 +9,11 @@ import { IPtyService, IProcessDataEvent, IShellLaunchConfig, ITerminalDimensions import { Client } from 'vs/base/parts/ipc/node/ipc.cp'; import { FileAccess } from 'vs/base/common/network'; import { ProxyChannel } from 'vs/base/parts/ipc/common/ipc'; -import { IProcessEnvironment } from 'vs/base/common/platform'; +import { IProcessEnvironment, OperatingSystem } from 'vs/base/common/platform'; import { Emitter } from 'vs/base/common/event'; import { LogLevelChannelClient } from 'vs/platform/log/common/logIpc'; import { IGetTerminalLayoutInfoArgs, IProcessDetails, IPtyHostProcessReplayEvent, ISetTerminalLayoutInfoArgs } from 'vs/platform/terminal/common/terminalProcess'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; enum Constants { MaxRestarts = 5 @@ -36,6 +37,7 @@ export class PtyHostService extends Disposable implements IPtyService { private _proxy: IPtyService; private _restartCount = 0; + private _isResponsive = true; private _isDisposed = false; private _heartbeatFirstTimeout?: NodeJS.Timeout; @@ -49,7 +51,6 @@ export class PtyHostService extends Disposable implements IPtyService { readonly onPtyHostUnresponsive = this._onPtyHostUnresponsive.event; private readonly _onPtyHostResponsive = this._register(new Emitter()); readonly onPtyHostResponsive = this._onPtyHostResponsive.event; - private readonly _onProcessData = this._register(new Emitter<{ id: number, event: IProcessDataEvent | string }>()); readonly onProcessData = this._onProcessData.event; private readonly _onProcessExit = this._register(new Emitter<{ id: number, event: number | undefined }>()); @@ -70,7 +71,8 @@ export class PtyHostService extends Disposable implements IPtyService { readonly onProcessOrphanQuestion = this._onProcessOrphanQuestion.event; constructor( - @ILogService private readonly _logService: ILogService + @ILogService private readonly _logService: ILogService, + @ITelemetryService private readonly _telemetryService: ITelemetryService ) { super(); @@ -102,6 +104,10 @@ export class PtyHostService extends Disposable implements IPtyService { // Handle exit this._register(client.onDidProcessExit(e => { + /* __GDPR__ + "ptyHost/exit" : {} + */ + this._telemetryService.publicLog('ptyHost/exit'); this._onPtyHostExit.fire(e.code); if (!this._isDisposed) { if (this._restartCount <= Constants.MaxRestarts) { @@ -135,7 +141,7 @@ export class PtyHostService extends Disposable implements IPtyService { return [client, proxy]; } - dispose() { + override dispose() { this._isDisposed = true; super.dispose(); } @@ -153,10 +159,12 @@ export class PtyHostService extends Disposable implements IPtyService { detachFromProcess(id: number): Promise { return this._proxy.detachFromProcess(id); } - listProcesses(reduceGraceTime: boolean): Promise { - return this._proxy.listProcesses(reduceGraceTime); + listProcesses(): Promise { + return this._proxy.listProcesses(); + } + reduceConnectionGraceTime(): Promise { + return this._proxy.reduceConnectionGraceTime(); } - start(id: number): Promise { return this._proxy.start(id); } @@ -166,6 +174,9 @@ export class PtyHostService extends Disposable implements IPtyService { input(id: number, data: string): Promise { return this._proxy.input(id, data); } + processBinary(id: number, data: string): Promise { + return this._proxy.processBinary(id, data); + } resize(id: number, cols: number, rows: number): Promise { return this._proxy.resize(id, cols, rows); } @@ -185,6 +196,13 @@ export class PtyHostService extends Disposable implements IPtyService { return this._proxy.orphanQuestionReply(id); } + getDefaultSystemShell(osOverride?: OperatingSystem): Promise { + return this._proxy.getDefaultSystemShell(osOverride); + } + getShellEnvironment(): Promise { + return this._proxy.getShellEnvironment(); + } + setTerminalLayoutInfo(args: ISetTerminalLayoutInfoArgs): Promise { return this._proxy.setTerminalLayoutInfo(args); } @@ -193,6 +211,11 @@ export class PtyHostService extends Disposable implements IPtyService { } async restartPtyHost(): Promise { + /* __GDPR__ + "ptyHost/restart" : {} + */ + this._telemetryService.publicLog('ptyHost/restart'); + this._isResponsive = true; this._disposePtyHost(); [this._client, this._proxy] = this._startPtyHost(); } @@ -207,6 +230,13 @@ export class PtyHostService extends Disposable implements IPtyService { private _handleHeartbeat() { this._clearHeartbeatTimeouts(); this._heartbeatFirstTimeout = setTimeout(() => this._handleHeartbeatFirstTimeout(), HeartbeatConstants.BeatInterval * HeartbeatConstants.FirstWaitMultiplier); + if (!this._isResponsive) { + /* __GDPR__ + "ptyHost/responsive" : {} + */ + this._telemetryService.publicLog('ptyHost/responsive'); + this._isResponsive = true; + } this._onPtyHostResponsive.fire(); } @@ -219,12 +249,23 @@ export class PtyHostService extends Disposable implements IPtyService { private _handleHeartbeatSecondTimeout() { this._logService.error(`No ptyHost heartbeat after ${(HeartbeatConstants.BeatInterval * HeartbeatConstants.FirstWaitMultiplier + HeartbeatConstants.BeatInterval * HeartbeatConstants.FirstWaitMultiplier) / 1000} seconds`); this._heartbeatSecondTimeout = undefined; + if (this._isResponsive) { + /* __GDPR__ + "ptyHost/responsive" : {} + */ + this._telemetryService.publicLog('ptyHost/unresponsive'); + this._isResponsive = false; + } this._onPtyHostUnresponsive.fire(); } private _handleUnresponsiveCreateProcess() { this._clearHeartbeatTimeouts(); this._logService.error(`No ptyHost response to createProcess after ${HeartbeatConstants.CreateProcessTimeout / 1000} seconds`); + /* __GDPR__ + "ptyHost/responsive" : {} + */ + this._telemetryService.publicLog('ptyHost/responsive'); this._onPtyHostUnresponsive.fire(); } diff --git a/src/vs/platform/terminal/node/ptyService.ts b/src/vs/platform/terminal/node/ptyService.ts index 12197344..d9ea875c 100644 --- a/src/vs/platform/terminal/node/ptyService.ts +++ b/src/vs/platform/terminal/node/ptyService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; -import { IProcessEnvironment } from 'vs/base/common/platform'; +import { IProcessEnvironment, OperatingSystem, OS } from 'vs/base/common/platform'; import { IPtyService, IProcessDataEvent, IShellLaunchConfig, ITerminalDimensionsOverride, ITerminalLaunchError, LocalReconnectConstants, ITerminalsLayoutInfo, IRawTerminalInstanceLayoutInfo, ITerminalTabLayoutInfoById, ITerminalInstanceLayoutInfoById, TerminalShellType } from 'vs/platform/terminal/common/terminal'; import { AutoOpenBarrier, Queue, RunOnceScheduler } from 'vs/base/common/async'; import { Emitter } from 'vs/base/common/event'; @@ -13,6 +13,7 @@ import { TerminalProcess } from 'vs/platform/terminal/node/terminalProcess'; import { ISetTerminalLayoutInfoArgs, ITerminalTabLayoutInfoDto, IProcessDetails, IGetTerminalLayoutInfoArgs, IPtyHostProcessReplayEvent } from 'vs/platform/terminal/common/terminalProcess'; import { ILogService } from 'vs/platform/log/common/log'; import { TerminalDataBufferer } from 'vs/platform/terminal/common/terminalDataBuffering'; +import { getSystemShell } from 'vs/base/node/shell'; type WorkspaceId = string; @@ -87,7 +88,7 @@ export class PtyService extends Disposable implements IPtyService { if (process.onProcessResolvedShellLaunchConfig) { process.onProcessResolvedShellLaunchConfig(event => this._onProcessResolvedShellLaunchConfig.fire({ id, event })); } - const persistentProcess = new PersistentTerminalProcess(id, process, workspaceId, workspaceName, shouldPersist, cols, rows, this._logService); + const persistentProcess = new PersistentTerminalProcess(id, process, workspaceId, workspaceName, shouldPersist, cols, rows, this._logService, shellLaunchConfig.icon); process.onProcessExit(() => { persistentProcess.dispose(); this._ptys.delete(id); @@ -114,13 +115,13 @@ export class PtyService extends Disposable implements IPtyService { this._throwIfNoPty(id).detach(); } - async listProcesses(reduceGraceTime: boolean): Promise { - if (reduceGraceTime) { - for (const pty of this._ptys.values()) { - pty.reduceGraceTime(); - } + async reduceConnectionGraceTime(): Promise { + for (const pty of this._ptys.values()) { + pty.reduceGraceTime(); } + } + async listProcesses(): Promise { const persistentProcesses = Array.from(this._ptys.entries()).filter(([_, pty]) => pty.shouldPersistTerminal); this._logService.info(`Listing ${persistentProcesses.length} persistent terminals, ${this._ptys.size} total terminals`); @@ -133,11 +134,15 @@ export class PtyService extends Disposable implements IPtyService { return this._throwIfNoPty(id).start(); } async shutdown(id: number, immediate: boolean): Promise { - return this._throwIfNoPty(id).shutdown(immediate); + // Don't throw if the pty is already shutdown + return this._ptys.get(id)?.shutdown(immediate); } async input(id: number, data: string): Promise { return this._throwIfNoPty(id).input(data); } + async processBinary(id: number, data: string): Promise { + return this._throwIfNoPty(id).writeBinary(data); + } async resize(id: number, cols: number, rows: number): Promise { return this._throwIfNoPty(id).resize(cols, rows); } @@ -157,6 +162,14 @@ export class PtyService extends Disposable implements IPtyService { return this._throwIfNoPty(id).orphanQuestionReply(); } + async getDefaultSystemShell(osOverride: OperatingSystem = OS): Promise { + return getSystemShell(osOverride, process.env); + } + + async getShellEnvironment(): Promise { + return { ...process.env }; + } + async setTerminalLayoutInfo(args: ISetTerminalLayoutInfoArgs): Promise { this._workspaceLayoutInfos.set(args.workspaceId, args); } @@ -210,7 +223,8 @@ export class PtyService extends Disposable implements IPtyService { workspaceId: persistentProcess.workspaceId, workspaceName: persistentProcess.workspaceName, cwd, - isOrphan + isOrphan, + icon: persistentProcess.icon }; } @@ -248,7 +262,7 @@ export class PersistentTerminalProcess extends Disposable { readonly onProcessShellTypeChanged = this._onProcessShellTypeChanged.event; private readonly _onProcessOverrideDimensions = this._register(new Emitter()); readonly onProcessOverrideDimensions = this._onProcessOverrideDimensions.event; - private readonly _onProcessData = this._register(new Emitter()); + private readonly _onProcessData = this._register(new Emitter()); readonly onProcessData = this._onProcessData.event; private readonly _onProcessOrphanQuestion = this._register(new Emitter()); readonly onProcessOrphanQuestion = this._onProcessOrphanQuestion.event; @@ -260,6 +274,7 @@ export class PersistentTerminalProcess extends Disposable { get pid(): number { return this._pid; } get title(): string { return this._terminalProcess.currentTitle; } + get icon(): string | undefined { return this._icon; } constructor( private _persistentProcessId: number, @@ -268,7 +283,8 @@ export class PersistentTerminalProcess extends Disposable { public readonly workspaceName: string, public readonly shouldPersistTerminal: boolean, cols: number, rows: number, - private readonly _logService: ILogService + private readonly _logService: ILogService, + private readonly _icon?: string ) { super(); this._recorder = new TerminalRecorder(cols, rows); @@ -292,12 +308,12 @@ export class PersistentTerminalProcess extends Disposable { this._register(this._terminalProcess.onProcessShellTypeChanged(e => this._onProcessShellTypeChanged.fire(e))); // Data buffering to reduce the amount of messages going to the renderer - this._bufferer = new TerminalDataBufferer((_, data) => this._onProcessData.fire({ data: data, sync: true })); + this._bufferer = new TerminalDataBufferer((_, data) => this._onProcessData.fire(data)); this._register(this._bufferer.startBuffering(this._persistentProcessId, this._terminalProcess.onProcessData)); this._register(this._terminalProcess.onProcessExit(() => this._bufferer.stopBuffering(this._persistentProcessId))); // Data recording for reconnect - this._register(this.onProcessData(e => this._recorder.recordData(e.data))); + this._register(this.onProcessData(e => this._recorder.recordData(e))); } attach(): void { @@ -337,6 +353,9 @@ export class PersistentTerminalProcess extends Disposable { } return this._terminalProcess.input(data); } + writeBinary(data: string): Promise { + return this._terminalProcess.processBinary(data); + } resize(cols: number, rows: number): void { if (this._inReplay) { return; diff --git a/src/vs/platform/terminal/node/terminalEnvironment.ts b/src/vs/platform/terminal/node/terminalEnvironment.ts index 3ec789f2..91f40c9d 100644 --- a/src/vs/platform/terminal/node/terminalEnvironment.ts +++ b/src/vs/platform/terminal/node/terminalEnvironment.ts @@ -6,7 +6,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 * as pfs from 'vs/base/node/pfs'; import { isString } from 'vs/base/common/types'; import { getCaseInsensitive } from 'vs/base/common/objects'; import { IProcessEnvironment, isWindows } from 'vs/base/common/platform'; @@ -20,7 +20,7 @@ export function getWindowsBuildNumber(): number { return buildNumber; } -export async function findExecutable(command: string, cwd?: string, paths?: string[], env: IProcessEnvironment = process.env as IProcessEnvironment): Promise { +export async function findExecutable(command: string, cwd?: string, paths?: string[], env: IProcessEnvironment = process.env as IProcessEnvironment, exists: (path: string) => Promise = pfs.exists): Promise { // If we have an absolute path then we take it. if (path.isAbsolute(command)) { return await exists(command) ? command : undefined; diff --git a/src/vs/platform/terminal/node/terminalProcess.ts b/src/vs/platform/terminal/node/terminalProcess.ts index e26fb834..3626928f 100644 --- a/src/vs/platform/terminal/node/terminalProcess.ts +++ b/src/vs/platform/terminal/node/terminalProcess.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import * as path from 'vs/base/common/path'; -import * as platform from 'vs/base/common/platform'; import type * as pty from 'node-pty'; import * as fs from 'fs'; import * as os from 'os'; @@ -17,6 +16,8 @@ import { findExecutable, getWindowsBuildNumber } from 'vs/platform/terminal/node import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; import { WindowsShellHelper } from 'vs/platform/terminal/node/windowsShellHelper'; +import { IProcessEnvironment, isLinux, isMacintosh, isWindows } from 'vs/base/common/platform'; +import { timeout } from 'vs/base/common/async'; // Writing large amounts of data can be corrupted for some reason, after looking into this is // appears to be a race condition around writing to the FD which may be based on how powerful the @@ -44,10 +45,34 @@ const enum ShutdownConstants { MaximumShutdownTime = 5000 } +const enum Constants { + /** + * The minimum duration between kill and spawn calls on Windows/conpty as a mitigation for a + * hang issue. See: + * - https://github.com/microsoft/vscode/issues/71966 + * - https://github.com/microsoft/vscode/issues/117956 + * - https://github.com/microsoft/vscode/issues/121336 + */ + KillSpawnThrottleInterval = 250, + /** + * The amount of time to wait when a call is throttles beyond the exact amount, this is used to + * try prevent early timeouts causing a kill/spawn call to happen at double the regular + * interval. + */ + KillSpawnSpacingDuration = 50, +} + +interface IWriteObject { + data: string, + isBinary: boolean +} + export class TerminalProcess extends Disposable implements ITerminalChildProcess { readonly id = 0; readonly shouldPersist = false; + private static _lastKillOrStart = 0; + private _exitCode: number | undefined; private _exitMessage: string | undefined; private _closeTimeout: any; @@ -57,7 +82,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess private _isDisposed: boolean = false; private _windowsShellHelper: WindowsShellHelper | undefined; private _titleInterval: NodeJS.Timer | null = null; - private _writeQueue: string[] = []; + private _writeQueue: IWriteObject[] = []; private _writeTimeout: NodeJS.Timeout | undefined; private _delayedResizer: DelayedResizer | undefined; private readonly _initialCwd: string; @@ -86,17 +111,17 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess cwd: string, cols: number, rows: number, - env: platform.IProcessEnvironment, + env: IProcessEnvironment, /** * environment used for `findExecutable` */ - private readonly _executableEnv: platform.IProcessEnvironment, + private readonly _executableEnv: IProcessEnvironment, windowsEnableConpty: boolean, @ILogService private readonly _logService: ILogService ) { super(); let name: string; - if (platform.isWindows) { + if (isWindows) { name = path.basename(this._shellLaunchConfig.executable || ''); } else { // Using 'xterm-256color' here helps ensure that the majority of Linux distributions will use a @@ -108,7 +133,8 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess this._ptyOptions = { name, cwd, - env, + // TODO: When node-pty is updated this cast can be removed + env: env as { [key: string]: string; }, cols, rows, useConpty, @@ -116,7 +142,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess conptyInheritCursor: useConpty && !!_shellLaunchConfig.initialText }; // Delay resizes to avoid conpty not respecting very early resize calls - if (platform.isWindows) { + if (isWindows) { if (useConpty && cols === 0 && rows === 0 && this._shellLaunchConfig.executable?.endsWith('Git\\bin\\bash.exe')) { this._delayedResizer = new DelayedResizer(); this._register(this._delayedResizer.onTrigger(dimensions => { @@ -187,6 +213,9 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess if (!executable) { return { message: localize('launchFail.executableDoesNotExist', "Path to shell executable \"{0}\" does not exist", slc.executable) }; } + // Set the executable explicitly here so that node-pty doesn't need to search the + // $PATH too. + slc.executable = executable; } } return undefined; @@ -194,6 +223,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess private async setupPtyProcess(shellLaunchConfig: IShellLaunchConfig, options: pty.IPtyForkOptions): Promise { const args = shellLaunchConfig.args || []; + await this._throttleKillSpawn(); this._logService.trace('IPty#spawn', shellLaunchConfig.executable, args, options); const ptyProcess = (await import('node-pty')).spawn(shellLaunchConfig.executable!, args, options); this._ptyProcess = ptyProcess; @@ -213,7 +243,6 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess // Refire the data event this._onProcessData.fire(data); if (this._closeTimeout) { - clearTimeout(this._closeTimeout); this._queueProcessExit(); } this._windowsShellHelper?.checkShell(); @@ -226,7 +255,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess this._sendProcessId(ptyProcess.pid); } - public dispose(): void { + public override dispose(): void { this._isDisposed = true; if (this._titleInterval) { clearInterval(this._titleInterval); @@ -239,7 +268,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess // Send initial timeout async to give event listeners a chance to init setTimeout(() => this._sendProcessTitle(ptyProcess), 0); // Setup polling for non-Windows, for Windows `process` doesn't change - if (!platform.isWindows) { + if (!isWindows) { this._titleInterval = setInterval(() => { if (this._currentTitle !== ptyProcess.process) { this._sendProcessTitle(ptyProcess); @@ -271,6 +300,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess // point but we want to make sure try { if (this._ptyProcess) { + await this._throttleKillSpawn(); this._logService.trace('IPty#kill'); this._ptyProcess.kill(); } @@ -281,6 +311,19 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess this.dispose(); } + private async _throttleKillSpawn(): Promise { + // Only throttle on Windows/conpty + if (!isWindows || !('useConpty' in this._ptyOptions) || !this._ptyOptions.useConpty) { + return; + } + // Use a loop to ensure multiple calls in a single interval space out + while (Date.now() - TerminalProcess._lastKillOrStart < Constants.KillSpawnThrottleInterval) { + this._logService.trace('Throttling kill/spawn call'); + await timeout(Constants.KillSpawnThrottleInterval - (Date.now() - TerminalProcess._lastKillOrStart) + Constants.KillSpawnSpacingDuration); + } + TerminalProcess._lastKillOrStart = Date.now(); + } + private _sendProcessId(pid: number) { this._onProcessReady.fire({ pid, cwd: this._initialCwd }); } @@ -294,7 +337,10 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess } public shutdown(immediate: boolean): void { - if (immediate) { + // don't force immediate disposal of the terminal processes on Windows as an additional + // mitigation for https://github.com/microsoft/vscode/issues/71966 which causes the pty host + // to become unresponsive, disconnecting all terminals across all windows. + if (immediate && !isWindows) { this._kill(); } else { if (!this._closeTimeout && !this._isDisposed) { @@ -310,16 +356,24 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess } } - public input(data: string): void { + public input(data: string, isBinary?: boolean): void { if (this._isDisposed || !this._ptyProcess) { return; } for (let i = 0; i <= Math.floor(data.length / WRITE_MAX_CHUNK_SIZE); i++) { - this._writeQueue.push(data.substr(i * WRITE_MAX_CHUNK_SIZE, WRITE_MAX_CHUNK_SIZE)); + const obj = { + isBinary: isBinary || false, + data: data.substr(i * WRITE_MAX_CHUNK_SIZE, WRITE_MAX_CHUNK_SIZE) + }; + this._writeQueue.push(obj); } this._startWrite(); } + public async processBinary(data: string): Promise { + this.input(data, true); + } + private _startWrite(): void { // Don't write if it's already queued of is there is nothing to write if (this._writeTimeout !== undefined || this._writeQueue.length === 0) { @@ -342,9 +396,12 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess } private _doWrite(): void { - const data = this._writeQueue.shift()!; - this._logService.trace('IPty#write', `${data.length} characters`); - this._ptyProcess!.write(data); + const object = this._writeQueue.shift()!; + if (object.isBinary) { + this._ptyProcess!.write(Buffer.from(object.data, 'binary') as any); + } else { + this._ptyProcess!.write(object.data); + } } public resize(cols: number, rows: number): void { @@ -405,7 +462,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess } public getCwd(): Promise { - if (platform.isMacintosh) { + if (isMacintosh) { // Disable cwd lookup on macOS Big Sur due to spawn blocking thread (darwin v20 is macOS // Big Sur) https://github.com/Microsoft/vscode/issues/105446 const osRelease = os.release().split('.'); @@ -428,7 +485,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess } } - if (platform.isLinux) { + if (isLinux) { return new Promise(resolve => { if (!this._ptyProcess) { resolve(this._initialCwd); @@ -477,7 +534,7 @@ class DelayedResizer extends Disposable { }); } - dispose(): void { + override dispose(): void { super.dispose(); clearTimeout(this._timeout); } diff --git a/src/vs/platform/terminal/node/windowsShellHelper.ts b/src/vs/platform/terminal/node/windowsShellHelper.ts index 829afecb..64c3d205 100644 --- a/src/vs/platform/terminal/node/windowsShellHelper.ts +++ b/src/vs/platform/terminal/node/windowsShellHelper.ts @@ -3,13 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as platform from 'vs/base/common/platform'; import { Emitter, Event } from 'vs/base/common/event'; import type * as WindowsProcessTreeType from 'windows-process-tree'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { TerminalShellType, WindowsShellType } from 'vs/platform/terminal/common/terminal'; import { debounce } from 'vs/base/common/decorators'; import { timeout } from 'vs/base/common/async'; +import { isWindows, platform } from 'vs/base/common/platform'; export interface IWindowsShellHelper extends IDisposable { readonly onShellNameChanged: Event; @@ -51,8 +51,8 @@ export class WindowsShellHelper extends Disposable implements IWindowsShellHelpe ) { super(); - if (!platform.isWindows) { - throw new Error(`WindowsShellHelper cannot be instantiated on ${platform.platform}`); + if (!isWindows) { + throw new Error(`WindowsShellHelper cannot be instantiated on ${platform}`); } this._isDisposed = false; @@ -69,7 +69,7 @@ export class WindowsShellHelper extends Disposable implements IWindowsShellHelpe @debounce(500) async checkShell(): Promise { - if (platform.isWindows) { + if (isWindows) { // Wait to give the shell some time to actually launch a process, this // could lead to a race condition but it would be recovered from when // data stops and should cover the majority of cases @@ -112,7 +112,7 @@ export class WindowsShellHelper extends Disposable implements IWindowsShellHelpe return this.traverseTree(tree.children[favouriteChild]); } - public dispose(): void { + public override dispose(): void { this._isDisposed = true; super.dispose(); } diff --git a/src/vs/platform/theme/common/colorRegistry.ts b/src/vs/platform/theme/common/colorRegistry.ts index 9f32e882..0d0f8683 100644 --- a/src/vs/platform/theme/common/colorRegistry.ts +++ b/src/vs/platform/theme/common/colorRegistry.ts @@ -228,6 +228,7 @@ export const simpleCheckboxBorder = registerColor('checkbox.border', { dark: sel export const buttonForeground = registerColor('button.foreground', { dark: Color.white, light: Color.white, hc: Color.white }, nls.localize('buttonForeground', "Button foreground color.")); export const buttonBackground = registerColor('button.background', { dark: '#0E639C', light: '#007ACC', hc: null }, nls.localize('buttonBackground', "Button background color.")); export const buttonHoverBackground = registerColor('button.hoverBackground', { dark: lighten(buttonBackground, 0.2), light: darken(buttonBackground, 0.2), hc: null }, nls.localize('buttonHoverBackground', "Button background color when hovering.")); +export const buttonBorder = registerColor('button.border', { dark: contrastBorder, light: contrastBorder, hc: contrastBorder }, nls.localize('buttonBorder', "Button border color.")); export const buttonSecondaryForeground = registerColor('button.secondaryForeground', { dark: Color.white, light: Color.white, hc: Color.white }, nls.localize('buttonSecondaryForeground', "Secondary button foreground color.")); export const buttonSecondaryBackground = registerColor('button.secondaryBackground', { dark: '#3A3D41', light: '#5F6A79', hc: null }, nls.localize('buttonSecondaryBackground', "Secondary button background color.")); @@ -291,6 +292,14 @@ export const quickInputTitleBackground = registerColor('quickInputTitle.backgrou export const pickerGroupForeground = registerColor('pickerGroup.foreground', { dark: '#3794FF', light: '#0066BF', hc: Color.white }, nls.localize('pickerGroupForeground', "Quick picker color for grouping labels.")); export const pickerGroupBorder = registerColor('pickerGroup.border', { dark: '#3F3F46', light: '#CCCEDB', hc: Color.white }, nls.localize('pickerGroupBorder', "Quick picker color for grouping borders.")); +/** + * Keybinding label + */ +export const keybindingLabelBackground = registerColor('keybindingLabel.background', { dark: new Color(new RGBA(128, 128, 128, 0.17)), light: new Color(new RGBA(221, 221, 221, 0.4)), hc: Color.transparent }, nls.localize('keybindingLabelBackground', "Keybinding label background color. The keybinding label is used to represent a keyboard shortcut.")); +export const keybindingLabelForeground = registerColor('keybindingLabel.foreground', { dark: Color.fromHex('#CCCCCC'), light: Color.fromHex('#555555'), hc: Color.white }, nls.localize('keybindingLabelForeground', "Keybinding label foreground color. The keybinding label is used to represent a keyboard shortcut.")); +export const keybindingLabelBorder = registerColor('keybindingLabel.border', { dark: new Color(new RGBA(51, 51, 51, 0.6)), light: new Color(new RGBA(204, 204, 204, 0.4)), hc: new Color(new RGBA(111, 195, 223)) }, nls.localize('keybindingLabelBorder', "Keybinding label border color. The keybinding label is used to represent a keyboard shortcut.")); +export const keybindingLabelBottomBorder = registerColor('keybindingLabel.bottomBorder', { dark: new Color(new RGBA(68, 68, 68, 0.6)), light: new Color(new RGBA(187, 187, 187, 0.4)), hc: new Color(new RGBA(111, 195, 223)) }, nls.localize('keybindingLabelBottomBorder', "Keybinding label border bottom color. The keybinding label is used to represent a keyboard shortcut.")); + /** * Editor selection colors. */ @@ -404,6 +413,13 @@ export const menuSelectionBackground = registerColor('menu.selectionBackground', export const menuSelectionBorder = registerColor('menu.selectionBorder', { dark: null, light: null, hc: activeContrastBorder }, nls.localize('menuSelectionBorder', "Border color of the selected menu item in menus.")); export const menuSeparatorBackground = registerColor('menu.separatorBackground', { dark: '#BBBBBB', light: '#888888', hc: contrastBorder }, nls.localize('menuSeparatorBackground', "Color of a separator menu item in menus.")); +/** + * Toolbar colors + */ +export const toolbarHoverBackground = registerColor('toolbar.hoverBackground', { dark: '#5a5d5e50', light: '#b8b8b850', hc: null }, nls.localize('toolbarHoverBackground', "Toolbar background when hovering over actions using the mouse")); +export const toolbarHoverOutline = registerColor('toolbar.hoverOutline', { dark: null, light: null, hc: activeContrastBorder }, nls.localize('toolbarHoverOutline', "Toolbar outline when hovering over actions using the mouse")); +export const toolbarActiveBackground = registerColor('toolbar.activeBackground', { dark: lighten(toolbarHoverBackground, 0.1), light: darken(toolbarHoverBackground, 0.1), hc: null }, nls.localize('toolbarActiveBackground', "Toolbar background when holding the mouse over actions")); + /** * Snippet placeholder colors */ diff --git a/src/vs/platform/theme/common/styler.ts b/src/vs/platform/theme/common/styler.ts index 1e7068d0..9827da48 100644 --- a/src/vs/platform/theme/common/styler.ts +++ b/src/vs/platform/theme/common/styler.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IColorTheme, IThemeService } from 'vs/platform/theme/common/themeService'; -import { focusBorder, inputBackground, inputForeground, ColorIdentifier, selectForeground, selectBackground, selectListBackground, selectBorder, inputBorder, foreground, editorBackground, contrastBorder, inputActiveOptionBorder, inputActiveOptionBackground, inputActiveOptionForeground, listFocusBackground, listFocusForeground, listActiveSelectionBackground, listActiveSelectionForeground, listInactiveSelectionForeground, listInactiveSelectionBackground, listInactiveFocusBackground, listHoverBackground, listHoverForeground, listDropBackground, pickerGroupBorder, pickerGroupForeground, widgetShadow, inputValidationInfoBorder, inputValidationInfoBackground, inputValidationWarningBorder, inputValidationWarningBackground, inputValidationErrorBorder, inputValidationErrorBackground, activeContrastBorder, buttonForeground, buttonBackground, buttonHoverBackground, ColorFunction, badgeBackground, badgeForeground, progressBarBackground, breadcrumbsForeground, breadcrumbsFocusForeground, breadcrumbsActiveSelectionForeground, breadcrumbsBackground, editorWidgetBorder, inputValidationInfoForeground, inputValidationWarningForeground, inputValidationErrorForeground, menuForeground, menuBackground, menuSelectionForeground, menuSelectionBackground, menuSelectionBorder, menuBorder, menuSeparatorBackground, darken, listFilterWidgetOutline, listFilterWidgetNoMatchesOutline, listFilterWidgetBackground, editorWidgetBackground, treeIndentGuidesStroke, editorWidgetForeground, simpleCheckboxBackground, simpleCheckboxBorder, simpleCheckboxForeground, ColorValue, resolveColorValue, textLinkForeground, problemsWarningIconForeground, problemsErrorIconForeground, problemsInfoIconForeground, buttonSecondaryBackground, buttonSecondaryForeground, buttonSecondaryHoverBackground, listFocusOutline, listInactiveFocusOutline, tableColumnsBorder, quickInputListFocusBackground } from 'vs/platform/theme/common/colorRegistry'; +import { focusBorder, inputBackground, inputForeground, ColorIdentifier, selectForeground, selectBackground, selectListBackground, selectBorder, inputBorder, contrastBorder, inputActiveOptionBorder, inputActiveOptionBackground, inputActiveOptionForeground, listFocusBackground, listFocusForeground, listActiveSelectionBackground, listActiveSelectionForeground, listInactiveSelectionForeground, listInactiveSelectionBackground, listInactiveFocusBackground, listHoverBackground, listHoverForeground, listDropBackground, pickerGroupForeground, widgetShadow, inputValidationInfoBorder, inputValidationInfoBackground, inputValidationWarningBorder, inputValidationWarningBackground, inputValidationErrorBorder, inputValidationErrorBackground, activeContrastBorder, buttonForeground, buttonBackground, buttonHoverBackground, ColorFunction, badgeBackground, badgeForeground, progressBarBackground, breadcrumbsForeground, breadcrumbsFocusForeground, breadcrumbsActiveSelectionForeground, breadcrumbsBackground, editorWidgetBorder, inputValidationInfoForeground, inputValidationWarningForeground, inputValidationErrorForeground, menuForeground, menuBackground, menuSelectionForeground, menuSelectionBackground, menuSelectionBorder, menuBorder, menuSeparatorBackground, listFilterWidgetOutline, listFilterWidgetNoMatchesOutline, listFilterWidgetBackground, editorWidgetBackground, treeIndentGuidesStroke, editorWidgetForeground, simpleCheckboxBackground, simpleCheckboxBorder, simpleCheckboxForeground, ColorValue, resolveColorValue, textLinkForeground, problemsWarningIconForeground, problemsErrorIconForeground, problemsInfoIconForeground, buttonSecondaryBackground, buttonSecondaryForeground, buttonSecondaryHoverBackground, listFocusOutline, listInactiveFocusOutline, tableColumnsBorder, quickInputListFocusBackground, buttonBorder, keybindingLabelForeground, keybindingLabelBackground, keybindingLabelBorder, keybindingLabelBottomBorder } from 'vs/platform/theme/common/colorRegistry'; import { IDisposable } from 'vs/base/common/lifecycle'; import { Color } from 'vs/base/common/color'; import { IThemable, styleFn } from 'vs/base/common/styler'; @@ -58,9 +58,9 @@ export interface ICheckboxStyleOverrides extends IStyleOverrides { export function attachCheckboxStyler(widget: IThemable, themeService: IThemeService, style?: ICheckboxStyleOverrides): IDisposable { return attachStyler(themeService, { - inputActiveOptionBorder: (style && style.inputActiveOptionBorderColor) || inputActiveOptionBorder, - inputActiveOptionForeground: (style && style.inputActiveOptionForegroundColor) || inputActiveOptionForeground, - inputActiveOptionBackground: (style && style.inputActiveOptionBackgroundColor) || inputActiveOptionBackground + inputActiveOptionBorder: style?.inputActiveOptionBorderColor || inputActiveOptionBorder, + inputActiveOptionForeground: style?.inputActiveOptionForegroundColor || inputActiveOptionForeground, + inputActiveOptionBackground: style?.inputActiveOptionBackgroundColor || inputActiveOptionBackground } as ICheckboxStyleOverrides, widget); } @@ -71,8 +71,8 @@ export interface IBadgeStyleOverrides extends IStyleOverrides { export function attachBadgeStyler(widget: IThemable, themeService: IThemeService, style?: IBadgeStyleOverrides): IDisposable { return attachStyler(themeService, { - badgeBackground: (style && style.badgeBackground) || badgeBackground, - badgeForeground: (style && style.badgeForeground) || badgeForeground, + badgeBackground: style?.badgeBackground || badgeBackground, + badgeForeground: style?.badgeForeground || badgeForeground, badgeBorder: contrastBorder } as IBadgeStyleOverrides, widget); } @@ -97,18 +97,18 @@ export interface IInputBoxStyleOverrides extends IStyleOverrides { export function attachInputBoxStyler(widget: IThemable, themeService: IThemeService, style?: IInputBoxStyleOverrides): IDisposable { return attachStyler(themeService, { - inputBackground: (style && style.inputBackground) || inputBackground, - inputForeground: (style && style.inputForeground) || inputForeground, - inputBorder: (style && style.inputBorder) || inputBorder, - inputValidationInfoBorder: (style && style.inputValidationInfoBorder) || inputValidationInfoBorder, - inputValidationInfoBackground: (style && style.inputValidationInfoBackground) || inputValidationInfoBackground, - inputValidationInfoForeground: (style && style.inputValidationInfoForeground) || inputValidationInfoForeground, - inputValidationWarningBorder: (style && style.inputValidationWarningBorder) || inputValidationWarningBorder, - inputValidationWarningBackground: (style && style.inputValidationWarningBackground) || inputValidationWarningBackground, - inputValidationWarningForeground: (style && style.inputValidationWarningForeground) || inputValidationWarningForeground, - inputValidationErrorBorder: (style && style.inputValidationErrorBorder) || inputValidationErrorBorder, - inputValidationErrorBackground: (style && style.inputValidationErrorBackground) || inputValidationErrorBackground, - inputValidationErrorForeground: (style && style.inputValidationErrorForeground) || inputValidationErrorForeground + inputBackground: style?.inputBackground || inputBackground, + inputForeground: style?.inputForeground || inputForeground, + inputBorder: style?.inputBorder || inputBorder, + inputValidationInfoBorder: style?.inputValidationInfoBorder || inputValidationInfoBorder, + inputValidationInfoBackground: style?.inputValidationInfoBackground || inputValidationInfoBackground, + inputValidationInfoForeground: style?.inputValidationInfoForeground || inputValidationInfoForeground, + inputValidationWarningBorder: style?.inputValidationWarningBorder || inputValidationWarningBorder, + inputValidationWarningBackground: style?.inputValidationWarningBackground || inputValidationWarningBackground, + inputValidationWarningForeground: style?.inputValidationWarningForeground || inputValidationWarningForeground, + inputValidationErrorBorder: style?.inputValidationErrorBorder || inputValidationErrorBorder, + inputValidationErrorBackground: style?.inputValidationErrorBackground || inputValidationErrorBackground, + inputValidationErrorForeground: style?.inputValidationErrorForeground || inputValidationErrorForeground } as IInputBoxStyleOverrides, widget); } @@ -123,90 +123,42 @@ export interface ISelectBoxStyleOverrides extends IStyleOverrides, IListStyleOve export function attachSelectBoxStyler(widget: IThemable, themeService: IThemeService, style?: ISelectBoxStyleOverrides): IDisposable { return attachStyler(themeService, { - selectBackground: (style && style.selectBackground) || selectBackground, - selectListBackground: (style && style.selectListBackground) || selectListBackground, - selectForeground: (style && style.selectForeground) || selectForeground, - decoratorRightForeground: (style && style.pickerGroupForeground) || pickerGroupForeground, - selectBorder: (style && style.selectBorder) || selectBorder, - focusBorder: (style && style.focusBorder) || focusBorder, - listFocusBackground: (style && style.listFocusBackground) || quickInputListFocusBackground, - listFocusForeground: (style && style.listFocusForeground) || listFocusForeground, - listFocusOutline: (style && style.listFocusOutline) || ((theme: IColorTheme) => theme.type === ColorScheme.HIGH_CONTRAST ? activeContrastBorder : Color.transparent), - listHoverBackground: (style && style.listHoverBackground) || listHoverBackground, - listHoverForeground: (style && style.listHoverForeground) || listHoverForeground, - listHoverOutline: (style && style.listFocusOutline) || activeContrastBorder, - selectListBorder: (style && style.selectListBorder) || editorWidgetBorder + selectBackground: style?.selectBackground || selectBackground, + selectListBackground: style?.selectListBackground || selectListBackground, + selectForeground: style?.selectForeground || selectForeground, + decoratorRightForeground: style?.pickerGroupForeground || pickerGroupForeground, + selectBorder: style?.selectBorder || selectBorder, + focusBorder: style?.focusBorder || focusBorder, + listFocusBackground: style?.listFocusBackground || quickInputListFocusBackground, + listFocusForeground: style?.listFocusForeground || listFocusForeground, + listFocusOutline: style?.listFocusOutline || ((theme: IColorTheme) => theme.type === ColorScheme.HIGH_CONTRAST ? activeContrastBorder : Color.transparent), + listHoverBackground: style?.listHoverBackground || listHoverBackground, + listHoverForeground: style?.listHoverForeground || listHoverForeground, + listHoverOutline: style?.listFocusOutline || activeContrastBorder, + selectListBorder: style?.selectListBorder || editorWidgetBorder } as ISelectBoxStyleOverrides, widget); } export function attachFindReplaceInputBoxStyler(widget: IThemable, themeService: IThemeService, style?: IInputBoxStyleOverrides): IDisposable { return attachStyler(themeService, { - inputBackground: (style && style.inputBackground) || inputBackground, - inputForeground: (style && style.inputForeground) || inputForeground, - inputBorder: (style && style.inputBorder) || inputBorder, - inputActiveOptionBorder: (style && style.inputActiveOptionBorder) || inputActiveOptionBorder, - inputActiveOptionForeground: (style && style.inputActiveOptionForeground) || inputActiveOptionForeground, - inputActiveOptionBackground: (style && style.inputActiveOptionBackground) || inputActiveOptionBackground, - inputValidationInfoBorder: (style && style.inputValidationInfoBorder) || inputValidationInfoBorder, - inputValidationInfoBackground: (style && style.inputValidationInfoBackground) || inputValidationInfoBackground, - inputValidationInfoForeground: (style && style.inputValidationInfoForeground) || inputValidationInfoForeground, - inputValidationWarningBorder: (style && style.inputValidationWarningBorder) || inputValidationWarningBorder, - inputValidationWarningBackground: (style && style.inputValidationWarningBackground) || inputValidationWarningBackground, - inputValidationWarningForeground: (style && style.inputValidationWarningForeground) || inputValidationWarningForeground, - inputValidationErrorBorder: (style && style.inputValidationErrorBorder) || inputValidationErrorBorder, - inputValidationErrorBackground: (style && style.inputValidationErrorBackground) || inputValidationErrorBackground, - inputValidationErrorForeground: (style && style.inputValidationErrorForeground) || inputValidationErrorForeground + inputBackground: style?.inputBackground || inputBackground, + inputForeground: style?.inputForeground || inputForeground, + inputBorder: style?.inputBorder || inputBorder, + inputActiveOptionBorder: style?.inputActiveOptionBorder || inputActiveOptionBorder, + inputActiveOptionForeground: style?.inputActiveOptionForeground || inputActiveOptionForeground, + inputActiveOptionBackground: style?.inputActiveOptionBackground || inputActiveOptionBackground, + inputValidationInfoBorder: style?.inputValidationInfoBorder || inputValidationInfoBorder, + inputValidationInfoBackground: style?.inputValidationInfoBackground || inputValidationInfoBackground, + inputValidationInfoForeground: style?.inputValidationInfoForeground || inputValidationInfoForeground, + inputValidationWarningBorder: style?.inputValidationWarningBorder || inputValidationWarningBorder, + inputValidationWarningBackground: style?.inputValidationWarningBackground || inputValidationWarningBackground, + inputValidationWarningForeground: style?.inputValidationWarningForeground || inputValidationWarningForeground, + inputValidationErrorBorder: style?.inputValidationErrorBorder || inputValidationErrorBorder, + inputValidationErrorBackground: style?.inputValidationErrorBackground || inputValidationErrorBackground, + inputValidationErrorForeground: style?.inputValidationErrorForeground || inputValidationErrorForeground } as IInputBoxStyleOverrides, widget); } -export interface IQuickInputStyleOverrides extends IListStyleOverrides, IInputBoxStyleOverrides, IProgressBarStyleOverrides { - foreground?: ColorIdentifier; - background?: ColorIdentifier; - borderColor?: ColorIdentifier; - widgetShadow?: ColorIdentifier; - pickerGroupForeground?: ColorIdentifier; - pickerGroupBorder?: ColorIdentifier; -} - -export function attachQuickInputStyler(widget: IThemable, themeService: IThemeService, style?: IQuickInputStyleOverrides): IDisposable { - return attachStyler(themeService, { - foreground: (style && style.foreground) || foreground, - background: (style && style.background) || editorBackground, - borderColor: style && style.borderColor || contrastBorder, - widgetShadow: style && style.widgetShadow || widgetShadow, - progressBarBackground: style && style.progressBarBackground || progressBarBackground, - pickerGroupForeground: style && style.pickerGroupForeground || pickerGroupForeground, - pickerGroupBorder: style && style.pickerGroupBorder || pickerGroupBorder, - inputBackground: (style && style.inputBackground) || inputBackground, - inputForeground: (style && style.inputForeground) || inputForeground, - inputBorder: (style && style.inputBorder) || inputBorder, - inputValidationInfoBorder: (style && style.inputValidationInfoBorder) || inputValidationInfoBorder, - inputValidationInfoBackground: (style && style.inputValidationInfoBackground) || inputValidationInfoBackground, - inputValidationInfoForeground: (style && style.inputValidationInfoForeground) || inputValidationInfoForeground, - inputValidationWarningBorder: (style && style.inputValidationWarningBorder) || inputValidationWarningBorder, - inputValidationWarningBackground: (style && style.inputValidationWarningBackground) || inputValidationWarningBackground, - inputValidationWarningForeground: (style && style.inputValidationWarningForeground) || inputValidationWarningForeground, - inputValidationErrorBorder: (style && style.inputValidationErrorBorder) || inputValidationErrorBorder, - inputValidationErrorBackground: (style && style.inputValidationErrorBackground) || inputValidationErrorBackground, - inputValidationErrorForeground: (style && style.inputValidationErrorForeground) || inputValidationErrorForeground, - listFocusBackground: (style && style.listFocusBackground) || listFocusBackground, - listFocusForeground: (style && style.listFocusForeground) || listFocusForeground, - listActiveSelectionBackground: (style && style.listActiveSelectionBackground) || darken(listActiveSelectionBackground, 0.1), - listActiveSelectionForeground: (style && style.listActiveSelectionForeground) || listActiveSelectionForeground, - listFocusAndSelectionBackground: style && style.listFocusAndSelectionBackground || listActiveSelectionBackground, - listFocusAndSelectionForeground: (style && style.listFocusAndSelectionForeground) || listActiveSelectionForeground, - listInactiveSelectionBackground: (style && style.listInactiveSelectionBackground) || listInactiveSelectionBackground, - listInactiveSelectionForeground: (style && style.listInactiveSelectionForeground) || listInactiveSelectionForeground, - listInactiveFocusBackground: (style && style.listInactiveFocusBackground) || listInactiveFocusBackground, - listHoverBackground: (style && style.listHoverBackground) || listHoverBackground, - listHoverForeground: (style && style.listHoverForeground) || listHoverForeground, - listDropBackground: (style && style.listDropBackground) || listDropBackground, - listFocusOutline: (style && style.listFocusOutline) || activeContrastBorder, - listSelectionOutline: (style && style.listSelectionOutline) || activeContrastBorder, - listHoverOutline: (style && style.listHoverOutline) || activeContrastBorder - } as IQuickInputStyleOverrides, widget); -} - export interface IListStyleOverrides extends IStyleOverrides { listBackground?: ColorIdentifier; listFocusBackground?: ColorIdentifier; @@ -269,27 +221,46 @@ export interface IButtonStyleOverrides extends IStyleOverrides { buttonSecondaryForeground?: ColorIdentifier; buttonSecondaryBackground?: ColorIdentifier; buttonSecondaryHoverBackground?: ColorIdentifier; + buttonBorder?: ColorIdentifier; } export function attachButtonStyler(widget: IThemable, themeService: IThemeService, style?: IButtonStyleOverrides): IDisposable { return attachStyler(themeService, { - buttonForeground: (style && style.buttonForeground) || buttonForeground, - buttonBackground: (style && style.buttonBackground) || buttonBackground, - buttonHoverBackground: (style && style.buttonHoverBackground) || buttonHoverBackground, - buttonSecondaryForeground: (style && style.buttonSecondaryForeground) || buttonSecondaryForeground, - buttonSecondaryBackground: (style && style.buttonSecondaryBackground) || buttonSecondaryBackground, - buttonSecondaryHoverBackground: (style && style.buttonSecondaryHoverBackground) || buttonSecondaryHoverBackground, - buttonBorder: contrastBorder + buttonForeground: style?.buttonForeground || buttonForeground, + buttonBackground: style?.buttonBackground || buttonBackground, + buttonHoverBackground: style?.buttonHoverBackground || buttonHoverBackground, + buttonSecondaryForeground: style?.buttonSecondaryForeground || buttonSecondaryForeground, + buttonSecondaryBackground: style?.buttonSecondaryBackground || buttonSecondaryBackground, + buttonSecondaryHoverBackground: style?.buttonSecondaryHoverBackground || buttonSecondaryHoverBackground, + buttonBorder: style?.buttonBorder || buttonBorder, } as IButtonStyleOverrides, widget); } +export interface IKeybindingLabelStyleOverrides extends IStyleOverrides { + keybindingLabelBackground?: ColorIdentifier; + keybindingLabelForeground?: ColorIdentifier; + keybindingLabelBorder?: ColorIdentifier; + keybindingLabelBottomBorder?: ColorIdentifier; + keybindingLabelShadow?: ColorIdentifier; +} + +export function attachKeybindingLabelStyler(widget: IThemable, themeService: IThemeService, style?: IKeybindingLabelStyleOverrides): IDisposable { + return attachStyler(themeService, { + keybindingLabelBackground: (style && style.keybindingLabelBackground) || keybindingLabelBackground, + keybindingLabelForeground: (style && style.keybindingLabelForeground) || keybindingLabelForeground, + keybindingLabelBorder: (style && style.keybindingLabelBorder) || keybindingLabelBorder, + keybindingLabelBottomBorder: (style && style.keybindingLabelBottomBorder) || keybindingLabelBottomBorder, + keybindingLabelShadow: (style && style.keybindingLabelShadow) || widgetShadow + } as IKeybindingLabelStyleOverrides, widget); +} + export interface ILinkStyleOverrides extends IStyleOverrides { textLinkForeground?: ColorIdentifier; } export function attachLinkStyler(widget: IThemable, themeService: IThemeService, style?: ILinkStyleOverrides): IDisposable { return attachStyler(themeService, { - textLinkForeground: (style && style.textLinkForeground) || textLinkForeground, + textLinkForeground: style?.textLinkForeground || textLinkForeground, } as ILinkStyleOverrides, widget); } @@ -299,7 +270,7 @@ export interface IProgressBarStyleOverrides extends IStyleOverrides { export function attachProgressBarStyler(widget: IThemable, themeService: IThemeService, style?: IProgressBarStyleOverrides): IDisposable { return attachStyler(themeService, { - progressBarBackground: (style && style.progressBarBackground) || progressBarBackground + progressBarBackground: style?.progressBarBackground || progressBarBackground } as IProgressBarStyleOverrides, widget); } @@ -353,7 +324,7 @@ export function attachMenuStyler(widget: IThemable, themeService: IThemeService, return attachStyler(themeService, { ...defaultMenuStyles, ...style }, widget); } -export interface IDialogStyleOverrides extends IButtonStyleOverrides { +export interface IDialogStyleOverrides extends IButtonStyleOverrides, ILinkStyleOverrides { dialogForeground?: ColorIdentifier; dialogBackground?: ColorIdentifier; dialogShadow?: ColorIdentifier; @@ -376,8 +347,11 @@ export const defaultDialogStyles = { dialogBorder: contrastBorder, buttonForeground: buttonForeground, buttonBackground: buttonBackground, + buttonSecondaryBackground: buttonSecondaryBackground, + buttonSecondaryForeground: buttonSecondaryForeground, + buttonSecondaryHoverBackground: buttonSecondaryHoverBackground, buttonHoverBackground: buttonHoverBackground, - buttonBorder: contrastBorder, + buttonBorder: buttonBorder, checkboxBorder: simpleCheckboxBorder, checkboxBackground: simpleCheckboxBackground, checkboxForeground: simpleCheckboxForeground, @@ -386,7 +360,8 @@ export const defaultDialogStyles = { infoIconForeground: problemsInfoIconForeground, inputBackground: inputBackground, inputForeground: inputForeground, - inputBorder: inputBorder + inputBorder: inputBorder, + textLinkForeground: textLinkForeground }; diff --git a/src/vs/platform/undoRedo/common/undoRedoService.ts b/src/vs/platform/undoRedo/common/undoRedoService.ts index 7e205e70..7f3d251a 100644 --- a/src/vs/platform/undoRedo/common/undoRedoService.ts +++ b/src/vs/platform/undoRedo/common/undoRedoService.ts @@ -814,14 +814,14 @@ export class UndoRedoService implements IUndoRedoService { private _tryToSplitAndUndo(strResource: string, element: WorkspaceStackElement, ignoreResources: RemovedResources | null, message: string): WorkspaceVerificationError { if (element.canSplit()) { this._splitPastWorkspaceElement(element, ignoreResources); - this._notificationService.info(message); + this._notificationService.warn(message); return new WorkspaceVerificationError(this._undo(strResource, 0, true)); } else { // Cannot safely split this workspace element => flush all undo/redo stacks for (const strResource of element.strResources) { this.removeElements(strResource); } - this._notificationService.info(message); + this._notificationService.warn(message); return new WorkspaceVerificationError(); } } @@ -1009,7 +1009,7 @@ export class UndoRedoService implements IUndoRedoService { { key: 'cannotResourceUndoDueToInProgressUndoRedo', comment: ['{0} is a label for an operation.'] }, "Could not undo '{0}' because there is already an undo or redo operation running.", element.label ); - this._notificationService.info(message); + this._notificationService.warn(message); return; } return this._invokeResourcePrepare(element, (cleanup) => { @@ -1167,14 +1167,14 @@ export class UndoRedoService implements IUndoRedoService { private _tryToSplitAndRedo(strResource: string, element: WorkspaceStackElement, ignoreResources: RemovedResources | null, message: string): WorkspaceVerificationError { if (element.canSplit()) { this._splitFutureWorkspaceElement(element, ignoreResources); - this._notificationService.info(message); + this._notificationService.warn(message); return new WorkspaceVerificationError(this._redo(strResource)); } else { // Cannot safely split this workspace element => flush all undo/redo stacks for (const strResource of element.strResources) { this.removeElements(strResource); } - this._notificationService.info(message); + this._notificationService.warn(message); return new WorkspaceVerificationError(); } } @@ -1298,7 +1298,7 @@ export class UndoRedoService implements IUndoRedoService { { key: 'cannotResourceRedoDueToInProgressUndoRedo', comment: ['{0} is a label for an operation.'] }, "Could not redo '{0}' because there is already an undo or redo operation running.", element.label ); - this._notificationService.info(message); + this._notificationService.warn(message); return; } diff --git a/src/vs/platform/undoRedo/test/common/undoRedoService.test.ts b/src/vs/platform/undoRedo/test/common/undoRedoService.test.ts index 49f49445..fb2d35f1 100644 --- a/src/vs/platform/undoRedo/test/common/undoRedoService.test.ts +++ b/src/vs/platform/undoRedo/test/common/undoRedoService.test.ts @@ -133,7 +133,7 @@ suite('UndoRedoService', () => { const resource1 = URI.file('test1.txt'); const resource2 = URI.file('test2.txt'); const service = createUndoRedoService(new class extends mock() { - async show() { + override async show() { return { choice: 0 // confirm! }; diff --git a/src/vs/platform/update/common/update.ts b/src/vs/platform/update/common/update.ts index 13e322a9..cf66654c 100644 --- a/src/vs/platform/update/common/update.ts +++ b/src/vs/platform/update/common/update.ts @@ -51,7 +51,7 @@ export const enum UpdateType { export type Uninitialized = { type: StateType.Uninitialized }; export type Idle = { type: StateType.Idle, updateType: UpdateType, error?: string }; -export type CheckingForUpdates = { type: StateType.CheckingForUpdates, context: any }; +export type CheckingForUpdates = { type: StateType.CheckingForUpdates, explicit: boolean }; export type AvailableForDownload = { type: StateType.AvailableForDownload, update: IUpdate }; export type Downloading = { type: StateType.Downloading, update: IUpdate }; export type Downloaded = { type: StateType.Downloaded, update: IUpdate }; @@ -63,7 +63,7 @@ export type State = Uninitialized | Idle | CheckingForUpdates | AvailableForDown export const State = { Uninitialized: { type: StateType.Uninitialized } as Uninitialized, Idle: (updateType: UpdateType, error?: string) => ({ type: StateType.Idle, updateType, error }) as Idle, - CheckingForUpdates: (context: any) => ({ type: StateType.CheckingForUpdates, context } as CheckingForUpdates), + CheckingForUpdates: (explicit: boolean) => ({ type: StateType.CheckingForUpdates, explicit } as CheckingForUpdates), AvailableForDownload: (update: IUpdate) => ({ type: StateType.AvailableForDownload, update } as AvailableForDownload), Downloading: (update: IUpdate) => ({ type: StateType.Downloading, update } as Downloading), Downloaded: (update: IUpdate) => ({ type: StateType.Downloaded, update } as Downloaded), @@ -86,7 +86,7 @@ export interface IUpdateService { readonly onStateChange: Event; readonly state: State; - checkForUpdates(context: any): Promise; + checkForUpdates(explicit: boolean): Promise; downloadUpdate(): Promise; applyUpdate(): Promise; quitAndInstall(): Promise; diff --git a/src/vs/platform/update/common/updateIpc.ts b/src/vs/platform/update/common/updateIpc.ts index 26dcaa32..6340754e 100644 --- a/src/vs/platform/update/common/updateIpc.ts +++ b/src/vs/platform/update/common/updateIpc.ts @@ -52,8 +52,8 @@ export class UpdateChannelClient implements IUpdateService { this.channel.call('_getInitialState').then(state => this.state = state); } - checkForUpdates(context: any): Promise { - return this.channel.call('checkForUpdates', context); + checkForUpdates(explicit: boolean): Promise { + return this.channel.call('checkForUpdates', explicit); } downloadUpdate(): Promise { diff --git a/src/vs/platform/update/electron-main/abstractUpdateService.ts b/src/vs/platform/update/electron-main/abstractUpdateService.ts index 29e938df..289b57ce 100644 --- a/src/vs/platform/update/electron-main/abstractUpdateService.ts +++ b/src/vs/platform/update/electron-main/abstractUpdateService.ts @@ -97,7 +97,7 @@ export abstract class AbstractUpdateService implements IUpdateService { this.logService.info('update#ctor - startup checks only; automatic updates are disabled by user preference'); // Check for updates only once after 30 seconds - setTimeout(() => this.checkForUpdates(null), 30 * 1000); + setTimeout(() => this.checkForUpdates(false), 30 * 1000); } else { // Start checking for updates after 30 seconds this.scheduleCheckForUpdates(30 * 1000).then(undefined, err => this.logService.error(err)); @@ -110,21 +110,21 @@ export abstract class AbstractUpdateService implements IUpdateService { private scheduleCheckForUpdates(delay = 60 * 60 * 1000): Promise { return timeout(delay) - .then(() => this.checkForUpdates(null)) + .then(() => this.checkForUpdates(false)) .then(() => { // Check again after 1 hour return this.scheduleCheckForUpdates(60 * 60 * 1000); }); } - async checkForUpdates(context: any): Promise { + async checkForUpdates(explicit: boolean): Promise { this.logService.trace('update#checkForUpdates, state = ', this.state.type); if (this.state.type !== StateType.Idle) { return; } - this.doCheckForUpdates(context); + this.doCheckForUpdates(explicit); } async downloadUpdate(): Promise { diff --git a/src/vs/platform/update/electron-main/updateService.darwin.ts b/src/vs/platform/update/electron-main/updateService.darwin.ts index b876e881..401da74a 100644 --- a/src/vs/platform/update/electron-main/updateService.darwin.ts +++ b/src/vs/platform/update/electron-main/updateService.darwin.ts @@ -19,8 +19,6 @@ import { IProductService } from 'vs/platform/product/common/productService'; export class DarwinUpdateService extends AbstractUpdateService { - declare readonly _serviceBrand: undefined; - private readonly disposables = new DisposableStore(); @memoize private get onRawError(): Event { return Event.fromNodeEventEmitter(electron.autoUpdater, 'error', (_, message) => message); } @@ -40,7 +38,7 @@ export class DarwinUpdateService extends AbstractUpdateService { super(lifecycleMainService, configurationService, environmentMainService, requestService, logService, productService); } - initialize(): void { + override initialize(): void { super.initialize(); this.onRawError(this.onError, this, this.disposables); this.onRawUpdateAvailable(this.onUpdateAvailable, this, this.disposables); @@ -52,7 +50,7 @@ export class DarwinUpdateService extends AbstractUpdateService { this.logService.error('UpdateService error:', err); // only show message when explicitly checking for updates - const shouldShowMessage = this.state.type === StateType.CheckingForUpdates ? !!this.state.context : true; + const shouldShowMessage = this.state.type === StateType.CheckingForUpdates ? this.state.explicit : true; const message: string | undefined = shouldShowMessage ? err : undefined; this.setState(State.Idle(UpdateType.Archive, message)); } @@ -105,12 +103,12 @@ export class DarwinUpdateService extends AbstractUpdateService { if (this.state.type !== StateType.CheckingForUpdates) { return; } - this.telemetryService.publicLog2<{ explicit: boolean }, UpdateNotAvailableClassification>('update:notAvailable', { explicit: !!this.state.context }); + this.telemetryService.publicLog2<{ explicit: boolean }, UpdateNotAvailableClassification>('update:notAvailable', { explicit: this.state.explicit }); this.setState(State.Idle(UpdateType.Archive)); } - protected doQuitAndInstall(): void { + protected override doQuitAndInstall(): void { this.logService.trace('update#quitAndInstall(): running raw#quitAndInstall()'); electron.autoUpdater.quitAndInstall(); } diff --git a/src/vs/platform/update/electron-main/updateService.linux.ts b/src/vs/platform/update/electron-main/updateService.linux.ts index 7387636b..d121dbbb 100644 --- a/src/vs/platform/update/electron-main/updateService.linux.ts +++ b/src/vs/platform/update/electron-main/updateService.linux.ts @@ -17,8 +17,6 @@ import { INativeHostMainService } from 'vs/platform/native/electron-main/nativeH export class LinuxUpdateService extends AbstractUpdateService { - declare readonly _serviceBrand: undefined; - constructor( @ILifecycleMainService lifecycleMainService: ILifecycleMainService, @IConfigurationService configurationService: IConfigurationService, @@ -62,7 +60,7 @@ export class LinuxUpdateService extends AbstractUpdateService { }); } - protected async doDownloadUpdate(state: AvailableForDownload): Promise { + protected override 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 (this.productService.downloadUrl && this.productService.downloadUrl.length > 0) { diff --git a/src/vs/platform/update/electron-main/updateService.snap.ts b/src/vs/platform/update/electron-main/updateService.snap.ts index e0d6010d..d14018a8 100644 --- a/src/vs/platform/update/electron-main/updateService.snap.ts +++ b/src/vs/platform/update/electron-main/updateService.snap.ts @@ -15,7 +15,7 @@ import { spawn } from 'child_process'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { UpdateNotAvailableClassification } from 'vs/platform/update/electron-main/abstractUpdateService'; -abstract class AbstractUpdateService2 implements IUpdateService { +abstract class AbstractUpdateService implements IUpdateService { declare readonly _serviceBrand: undefined; @@ -52,21 +52,21 @@ abstract class AbstractUpdateService2 implements IUpdateService { private scheduleCheckForUpdates(delay = 60 * 60 * 1000): Promise { return timeout(delay) - .then(() => this.checkForUpdates(null)) + .then(() => this.checkForUpdates(false)) .then(() => { // Check again after 1 hour return this.scheduleCheckForUpdates(60 * 60 * 1000); }); } - async checkForUpdates(context: any): Promise { + async checkForUpdates(explicit: boolean): Promise { this.logService.trace('update#checkForUpdates, state = ', this.state.type); if (this.state.type !== StateType.Idle) { return; } - this.doCheckForUpdates(context); + this.doCheckForUpdates(explicit); } async downloadUpdate(): Promise { @@ -132,9 +132,7 @@ abstract class AbstractUpdateService2 implements IUpdateService { protected abstract doCheckForUpdates(context: any): void; } -export class SnapUpdateService extends AbstractUpdateService2 { - - declare readonly _serviceBrand: undefined; +export class SnapUpdateService extends AbstractUpdateService { constructor( private snap: string, @@ -150,7 +148,7 @@ export class SnapUpdateService extends AbstractUpdateService2 { const onChange = Event.fromNodeEventEmitter(watcher, 'change', (_, fileName: string) => fileName); const onCurrentChange = Event.filter(onChange, n => n === 'current'); const onDebouncedCurrentChange = Event.debounce(onCurrentChange, (_, e) => e, 2000); - const listener = onDebouncedCurrentChange(this.checkForUpdates, this); + const listener = onDebouncedCurrentChange(() => this.checkForUpdates(false)); lifecycleMainService.onWillShutdown(() => { listener.dispose(); @@ -158,24 +156,24 @@ export class SnapUpdateService extends AbstractUpdateService2 { }); } - protected doCheckForUpdates(context: any): void { - this.setState(State.CheckingForUpdates(context)); + protected doCheckForUpdates(): void { + this.setState(State.CheckingForUpdates(false)); this.isUpdateAvailable().then(result => { if (result) { this.setState(State.Ready({ version: 'something', productVersion: 'something' })); } else { - this.telemetryService.publicLog2<{ explicit: boolean }, UpdateNotAvailableClassification>('update:notAvailable', { explicit: !!context }); + this.telemetryService.publicLog2<{ explicit: boolean }, UpdateNotAvailableClassification>('update:notAvailable', { explicit: false }); this.setState(State.Idle(UpdateType.Snap)); } }, err => { this.logService.error(err); - this.telemetryService.publicLog2<{ explicit: boolean }, UpdateNotAvailableClassification>('update:notAvailable', { explicit: !!context }); + this.telemetryService.publicLog2<{ explicit: boolean }, UpdateNotAvailableClassification>('update:notAvailable', { explicit: false }); this.setState(State.Idle(UpdateType.Snap, err.message || err)); }); } - protected doQuitAndInstall(): void { + protected override doQuitAndInstall(): void { this.logService.trace('update#quitAndInstall(): running raw#quitAndInstall()'); // Allow 3 seconds for VS Code to close diff --git a/src/vs/platform/update/electron-main/updateService.win32.ts b/src/vs/platform/update/electron-main/updateService.win32.ts index 62d82342..eab8c3f1 100644 --- a/src/vs/platform/update/electron-main/updateService.win32.ts +++ b/src/vs/platform/update/electron-main/updateService.win32.ts @@ -49,8 +49,6 @@ function getUpdateType(): UpdateType { export class Win32UpdateService extends AbstractUpdateService { - declare readonly _serviceBrand: undefined; - private availableUpdate: IAvailableUpdate | undefined; @memoize @@ -73,7 +71,7 @@ export class Win32UpdateService extends AbstractUpdateService { super(lifecycleMainService, configurationService, environmentMainService, requestService, logService, productService); } - initialize(): void { + override initialize(): void { super.initialize(); if (getUpdateType() === UpdateType.Setup) { @@ -177,7 +175,7 @@ export class Win32UpdateService extends AbstractUpdateService { }); } - protected async doDownloadUpdate(state: AvailableForDownload): Promise { + protected override async doDownloadUpdate(state: AvailableForDownload): Promise { if (state.update.url) { this.nativeHostMainService.openExternal(undefined, state.update.url); } @@ -206,7 +204,7 @@ export class Win32UpdateService extends AbstractUpdateService { await Promise.all(promises); } - protected async doApplyUpdate(): Promise { + protected override async doApplyUpdate(): Promise { if (this.state.type !== StateType.Downloaded && this.state.type !== StateType.Downloading) { return Promise.resolve(undefined); } @@ -242,7 +240,7 @@ export class Win32UpdateService extends AbstractUpdateService { .then(() => this.setState(State.Ready(update))); } - protected doQuitAndInstall(): void { + protected override doQuitAndInstall(): void { if (this.state.type !== StateType.Ready || !this.availableUpdate) { return; } @@ -259,7 +257,7 @@ export class Win32UpdateService extends AbstractUpdateService { } } - protected getUpdateType(): UpdateType { + protected override getUpdateType(): UpdateType { return getUpdateType(); } } diff --git a/src/vs/platform/userDataSync/common/extensionsSync.ts b/src/vs/platform/userDataSync/common/extensionsSync.ts index 3f96579b..f4a529b1 100644 --- a/src/vs/platform/userDataSync/common/extensionsSync.ts +++ b/src/vs/platform/userDataSync/common/extensionsSync.ts @@ -74,12 +74,12 @@ async function parseAndMigrateExtensions(syncData: ISyncData, extensionManagemen return extensions; } -function getExtensionStorageState(publisher: string, name: string, storageService: IStorageService): IStringDictionary { +export function getExtensionStorageState(publisher: string, name: string, storageService: IStorageService): IStringDictionary { const extensionStorageValue = storageService.get(getExtensionId(publisher, name) /* use the same id used in extension host */, StorageScope.GLOBAL) || '{}'; return JSON.parse(extensionStorageValue); } -function storeExtensionStorageState(publisher: string, name: string, extensionState: IStringDictionary, storageService: IStorageService): void { +export function storeExtensionStorageState(publisher: string, name: string, extensionState: IStringDictionary, storageService: IStorageService): void { storageService.store(getExtensionId(publisher, name) /* use the same id used in extension host */, JSON.stringify(extensionState), StorageScope.GLOBAL, StorageTarget.MACHINE); } @@ -95,7 +95,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse /* Version 5: Introduce extension state */ protected readonly version: number = 5; - protected isEnabled(): boolean { return super.isEnabled() && this.extensionGalleryService.isEnabled(); } + protected override isEnabled(): boolean { return super.isEnabled() && this.extensionGalleryService.isEnabled(); } private readonly previewResource: URI = this.extUri.joinPath(this.syncPreviewFolder, 'extensions.json'); private readonly localResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }); private readonly remoteResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }); @@ -292,7 +292,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse return [{ resource: this.extUri.joinPath(uri, 'extensions.json'), comparableResource: ExtensionsSynchroniser.EXTENSIONS_DATA_URI }]; } - async resolveContent(uri: URI): Promise { + override async resolveContent(uri: URI): Promise { if (this.extUri.isEqual(uri, ExtensionsSynchroniser.EXTENSIONS_DATA_URI)) { const installedExtensions = await this.extensionManagementService.getInstalled(); const ignoredExtensions = this.ignoredExtensionsManagementService.getIgnoredExtensions(installedExtensions); @@ -494,13 +494,17 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse } -export class ExtensionsInitializer extends AbstractInitializer { +export interface IExtensionsInitializerPreviewResult { + readonly installedExtensions: ILocalExtension[]; + readonly disabledExtensions: IExtensionIdentifier[]; + readonly newExtensions: IExtensionIdentifier[]; + readonly remoteExtensions: ISyncExtension[]; +} + +export abstract class AbstractExtensionsInitializer extends AbstractInitializer { constructor( - @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, - @IExtensionGalleryService private readonly galleryService: IExtensionGalleryService, - @IGlobalExtensionEnablementService private readonly extensionEnablementService: IGlobalExtensionEnablementService, - @IStorageService private readonly storageService: IStorageService, + @IExtensionManagementService protected readonly extensionManagementService: IExtensionManagementService, @IIgnoredExtensionsManagementService private readonly ignoredExtensionsManagementService: IIgnoredExtensionsManagementService, @IFileService fileService: IFileService, @IEnvironmentService environmentService: IEnvironmentService, @@ -509,90 +513,34 @@ export class ExtensionsInitializer extends AbstractInitializer { super(SyncResource.Extensions, environmentService, logService, fileService); } - async doInitialize(remoteUserData: IRemoteUserData): Promise { - const remoteExtensions: ISyncExtension[] | null = remoteUserData.syncData ? await parseAndMigrateExtensions(remoteUserData.syncData, this.extensionManagementService) : null; - if (!remoteExtensions) { - this.logService.info('Skipping initializing extensions because remote extensions does not exist.'); - return; - } - - await this.initializeRemoteExtensions(remoteExtensions); + protected async parseExtensions(remoteUserData: IRemoteUserData): Promise { + return remoteUserData.syncData ? await parseAndMigrateExtensions(remoteUserData.syncData, this.extensionManagementService) : null; } - protected async initializeRemoteExtensions(remoteExtensions: ISyncExtension[]): Promise { - const newlyEnabledExtensions: ILocalExtension[] = []; - const installedExtensions = await this.extensionManagementService.getInstalled(); - const newExtensionsToSync = new Map(); - const installedExtensionsToSync: { syncExtension: ISyncExtension, installedExtension: ILocalExtension }[] = []; - const toInstall: { names: string[], uuids: string[] } = { names: [], uuids: [] }; - const toDisable: IExtensionIdentifier[] = []; + protected generatePreview(remoteExtensions: ISyncExtension[], localExtensions: ILocalExtension[]): IExtensionsInitializerPreviewResult { + const installedExtensions: ILocalExtension[] = []; + const newExtensions: IExtensionIdentifier[] = []; + const disabledExtensions: IExtensionIdentifier[] = []; for (const extension of remoteExtensions) { if (this.ignoredExtensionsManagementService.hasToNeverSyncExtension(extension.identifier.id)) { // Skip extension ignored to sync continue; } - const installedExtension = installedExtensions.find(i => areSameExtensions(i.identifier, extension.identifier)); + const installedExtension = localExtensions.find(i => areSameExtensions(i.identifier, extension.identifier)); if (installedExtension) { - installedExtensionsToSync.push({ syncExtension: extension, installedExtension }); + installedExtensions.push(installedExtension); if (extension.disabled) { - toDisable.push(extension.identifier); + disabledExtensions.push(extension.identifier); } - } else { - if (extension.installed) { - newExtensionsToSync.set(extension.identifier.id.toLowerCase(), extension); - if (extension.identifier.uuid) { - toInstall.uuids.push(extension.identifier.uuid); - } else { - toInstall.names.push(extension.identifier.id); - } - if (extension.disabled) { - toDisable.push(extension.identifier); - } + } else if (extension.installed) { + newExtensions.push(extension.identifier); + if (extension.disabled) { + disabledExtensions.push(extension.identifier); } } } - - // 1. Initialise already installed extensions state - for (const { syncExtension, installedExtension } of installedExtensionsToSync) { - if (syncExtension.state) { - const extensionState = getExtensionStorageState(installedExtension.manifest.publisher, installedExtension.manifest.name, this.storageService); - Object.keys(syncExtension.state).forEach(key => extensionState[key] = syncExtension.state![key]); - storeExtensionStorageState(installedExtension.manifest.publisher, installedExtension.manifest.name, extensionState, this.storageService); - } - } - - // 2. Initialise extensions enablement - if (toDisable.length) { - for (const identifier of toDisable) { - this.logService.trace(`Disabling extension...`, identifier.id); - await this.extensionEnablementService.disableExtension(identifier); - this.logService.info(`Disabling extension`, identifier.id); - } - } - - // 3. Install extensions - if (toInstall.names.length || toInstall.uuids.length) { - const galleryExtensions = (await this.galleryService.query({ ids: toInstall.uuids, names: toInstall.names, pageSize: toInstall.uuids.length + toInstall.names.length }, CancellationToken.None)).firstPage; - for (const galleryExtension of galleryExtensions) { - try { - const extensionToSync = newExtensionsToSync.get(galleryExtension.identifier.id.toLowerCase())!; - if (extensionToSync.state) { - storeExtensionStorageState(galleryExtension.publisher, galleryExtension.name, extensionToSync.state, this.storageService); - } - this.logService.trace(`Installing extension...`, galleryExtension.identifier.id); - const local = await this.extensionManagementService.installFromGallery(galleryExtension, { isMachineScoped: false } /* pass options to prevent install and sync dialog in web */); - if (!toDisable.some(identifier => areSameExtensions(identifier, galleryExtension.identifier))) { - newlyEnabledExtensions.push(local); - } - this.logService.info(`Installed extension.`, galleryExtension.identifier.id); - } catch (error) { - this.logService.error(error); - } - } - } - - return newlyEnabledExtensions; + return { installedExtensions, newExtensions, disabledExtensions, remoteExtensions }; } } diff --git a/src/vs/platform/userDataSync/common/globalStateSync.ts b/src/vs/platform/userDataSync/common/globalStateSync.ts index 2e58f17a..545a690e 100644 --- a/src/vs/platform/userDataSync/common/globalStateSync.ts +++ b/src/vs/platform/userDataSync/common/globalStateSync.ts @@ -81,7 +81,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, @IUserDataSyncBackupStoreService userDataSyncBackupStoreService: IUserDataSyncBackupStoreService, @IUserDataSyncLogService logService: IUserDataSyncLogService, - @IEnvironmentService readonly environmentService: IEnvironmentService, + @IEnvironmentService environmentService: IEnvironmentService, @IUserDataSyncResourceEnablementService userDataSyncResourceEnablementService: IUserDataSyncResourceEnablementService, @ITelemetryService telemetryService: ITelemetryService, @IConfigurationService configurationService: IConfigurationService, @@ -230,7 +230,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs return [{ resource: this.extUri.joinPath(uri, 'globalState.json'), comparableResource: GlobalStateSynchroniser.GLOBAL_STATE_DATA_URI }]; } - async resolveContent(uri: URI): Promise { + override async resolveContent(uri: URI): Promise { if (this.extUri.isEqual(uri, GlobalStateSynchroniser.GLOBAL_STATE_DATA_URI)) { const localGlobalState = await this.getLocalGlobalState(); return formatAndStringify(localGlobalState); diff --git a/src/vs/platform/userDataSync/common/keybindingsSync.ts b/src/vs/platform/userDataSync/common/keybindingsSync.ts index 7f936367..38371cca 100644 --- a/src/vs/platform/userDataSync/common/keybindingsSync.ts +++ b/src/vs/platform/userDataSync/common/keybindingsSync.ts @@ -258,7 +258,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem return [{ resource: this.extUri.joinPath(uri, 'keybindings.json'), comparableResource }]; } - async resolveContent(uri: URI): Promise { + override async resolveContent(uri: URI): Promise { if (this.extUri.isEqual(this.remoteResource, uri) || this.extUri.isEqual(this.localResource, uri) || this.extUri.isEqual(this.acceptedResource, uri)) { return this.resolvePreviewContent(uri); } diff --git a/src/vs/platform/userDataSync/common/settingsSync.ts b/src/vs/platform/userDataSync/common/settingsSync.ts index d4e31ee0..5a5266e3 100644 --- a/src/vs/platform/userDataSync/common/settingsSync.ts +++ b/src/vs/platform/userDataSync/common/settingsSync.ts @@ -247,7 +247,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement return [{ resource: this.extUri.joinPath(uri, 'settings.json'), comparableResource }]; } - async resolveContent(uri: URI): Promise { + override async resolveContent(uri: URI): Promise { if (this.extUri.isEqual(this.remoteResource, uri) || this.extUri.isEqual(this.localResource, uri) || this.extUri.isEqual(this.acceptedResource, uri)) { return this.resolvePreviewContent(uri); } @@ -271,7 +271,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement return null; } - protected async resolvePreviewContent(resource: URI): Promise { + protected override async resolvePreviewContent(resource: URI): Promise { let content = await super.resolvePreviewContent(resource); if (content) { const formatUtils = await this.getFormattingOptions(); diff --git a/src/vs/platform/userDataSync/common/snippetsSync.ts b/src/vs/platform/userDataSync/common/snippetsSync.ts index 3068afb6..adf6e9d1 100644 --- a/src/vs/platform/userDataSync/common/snippetsSync.ts +++ b/src/vs/platform/userDataSync/common/snippetsSync.ts @@ -355,7 +355,7 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD return []; } - async resolveContent(uri: URI): Promise { + override async resolveContent(uri: URI): Promise { if (this.extUri.isEqualOrParent(uri, this.syncPreviewFolder.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' })) || this.extUri.isEqualOrParent(uri, this.syncPreviewFolder.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' })) || this.extUri.isEqualOrParent(uri, this.syncPreviewFolder.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }))) { diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index f15de01d..5a133810 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -250,7 +250,7 @@ export class UserDataSyncError extends Error { } export class UserDataSyncStoreError extends UserDataSyncError { - constructor(message: string, readonly url: string, code: UserDataSyncErrorCode, readonly operationId: string | undefined) { + constructor(message: string, readonly url: string, code: UserDataSyncErrorCode, operationId: string | undefined) { super(message, code, undefined, operationId); } } diff --git a/src/vs/platform/userDataSync/common/userDataSyncService.ts b/src/vs/platform/userDataSync/common/userDataSyncService.ts index b059b81e..c8f72940 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncService.ts @@ -727,7 +727,7 @@ class ManualSyncTask extends Disposable implements IManualSyncTask { this.synchronizingResources = []; } - dispose(): void { + override dispose(): void { this.reset(); this.isDisposed = true; } diff --git a/src/vs/platform/userDataSync/common/userDataSyncServiceIpc.ts b/src/vs/platform/userDataSync/common/userDataSyncServiceIpc.ts index ee55c0be..92304d39 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncServiceIpc.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncServiceIpc.ts @@ -356,7 +356,7 @@ class ManualSyncTaskChannelClient extends Disposable implements IManualSyncTask private _disposed = false; isDiposed() { return this._disposed; } - dispose(): void { + override dispose(): void { this._disposed = true; this.channel.call('dispose'); } diff --git a/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts index c12dedba..803296e4 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts @@ -10,7 +10,8 @@ import { joinPath, relativePath } from 'vs/base/common/resources'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IHeaders, IRequestOptions, IRequestContext } from 'vs/base/parts/request/common/request'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IProductService, ConfigurationSyncStore } from 'vs/platform/product/common/productService'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { ConfigurationSyncStore } from 'vs/base/common/product'; import { getServiceMachineId } from 'vs/platform/serviceMachineId/common/serviceMachineId'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IFileService } from 'vs/platform/files/common/files'; diff --git a/src/vs/platform/userDataSync/test/common/extensionsMerge.test.ts b/src/vs/platform/userDataSync/test/common/extensionsMerge.test.ts index a388892b..ad643c1b 100644 --- a/src/vs/platform/userDataSync/test/common/extensionsMerge.test.ts +++ b/src/vs/platform/userDataSync/test/common/extensionsMerge.test.ts @@ -18,10 +18,10 @@ suite('ExtensionsMerge', () => { const actual = merge(localExtensions, null, null, [], []); - assert.deepEqual(actual.added, []); - assert.deepEqual(actual.removed, []); - assert.deepEqual(actual.updated, []); - assert.deepEqual(actual.remote, localExtensions); + assert.deepStrictEqual(actual.added, []); + assert.deepStrictEqual(actual.removed, []); + assert.deepStrictEqual(actual.updated, []); + assert.deepStrictEqual(actual.remote, localExtensions); }); test('merge returns local extension if remote does not exist with ignored extensions', () => { @@ -37,10 +37,10 @@ suite('ExtensionsMerge', () => { const actual = merge(localExtensions, null, null, [], ['a']); - assert.deepEqual(actual.added, []); - assert.deepEqual(actual.removed, []); - assert.deepEqual(actual.updated, []); - assert.deepEqual(actual.remote, expected); + assert.deepStrictEqual(actual.added, []); + assert.deepStrictEqual(actual.removed, []); + assert.deepStrictEqual(actual.updated, []); + assert.deepStrictEqual(actual.remote, expected); }); test('merge returns local extension if remote does not exist with ignored extensions (ignore case)', () => { @@ -56,10 +56,10 @@ suite('ExtensionsMerge', () => { const actual = merge(localExtensions, null, null, [], ['A']); - assert.deepEqual(actual.added, []); - assert.deepEqual(actual.removed, []); - assert.deepEqual(actual.updated, []); - assert.deepEqual(actual.remote, expected); + assert.deepStrictEqual(actual.added, []); + assert.deepStrictEqual(actual.removed, []); + assert.deepStrictEqual(actual.updated, []); + assert.deepStrictEqual(actual.remote, expected); }); test('merge returns local extension if remote does not exist with skipped extensions', () => { @@ -79,10 +79,10 @@ suite('ExtensionsMerge', () => { const actual = merge(localExtensions, null, null, skippedExtension, []); - assert.deepEqual(actual.added, []); - assert.deepEqual(actual.removed, []); - assert.deepEqual(actual.updated, []); - assert.deepEqual(actual.remote, expected); + assert.deepStrictEqual(actual.added, []); + assert.deepStrictEqual(actual.removed, []); + assert.deepStrictEqual(actual.updated, []); + assert.deepStrictEqual(actual.remote, expected); }); test('merge returns local extension if remote does not exist with skipped and ignored extensions', () => { @@ -101,10 +101,10 @@ suite('ExtensionsMerge', () => { const actual = merge(localExtensions, null, null, skippedExtension, ['a']); - assert.deepEqual(actual.added, []); - assert.deepEqual(actual.removed, []); - assert.deepEqual(actual.updated, []); - assert.deepEqual(actual.remote, expected); + assert.deepStrictEqual(actual.added, []); + assert.deepStrictEqual(actual.removed, []); + assert.deepStrictEqual(actual.updated, []); + assert.deepStrictEqual(actual.remote, expected); }); test('merge local and remote extensions when there is no base', () => { @@ -125,10 +125,10 @@ suite('ExtensionsMerge', () => { const actual = merge(localExtensions, remoteExtensions, null, [], []); - assert.deepEqual(actual.added, [{ identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' }, { identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' }]); - assert.deepEqual(actual.removed, []); - assert.deepEqual(actual.updated, []); - assert.deepEqual(actual.remote, expected); + assert.deepStrictEqual(actual.added, [{ identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' }, { identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' }]); + assert.deepStrictEqual(actual.removed, []); + assert.deepStrictEqual(actual.updated, []); + assert.deepStrictEqual(actual.remote, expected); }); test('merge local and remote extensions when there is no base and with ignored extensions', () => { @@ -148,10 +148,10 @@ suite('ExtensionsMerge', () => { const actual = merge(localExtensions, remoteExtensions, null, [], ['a']); - assert.deepEqual(actual.added, [{ identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' }, { identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' }]); - assert.deepEqual(actual.removed, []); - assert.deepEqual(actual.updated, []); - assert.deepEqual(actual.remote, expected); + assert.deepStrictEqual(actual.added, [{ identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' }, { identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' }]); + assert.deepStrictEqual(actual.removed, []); + assert.deepStrictEqual(actual.updated, []); + assert.deepStrictEqual(actual.remote, expected); }); test('merge local and remote extensions when remote is moved forwarded', () => { @@ -170,10 +170,10 @@ suite('ExtensionsMerge', () => { const actual = merge(localExtensions, remoteExtensions, baseExtensions, [], []); - assert.deepEqual(actual.added, [{ identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' }, { identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' }]); - assert.deepEqual(actual.removed, [{ id: 'a', uuid: 'a' }, { id: 'd', uuid: 'd' }]); - assert.deepEqual(actual.updated, []); - assert.equal(actual.remote, null); + assert.deepStrictEqual(actual.added, [{ identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' }, { identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' }]); + assert.deepStrictEqual(actual.removed, [{ id: 'a', uuid: 'a' }, { id: 'd', uuid: 'd' }]); + assert.deepStrictEqual(actual.updated, []); + assert.strictEqual(actual.remote, null); }); test('merge local and remote extensions when remote is moved forwarded with disabled extension', () => { @@ -193,10 +193,10 @@ suite('ExtensionsMerge', () => { const actual = merge(localExtensions, remoteExtensions, baseExtensions, [], []); - assert.deepEqual(actual.added, [{ identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' }, { identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' }]); - assert.deepEqual(actual.removed, [{ id: 'a', uuid: 'a' }]); - assert.deepEqual(actual.updated, [{ identifier: { id: 'd', uuid: 'd' }, disabled: true, installed: true, version: '1.0.0' }]); - assert.equal(actual.remote, null); + assert.deepStrictEqual(actual.added, [{ identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' }, { identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' }]); + assert.deepStrictEqual(actual.removed, [{ id: 'a', uuid: 'a' }]); + assert.deepStrictEqual(actual.updated, [{ identifier: { id: 'd', uuid: 'd' }, disabled: true, installed: true, version: '1.0.0' }]); + assert.strictEqual(actual.remote, null); }); test('merge local and remote extensions when remote moved forwarded with ignored extensions', () => { @@ -215,10 +215,10 @@ suite('ExtensionsMerge', () => { const actual = merge(localExtensions, remoteExtensions, baseExtensions, [], ['a']); - assert.deepEqual(actual.added, [{ identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' }, { identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' }]); - assert.deepEqual(actual.removed, [{ id: 'd', uuid: 'd' }]); - assert.deepEqual(actual.updated, []); - assert.equal(actual.remote, null); + assert.deepStrictEqual(actual.added, [{ identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' }, { identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' }]); + assert.deepStrictEqual(actual.removed, [{ id: 'd', uuid: 'd' }]); + assert.deepStrictEqual(actual.updated, []); + assert.strictEqual(actual.remote, null); }); test('merge local and remote extensions when remote is moved forwarded with skipped extensions', () => { @@ -239,10 +239,10 @@ suite('ExtensionsMerge', () => { const actual = merge(localExtensions, remoteExtensions, baseExtensions, skippedExtensions, []); - assert.deepEqual(actual.added, [{ identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' }, { identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' }]); - assert.deepEqual(actual.removed, [{ id: 'd', uuid: 'd' }]); - assert.deepEqual(actual.updated, []); - assert.equal(actual.remote, null); + assert.deepStrictEqual(actual.added, [{ identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' }, { identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' }]); + assert.deepStrictEqual(actual.removed, [{ id: 'd', uuid: 'd' }]); + assert.deepStrictEqual(actual.updated, []); + assert.strictEqual(actual.remote, null); }); test('merge local and remote extensions when remote is moved forwarded with skipped and ignored extensions', () => { @@ -263,10 +263,10 @@ suite('ExtensionsMerge', () => { const actual = merge(localExtensions, remoteExtensions, baseExtensions, skippedExtensions, ['b']); - assert.deepEqual(actual.added, [{ identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' }]); - assert.deepEqual(actual.removed, [{ id: 'd', uuid: 'd' }]); - assert.deepEqual(actual.updated, []); - assert.equal(actual.remote, null); + assert.deepStrictEqual(actual.added, [{ identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' }]); + assert.deepStrictEqual(actual.removed, [{ id: 'd', uuid: 'd' }]); + assert.deepStrictEqual(actual.updated, []); + assert.strictEqual(actual.remote, null); }); test('merge local and remote extensions when local is moved forwarded', () => { @@ -285,10 +285,10 @@ suite('ExtensionsMerge', () => { const actual = merge(localExtensions, remoteExtensions, baseExtensions, [], []); - assert.deepEqual(actual.added, []); - assert.deepEqual(actual.removed, []); - assert.deepEqual(actual.updated, []); - assert.deepEqual(actual.remote, localExtensions); + assert.deepStrictEqual(actual.added, []); + assert.deepStrictEqual(actual.removed, []); + assert.deepStrictEqual(actual.updated, []); + assert.deepStrictEqual(actual.remote, localExtensions); }); test('merge local and remote extensions when local is moved forwarded with disabled extensions', () => { @@ -308,10 +308,10 @@ suite('ExtensionsMerge', () => { const actual = merge(localExtensions, remoteExtensions, baseExtensions, [], []); - assert.deepEqual(actual.added, []); - assert.deepEqual(actual.removed, []); - assert.deepEqual(actual.updated, []); - assert.deepEqual(actual.remote, localExtensions); + assert.deepStrictEqual(actual.added, []); + assert.deepStrictEqual(actual.removed, []); + assert.deepStrictEqual(actual.updated, []); + assert.deepStrictEqual(actual.remote, localExtensions); }); test('merge local and remote extensions when local is moved forwarded with ignored settings', () => { @@ -330,10 +330,10 @@ suite('ExtensionsMerge', () => { const actual = merge(localExtensions, remoteExtensions, baseExtensions, [], ['b']); - assert.deepEqual(actual.added, []); - assert.deepEqual(actual.removed, []); - assert.deepEqual(actual.updated, []); - assert.deepEqual(actual.remote, [ + assert.deepStrictEqual(actual.added, []); + assert.deepStrictEqual(actual.removed, []); + assert.deepStrictEqual(actual.updated, []); + assert.deepStrictEqual(actual.remote, [ { identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' }, ]); }); @@ -362,10 +362,10 @@ suite('ExtensionsMerge', () => { const actual = merge(localExtensions, remoteExtensions, baseExtensions, skippedExtensions, []); - assert.deepEqual(actual.added, []); - assert.deepEqual(actual.removed, []); - assert.deepEqual(actual.updated, []); - assert.deepEqual(actual.remote, expected); + assert.deepStrictEqual(actual.added, []); + assert.deepStrictEqual(actual.removed, []); + assert.deepStrictEqual(actual.updated, []); + assert.deepStrictEqual(actual.remote, expected); }); test('merge local and remote extensions when local is moved forwarded with skipped and ignored extensions', () => { @@ -391,10 +391,10 @@ suite('ExtensionsMerge', () => { const actual = merge(localExtensions, remoteExtensions, baseExtensions, skippedExtensions, ['c']); - assert.deepEqual(actual.added, []); - assert.deepEqual(actual.removed, []); - assert.deepEqual(actual.updated, []); - assert.deepEqual(actual.remote, expected); + assert.deepStrictEqual(actual.added, []); + assert.deepStrictEqual(actual.removed, []); + assert.deepStrictEqual(actual.updated, []); + assert.deepStrictEqual(actual.remote, expected); }); test('merge local and remote extensions when both moved forwarded', () => { @@ -420,10 +420,10 @@ suite('ExtensionsMerge', () => { const actual = merge(localExtensions, remoteExtensions, baseExtensions, [], []); - assert.deepEqual(actual.added, [{ identifier: { id: 'e', uuid: 'e' }, installed: true, version: '1.0.0' }]); - assert.deepEqual(actual.removed, [{ id: 'a', uuid: 'a' }]); - assert.deepEqual(actual.updated, []); - assert.deepEqual(actual.remote, expected); + assert.deepStrictEqual(actual.added, [{ identifier: { id: 'e', uuid: 'e' }, installed: true, version: '1.0.0' }]); + assert.deepStrictEqual(actual.removed, [{ id: 'a', uuid: 'a' }]); + assert.deepStrictEqual(actual.updated, []); + assert.deepStrictEqual(actual.remote, expected); }); test('merge local and remote extensions when both moved forwarded with ignored extensions', () => { @@ -449,10 +449,10 @@ suite('ExtensionsMerge', () => { const actual = merge(localExtensions, remoteExtensions, baseExtensions, [], ['a', 'e']); - assert.deepEqual(actual.added, []); - assert.deepEqual(actual.removed, []); - assert.deepEqual(actual.updated, []); - assert.deepEqual(actual.remote, expected); + assert.deepStrictEqual(actual.added, []); + assert.deepStrictEqual(actual.removed, []); + assert.deepStrictEqual(actual.updated, []); + assert.deepStrictEqual(actual.remote, expected); }); test('merge local and remote extensions when both moved forwarded with skipped extensions', () => { @@ -480,10 +480,10 @@ suite('ExtensionsMerge', () => { const actual = merge(localExtensions, remoteExtensions, baseExtensions, skippedExtensions, []); - assert.deepEqual(actual.added, [{ identifier: { id: 'e', uuid: 'e' }, installed: true, version: '1.0.0' }]); - assert.deepEqual(actual.removed, []); - assert.deepEqual(actual.updated, []); - assert.deepEqual(actual.remote, expected); + assert.deepStrictEqual(actual.added, [{ identifier: { id: 'e', uuid: 'e' }, installed: true, version: '1.0.0' }]); + assert.deepStrictEqual(actual.removed, []); + assert.deepStrictEqual(actual.updated, []); + assert.deepStrictEqual(actual.remote, expected); }); test('merge local and remote extensions when both moved forwarded with skipped and ignoredextensions', () => { @@ -511,10 +511,10 @@ suite('ExtensionsMerge', () => { const actual = merge(localExtensions, remoteExtensions, baseExtensions, skippedExtensions, ['e']); - assert.deepEqual(actual.added, []); - assert.deepEqual(actual.removed, []); - assert.deepEqual(actual.updated, []); - assert.deepEqual(actual.remote, expected); + assert.deepStrictEqual(actual.added, []); + assert.deepStrictEqual(actual.removed, []); + assert.deepStrictEqual(actual.updated, []); + assert.deepStrictEqual(actual.remote, expected); }); test('merge when remote extension has no uuid and different extension id case', () => { @@ -536,10 +536,10 @@ suite('ExtensionsMerge', () => { const actual = merge(localExtensions, remoteExtensions, null, [], []); - assert.deepEqual(actual.added, [{ identifier: { id: 'd', uuid: 'd' }, installed: true, version: '1.0.0' }]); - assert.deepEqual(actual.removed, []); - assert.deepEqual(actual.updated, []); - assert.deepEqual(actual.remote, expected); + assert.deepStrictEqual(actual.added, [{ identifier: { id: 'd', uuid: 'd' }, installed: true, version: '1.0.0' }]); + assert.deepStrictEqual(actual.removed, []); + assert.deepStrictEqual(actual.updated, []); + assert.deepStrictEqual(actual.remote, expected); }); test('merge when remote extension is not an installed extension', () => { @@ -553,10 +553,10 @@ suite('ExtensionsMerge', () => { const actual = merge(localExtensions, remoteExtensions, null, [], []); - assert.deepEqual(actual.added, []); - assert.deepEqual(actual.removed, []); - assert.deepEqual(actual.updated, []); - assert.deepEqual(actual.remote, null); + assert.deepStrictEqual(actual.added, []); + assert.deepStrictEqual(actual.removed, []); + assert.deepStrictEqual(actual.updated, []); + assert.deepStrictEqual(actual.remote, null); }); test('merge when remote extension is not an installed extension but is an installed extension locally', () => { @@ -569,10 +569,10 @@ suite('ExtensionsMerge', () => { const actual = merge(localExtensions, remoteExtensions, null, [], []); - assert.deepEqual(actual.added, []); - assert.deepEqual(actual.removed, []); - assert.deepEqual(actual.updated, []); - assert.deepEqual(actual.remote, localExtensions); + assert.deepStrictEqual(actual.added, []); + assert.deepStrictEqual(actual.removed, []); + assert.deepStrictEqual(actual.updated, []); + assert.deepStrictEqual(actual.remote, localExtensions); }); test('merge when an extension is not an installed extension remotely and does not exist locally', () => { @@ -586,10 +586,10 @@ suite('ExtensionsMerge', () => { const actual = merge(localExtensions, remoteExtensions, remoteExtensions, [], []); - assert.deepEqual(actual.added, []); - assert.deepEqual(actual.removed, []); - assert.deepEqual(actual.updated, []); - assert.deepEqual(actual.remote, null); + assert.deepStrictEqual(actual.added, []); + assert.deepStrictEqual(actual.removed, []); + assert.deepStrictEqual(actual.updated, []); + assert.deepStrictEqual(actual.remote, null); }); test('merge when an extension is an installed extension remotely but not locally and updated locally', () => { @@ -605,10 +605,10 @@ suite('ExtensionsMerge', () => { const actual = merge(localExtensions, remoteExtensions, remoteExtensions, [], []); - assert.deepEqual(actual.added, []); - assert.deepEqual(actual.removed, []); - assert.deepEqual(actual.updated, []); - assert.deepEqual(actual.remote, expected); + assert.deepStrictEqual(actual.added, []); + assert.deepStrictEqual(actual.removed, []); + assert.deepStrictEqual(actual.updated, []); + assert.deepStrictEqual(actual.remote, expected); }); test('merge when an extension is an installed extension remotely but not locally and updated remotely', () => { @@ -621,10 +621,10 @@ suite('ExtensionsMerge', () => { const actual = merge(localExtensions, remoteExtensions, localExtensions, [], []); - assert.deepEqual(actual.added, []); - assert.deepEqual(actual.removed, []); - assert.deepEqual(actual.updated, remoteExtensions); - assert.deepEqual(actual.remote, null); + assert.deepStrictEqual(actual.added, []); + assert.deepStrictEqual(actual.removed, []); + assert.deepStrictEqual(actual.updated, remoteExtensions); + assert.deepStrictEqual(actual.remote, null); }); test('merge not installed extensions', () => { @@ -641,10 +641,10 @@ suite('ExtensionsMerge', () => { const actual = merge(localExtensions, remoteExtensions, null, [], []); - assert.deepEqual(actual.added, []); - assert.deepEqual(actual.removed, []); - assert.deepEqual(actual.updated, []); - assert.deepEqual(actual.remote, expected); + assert.deepStrictEqual(actual.added, []); + assert.deepStrictEqual(actual.removed, []); + assert.deepStrictEqual(actual.updated, []); + assert.deepStrictEqual(actual.remote, expected); }); }); diff --git a/src/vs/platform/userDataSync/test/common/globalStateMerge.test.ts b/src/vs/platform/userDataSync/test/common/globalStateMerge.test.ts index 27fb0a23..a6d0f476 100644 --- a/src/vs/platform/userDataSync/test/common/globalStateMerge.test.ts +++ b/src/vs/platform/userDataSync/test/common/globalStateMerge.test.ts @@ -15,10 +15,10 @@ suite('GlobalStateMerge', () => { const actual = merge(local, remote, null, { machine: [], unregistered: [] }, new NullLogService()); - assert.deepEqual(actual.local.added, {}); - assert.deepEqual(actual.local.updated, {}); - assert.deepEqual(actual.local.removed, []); - assert.deepEqual(actual.remote, null); + assert.deepStrictEqual(actual.local.added, {}); + assert.deepStrictEqual(actual.local.updated, {}); + assert.deepStrictEqual(actual.local.removed, []); + assert.deepStrictEqual(actual.remote, null); }); test('merge when local and remote are same with multiple entries and local is not synced yet', async () => { @@ -27,10 +27,10 @@ suite('GlobalStateMerge', () => { const actual = merge(local, remote, null, { machine: [], unregistered: [] }, new NullLogService()); - assert.deepEqual(actual.local.added, {}); - assert.deepEqual(actual.local.updated, {}); - assert.deepEqual(actual.local.removed, []); - assert.deepEqual(actual.remote, null); + assert.deepStrictEqual(actual.local.added, {}); + assert.deepStrictEqual(actual.local.updated, {}); + assert.deepStrictEqual(actual.local.removed, []); + assert.deepStrictEqual(actual.remote, null); }); test('merge when local and remote are same with multiple entries in different order and local is not synced yet', async () => { @@ -39,10 +39,10 @@ suite('GlobalStateMerge', () => { const actual = merge(local, remote, null, { machine: [], unregistered: [] }, new NullLogService()); - assert.deepEqual(actual.local.added, {}); - assert.deepEqual(actual.local.updated, {}); - assert.deepEqual(actual.local.removed, []); - assert.deepEqual(actual.remote, null); + assert.deepStrictEqual(actual.local.added, {}); + assert.deepStrictEqual(actual.local.updated, {}); + assert.deepStrictEqual(actual.local.removed, []); + assert.deepStrictEqual(actual.remote, null); }); test('merge when local and remote are same with different base content', async () => { @@ -52,10 +52,10 @@ suite('GlobalStateMerge', () => { const actual = merge(local, remote, base, { machine: [], unregistered: [] }, new NullLogService()); - assert.deepEqual(actual.local.added, {}); - assert.deepEqual(actual.local.updated, {}); - assert.deepEqual(actual.local.removed, []); - assert.deepEqual(actual.remote, null); + assert.deepStrictEqual(actual.local.added, {}); + assert.deepStrictEqual(actual.local.updated, {}); + assert.deepStrictEqual(actual.local.removed, []); + assert.deepStrictEqual(actual.remote, null); }); test('merge when a new entry is added to remote and local has not synced yet', async () => { @@ -64,10 +64,10 @@ suite('GlobalStateMerge', () => { const actual = merge(local, remote, null, { machine: [], unregistered: [] }, new NullLogService()); - assert.deepEqual(actual.local.added, { 'b': { version: 1, value: 'b' } }); - assert.deepEqual(actual.local.updated, {}); - assert.deepEqual(actual.local.removed, []); - assert.deepEqual(actual.remote, null); + assert.deepStrictEqual(actual.local.added, { 'b': { version: 1, value: 'b' } }); + assert.deepStrictEqual(actual.local.updated, {}); + assert.deepStrictEqual(actual.local.removed, []); + assert.deepStrictEqual(actual.remote, null); }); test('merge when multiple new entries are added to remote and local is not synced yet', async () => { @@ -76,10 +76,10 @@ suite('GlobalStateMerge', () => { const actual = merge(local, remote, null, { machine: [], unregistered: [] }, new NullLogService()); - assert.deepEqual(actual.local.added, { 'b': { version: 1, value: 'b' }, 'a': { version: 1, value: 'a' } }); - assert.deepEqual(actual.local.updated, {}); - assert.deepEqual(actual.local.removed, []); - assert.deepEqual(actual.remote, null); + assert.deepStrictEqual(actual.local.added, { 'b': { version: 1, value: 'b' }, 'a': { version: 1, value: 'a' } }); + assert.deepStrictEqual(actual.local.updated, {}); + assert.deepStrictEqual(actual.local.removed, []); + assert.deepStrictEqual(actual.remote, null); }); test('merge when new entry is added to remote from base and local has not changed', async () => { @@ -88,10 +88,10 @@ suite('GlobalStateMerge', () => { const actual = merge(local, remote, local, { machine: [], unregistered: [] }, new NullLogService()); - assert.deepEqual(actual.local.added, { 'b': { version: 1, value: 'b' } }); - assert.deepEqual(actual.local.updated, {}); - assert.deepEqual(actual.local.removed, []); - assert.deepEqual(actual.remote, null); + assert.deepStrictEqual(actual.local.added, { 'b': { version: 1, value: 'b' } }); + assert.deepStrictEqual(actual.local.updated, {}); + assert.deepStrictEqual(actual.local.removed, []); + assert.deepStrictEqual(actual.remote, null); }); test('merge when an entry is removed from remote from base and local has not changed', async () => { @@ -100,10 +100,10 @@ suite('GlobalStateMerge', () => { const actual = merge(local, remote, local, { machine: [], unregistered: [] }, new NullLogService()); - assert.deepEqual(actual.local.added, {}); - assert.deepEqual(actual.local.updated, {}); - assert.deepEqual(actual.local.removed, ['b']); - assert.deepEqual(actual.remote, null); + assert.deepStrictEqual(actual.local.added, {}); + assert.deepStrictEqual(actual.local.updated, {}); + assert.deepStrictEqual(actual.local.removed, ['b']); + assert.deepStrictEqual(actual.remote, null); }); test('merge when all entries are removed from base and local has not changed', async () => { @@ -112,10 +112,10 @@ suite('GlobalStateMerge', () => { const actual = merge(local, remote, local, { machine: [], unregistered: [] }, new NullLogService()); - assert.deepEqual(actual.local.added, {}); - assert.deepEqual(actual.local.updated, {}); - assert.deepEqual(actual.local.removed, ['b', 'a']); - assert.deepEqual(actual.remote, null); + assert.deepStrictEqual(actual.local.added, {}); + assert.deepStrictEqual(actual.local.updated, {}); + assert.deepStrictEqual(actual.local.removed, ['b', 'a']); + assert.deepStrictEqual(actual.remote, null); }); test('merge when an entry is updated in remote from base and local has not changed', async () => { @@ -124,10 +124,10 @@ suite('GlobalStateMerge', () => { const actual = merge(local, remote, local, { machine: [], unregistered: [] }, new NullLogService()); - assert.deepEqual(actual.local.added, {}); - assert.deepEqual(actual.local.updated, { 'a': { version: 1, value: 'b' } }); - assert.deepEqual(actual.local.removed, []); - assert.deepEqual(actual.remote, null); + assert.deepStrictEqual(actual.local.added, {}); + assert.deepStrictEqual(actual.local.updated, { 'a': { version: 1, value: 'b' } }); + assert.deepStrictEqual(actual.local.removed, []); + assert.deepStrictEqual(actual.remote, null); }); test('merge when remote has moved forwarded with multiple changes and local stays with base', async () => { @@ -136,10 +136,10 @@ suite('GlobalStateMerge', () => { const actual = merge(local, remote, local, { machine: [], unregistered: [] }, new NullLogService()); - assert.deepEqual(actual.local.added, { 'c': { version: 1, value: 'c' } }); - assert.deepEqual(actual.local.updated, { 'a': { version: 1, value: 'd' } }); - assert.deepEqual(actual.local.removed, ['b']); - assert.deepEqual(actual.remote, null); + assert.deepStrictEqual(actual.local.added, { 'c': { version: 1, value: 'c' } }); + assert.deepStrictEqual(actual.local.updated, { 'a': { version: 1, value: 'd' } }); + assert.deepStrictEqual(actual.local.removed, ['b']); + assert.deepStrictEqual(actual.remote, null); }); test('merge when new entries are added to local and local is not synced yet', async () => { @@ -148,10 +148,10 @@ suite('GlobalStateMerge', () => { const actual = merge(local, remote, null, { machine: [], unregistered: [] }, new NullLogService()); - assert.deepEqual(actual.local.added, {}); - assert.deepEqual(actual.local.updated, {}); - assert.deepEqual(actual.local.removed, []); - assert.deepEqual(actual.remote, local); + assert.deepStrictEqual(actual.local.added, {}); + assert.deepStrictEqual(actual.local.updated, {}); + assert.deepStrictEqual(actual.local.removed, []); + assert.deepStrictEqual(actual.remote, local); }); test('merge when multiple new entries are added to local from base and remote is not changed', async () => { @@ -160,10 +160,10 @@ suite('GlobalStateMerge', () => { const actual = merge(local, remote, remote, { machine: [], unregistered: [] }, new NullLogService()); - assert.deepEqual(actual.local.added, {}); - assert.deepEqual(actual.local.updated, {}); - assert.deepEqual(actual.local.removed, []); - assert.deepEqual(actual.remote, local); + assert.deepStrictEqual(actual.local.added, {}); + assert.deepStrictEqual(actual.local.updated, {}); + assert.deepStrictEqual(actual.local.removed, []); + assert.deepStrictEqual(actual.remote, local); }); test('merge when an entry is removed from local from base and remote has not changed', async () => { @@ -172,10 +172,10 @@ suite('GlobalStateMerge', () => { const actual = merge(local, remote, remote, { machine: [], unregistered: [] }, new NullLogService()); - assert.deepEqual(actual.local.added, {}); - assert.deepEqual(actual.local.updated, {}); - assert.deepEqual(actual.local.removed, []); - assert.deepEqual(actual.remote, local); + assert.deepStrictEqual(actual.local.added, {}); + assert.deepStrictEqual(actual.local.updated, {}); + assert.deepStrictEqual(actual.local.removed, []); + assert.deepStrictEqual(actual.remote, local); }); test('merge when an entry is updated in local from base and remote has not changed', async () => { @@ -184,10 +184,10 @@ suite('GlobalStateMerge', () => { const actual = merge(local, remote, remote, { machine: [], unregistered: [] }, new NullLogService()); - assert.deepEqual(actual.local.added, {}); - assert.deepEqual(actual.local.updated, {}); - assert.deepEqual(actual.local.removed, []); - assert.deepEqual(actual.remote, local); + assert.deepStrictEqual(actual.local.added, {}); + assert.deepStrictEqual(actual.local.updated, {}); + assert.deepStrictEqual(actual.local.removed, []); + assert.deepStrictEqual(actual.remote, local); }); test('merge when local has moved forwarded with multiple changes and remote stays with base', async () => { @@ -196,10 +196,10 @@ suite('GlobalStateMerge', () => { const actual = merge(local, remote, remote, { machine: [], unregistered: [] }, new NullLogService()); - assert.deepEqual(actual.local.added, {}); - assert.deepEqual(actual.local.updated, {}); - assert.deepEqual(actual.local.removed, []); - assert.deepEqual(actual.remote, local); + assert.deepStrictEqual(actual.local.added, {}); + assert.deepStrictEqual(actual.local.updated, {}); + assert.deepStrictEqual(actual.local.removed, []); + assert.deepStrictEqual(actual.remote, local); }); test('merge when local and remote with one entry but different value and local is not synced yet', async () => { @@ -208,10 +208,10 @@ suite('GlobalStateMerge', () => { const actual = merge(local, remote, null, { machine: [], unregistered: [] }, new NullLogService()); - assert.deepEqual(actual.local.added, {}); - assert.deepEqual(actual.local.updated, { 'a': { version: 1, value: 'b' } }); - assert.deepEqual(actual.local.removed, []); - assert.deepEqual(actual.remote, null); + assert.deepStrictEqual(actual.local.added, {}); + assert.deepStrictEqual(actual.local.updated, { 'a': { version: 1, value: 'b' } }); + assert.deepStrictEqual(actual.local.removed, []); + assert.deepStrictEqual(actual.remote, null); }); test('merge when the entry is removed in remote but updated in local and a new entry is added in remote', async () => { @@ -221,10 +221,10 @@ suite('GlobalStateMerge', () => { const actual = merge(local, remote, base, { machine: [], unregistered: [] }, new NullLogService()); - assert.deepEqual(actual.local.added, { 'c': { version: 1, value: 'c' } }); - assert.deepEqual(actual.local.updated, {}); - assert.deepEqual(actual.local.removed, []); - assert.deepEqual(actual.remote, { 'a': { version: 1, value: 'a' }, 'c': { version: 1, value: 'c' }, 'b': { version: 1, value: 'd' } }); + assert.deepStrictEqual(actual.local.added, { 'c': { version: 1, value: 'c' } }); + assert.deepStrictEqual(actual.local.updated, {}); + assert.deepStrictEqual(actual.local.removed, []); + assert.deepStrictEqual(actual.remote, { 'a': { version: 1, value: 'a' }, 'c': { version: 1, value: 'c' }, 'b': { version: 1, value: 'd' } }); }); test('merge with single entry and local is empty', async () => { @@ -234,10 +234,10 @@ suite('GlobalStateMerge', () => { const actual = merge(local, remote, base, { machine: [], unregistered: [] }, new NullLogService()); - assert.deepEqual(actual.local.added, {}); - assert.deepEqual(actual.local.updated, {}); - assert.deepEqual(actual.local.removed, []); - assert.deepEqual(actual.remote, local); + assert.deepStrictEqual(actual.local.added, {}); + assert.deepStrictEqual(actual.local.updated, {}); + assert.deepStrictEqual(actual.local.removed, []); + assert.deepStrictEqual(actual.remote, local); }); test('merge when local and remote has moved forward with conflicts', async () => { @@ -247,10 +247,10 @@ suite('GlobalStateMerge', () => { const actual = merge(local, remote, base, { machine: [], unregistered: [] }, new NullLogService()); - assert.deepEqual(actual.local.added, {}); - assert.deepEqual(actual.local.updated, {}); - assert.deepEqual(actual.local.removed, []); - assert.deepEqual(actual.remote, local); + assert.deepStrictEqual(actual.local.added, {}); + assert.deepStrictEqual(actual.local.updated, {}); + assert.deepStrictEqual(actual.local.removed, []); + assert.deepStrictEqual(actual.remote, local); }); test('merge when a new entry is added to remote but scoped to machine locally and local is not synced yet', async () => { @@ -259,10 +259,10 @@ suite('GlobalStateMerge', () => { const actual = merge(local, remote, null, { machine: ['b'], unregistered: [] }, new NullLogService()); - assert.deepEqual(actual.local.added, {}); - assert.deepEqual(actual.local.updated, {}); - assert.deepEqual(actual.local.removed, []); - assert.deepEqual(actual.remote, null); + assert.deepStrictEqual(actual.local.added, {}); + assert.deepStrictEqual(actual.local.updated, {}); + assert.deepStrictEqual(actual.local.removed, []); + assert.deepStrictEqual(actual.remote, null); }); test('merge when an entry is updated to remote but scoped to machine locally', async () => { @@ -271,10 +271,10 @@ suite('GlobalStateMerge', () => { const actual = merge(local, remote, local, { machine: ['a'], unregistered: [] }, new NullLogService()); - assert.deepEqual(actual.local.added, {}); - assert.deepEqual(actual.local.updated, {}); - assert.deepEqual(actual.local.removed, []); - assert.deepEqual(actual.remote, null); + assert.deepStrictEqual(actual.local.added, {}); + assert.deepStrictEqual(actual.local.updated, {}); + assert.deepStrictEqual(actual.local.removed, []); + assert.deepStrictEqual(actual.remote, null); }); test('merge when a local value is removed and scoped to machine locally', async () => { @@ -284,10 +284,10 @@ suite('GlobalStateMerge', () => { const actual = merge(local, remote, base, { machine: ['b'], unregistered: [] }, new NullLogService()); - assert.deepEqual(actual.local.added, {}); - assert.deepEqual(actual.local.updated, {}); - assert.deepEqual(actual.local.removed, []); - assert.deepEqual(actual.remote, local); + assert.deepStrictEqual(actual.local.added, {}); + assert.deepStrictEqual(actual.local.updated, {}); + assert.deepStrictEqual(actual.local.removed, []); + assert.deepStrictEqual(actual.remote, local); }); test('merge when local moved forwared by changing a key to machine scope', async () => { @@ -297,10 +297,10 @@ suite('GlobalStateMerge', () => { const actual = merge(local, remote, base, { machine: ['b'], unregistered: [] }, new NullLogService()); - assert.deepEqual(actual.local.added, {}); - assert.deepEqual(actual.local.updated, {}); - assert.deepEqual(actual.local.removed, []); - assert.deepEqual(actual.remote, local); + assert.deepStrictEqual(actual.local.added, {}); + assert.deepStrictEqual(actual.local.updated, {}); + assert.deepStrictEqual(actual.local.removed, []); + assert.deepStrictEqual(actual.remote, local); }); test('merge should not remove remote keys if not registered', async () => { @@ -310,10 +310,10 @@ suite('GlobalStateMerge', () => { const actual = merge(local, remote, base, { machine: [], unregistered: ['c'] }, new NullLogService()); - assert.deepEqual(actual.local.added, {}); - assert.deepEqual(actual.local.updated, {}); - assert.deepEqual(actual.local.removed, []); - assert.deepEqual(actual.remote, { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' }, 'c': { version: 1, value: 'c' } }); + assert.deepStrictEqual(actual.local.added, {}); + assert.deepStrictEqual(actual.local.updated, {}); + assert.deepStrictEqual(actual.local.removed, []); + assert.deepStrictEqual(actual.remote, { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' }, 'c': { version: 1, value: 'c' } }); }); }); diff --git a/src/vs/platform/userDataSync/test/common/globalStateSync.test.ts b/src/vs/platform/userDataSync/test/common/globalStateSync.test.ts index 324469b7..6a39aa5e 100644 --- a/src/vs/platform/userDataSync/test/common/globalStateSync.test.ts +++ b/src/vs/platform/userDataSync/test/common/globalStateSync.test.ts @@ -37,30 +37,30 @@ suite('GlobalStateSync', () => { teardown(() => disposableStore.clear()); test('when global state does not exist', async () => { - assert.deepEqual(await testObject.getLastSyncUserData(), null); + assert.deepStrictEqual(await testObject.getLastSyncUserData(), null); let manifest = await testClient.manifest(); server.reset(); await testObject.sync(manifest); - assert.deepEqual(server.requests, [ + assert.deepStrictEqual(server.requests, [ { type: 'GET', url: `${server.url}/v1/resource/${testObject.resource}/latest`, headers: {} }, ]); const lastSyncUserData = await testObject.getLastSyncUserData(); const remoteUserData = await testObject.getRemoteUserData(null); - assert.deepEqual(lastSyncUserData!.ref, remoteUserData.ref); - assert.deepEqual(lastSyncUserData!.syncData, remoteUserData.syncData); - assert.equal(lastSyncUserData!.syncData, null); + assert.deepStrictEqual(lastSyncUserData!.ref, remoteUserData.ref); + assert.deepStrictEqual(lastSyncUserData!.syncData, remoteUserData.syncData); + assert.strictEqual(lastSyncUserData!.syncData, null); manifest = await testClient.manifest(); server.reset(); await testObject.sync(manifest); - assert.deepEqual(server.requests, []); + assert.deepStrictEqual(server.requests, []); manifest = await testClient.manifest(); server.reset(); await testObject.sync(manifest); - assert.deepEqual(server.requests, []); + assert.deepStrictEqual(server.requests, []); }); test('when global state is created after first sync', async () => { @@ -72,15 +72,15 @@ suite('GlobalStateSync', () => { server.reset(); await testObject.sync(manifest); - assert.deepEqual(server.requests, [ + assert.deepStrictEqual(server.requests, [ { type: 'POST', url: `${server.url}/v1/resource/${testObject.resource}`, headers: { 'If-Match': lastSyncUserData?.ref } }, ]); lastSyncUserData = await testObject.getLastSyncUserData(); const remoteUserData = await testObject.getRemoteUserData(null); - assert.deepEqual(lastSyncUserData!.ref, remoteUserData.ref); - assert.deepEqual(lastSyncUserData!.syncData, remoteUserData.syncData); - assert.deepEqual(JSON.parse(lastSyncUserData!.syncData!.content).storage, { 'a': { version: 1, value: 'value1' } }); + assert.deepStrictEqual(lastSyncUserData!.ref, remoteUserData.ref); + assert.deepStrictEqual(lastSyncUserData!.syncData, remoteUserData.syncData); + assert.deepStrictEqual(JSON.parse(lastSyncUserData!.syncData!.content).storage, { 'a': { version: 1, value: 'value1' } }); }); test('first time sync - outgoing to server (no state)', async () => { @@ -89,13 +89,13 @@ suite('GlobalStateSync', () => { await updateLocale(testClient); await testObject.sync(await testClient.manifest()); - assert.equal(testObject.status, SyncStatus.Idle); - assert.deepEqual(testObject.conflicts, []); + assert.strictEqual(testObject.status, SyncStatus.Idle); + assert.deepStrictEqual(testObject.conflicts, []); const { content } = await testClient.read(testObject.resource); assert.ok(content !== null); const actual = parseGlobalState(content!); - assert.deepEqual(actual.storage, { 'globalState.argv.locale': { version: 1, value: 'en' }, 'a': { version: 1, value: 'value1' } }); + assert.deepStrictEqual(actual.storage, { 'globalState.argv.locale': { version: 1, value: 'en' }, 'a': { version: 1, value: 'value1' } }); }); test('first time sync - incoming from server (no state)', async () => { @@ -104,11 +104,11 @@ suite('GlobalStateSync', () => { await client2.sync(); await testObject.sync(await testClient.manifest()); - assert.equal(testObject.status, SyncStatus.Idle); - assert.deepEqual(testObject.conflicts, []); + assert.strictEqual(testObject.status, SyncStatus.Idle); + assert.deepStrictEqual(testObject.conflicts, []); - assert.equal(readStorage('a', testClient), 'value1'); - assert.equal(await readLocale(testClient), 'en'); + assert.strictEqual(readStorage('a', testClient), 'value1'); + assert.strictEqual(await readLocale(testClient), 'en'); }); test('first time sync when storage exists', async () => { @@ -117,16 +117,16 @@ suite('GlobalStateSync', () => { updateUserStorage('b', 'value2', testClient); await testObject.sync(await testClient.manifest()); - assert.equal(testObject.status, SyncStatus.Idle); - assert.deepEqual(testObject.conflicts, []); + assert.strictEqual(testObject.status, SyncStatus.Idle); + assert.deepStrictEqual(testObject.conflicts, []); - assert.equal(readStorage('a', testClient), 'value1'); - assert.equal(readStorage('b', testClient), 'value2'); + assert.strictEqual(readStorage('a', testClient), 'value1'); + assert.strictEqual(readStorage('b', testClient), 'value2'); const { content } = await testClient.read(testObject.resource); assert.ok(content !== null); const actual = parseGlobalState(content!); - assert.deepEqual(actual.storage, { 'a': { version: 1, value: 'value1' }, 'b': { version: 1, value: 'value2' } }); + assert.deepStrictEqual(actual.storage, { 'a': { version: 1, value: 'value1' }, 'b': { version: 1, value: 'value2' } }); }); test('first time sync when storage exists - has conflicts', async () => { @@ -136,15 +136,15 @@ suite('GlobalStateSync', () => { updateUserStorage('a', 'value2', client2); await testObject.sync(await testClient.manifest()); - assert.equal(testObject.status, SyncStatus.Idle); - assert.deepEqual(testObject.conflicts, []); + assert.strictEqual(testObject.status, SyncStatus.Idle); + assert.deepStrictEqual(testObject.conflicts, []); - assert.equal(readStorage('a', testClient), 'value1'); + assert.strictEqual(readStorage('a', testClient), 'value1'); const { content } = await testClient.read(testObject.resource); assert.ok(content !== null); const actual = parseGlobalState(content!); - assert.deepEqual(actual.storage, { 'a': { version: 1, value: 'value1' } }); + assert.deepStrictEqual(actual.storage, { 'a': { version: 1, value: 'value1' } }); }); test('sync adding a storage value', async () => { @@ -153,16 +153,16 @@ suite('GlobalStateSync', () => { updateUserStorage('b', 'value2', testClient); await testObject.sync(await testClient.manifest()); - assert.equal(testObject.status, SyncStatus.Idle); - assert.deepEqual(testObject.conflicts, []); + assert.strictEqual(testObject.status, SyncStatus.Idle); + assert.deepStrictEqual(testObject.conflicts, []); - assert.equal(readStorage('a', testClient), 'value1'); - assert.equal(readStorage('b', testClient), 'value2'); + assert.strictEqual(readStorage('a', testClient), 'value1'); + assert.strictEqual(readStorage('b', testClient), 'value2'); const { content } = await testClient.read(testObject.resource); assert.ok(content !== null); const actual = parseGlobalState(content!); - assert.deepEqual(actual.storage, { 'a': { version: 1, value: 'value1' }, 'b': { version: 1, value: 'value2' } }); + assert.deepStrictEqual(actual.storage, { 'a': { version: 1, value: 'value1' }, 'b': { version: 1, value: 'value2' } }); }); test('sync updating a storage value', async () => { @@ -171,15 +171,15 @@ suite('GlobalStateSync', () => { updateUserStorage('a', 'value2', testClient); await testObject.sync(await testClient.manifest()); - assert.equal(testObject.status, SyncStatus.Idle); - assert.deepEqual(testObject.conflicts, []); + assert.strictEqual(testObject.status, SyncStatus.Idle); + assert.deepStrictEqual(testObject.conflicts, []); - assert.equal(readStorage('a', testClient), 'value2'); + assert.strictEqual(readStorage('a', testClient), 'value2'); const { content } = await testClient.read(testObject.resource); assert.ok(content !== null); const actual = parseGlobalState(content!); - assert.deepEqual(actual.storage, { 'a': { version: 1, value: 'value2' } }); + assert.deepStrictEqual(actual.storage, { 'a': { version: 1, value: 'value2' } }); }); test('sync removing a storage value', async () => { @@ -189,16 +189,16 @@ suite('GlobalStateSync', () => { removeStorage('b', testClient); await testObject.sync(await testClient.manifest()); - assert.equal(testObject.status, SyncStatus.Idle); - assert.deepEqual(testObject.conflicts, []); + assert.strictEqual(testObject.status, SyncStatus.Idle); + assert.deepStrictEqual(testObject.conflicts, []); - assert.equal(readStorage('a', testClient), 'value1'); - assert.equal(readStorage('b', testClient), undefined); + assert.strictEqual(readStorage('a', testClient), 'value1'); + assert.strictEqual(readStorage('b', testClient), undefined); const { content } = await testClient.read(testObject.resource); assert.ok(content !== null); const actual = parseGlobalState(content!); - assert.deepEqual(actual.storage, { 'a': { version: 1, value: 'value1' } }); + assert.deepStrictEqual(actual.storage, { 'a': { version: 1, value: 'value1' } }); }); function parseGlobalState(content: string): IGlobalState { diff --git a/src/vs/platform/userDataSync/test/common/keybindingsMerge.test.ts b/src/vs/platform/userDataSync/test/common/keybindingsMerge.test.ts index 24475726..f70f7a82 100644 --- a/src/vs/platform/userDataSync/test/common/keybindingsMerge.test.ts +++ b/src/vs/platform/userDataSync/test/common/keybindingsMerge.test.ts @@ -15,7 +15,7 @@ suite('KeybindingsMerge - No Conflicts', () => { const actual = await mergeKeybindings(localContent, remoteContent, null); assert.ok(!actual.hasChanges); assert.ok(!actual.hasConflicts); - assert.equal(actual.mergeContent, localContent); + assert.strictEqual(actual.mergeContent, localContent); }); test('merge when local and remote are same with similar when contexts', async () => { @@ -24,7 +24,7 @@ suite('KeybindingsMerge - No Conflicts', () => { const actual = await mergeKeybindings(localContent, remoteContent, null); assert.ok(!actual.hasChanges); assert.ok(!actual.hasConflicts); - assert.equal(actual.mergeContent, localContent); + assert.strictEqual(actual.mergeContent, localContent); }); test('merge when local and remote has entries in different order', async () => { @@ -39,7 +39,7 @@ suite('KeybindingsMerge - No Conflicts', () => { const actual = await mergeKeybindings(localContent, remoteContent, null); assert.ok(!actual.hasChanges); assert.ok(!actual.hasConflicts); - assert.equal(actual.mergeContent, localContent); + assert.strictEqual(actual.mergeContent, localContent); }); test('merge when local and remote are same with multiple entries', async () => { @@ -56,7 +56,7 @@ suite('KeybindingsMerge - No Conflicts', () => { const actual = await mergeKeybindings(localContent, remoteContent, null); assert.ok(!actual.hasChanges); assert.ok(!actual.hasConflicts); - assert.equal(actual.mergeContent, localContent); + assert.strictEqual(actual.mergeContent, localContent); }); test('merge when local and remote are same with different base content', async () => { @@ -77,7 +77,7 @@ suite('KeybindingsMerge - No Conflicts', () => { const actual = await mergeKeybindings(localContent, remoteContent, baseContent); assert.ok(!actual.hasChanges); assert.ok(!actual.hasConflicts); - assert.equal(actual.mergeContent, localContent); + assert.strictEqual(actual.mergeContent, localContent); }); test('merge when local and remote are same with multiple entries in different order', async () => { @@ -94,7 +94,7 @@ suite('KeybindingsMerge - No Conflicts', () => { const actual = await mergeKeybindings(localContent, remoteContent, null); assert.ok(!actual.hasChanges); assert.ok(!actual.hasConflicts); - assert.equal(actual.mergeContent, localContent); + assert.strictEqual(actual.mergeContent, localContent); }); test('merge when local and remote are same when remove entry is in different order', async () => { @@ -111,7 +111,7 @@ suite('KeybindingsMerge - No Conflicts', () => { const actual = await mergeKeybindings(localContent, remoteContent, null); assert.ok(!actual.hasChanges); assert.ok(!actual.hasConflicts); - assert.equal(actual.mergeContent, localContent); + assert.strictEqual(actual.mergeContent, localContent); }); test('merge when a new entry is added to remote', async () => { @@ -127,7 +127,7 @@ suite('KeybindingsMerge - No Conflicts', () => { const actual = await mergeKeybindings(localContent, remoteContent, null); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); - assert.equal(actual.mergeContent, remoteContent); + assert.strictEqual(actual.mergeContent, remoteContent); }); test('merge when multiple new entries are added to remote', async () => { @@ -144,7 +144,7 @@ suite('KeybindingsMerge - No Conflicts', () => { const actual = await mergeKeybindings(localContent, remoteContent, null); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); - assert.equal(actual.mergeContent, remoteContent); + assert.strictEqual(actual.mergeContent, remoteContent); }); test('merge when multiple new entries are added to remote from base and local has not changed', async () => { @@ -161,7 +161,7 @@ suite('KeybindingsMerge - No Conflicts', () => { const actual = await mergeKeybindings(localContent, remoteContent, localContent); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); - assert.equal(actual.mergeContent, remoteContent); + assert.strictEqual(actual.mergeContent, remoteContent); }); test('merge when an entry is removed from remote from base and local has not changed', async () => { @@ -177,7 +177,7 @@ suite('KeybindingsMerge - No Conflicts', () => { const actual = await mergeKeybindings(localContent, remoteContent, localContent); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); - assert.equal(actual.mergeContent, remoteContent); + assert.strictEqual(actual.mergeContent, remoteContent); }); test('merge when an entry (same command) is removed from remote from base and local has not changed', async () => { @@ -191,7 +191,7 @@ suite('KeybindingsMerge - No Conflicts', () => { const actual = await mergeKeybindings(localContent, remoteContent, localContent); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); - assert.equal(actual.mergeContent, remoteContent); + assert.strictEqual(actual.mergeContent, remoteContent); }); test('merge when an entry is updated in remote from base and local has not changed', async () => { @@ -204,7 +204,7 @@ suite('KeybindingsMerge - No Conflicts', () => { const actual = await mergeKeybindings(localContent, remoteContent, localContent); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); - assert.equal(actual.mergeContent, remoteContent); + assert.strictEqual(actual.mergeContent, remoteContent); }); test('merge when a command with multiple entries is updated from remote from base and local has not changed', async () => { @@ -223,7 +223,7 @@ suite('KeybindingsMerge - No Conflicts', () => { const actual = await mergeKeybindings(localContent, remoteContent, localContent); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); - assert.equal(actual.mergeContent, remoteContent); + assert.strictEqual(actual.mergeContent, remoteContent); }); test('merge when remote has moved forwareded with multiple changes and local stays with base', async () => { @@ -246,7 +246,7 @@ suite('KeybindingsMerge - No Conflicts', () => { const actual = await mergeKeybindings(localContent, remoteContent, localContent); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); - assert.equal(actual.mergeContent, remoteContent); + assert.strictEqual(actual.mergeContent, remoteContent); }); test('merge when a new entry is added to local', async () => { @@ -262,7 +262,7 @@ suite('KeybindingsMerge - No Conflicts', () => { const actual = await mergeKeybindings(localContent, remoteContent, null); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); - assert.equal(actual.mergeContent, localContent); + assert.strictEqual(actual.mergeContent, localContent); }); test('merge when multiple new entries are added to local', async () => { @@ -279,7 +279,7 @@ suite('KeybindingsMerge - No Conflicts', () => { const actual = await mergeKeybindings(localContent, remoteContent, null); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); - assert.equal(actual.mergeContent, localContent); + assert.strictEqual(actual.mergeContent, localContent); }); test('merge when multiple new entries are added to local from base and remote is not changed', async () => { @@ -296,7 +296,7 @@ suite('KeybindingsMerge - No Conflicts', () => { const actual = await mergeKeybindings(localContent, remoteContent, remoteContent); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); - assert.equal(actual.mergeContent, localContent); + assert.strictEqual(actual.mergeContent, localContent); }); test('merge when an entry is removed from local from base and remote has not changed', async () => { @@ -312,7 +312,7 @@ suite('KeybindingsMerge - No Conflicts', () => { const actual = await mergeKeybindings(localContent, remoteContent, remoteContent); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); - assert.equal(actual.mergeContent, localContent); + assert.strictEqual(actual.mergeContent, localContent); }); test('merge when an entry (with same command) is removed from local from base and remote has not changed', async () => { @@ -326,7 +326,7 @@ suite('KeybindingsMerge - No Conflicts', () => { const actual = await mergeKeybindings(localContent, remoteContent, remoteContent); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); - assert.equal(actual.mergeContent, localContent); + assert.strictEqual(actual.mergeContent, localContent); }); test('merge when an entry is updated in local from base and remote has not changed', async () => { @@ -339,7 +339,7 @@ suite('KeybindingsMerge - No Conflicts', () => { const actual = await mergeKeybindings(localContent, remoteContent, remoteContent); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); - assert.equal(actual.mergeContent, localContent); + assert.strictEqual(actual.mergeContent, localContent); }); test('merge when a command with multiple entries is updated from local from base and remote has not changed', async () => { @@ -358,7 +358,7 @@ suite('KeybindingsMerge - No Conflicts', () => { const actual = await mergeKeybindings(localContent, remoteContent, remoteContent); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); - assert.equal(actual.mergeContent, localContent); + assert.strictEqual(actual.mergeContent, localContent); }); test('merge when local has moved forwareded with multiple changes and remote stays with base', async () => { @@ -390,7 +390,7 @@ suite('KeybindingsMerge - No Conflicts', () => { const actual = await mergeKeybindings(localContent, remoteContent, remoteContent); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); - assert.equal(actual.mergeContent, expected); + assert.strictEqual(actual.mergeContent, expected); }); test('merge when local and remote has moved forwareded with conflicts', async () => { @@ -431,7 +431,7 @@ suite('KeybindingsMerge - No Conflicts', () => { const actual = await mergeKeybindings(localContent, remoteContent, baseContent); assert.ok(actual.hasChanges); assert.ok(!actual.hasConflicts); - assert.equal(actual.mergeContent, expected); + assert.strictEqual(actual.mergeContent, expected); }); test('merge when local and remote with one entry but different value', async () => { @@ -440,7 +440,7 @@ suite('KeybindingsMerge - No Conflicts', () => { const actual = await mergeKeybindings(localContent, remoteContent, null); assert.ok(actual.hasChanges); assert.ok(actual.hasConflicts); - assert.equal(actual.mergeContent, + assert.strictEqual(actual.mergeContent, `[ { "key": "alt+d", @@ -462,7 +462,7 @@ suite('KeybindingsMerge - No Conflicts', () => { const actual = await mergeKeybindings(localContent, remoteContent, null); assert.ok(actual.hasChanges); assert.ok(actual.hasConflicts); - assert.equal(actual.mergeContent, + assert.strictEqual(actual.mergeContent, `[ { "key": "alt+d", @@ -484,7 +484,7 @@ suite('KeybindingsMerge - No Conflicts', () => { const actual = await mergeKeybindings(localContent, remoteContent, baseContent); assert.ok(actual.hasChanges); assert.ok(actual.hasConflicts); - assert.equal(actual.mergeContent, + assert.strictEqual(actual.mergeContent, `[]`); }); @@ -495,7 +495,7 @@ suite('KeybindingsMerge - No Conflicts', () => { const actual = await mergeKeybindings(localContent, remoteContent, baseContent); assert.ok(actual.hasChanges); assert.ok(actual.hasConflicts); - assert.equal(actual.mergeContent, + assert.strictEqual(actual.mergeContent, `[ { "key": "alt+b", @@ -511,7 +511,7 @@ suite('KeybindingsMerge - No Conflicts', () => { const actual = await mergeKeybindings(localContent, remoteContent, baseContent); assert.ok(actual.hasChanges); assert.ok(actual.hasConflicts); - assert.equal(actual.mergeContent, + assert.strictEqual(actual.mergeContent, `[ { "key": "alt+c", @@ -528,7 +528,7 @@ suite('KeybindingsMerge - No Conflicts', () => { const actual = await mergeKeybindings(localContent, remoteContent, baseContent); assert.ok(actual.hasChanges); assert.ok(actual.hasConflicts); - assert.equal(actual.mergeContent, + assert.strictEqual(actual.mergeContent, `[ { "key": "alt+c", @@ -571,7 +571,7 @@ suite('KeybindingsMerge - No Conflicts', () => { const actual = await mergeKeybindings(localContent, remoteContent, baseContent); assert.ok(actual.hasChanges); assert.ok(actual.hasConflicts); - assert.equal(actual.mergeContent, + assert.strictEqual(actual.mergeContent, `[ { "key": "alt+d", diff --git a/src/vs/platform/userDataSync/test/common/keybindingsSync.test.ts b/src/vs/platform/userDataSync/test/common/keybindingsSync.test.ts index 9007dcdb..51c2a07f 100644 --- a/src/vs/platform/userDataSync/test/common/keybindingsSync.test.ts +++ b/src/vs/platform/userDataSync/test/common/keybindingsSync.test.ts @@ -34,31 +34,31 @@ suite('KeybindingsSync', () => { const fileService = client.instantiationService.get(IFileService); const keybindingsResource = client.instantiationService.get(IEnvironmentService).keybindingsResource; - assert.deepEqual(await testObject.getLastSyncUserData(), null); + assert.deepStrictEqual(await testObject.getLastSyncUserData(), null); let manifest = await client.manifest(); server.reset(); await testObject.sync(manifest); - assert.deepEqual(server.requests, [ + assert.deepStrictEqual(server.requests, [ { type: 'GET', url: `${server.url}/v1/resource/${testObject.resource}/latest`, headers: {} }, ]); assert.ok(!await fileService.exists(keybindingsResource)); const lastSyncUserData = await testObject.getLastSyncUserData(); const remoteUserData = await testObject.getRemoteUserData(null); - assert.deepEqual(lastSyncUserData!.ref, remoteUserData.ref); - assert.deepEqual(lastSyncUserData!.syncData, remoteUserData.syncData); - assert.equal(lastSyncUserData!.syncData, null); + assert.deepStrictEqual(lastSyncUserData!.ref, remoteUserData.ref); + assert.deepStrictEqual(lastSyncUserData!.syncData, remoteUserData.syncData); + assert.strictEqual(lastSyncUserData!.syncData, null); manifest = await client.manifest(); server.reset(); await testObject.sync(manifest); - assert.deepEqual(server.requests, []); + assert.deepStrictEqual(server.requests, []); manifest = await client.manifest(); server.reset(); await testObject.sync(manifest); - assert.deepEqual(server.requests, []); + assert.deepStrictEqual(server.requests, []); }); test('when keybindings file is empty and remote has no changes', async () => { @@ -70,9 +70,9 @@ suite('KeybindingsSync', () => { const lastSyncUserData = await testObject.getLastSyncUserData(); const remoteUserData = await testObject.getRemoteUserData(null); - assert.equal(getKeybindingsContentFromSyncContent(lastSyncUserData!.syncData!.content!, true), '[]'); - assert.equal(getKeybindingsContentFromSyncContent(remoteUserData!.syncData!.content!, true), '[]'); - assert.equal((await fileService.readFile(keybindingsResource)).value.toString(), ''); + assert.strictEqual(getKeybindingsContentFromSyncContent(lastSyncUserData!.syncData!.content!, true), '[]'); + assert.strictEqual(getKeybindingsContentFromSyncContent(remoteUserData!.syncData!.content!, true), '[]'); + assert.strictEqual((await fileService.readFile(keybindingsResource)).value.toString(), ''); }); test('when keybindings file is empty and remote has changes', async () => { @@ -95,9 +95,9 @@ suite('KeybindingsSync', () => { const lastSyncUserData = await testObject.getLastSyncUserData(); const remoteUserData = await testObject.getRemoteUserData(null); - assert.equal(getKeybindingsContentFromSyncContent(lastSyncUserData!.syncData!.content!, true), content); - assert.equal(getKeybindingsContentFromSyncContent(remoteUserData!.syncData!.content!, true), content); - assert.equal((await fileService.readFile(keybindingsResource)).value.toString(), content); + assert.strictEqual(getKeybindingsContentFromSyncContent(lastSyncUserData!.syncData!.content!, true), content); + assert.strictEqual(getKeybindingsContentFromSyncContent(remoteUserData!.syncData!.content!, true), content); + assert.strictEqual((await fileService.readFile(keybindingsResource)).value.toString(), content); }); test('when keybindings file is empty with comment and remote has no changes', async () => { @@ -110,9 +110,9 @@ suite('KeybindingsSync', () => { const lastSyncUserData = await testObject.getLastSyncUserData(); const remoteUserData = await testObject.getRemoteUserData(null); - assert.equal(getKeybindingsContentFromSyncContent(lastSyncUserData!.syncData!.content!, true), expectedContent); - assert.equal(getKeybindingsContentFromSyncContent(remoteUserData!.syncData!.content!, true), expectedContent); - assert.equal((await fileService.readFile(keybindingsResource)).value.toString(), expectedContent); + assert.strictEqual(getKeybindingsContentFromSyncContent(lastSyncUserData!.syncData!.content!, true), expectedContent); + assert.strictEqual(getKeybindingsContentFromSyncContent(remoteUserData!.syncData!.content!, true), expectedContent); + assert.strictEqual((await fileService.readFile(keybindingsResource)).value.toString(), expectedContent); }); test('when keybindings file is empty and remote has keybindings', async () => { @@ -135,9 +135,9 @@ suite('KeybindingsSync', () => { const lastSyncUserData = await testObject.getLastSyncUserData(); const remoteUserData = await testObject.getRemoteUserData(null); - assert.equal(getKeybindingsContentFromSyncContent(lastSyncUserData!.syncData!.content!, true), content); - assert.equal(getKeybindingsContentFromSyncContent(remoteUserData!.syncData!.content!, true), content); - assert.equal((await fileService.readFile(keybindingsResource)).value.toString(), content); + assert.strictEqual(getKeybindingsContentFromSyncContent(lastSyncUserData!.syncData!.content!, true), content); + assert.strictEqual(getKeybindingsContentFromSyncContent(remoteUserData!.syncData!.content!, true), content); + assert.strictEqual((await fileService.readFile(keybindingsResource)).value.toString(), content); }); test('when keybindings file is empty and remote has empty array', async () => { @@ -159,9 +159,9 @@ suite('KeybindingsSync', () => { const lastSyncUserData = await testObject.getLastSyncUserData(); const remoteUserData = await testObject.getRemoteUserData(null); - assert.equal(getKeybindingsContentFromSyncContent(lastSyncUserData!.syncData!.content!, true), content); - assert.equal(getKeybindingsContentFromSyncContent(remoteUserData!.syncData!.content!, true), content); - assert.equal((await fileService.readFile(keybindingsResource)).value.toString(), expectedLocalContent); + assert.strictEqual(getKeybindingsContentFromSyncContent(lastSyncUserData!.syncData!.content!, true), content); + assert.strictEqual(getKeybindingsContentFromSyncContent(remoteUserData!.syncData!.content!, true), content); + assert.strictEqual((await fileService.readFile(keybindingsResource)).value.toString(), expectedLocalContent); }); test('when keybindings file is created after first sync', async () => { @@ -175,15 +175,15 @@ suite('KeybindingsSync', () => { server.reset(); await testObject.sync(manifest); - assert.deepEqual(server.requests, [ + assert.deepStrictEqual(server.requests, [ { type: 'POST', url: `${server.url}/v1/resource/${testObject.resource}`, headers: { 'If-Match': lastSyncUserData?.ref } }, ]); lastSyncUserData = await testObject.getLastSyncUserData(); const remoteUserData = await testObject.getRemoteUserData(null); - assert.deepEqual(lastSyncUserData!.ref, remoteUserData.ref); - assert.deepEqual(lastSyncUserData!.syncData, remoteUserData.syncData); - assert.equal(getKeybindingsContentFromSyncContent(lastSyncUserData!.syncData!.content!, true), '[]'); + assert.deepStrictEqual(lastSyncUserData!.ref, remoteUserData.ref); + assert.deepStrictEqual(lastSyncUserData!.syncData, remoteUserData.syncData); + assert.strictEqual(getKeybindingsContentFromSyncContent(lastSyncUserData!.syncData!.content!, true), '[]'); }); test('test apply remote when keybindings file does not exist', async () => { @@ -199,7 +199,7 @@ suite('KeybindingsSync', () => { const content = await testObject.resolveContent(preview.resourcePreviews[0].remoteResource); await testObject.accept(preview.resourcePreviews[0].remoteResource, content); await testObject.apply(false); - assert.deepEqual(server.requests, []); + assert.deepStrictEqual(server.requests, []); }); }); diff --git a/src/vs/platform/userDataSync/test/common/settingsMerge.test.ts b/src/vs/platform/userDataSync/test/common/settingsMerge.test.ts index 644011b9..b3689067 100644 --- a/src/vs/platform/userDataSync/test/common/settingsMerge.test.ts +++ b/src/vs/platform/userDataSync/test/common/settingsMerge.test.ts @@ -15,9 +15,9 @@ suite('SettingsMerge - Merge', () => { const localContent = stringify({ 'a': 1 }); const remoteContent = stringify({ 'a': 1 }); const actual = merge(localContent, remoteContent, null, [], [], formattingOptions); - assert.equal(actual.localContent, null); - assert.equal(actual.remoteContent, null); - assert.equal(actual.conflictsSettings.length, 0); + assert.strictEqual(actual.localContent, null); + assert.strictEqual(actual.remoteContent, null); + assert.strictEqual(actual.conflictsSettings.length, 0); assert.ok(!actual.hasConflicts); }); @@ -31,9 +31,9 @@ suite('SettingsMerge - Merge', () => { 'b': 2 }); const actual = merge(localContent, remoteContent, null, [], [], formattingOptions); - assert.equal(actual.localContent, null); - assert.equal(actual.remoteContent, null); - assert.equal(actual.conflictsSettings.length, 0); + assert.strictEqual(actual.localContent, null); + assert.strictEqual(actual.remoteContent, null); + assert.strictEqual(actual.conflictsSettings.length, 0); assert.ok(!actual.hasConflicts); }); @@ -47,10 +47,10 @@ suite('SettingsMerge - Merge', () => { 'b': 2 }); const actual = merge(localContent, remoteContent, null, [], [], formattingOptions); - assert.equal(actual.localContent, localContent); - assert.equal(actual.remoteContent, remoteContent); + assert.strictEqual(actual.localContent, localContent); + assert.strictEqual(actual.remoteContent, remoteContent); assert.ok(actual.hasConflicts); - assert.equal(actual.conflictsSettings.length, 0); + assert.strictEqual(actual.conflictsSettings.length, 0); }); test('merge when local and remote are same with different base content', async () => { @@ -67,9 +67,9 @@ suite('SettingsMerge - Merge', () => { 'b': 2 }); const actual = merge(localContent, remoteContent, baseContent, [], [], formattingOptions); - assert.equal(actual.localContent, localContent); - assert.equal(actual.remoteContent, remoteContent); - assert.equal(actual.conflictsSettings.length, 0); + assert.strictEqual(actual.localContent, localContent); + assert.strictEqual(actual.remoteContent, remoteContent); + assert.strictEqual(actual.conflictsSettings.length, 0); assert.ok(actual.hasConflicts); }); @@ -82,9 +82,9 @@ suite('SettingsMerge - Merge', () => { 'b': 2 }); const actual = merge(localContent, remoteContent, null, [], [], formattingOptions); - assert.equal(actual.localContent, remoteContent); - assert.equal(actual.remoteContent, null); - assert.equal(actual.conflictsSettings.length, 0); + assert.strictEqual(actual.localContent, remoteContent); + assert.strictEqual(actual.remoteContent, null); + assert.strictEqual(actual.conflictsSettings.length, 0); assert.ok(!actual.hasConflicts); }); @@ -98,9 +98,9 @@ suite('SettingsMerge - Merge', () => { 'c': 3, }); const actual = merge(localContent, remoteContent, null, [], [], formattingOptions); - assert.equal(actual.localContent, remoteContent); - assert.equal(actual.remoteContent, null); - assert.equal(actual.conflictsSettings.length, 0); + assert.strictEqual(actual.localContent, remoteContent); + assert.strictEqual(actual.remoteContent, null); + assert.strictEqual(actual.conflictsSettings.length, 0); assert.ok(!actual.hasConflicts); }); @@ -114,9 +114,9 @@ suite('SettingsMerge - Merge', () => { 'c': 3, }); const actual = merge(localContent, remoteContent, localContent, [], [], formattingOptions); - assert.equal(actual.localContent, remoteContent); - assert.equal(actual.remoteContent, null); - assert.equal(actual.conflictsSettings.length, 0); + assert.strictEqual(actual.localContent, remoteContent); + assert.strictEqual(actual.remoteContent, null); + assert.strictEqual(actual.conflictsSettings.length, 0); assert.ok(!actual.hasConflicts); }); @@ -129,9 +129,9 @@ suite('SettingsMerge - Merge', () => { 'a': 1, }); const actual = merge(localContent, remoteContent, localContent, [], [], formattingOptions); - assert.equal(actual.localContent, remoteContent); - assert.equal(actual.remoteContent, null); - assert.equal(actual.conflictsSettings.length, 0); + assert.strictEqual(actual.localContent, remoteContent); + assert.strictEqual(actual.remoteContent, null); + assert.strictEqual(actual.conflictsSettings.length, 0); assert.ok(!actual.hasConflicts); }); @@ -141,9 +141,9 @@ suite('SettingsMerge - Merge', () => { }); const remoteContent = stringify({}); const actual = merge(localContent, remoteContent, localContent, [], [], formattingOptions); - assert.equal(actual.localContent, remoteContent); - assert.equal(actual.remoteContent, null); - assert.equal(actual.conflictsSettings.length, 0); + assert.strictEqual(actual.localContent, remoteContent); + assert.strictEqual(actual.remoteContent, null); + assert.strictEqual(actual.conflictsSettings.length, 0); assert.ok(!actual.hasConflicts); }); @@ -155,9 +155,9 @@ suite('SettingsMerge - Merge', () => { 'a': 2 }); const actual = merge(localContent, remoteContent, localContent, [], [], formattingOptions); - assert.equal(actual.localContent, remoteContent); - assert.equal(actual.remoteContent, null); - assert.equal(actual.conflictsSettings.length, 0); + assert.strictEqual(actual.localContent, remoteContent); + assert.strictEqual(actual.remoteContent, null); + assert.strictEqual(actual.conflictsSettings.length, 0); assert.ok(!actual.hasConflicts); }); @@ -172,9 +172,9 @@ suite('SettingsMerge - Merge', () => { 'd': 4, }); const actual = merge(localContent, remoteContent, localContent, [], [], formattingOptions); - assert.equal(actual.localContent, remoteContent); - assert.equal(actual.remoteContent, null); - assert.equal(actual.conflictsSettings.length, 0); + assert.strictEqual(actual.localContent, remoteContent); + assert.strictEqual(actual.remoteContent, null); + assert.strictEqual(actual.conflictsSettings.length, 0); assert.ok(!actual.hasConflicts); }); @@ -191,9 +191,9 @@ suite('SettingsMerge - Merge', () => { 'b': 2, }); const actual = merge(localContent, remoteContent, localContent, [], [], formattingOptions); - assert.equal(actual.localContent, remoteContent); - assert.equal(actual.remoteContent, null); - assert.equal(actual.conflictsSettings.length, 0); + assert.strictEqual(actual.localContent, remoteContent); + assert.strictEqual(actual.remoteContent, null); + assert.strictEqual(actual.conflictsSettings.length, 0); assert.ok(!actual.hasConflicts); }); @@ -213,9 +213,9 @@ suite('SettingsMerge - Merge', () => { "c": 1, }`; const actual = merge(localContent, remoteContent, localContent, [], [], formattingOptions); - assert.equal(actual.localContent, remoteContent); - assert.equal(actual.remoteContent, null); - assert.equal(actual.conflictsSettings.length, 0); + assert.strictEqual(actual.localContent, remoteContent); + assert.strictEqual(actual.remoteContent, null); + assert.strictEqual(actual.conflictsSettings.length, 0); assert.ok(!actual.hasConflicts); }); @@ -235,9 +235,9 @@ suite('SettingsMerge - Merge', () => { "b": 2, }`; const actual = merge(localContent, remoteContent, localContent, [], [], formattingOptions); - assert.equal(actual.localContent, remoteContent); - assert.equal(actual.remoteContent, null); - assert.equal(actual.conflictsSettings.length, 0); + assert.strictEqual(actual.localContent, remoteContent); + assert.strictEqual(actual.remoteContent, null); + assert.strictEqual(actual.conflictsSettings.length, 0); assert.ok(!actual.hasConflicts); }); @@ -252,9 +252,9 @@ suite('SettingsMerge - Merge', () => { 'a': 1, }); const actual = merge(localContent, remoteContent, null, [], [], formattingOptions); - assert.equal(actual.localContent, null); - assert.equal(actual.remoteContent, localContent); - assert.equal(actual.conflictsSettings.length, 0); + assert.strictEqual(actual.localContent, null); + assert.strictEqual(actual.remoteContent, localContent); + assert.strictEqual(actual.conflictsSettings.length, 0); assert.ok(!actual.hasConflicts); }); @@ -269,9 +269,9 @@ suite('SettingsMerge - Merge', () => { 'a': 1, }); const actual = merge(localContent, remoteContent, remoteContent, [], [], formattingOptions); - assert.equal(actual.localContent, null); - assert.equal(actual.remoteContent, localContent); - assert.equal(actual.conflictsSettings.length, 0); + assert.strictEqual(actual.localContent, null); + assert.strictEqual(actual.remoteContent, localContent); + assert.strictEqual(actual.conflictsSettings.length, 0); assert.ok(!actual.hasConflicts); }); @@ -287,9 +287,9 @@ suite('SettingsMerge - Merge', () => { 'd': 4, }); const actual = merge(localContent, remoteContent, remoteContent, [], [], formattingOptions); - assert.equal(actual.localContent, null); - assert.equal(actual.remoteContent, localContent); - assert.equal(actual.conflictsSettings.length, 0); + assert.strictEqual(actual.localContent, null); + assert.strictEqual(actual.remoteContent, localContent); + assert.strictEqual(actual.conflictsSettings.length, 0); assert.ok(!actual.hasConflicts); }); @@ -303,9 +303,9 @@ suite('SettingsMerge - Merge', () => { 'c': 2, }); const actual = merge(localContent, remoteContent, remoteContent, [], [], formattingOptions); - assert.equal(actual.localContent, null); - assert.equal(actual.remoteContent, localContent); - assert.equal(actual.conflictsSettings.length, 0); + assert.strictEqual(actual.localContent, null); + assert.strictEqual(actual.remoteContent, localContent); + assert.strictEqual(actual.conflictsSettings.length, 0); assert.ok(!actual.hasConflicts); }); @@ -320,9 +320,9 @@ suite('SettingsMerge - Merge', () => { 'a': 1, }); const actual = merge(localContent, remoteContent, remoteContent, [], [], formattingOptions); - assert.equal(actual.localContent, null); - assert.equal(actual.remoteContent, localContent); - assert.equal(actual.conflictsSettings.length, 0); + assert.strictEqual(actual.localContent, null); + assert.strictEqual(actual.remoteContent, localContent); + assert.strictEqual(actual.conflictsSettings.length, 0); assert.ok(!actual.hasConflicts); }); @@ -338,9 +338,9 @@ suite('SettingsMerge - Merge', () => { "b": 2, }`; const actual = merge(localContent, remoteContent, remoteContent, [], [], formattingOptions); - assert.equal(actual.localContent, null); - assert.equal(actual.remoteContent, localContent); - assert.equal(actual.conflictsSettings.length, 0); + assert.strictEqual(actual.localContent, null); + assert.strictEqual(actual.remoteContent, localContent); + assert.strictEqual(actual.conflictsSettings.length, 0); assert.ok(!actual.hasConflicts); }); @@ -360,9 +360,9 @@ suite('SettingsMerge - Merge', () => { "c": 1, }`; const actual = merge(localContent, remoteContent, remoteContent, [], [], formattingOptions); - assert.equal(actual.localContent, null); - assert.equal(actual.remoteContent, localContent); - assert.equal(actual.conflictsSettings.length, 0); + assert.strictEqual(actual.localContent, null); + assert.strictEqual(actual.remoteContent, localContent); + assert.strictEqual(actual.conflictsSettings.length, 0); assert.ok(!actual.hasConflicts); }); @@ -382,9 +382,9 @@ suite('SettingsMerge - Merge', () => { "c": 1, }`; const actual = merge(localContent, remoteContent, remoteContent, [], [], formattingOptions); - assert.equal(actual.localContent, null); - assert.equal(actual.remoteContent, localContent); - assert.equal(actual.conflictsSettings.length, 0); + assert.strictEqual(actual.localContent, null); + assert.strictEqual(actual.remoteContent, localContent); + assert.strictEqual(actual.conflictsSettings.length, 0); assert.ok(!actual.hasConflicts); }); @@ -397,10 +397,10 @@ suite('SettingsMerge - Merge', () => { }); const expectedConflicts: IConflictSetting[] = [{ key: 'a', localValue: 1, remoteValue: 2 }]; const actual = merge(localContent, remoteContent, null, [], [], formattingOptions); - assert.equal(actual.localContent, localContent); - assert.equal(actual.remoteContent, remoteContent); + assert.strictEqual(actual.localContent, localContent); + assert.strictEqual(actual.remoteContent, remoteContent); assert.ok(actual.hasConflicts); - assert.deepEqual(actual.conflictsSettings, expectedConflicts); + assert.deepStrictEqual(actual.conflictsSettings, expectedConflicts); }); test('merge when the entry is removed in remote but updated in local and a new entry is added in remote', async () => { @@ -415,13 +415,13 @@ suite('SettingsMerge - Merge', () => { }); const expectedConflicts: IConflictSetting[] = [{ key: 'a', localValue: 2, remoteValue: undefined }]; const actual = merge(localContent, remoteContent, baseContent, [], [], formattingOptions); - assert.equal(actual.localContent, stringify({ + assert.strictEqual(actual.localContent, stringify({ 'a': 2, 'b': 2 })); - assert.equal(actual.remoteContent, remoteContent); + assert.strictEqual(actual.remoteContent, remoteContent); assert.ok(actual.hasConflicts); - assert.deepEqual(actual.conflictsSettings, expectedConflicts); + assert.deepStrictEqual(actual.conflictsSettings, expectedConflicts); }); test('merge with single entry and local is empty', async () => { @@ -434,10 +434,10 @@ suite('SettingsMerge - Merge', () => { }); const expectedConflicts: IConflictSetting[] = [{ key: 'a', localValue: undefined, remoteValue: 2 }]; const actual = merge(localContent, remoteContent, baseContent, [], [], formattingOptions); - assert.equal(actual.localContent, localContent); - assert.equal(actual.remoteContent, remoteContent); + assert.strictEqual(actual.localContent, localContent); + assert.strictEqual(actual.remoteContent, remoteContent); assert.ok(actual.hasConflicts); - assert.deepEqual(actual.conflictsSettings, expectedConflicts); + assert.deepStrictEqual(actual.conflictsSettings, expectedConflicts); }); test('merge when local and remote has moved forwareded with conflicts', async () => { @@ -467,14 +467,14 @@ suite('SettingsMerge - Merge', () => { { key: 'e', localValue: 4, remoteValue: 5 }, ]; const actual = merge(localContent, remoteContent, baseContent, [], [], formattingOptions); - assert.equal(actual.localContent, stringify({ + assert.strictEqual(actual.localContent, stringify({ 'a': 2, 'c': 3, 'd': 5, 'e': 4, 'f': 1, })); - assert.equal(actual.remoteContent, stringify({ + assert.strictEqual(actual.remoteContent, stringify({ 'b': 3, 'c': 3, 'd': 6, @@ -482,7 +482,7 @@ suite('SettingsMerge - Merge', () => { 'f': 1, })); assert.ok(actual.hasConflicts); - assert.deepEqual(actual.conflictsSettings, expectedConflicts); + assert.deepStrictEqual(actual.conflictsSettings, expectedConflicts); }); test('merge when local and remote has moved forwareded with change in order', async () => { @@ -505,20 +505,20 @@ suite('SettingsMerge - Merge', () => { 'c': 4, }); const actual = merge(localContent, remoteContent, baseContent, [], [], formattingOptions); - assert.equal(actual.localContent, stringify({ + assert.strictEqual(actual.localContent, stringify({ 'a': 2, 'c': 4, 'b': 2, 'e': 5, })); - assert.equal(actual.remoteContent, stringify({ + assert.strictEqual(actual.remoteContent, stringify({ 'a': 2, 'b': 2, 'e': 5, 'c': 4, })); assert.ok(actual.hasConflicts); - assert.deepEqual(actual.conflictsSettings, []); + assert.deepStrictEqual(actual.conflictsSettings, []); }); test('merge when local and remote has moved forwareded with comment changes', async () => { @@ -544,10 +544,10 @@ suite('SettingsMerge - Merge', () => { "c": 1 }`; const actual = merge(localContent, remoteContent, baseContent, [], [], formattingOptions); - assert.equal(actual.localContent, localContent); - assert.equal(actual.remoteContent, remoteContent); + assert.strictEqual(actual.localContent, localContent); + assert.strictEqual(actual.remoteContent, remoteContent); assert.ok(actual.hasConflicts); - assert.deepEqual(actual.conflictsSettings, []); + assert.deepStrictEqual(actual.conflictsSettings, []); }); test('resolve when local and remote has moved forwareded with resolved conflicts', async () => { @@ -574,14 +574,14 @@ suite('SettingsMerge - Merge', () => { { key: 'd', localValue: 5, remoteValue: 6 }, ]; const actual = merge(localContent, remoteContent, baseContent, [], [{ key: 'a', value: 2 }, { key: 'b', value: undefined }, { key: 'e', value: 5 }], formattingOptions); - assert.equal(actual.localContent, stringify({ + assert.strictEqual(actual.localContent, stringify({ 'a': 2, 'c': 3, 'd': 5, 'e': 5, 'f': 1, })); - assert.equal(actual.remoteContent, stringify({ + assert.strictEqual(actual.remoteContent, stringify({ 'c': 3, 'd': 6, 'e': 5, @@ -589,16 +589,16 @@ suite('SettingsMerge - Merge', () => { 'a': 2, })); assert.ok(actual.hasConflicts); - assert.deepEqual(actual.conflictsSettings, expectedConflicts); + assert.deepStrictEqual(actual.conflictsSettings, expectedConflicts); }); test('ignored setting is not merged when changed in local and remote', async () => { const localContent = stringify({ 'a': 1 }); const remoteContent = stringify({ 'a': 2 }); const actual = merge(localContent, remoteContent, null, ['a'], [], formattingOptions); - assert.equal(actual.localContent, null); - assert.equal(actual.remoteContent, null); - assert.equal(actual.conflictsSettings.length, 0); + assert.strictEqual(actual.localContent, null); + assert.strictEqual(actual.remoteContent, null); + assert.strictEqual(actual.conflictsSettings.length, 0); assert.ok(!actual.hasConflicts); }); @@ -607,9 +607,9 @@ suite('SettingsMerge - Merge', () => { const localContent = stringify({ 'a': 1 }); const remoteContent = stringify({ 'a': 2 }); const actual = merge(localContent, remoteContent, baseContent, ['a'], [], formattingOptions); - assert.equal(actual.localContent, null); - assert.equal(actual.remoteContent, null); - assert.equal(actual.conflictsSettings.length, 0); + assert.strictEqual(actual.localContent, null); + assert.strictEqual(actual.remoteContent, null); + assert.strictEqual(actual.conflictsSettings.length, 0); assert.ok(!actual.hasConflicts); }); @@ -617,9 +617,9 @@ suite('SettingsMerge - Merge', () => { const localContent = stringify({}); const remoteContent = stringify({ 'a': 1 }); const actual = merge(localContent, remoteContent, null, ['a'], [], formattingOptions); - assert.equal(actual.localContent, null); - assert.equal(actual.remoteContent, null); - assert.equal(actual.conflictsSettings.length, 0); + assert.strictEqual(actual.localContent, null); + assert.strictEqual(actual.remoteContent, null); + assert.strictEqual(actual.conflictsSettings.length, 0); assert.ok(!actual.hasConflicts); }); @@ -627,9 +627,9 @@ suite('SettingsMerge - Merge', () => { const localContent = stringify({ 'b': 2 }); const remoteContent = stringify({ 'a': 1, 'b': 2 }); const actual = merge(localContent, remoteContent, localContent, ['a'], [], formattingOptions); - assert.equal(actual.localContent, null); - assert.equal(actual.remoteContent, null); - assert.equal(actual.conflictsSettings.length, 0); + assert.strictEqual(actual.localContent, null); + assert.strictEqual(actual.remoteContent, null); + assert.strictEqual(actual.conflictsSettings.length, 0); assert.ok(!actual.hasConflicts); }); @@ -637,9 +637,9 @@ suite('SettingsMerge - Merge', () => { const localContent = stringify({ 'a': 1 }); const remoteContent = stringify({}); const actual = merge(localContent, remoteContent, null, ['a'], [], formattingOptions); - assert.equal(actual.localContent, null); - assert.equal(actual.remoteContent, null); - assert.equal(actual.conflictsSettings.length, 0); + assert.strictEqual(actual.localContent, null); + assert.strictEqual(actual.remoteContent, null); + assert.strictEqual(actual.conflictsSettings.length, 0); assert.ok(!actual.hasConflicts); }); @@ -647,9 +647,9 @@ suite('SettingsMerge - Merge', () => { const localContent = stringify({ 'a': 2 }); const remoteContent = stringify({}); const actual = merge(localContent, remoteContent, localContent, ['a'], [], formattingOptions); - assert.equal(actual.localContent, null); - assert.equal(actual.remoteContent, null); - assert.equal(actual.conflictsSettings.length, 0); + assert.strictEqual(actual.localContent, null); + assert.strictEqual(actual.remoteContent, null); + assert.strictEqual(actual.conflictsSettings.length, 0); assert.ok(!actual.hasConflicts); }); @@ -673,16 +673,16 @@ suite('SettingsMerge - Merge', () => { 'e': 6, }); const actual = merge(localContent, remoteContent, baseContent, ['a', 'e'], [], formattingOptions); - assert.equal(actual.localContent, stringify({ + assert.strictEqual(actual.localContent, stringify({ 'a': 1, 'b': 3, })); - assert.equal(actual.remoteContent, stringify({ + assert.strictEqual(actual.remoteContent, stringify({ 'a': 3, 'b': 3, 'e': 6, })); - assert.equal(actual.conflictsSettings.length, 0); + assert.strictEqual(actual.conflictsSettings.length, 0); assert.ok(!actual.hasConflicts); }); @@ -710,17 +710,17 @@ suite('SettingsMerge - Merge', () => { { key: 'b', localValue: 4, remoteValue: 3 }, ]; const actual = merge(localContent, remoteContent, baseContent, ['a', 'e'], [], formattingOptions); - assert.equal(actual.localContent, stringify({ + assert.strictEqual(actual.localContent, stringify({ 'a': 1, 'b': 4, 'd': 5, })); - assert.equal(actual.remoteContent, stringify({ + assert.strictEqual(actual.remoteContent, stringify({ 'a': 3, 'b': 3, 'e': 6, })); - assert.deepEqual(actual.conflictsSettings, expectedConflicts); + assert.deepStrictEqual(actual.conflictsSettings, expectedConflicts); assert.ok(actual.hasConflicts); }); @@ -735,9 +735,9 @@ suite('SettingsMerge - Merge', () => { "a": 1, }`; const actual = merge(localContent, remoteContent, null, [], [], formattingOptions); - assert.equal(actual.localContent, remoteContent); - assert.equal(actual.remoteContent, null); - assert.equal(actual.conflictsSettings.length, 0); + assert.strictEqual(actual.localContent, remoteContent); + assert.strictEqual(actual.remoteContent, null); + assert.strictEqual(actual.conflictsSettings.length, 0); assert.ok(!actual.hasConflicts); }); }); @@ -757,7 +757,7 @@ suite('SettingsMerge - Compute Remote Content', () => { 'e': 6, }); const actual = updateIgnoredSettings(localContent, remoteContent, [], formattingOptions); - assert.equal(actual, localContent); + assert.strictEqual(actual, localContent); }); test('ignored settings are not updated from remote content', async () => { @@ -778,7 +778,7 @@ suite('SettingsMerge - Compute Remote Content', () => { 'c': 3, }); const actual = updateIgnoredSettings(localContent, remoteContent, ['a'], formattingOptions); - assert.equal(actual, expected); + assert.strictEqual(actual, expected); }); }); @@ -808,7 +808,7 @@ suite('SettingsMerge - Add Setting', () => { const actual = addSetting('b', sourceContent, targetContent, formattingOptions); - assert.equal(actual, expected); + assert.strictEqual(actual, expected); }); test('Insert after a setting without comments at the end', () => { @@ -832,7 +832,7 @@ suite('SettingsMerge - Add Setting', () => { const actual = addSetting('b', sourceContent, targetContent, formattingOptions); - assert.equal(actual, expected); + assert.strictEqual(actual, expected); }); test('Insert between settings without comment', () => { @@ -858,7 +858,7 @@ suite('SettingsMerge - Add Setting', () => { const actual = addSetting('b', sourceContent, targetContent, formattingOptions); - assert.equal(actual, expected); + assert.strictEqual(actual, expected); }); test('Insert between settings and there is a comment in between in source', () => { @@ -885,7 +885,7 @@ suite('SettingsMerge - Add Setting', () => { const actual = addSetting('b', sourceContent, targetContent, formattingOptions); - assert.equal(actual, expected); + assert.strictEqual(actual, expected); }); test('Insert after a setting and after a comment at the end', () => { @@ -911,7 +911,7 @@ suite('SettingsMerge - Add Setting', () => { const actual = addSetting('b', sourceContent, targetContent, formattingOptions); - assert.equal(actual, expected); + assert.strictEqual(actual, expected); }); test('Insert after a setting ending with comma and after a comment at the end', () => { @@ -937,7 +937,7 @@ suite('SettingsMerge - Add Setting', () => { const actual = addSetting('b', sourceContent, targetContent, formattingOptions); - assert.equal(actual, expected); + assert.strictEqual(actual, expected); }); test('Insert after a comment and there are no settings', () => { @@ -960,7 +960,7 @@ suite('SettingsMerge - Add Setting', () => { const actual = addSetting('b', sourceContent, targetContent, formattingOptions); - assert.equal(actual, expected); + assert.strictEqual(actual, expected); }); test('Insert after a setting and between a comment and setting', () => { @@ -989,7 +989,7 @@ suite('SettingsMerge - Add Setting', () => { const actual = addSetting('b', sourceContent, targetContent, formattingOptions); - assert.equal(actual, expected); + assert.strictEqual(actual, expected); }); test('Insert after a setting between two comments and there is a setting after', () => { @@ -1021,7 +1021,7 @@ suite('SettingsMerge - Add Setting', () => { const actual = addSetting('b', sourceContent, targetContent, formattingOptions); - assert.equal(actual, expected); + assert.strictEqual(actual, expected); }); test('Insert after a setting between two comments on the same line and there is a setting after', () => { @@ -1051,7 +1051,7 @@ suite('SettingsMerge - Add Setting', () => { const actual = addSetting('b', sourceContent, targetContent, formattingOptions); - assert.equal(actual, expected); + assert.strictEqual(actual, expected); }); test('Insert after a setting between two line comments on the same line and there is a setting after', () => { @@ -1081,7 +1081,7 @@ suite('SettingsMerge - Add Setting', () => { const actual = addSetting('b', sourceContent, targetContent, formattingOptions); - assert.equal(actual, expected); + assert.strictEqual(actual, expected); }); test('Insert after a setting between two comments and there is no setting after', () => { @@ -1110,7 +1110,7 @@ suite('SettingsMerge - Add Setting', () => { const actual = addSetting('b', sourceContent, targetContent, formattingOptions); - assert.equal(actual, expected); + assert.strictEqual(actual, expected); }); test('Insert after a setting with comma and between two comments and there is no setting after', () => { @@ -1139,7 +1139,7 @@ suite('SettingsMerge - Add Setting', () => { const actual = addSetting('b', sourceContent, targetContent, formattingOptions); - assert.equal(actual, expected); + assert.strictEqual(actual, expected); }); test('Insert before a setting without comments', () => { @@ -1164,7 +1164,7 @@ suite('SettingsMerge - Add Setting', () => { const actual = addSetting('b', sourceContent, targetContent, formattingOptions); - assert.equal(actual, expected); + assert.strictEqual(actual, expected); }); test('Insert before a setting without comments at the end', () => { @@ -1188,7 +1188,7 @@ suite('SettingsMerge - Add Setting', () => { const actual = addSetting('b', sourceContent, targetContent, formattingOptions); - assert.equal(actual, expected); + assert.strictEqual(actual, expected); }); test('Insert before a setting with comment', () => { @@ -1215,7 +1215,7 @@ suite('SettingsMerge - Add Setting', () => { const actual = addSetting('b', sourceContent, targetContent, formattingOptions); - assert.equal(actual, expected); + assert.strictEqual(actual, expected); }); test('Insert before a setting and before a comment at the beginning', () => { @@ -1241,7 +1241,7 @@ suite('SettingsMerge - Add Setting', () => { const actual = addSetting('b', sourceContent, targetContent, formattingOptions); - assert.equal(actual, expected); + assert.strictEqual(actual, expected); }); test('Insert before a setting ending with comma and before a comment at the begninning', () => { @@ -1267,7 +1267,7 @@ suite('SettingsMerge - Add Setting', () => { const actual = addSetting('b', sourceContent, targetContent, formattingOptions); - assert.equal(actual, expected); + assert.strictEqual(actual, expected); }); test('Insert before a setting and between a setting and comment', () => { @@ -1296,7 +1296,7 @@ suite('SettingsMerge - Add Setting', () => { const actual = addSetting('b', sourceContent, targetContent, formattingOptions); - assert.equal(actual, expected); + assert.strictEqual(actual, expected); }); test('Insert before a setting between two comments and there is a setting before', () => { @@ -1328,7 +1328,7 @@ suite('SettingsMerge - Add Setting', () => { const actual = addSetting('b', sourceContent, targetContent, formattingOptions); - assert.equal(actual, expected); + assert.strictEqual(actual, expected); }); test('Insert before a setting between two comments on the same line and there is a setting before', () => { @@ -1359,7 +1359,7 @@ suite('SettingsMerge - Add Setting', () => { const actual = addSetting('b', sourceContent, targetContent, formattingOptions); - assert.equal(actual, expected); + assert.strictEqual(actual, expected); }); test('Insert before a setting between two line comments on the same line and there is a setting before', () => { @@ -1389,7 +1389,7 @@ suite('SettingsMerge - Add Setting', () => { const actual = addSetting('b', sourceContent, targetContent, formattingOptions); - assert.equal(actual, expected); + assert.strictEqual(actual, expected); }); test('Insert before a setting between two comments and there is no setting before', () => { @@ -1418,7 +1418,7 @@ suite('SettingsMerge - Add Setting', () => { const actual = addSetting('b', sourceContent, targetContent, formattingOptions); - assert.equal(actual, expected); + assert.strictEqual(actual, expected); }); test('Insert before a setting with comma and between two comments and there is no setting before', () => { @@ -1447,7 +1447,7 @@ suite('SettingsMerge - Add Setting', () => { const actual = addSetting('b', sourceContent, targetContent, formattingOptions); - assert.equal(actual, expected); + assert.strictEqual(actual, expected); }); test('Insert after a setting that is of object type', () => { @@ -1470,7 +1470,7 @@ suite('SettingsMerge - Add Setting', () => { const actual = addSetting('a', sourceContent, targetContent, formattingOptions); - assert.equal(actual, sourceContent); + assert.strictEqual(actual, sourceContent); }); test('Insert after a setting that is of array type', () => { @@ -1493,7 +1493,7 @@ suite('SettingsMerge - Add Setting', () => { const actual = addSetting('a', sourceContent, targetContent, formattingOptions); - assert.equal(actual, sourceContent); + assert.strictEqual(actual, sourceContent); }); test('Insert after a comment with comma separator of previous setting and no next nodes ', () => { @@ -1522,7 +1522,7 @@ suite('SettingsMerge - Add Setting', () => { const actual = addSetting('b', sourceContent, targetContent, formattingOptions); - assert.equal(actual, expected); + assert.strictEqual(actual, expected); }); test('Insert after a comment with comma separator of previous setting and there is a setting after ', () => { @@ -1554,7 +1554,7 @@ suite('SettingsMerge - Add Setting', () => { const actual = addSetting('b', sourceContent, targetContent, formattingOptions); - assert.equal(actual, expected); + assert.strictEqual(actual, expected); }); test('Insert after a comment with comma separator of previous setting and there is a comment after ', () => { @@ -1586,7 +1586,7 @@ suite('SettingsMerge - Add Setting', () => { const actual = addSetting('b', sourceContent, targetContent, formattingOptions); - assert.equal(actual, expected); + assert.strictEqual(actual, expected); }); }); diff --git a/src/vs/platform/userDataSync/test/common/settingsSync.test.ts b/src/vs/platform/userDataSync/test/common/settingsSync.test.ts index e83d8a6a..2f732f1d 100644 --- a/src/vs/platform/userDataSync/test/common/settingsSync.test.ts +++ b/src/vs/platform/userDataSync/test/common/settingsSync.test.ts @@ -52,31 +52,31 @@ suite('SettingsSync - Auto', () => { const fileService = client.instantiationService.get(IFileService); const settingResource = client.instantiationService.get(IEnvironmentService).settingsResource; - assert.deepEqual(await testObject.getLastSyncUserData(), null); + assert.deepStrictEqual(await testObject.getLastSyncUserData(), null); let manifest = await client.manifest(); server.reset(); await testObject.sync(manifest); - assert.deepEqual(server.requests, [ + assert.deepStrictEqual(server.requests, [ { type: 'GET', url: `${server.url}/v1/resource/${testObject.resource}/latest`, headers: {} }, ]); assert.ok(!await fileService.exists(settingResource)); const lastSyncUserData = await testObject.getLastSyncUserData(); const remoteUserData = await testObject.getRemoteUserData(null); - assert.deepEqual(lastSyncUserData!.ref, remoteUserData.ref); - assert.deepEqual(lastSyncUserData!.syncData, remoteUserData.syncData); - assert.equal(lastSyncUserData!.syncData, null); + assert.deepStrictEqual(lastSyncUserData!.ref, remoteUserData.ref); + assert.deepStrictEqual(lastSyncUserData!.syncData, remoteUserData.syncData); + assert.strictEqual(lastSyncUserData!.syncData, null); manifest = await client.manifest(); server.reset(); await testObject.sync(manifest); - assert.deepEqual(server.requests, []); + assert.deepStrictEqual(server.requests, []); manifest = await client.manifest(); server.reset(); await testObject.sync(manifest); - assert.deepEqual(server.requests, []); + assert.deepStrictEqual(server.requests, []); }); test('when settings file is empty and remote has no changes', async () => { @@ -88,9 +88,9 @@ suite('SettingsSync - Auto', () => { const lastSyncUserData = await testObject.getLastSyncUserData(); const remoteUserData = await testObject.getRemoteUserData(null); - assert.equal(parseSettingsSyncContent(lastSyncUserData!.syncData!.content!)?.settings, '{}'); - assert.equal(parseSettingsSyncContent(remoteUserData!.syncData!.content!)?.settings, '{}'); - assert.equal((await fileService.readFile(settingsResource)).value.toString(), ''); + assert.strictEqual(parseSettingsSyncContent(lastSyncUserData!.syncData!.content!)?.settings, '{}'); + assert.strictEqual(parseSettingsSyncContent(remoteUserData!.syncData!.content!)?.settings, '{}'); + assert.strictEqual((await fileService.readFile(settingsResource)).value.toString(), ''); }); test('when settings file is empty and remote has changes', async () => { @@ -129,9 +129,9 @@ suite('SettingsSync - Auto', () => { const lastSyncUserData = await testObject.getLastSyncUserData(); const remoteUserData = await testObject.getRemoteUserData(null); - assert.equal(parseSettingsSyncContent(lastSyncUserData!.syncData!.content!)?.settings, content); - assert.equal(parseSettingsSyncContent(remoteUserData!.syncData!.content!)?.settings, content); - assert.equal((await fileService.readFile(settingsResource)).value.toString(), content); + assert.strictEqual(parseSettingsSyncContent(lastSyncUserData!.syncData!.content!)?.settings, content); + assert.strictEqual(parseSettingsSyncContent(remoteUserData!.syncData!.content!)?.settings, content); + assert.strictEqual((await fileService.readFile(settingsResource)).value.toString(), content); }); test('when settings file is created after first sync', async () => { @@ -146,15 +146,15 @@ suite('SettingsSync - Auto', () => { server.reset(); await testObject.sync(manifest); - assert.deepEqual(server.requests, [ + assert.deepStrictEqual(server.requests, [ { type: 'POST', url: `${server.url}/v1/resource/${testObject.resource}`, headers: { 'If-Match': lastSyncUserData?.ref } }, ]); lastSyncUserData = await testObject.getLastSyncUserData(); const remoteUserData = await testObject.getRemoteUserData(null); - assert.deepEqual(lastSyncUserData!.ref, remoteUserData.ref); - assert.deepEqual(lastSyncUserData!.syncData, remoteUserData.syncData); - assert.equal(parseSettingsSyncContent(lastSyncUserData!.syncData!.content!)?.settings, '{}'); + assert.deepStrictEqual(lastSyncUserData!.ref, remoteUserData.ref); + assert.deepStrictEqual(lastSyncUserData!.syncData, remoteUserData.syncData); + assert.strictEqual(parseSettingsSyncContent(lastSyncUserData!.syncData!.content!)?.settings, '{}'); }); test('sync for first time to the server', async () => { @@ -187,7 +187,7 @@ suite('SettingsSync - Auto', () => { const { content } = await client.read(testObject.resource); assert.ok(content !== null); const actual = parseSettings(content!); - assert.deepEqual(actual, expected); + assert.deepStrictEqual(actual, expected); }); test('do not sync machine settings', async () => { @@ -211,7 +211,7 @@ suite('SettingsSync - Auto', () => { const { content } = await client.read(testObject.resource); assert.ok(content !== null); const actual = parseSettings(content!); - assert.deepEqual(actual, `{ + assert.deepStrictEqual(actual, `{ // Always "files.autoSave": "afterDelay", "files.simpleDialog.enable": true, @@ -242,7 +242,7 @@ suite('SettingsSync - Auto', () => { const { content } = await client.read(testObject.resource); assert.ok(content !== null); const actual = parseSettings(content!); - assert.deepEqual(actual, `{ + assert.deepStrictEqual(actual, `{ // Always "files.autoSave": "afterDelay", "files.simpleDialog.enable": true, @@ -273,7 +273,7 @@ suite('SettingsSync - Auto', () => { const { content } = await client.read(testObject.resource); assert.ok(content !== null); const actual = parseSettings(content!); - assert.deepEqual(actual, `{ + assert.deepStrictEqual(actual, `{ // Always "files.autoSave": "afterDelay", @@ -297,7 +297,7 @@ suite('SettingsSync - Auto', () => { const { content } = await client.read(testObject.resource); assert.ok(content !== null); const actual = parseSettings(content!); - assert.deepEqual(actual, `{ + assert.deepStrictEqual(actual, `{ }`); }); @@ -315,7 +315,7 @@ suite('SettingsSync - Auto', () => { const { content } = await client.read(testObject.resource); assert.ok(content !== null); const actual = parseSettings(content!); - assert.deepEqual(actual, `{ + assert.deepStrictEqual(actual, `{ , }`); }); @@ -367,7 +367,7 @@ suite('SettingsSync - Auto', () => { const { content } = await client.read(testObject.resource); assert.ok(content !== null); const actual = parseSettings(content!); - assert.deepEqual(actual, `{ + assert.deepStrictEqual(actual, `{ // Always "files.autoSave": "afterDelay", "files.simpleDialog.enable": true, @@ -415,7 +415,7 @@ suite('SettingsSync - Auto', () => { const { content } = await client.read(testObject.resource); assert.ok(content !== null); const actual = parseSettings(content!); - assert.deepEqual(actual, `{ + assert.deepStrictEqual(actual, `{ // Always "files.autoSave": "afterDelay", "files.simpleDialog.enable": true, @@ -462,7 +462,7 @@ suite('SettingsSync - Auto', () => { assert.fail('should fail with invalid content error'); } catch (e) { assert.ok(e instanceof UserDataSyncError); - assert.deepEqual((e).code, UserDataSyncErrorCode.LocalInvalidContent); + assert.deepStrictEqual((e).code, UserDataSyncErrorCode.LocalInvalidContent); } }); @@ -483,12 +483,12 @@ suite('SettingsSync - Auto', () => { }), client); await testObject.sync(await client.manifest()); - assert.equal(testObject.status, SyncStatus.HasConflicts); - assert.equal(testObject.conflicts[0].localResource.toString(), testObject.localResource); + assert.strictEqual(testObject.status, SyncStatus.HasConflicts); + assert.strictEqual(testObject.conflicts[0].localResource.toString(), testObject.localResource.toString()); const fileService = client.instantiationService.get(IFileService); const mergeContent = (await fileService.readFile(testObject.conflicts[0].previewResource)).value.toString(); - assert.deepEqual(JSON.parse(mergeContent), { + assert.deepStrictEqual(JSON.parse(mergeContent), { 'b': 1, 'settingsSync.ignoredSettings': ['a'] }); @@ -537,14 +537,14 @@ suite('SettingsSync - Manual', () => { await updateSettings(settingsContent, client); let preview = await testObject.preview(await client.manifest()); - assert.equal(testObject.status, SyncStatus.Syncing); + assert.strictEqual(testObject.status, SyncStatus.Syncing); preview = await testObject.accept(preview!.resourcePreviews[0].previewResource); preview = await testObject.apply(false); const { content } = await client.read(testObject.resource); assert.ok(content !== null); const actual = parseSettings(content!); - assert.deepEqual(actual, `{ + assert.deepStrictEqual(actual, `{ // Always "files.autoSave": "afterDelay", "files.simpleDialog.enable": true, diff --git a/src/vs/platform/userDataSync/test/common/snippetsMerge.test.ts b/src/vs/platform/userDataSync/test/common/snippetsMerge.test.ts index 55b33d2d..e175e69d 100644 --- a/src/vs/platform/userDataSync/test/common/snippetsMerge.test.ts +++ b/src/vs/platform/userDataSync/test/common/snippetsMerge.test.ts @@ -116,13 +116,13 @@ suite('SnippetsMerge', () => { const actual = merge(local, remote, null); - assert.deepEqual(actual.local.added, {}); - assert.deepEqual(actual.local.updated, {}); - assert.deepEqual(actual.local.removed, []); - assert.deepEqual(actual.conflicts, []); - assert.deepEqual(actual.remote.added, {}); - assert.deepEqual(actual.remote.updated, {}); - assert.deepEqual(actual.remote.removed, []); + assert.deepStrictEqual(actual.local.added, {}); + assert.deepStrictEqual(actual.local.updated, {}); + assert.deepStrictEqual(actual.local.removed, []); + assert.deepStrictEqual(actual.conflicts, []); + assert.deepStrictEqual(actual.remote.added, {}); + assert.deepStrictEqual(actual.remote.updated, {}); + assert.deepStrictEqual(actual.remote.removed, []); }); test('merge when local and remote are same with multiple entries', async () => { @@ -131,13 +131,13 @@ suite('SnippetsMerge', () => { const actual = merge(local, remote, null); - assert.deepEqual(actual.local.added, {}); - assert.deepEqual(actual.local.updated, {}); - assert.deepEqual(actual.local.removed, []); - assert.deepEqual(actual.conflicts, []); - assert.deepEqual(actual.remote.added, {}); - assert.deepEqual(actual.remote.updated, {}); - assert.deepEqual(actual.remote.removed, []); + assert.deepStrictEqual(actual.local.added, {}); + assert.deepStrictEqual(actual.local.updated, {}); + assert.deepStrictEqual(actual.local.removed, []); + assert.deepStrictEqual(actual.conflicts, []); + assert.deepStrictEqual(actual.remote.added, {}); + assert.deepStrictEqual(actual.remote.updated, {}); + assert.deepStrictEqual(actual.remote.removed, []); }); test('merge when local and remote are same with multiple entries in different order', async () => { @@ -146,13 +146,13 @@ suite('SnippetsMerge', () => { const actual = merge(local, remote, null); - assert.deepEqual(actual.local.added, {}); - assert.deepEqual(actual.local.updated, {}); - assert.deepEqual(actual.local.removed, []); - assert.deepEqual(actual.conflicts, []); - assert.deepEqual(actual.remote.added, {}); - assert.deepEqual(actual.remote.updated, {}); - assert.deepEqual(actual.remote.removed, []); + assert.deepStrictEqual(actual.local.added, {}); + assert.deepStrictEqual(actual.local.updated, {}); + assert.deepStrictEqual(actual.local.removed, []); + assert.deepStrictEqual(actual.conflicts, []); + assert.deepStrictEqual(actual.remote.added, {}); + assert.deepStrictEqual(actual.remote.updated, {}); + assert.deepStrictEqual(actual.remote.removed, []); }); test('merge when local and remote are same with different base content', async () => { @@ -162,13 +162,13 @@ suite('SnippetsMerge', () => { const actual = merge(local, remote, base); - assert.deepEqual(actual.local.added, {}); - assert.deepEqual(actual.local.updated, {}); - assert.deepEqual(actual.local.removed, []); - assert.deepEqual(actual.conflicts, []); - assert.deepEqual(actual.remote.added, {}); - assert.deepEqual(actual.remote.updated, {}); - assert.deepEqual(actual.remote.removed, []); + assert.deepStrictEqual(actual.local.added, {}); + assert.deepStrictEqual(actual.local.updated, {}); + assert.deepStrictEqual(actual.local.removed, []); + assert.deepStrictEqual(actual.conflicts, []); + assert.deepStrictEqual(actual.remote.added, {}); + assert.deepStrictEqual(actual.remote.updated, {}); + assert.deepStrictEqual(actual.remote.removed, []); }); test('merge when a new entry is added to remote', async () => { @@ -177,13 +177,13 @@ suite('SnippetsMerge', () => { const actual = merge(local, remote, null); - assert.deepEqual(actual.local.added, { 'typescript.json': tsSnippet1 }); - assert.deepEqual(actual.local.updated, {}); - assert.deepEqual(actual.local.removed, []); - assert.deepEqual(actual.conflicts, []); - assert.deepEqual(actual.remote.added, {}); - assert.deepEqual(actual.remote.updated, {}); - assert.deepEqual(actual.remote.removed, []); + assert.deepStrictEqual(actual.local.added, { 'typescript.json': tsSnippet1 }); + assert.deepStrictEqual(actual.local.updated, {}); + assert.deepStrictEqual(actual.local.removed, []); + assert.deepStrictEqual(actual.conflicts, []); + assert.deepStrictEqual(actual.remote.added, {}); + assert.deepStrictEqual(actual.remote.updated, {}); + assert.deepStrictEqual(actual.remote.removed, []); }); test('merge when multiple new entries are added to remote', async () => { @@ -192,13 +192,13 @@ suite('SnippetsMerge', () => { const actual = merge(local, remote, null); - assert.deepEqual(actual.local.added, remote); - assert.deepEqual(actual.local.updated, {}); - assert.deepEqual(actual.local.removed, []); - assert.deepEqual(actual.conflicts, []); - assert.deepEqual(actual.remote.added, {}); - assert.deepEqual(actual.remote.updated, {}); - assert.deepEqual(actual.remote.removed, []); + assert.deepStrictEqual(actual.local.added, remote); + assert.deepStrictEqual(actual.local.updated, {}); + assert.deepStrictEqual(actual.local.removed, []); + assert.deepStrictEqual(actual.conflicts, []); + assert.deepStrictEqual(actual.remote.added, {}); + assert.deepStrictEqual(actual.remote.updated, {}); + assert.deepStrictEqual(actual.remote.removed, []); }); test('merge when new entry is added to remote from base and local has not changed', async () => { @@ -207,13 +207,13 @@ suite('SnippetsMerge', () => { const actual = merge(local, remote, local); - assert.deepEqual(actual.local.added, { 'typescript.json': tsSnippet1 }); - assert.deepEqual(actual.local.updated, {}); - assert.deepEqual(actual.local.removed, []); - assert.deepEqual(actual.conflicts, []); - assert.deepEqual(actual.remote.added, {}); - assert.deepEqual(actual.remote.updated, {}); - assert.deepEqual(actual.remote.removed, []); + assert.deepStrictEqual(actual.local.added, { 'typescript.json': tsSnippet1 }); + assert.deepStrictEqual(actual.local.updated, {}); + assert.deepStrictEqual(actual.local.removed, []); + assert.deepStrictEqual(actual.conflicts, []); + assert.deepStrictEqual(actual.remote.added, {}); + assert.deepStrictEqual(actual.remote.updated, {}); + assert.deepStrictEqual(actual.remote.removed, []); }); test('merge when an entry is removed from remote from base and local has not changed', async () => { @@ -222,13 +222,13 @@ suite('SnippetsMerge', () => { const actual = merge(local, remote, local); - assert.deepEqual(actual.local.added, {}); - assert.deepEqual(actual.local.updated, {}); - assert.deepEqual(actual.local.removed, ['typescript.json']); - assert.deepEqual(actual.conflicts, []); - assert.deepEqual(actual.remote.added, {}); - assert.deepEqual(actual.remote.updated, {}); - assert.deepEqual(actual.remote.removed, []); + assert.deepStrictEqual(actual.local.added, {}); + assert.deepStrictEqual(actual.local.updated, {}); + assert.deepStrictEqual(actual.local.removed, ['typescript.json']); + assert.deepStrictEqual(actual.conflicts, []); + assert.deepStrictEqual(actual.remote.added, {}); + assert.deepStrictEqual(actual.remote.updated, {}); + assert.deepStrictEqual(actual.remote.removed, []); }); test('merge when all entries are removed from base and local has not changed', async () => { @@ -237,13 +237,13 @@ suite('SnippetsMerge', () => { const actual = merge(local, remote, local); - assert.deepEqual(actual.local.added, {}); - assert.deepEqual(actual.local.updated, {}); - assert.deepEqual(actual.local.removed, ['html.json', 'typescript.json']); - assert.deepEqual(actual.conflicts, []); - assert.deepEqual(actual.remote.added, {}); - assert.deepEqual(actual.remote.updated, {}); - assert.deepEqual(actual.remote.removed, []); + assert.deepStrictEqual(actual.local.added, {}); + assert.deepStrictEqual(actual.local.updated, {}); + assert.deepStrictEqual(actual.local.removed, ['html.json', 'typescript.json']); + assert.deepStrictEqual(actual.conflicts, []); + assert.deepStrictEqual(actual.remote.added, {}); + assert.deepStrictEqual(actual.remote.updated, {}); + assert.deepStrictEqual(actual.remote.removed, []); }); test('merge when an entry is updated in remote from base and local has not changed', async () => { @@ -252,13 +252,13 @@ suite('SnippetsMerge', () => { const actual = merge(local, remote, local); - assert.deepEqual(actual.local.added, {}); - assert.deepEqual(actual.local.updated, { 'html.json': htmlSnippet2 }); - assert.deepEqual(actual.local.removed, []); - assert.deepEqual(actual.conflicts, []); - assert.deepEqual(actual.remote.added, {}); - assert.deepEqual(actual.remote.updated, {}); - assert.deepEqual(actual.remote.removed, []); + assert.deepStrictEqual(actual.local.added, {}); + assert.deepStrictEqual(actual.local.updated, { 'html.json': htmlSnippet2 }); + assert.deepStrictEqual(actual.local.removed, []); + assert.deepStrictEqual(actual.conflicts, []); + assert.deepStrictEqual(actual.remote.added, {}); + assert.deepStrictEqual(actual.remote.updated, {}); + assert.deepStrictEqual(actual.remote.removed, []); }); test('merge when remote has moved forwarded with multiple changes and local stays with base', async () => { @@ -267,13 +267,13 @@ suite('SnippetsMerge', () => { const actual = merge(local, remote, local); - assert.deepEqual(actual.local.added, { 'c.json': cSnippet }); - assert.deepEqual(actual.local.updated, { 'html.json': htmlSnippet2 }); - assert.deepEqual(actual.local.removed, ['typescript.json']); - assert.deepEqual(actual.conflicts, []); - assert.deepEqual(actual.remote.added, {}); - assert.deepEqual(actual.remote.updated, {}); - assert.deepEqual(actual.remote.removed, []); + assert.deepStrictEqual(actual.local.added, { 'c.json': cSnippet }); + assert.deepStrictEqual(actual.local.updated, { 'html.json': htmlSnippet2 }); + assert.deepStrictEqual(actual.local.removed, ['typescript.json']); + assert.deepStrictEqual(actual.conflicts, []); + assert.deepStrictEqual(actual.remote.added, {}); + assert.deepStrictEqual(actual.remote.updated, {}); + assert.deepStrictEqual(actual.remote.removed, []); }); test('merge when a new entries are added to local', async () => { @@ -282,13 +282,13 @@ suite('SnippetsMerge', () => { const actual = merge(local, remote, null); - assert.deepEqual(actual.local.added, {}); - assert.deepEqual(actual.local.updated, {}); - assert.deepEqual(actual.local.removed, []); - assert.deepEqual(actual.conflicts, []); - assert.deepEqual(actual.remote.added, { 'c.json': cSnippet }); - assert.deepEqual(actual.remote.updated, {}); - assert.deepEqual(actual.remote.removed, []); + assert.deepStrictEqual(actual.local.added, {}); + assert.deepStrictEqual(actual.local.updated, {}); + assert.deepStrictEqual(actual.local.removed, []); + assert.deepStrictEqual(actual.conflicts, []); + assert.deepStrictEqual(actual.remote.added, { 'c.json': cSnippet }); + assert.deepStrictEqual(actual.remote.updated, {}); + assert.deepStrictEqual(actual.remote.removed, []); }); test('merge when multiple new entries are added to local from base and remote is not changed', async () => { @@ -297,13 +297,13 @@ suite('SnippetsMerge', () => { const actual = merge(local, remote, remote); - assert.deepEqual(actual.local.added, {}); - assert.deepEqual(actual.local.updated, {}); - assert.deepEqual(actual.local.removed, []); - assert.deepEqual(actual.conflicts, []); - assert.deepEqual(actual.remote.added, { 'html.json': htmlSnippet1, 'c.json': cSnippet }); - assert.deepEqual(actual.remote.updated, {}); - assert.deepEqual(actual.remote.removed, []); + assert.deepStrictEqual(actual.local.added, {}); + assert.deepStrictEqual(actual.local.updated, {}); + assert.deepStrictEqual(actual.local.removed, []); + assert.deepStrictEqual(actual.conflicts, []); + assert.deepStrictEqual(actual.remote.added, { 'html.json': htmlSnippet1, 'c.json': cSnippet }); + assert.deepStrictEqual(actual.remote.updated, {}); + assert.deepStrictEqual(actual.remote.removed, []); }); test('merge when an entry is removed from local from base and remote has not changed', async () => { @@ -312,13 +312,13 @@ suite('SnippetsMerge', () => { const actual = merge(local, remote, remote); - assert.deepEqual(actual.local.added, {}); - assert.deepEqual(actual.local.updated, {}); - assert.deepEqual(actual.local.removed, []); - assert.deepEqual(actual.conflicts, []); - assert.deepEqual(actual.remote.added, {}); - assert.deepEqual(actual.remote.updated, {}); - assert.deepEqual(actual.remote.removed, ['typescript.json']); + assert.deepStrictEqual(actual.local.added, {}); + assert.deepStrictEqual(actual.local.updated, {}); + assert.deepStrictEqual(actual.local.removed, []); + assert.deepStrictEqual(actual.conflicts, []); + assert.deepStrictEqual(actual.remote.added, {}); + assert.deepStrictEqual(actual.remote.updated, {}); + assert.deepStrictEqual(actual.remote.removed, ['typescript.json']); }); test('merge when an entry is updated in local from base and remote has not changed', async () => { @@ -327,13 +327,13 @@ suite('SnippetsMerge', () => { const actual = merge(local, remote, remote); - assert.deepEqual(actual.local.added, {}); - assert.deepEqual(actual.local.updated, {}); - assert.deepEqual(actual.local.removed, []); - assert.deepEqual(actual.conflicts, []); - assert.deepEqual(actual.remote.added, {}); - assert.deepEqual(actual.remote.updated, { 'html.json': htmlSnippet2 }); - assert.deepEqual(actual.remote.removed, []); + assert.deepStrictEqual(actual.local.added, {}); + assert.deepStrictEqual(actual.local.updated, {}); + assert.deepStrictEqual(actual.local.removed, []); + assert.deepStrictEqual(actual.conflicts, []); + assert.deepStrictEqual(actual.remote.added, {}); + assert.deepStrictEqual(actual.remote.updated, { 'html.json': htmlSnippet2 }); + assert.deepStrictEqual(actual.remote.removed, []); }); test('merge when local has moved forwarded with multiple changes and remote stays with base', async () => { @@ -342,13 +342,13 @@ suite('SnippetsMerge', () => { const actual = merge(local, remote, remote); - assert.deepEqual(actual.local.added, {}); - assert.deepEqual(actual.local.updated, {}); - assert.deepEqual(actual.local.removed, []); - assert.deepEqual(actual.conflicts, []); - assert.deepEqual(actual.remote.added, { 'c.json': cSnippet }); - assert.deepEqual(actual.remote.updated, { 'html.json': htmlSnippet2 }); - assert.deepEqual(actual.remote.removed, ['typescript.json']); + assert.deepStrictEqual(actual.local.added, {}); + assert.deepStrictEqual(actual.local.updated, {}); + assert.deepStrictEqual(actual.local.removed, []); + assert.deepStrictEqual(actual.conflicts, []); + assert.deepStrictEqual(actual.remote.added, { 'c.json': cSnippet }); + assert.deepStrictEqual(actual.remote.updated, { 'html.json': htmlSnippet2 }); + assert.deepStrictEqual(actual.remote.removed, ['typescript.json']); }); test('merge when local and remote with one entry but different value', async () => { @@ -357,13 +357,13 @@ suite('SnippetsMerge', () => { const actual = merge(local, remote, null); - assert.deepEqual(actual.local.added, {}); - assert.deepEqual(actual.local.updated, {}); - assert.deepEqual(actual.local.removed, []); - assert.deepEqual(actual.conflicts, ['html.json']); - assert.deepEqual(actual.remote.added, {}); - assert.deepEqual(actual.remote.updated, {}); - assert.deepEqual(actual.remote.removed, []); + assert.deepStrictEqual(actual.local.added, {}); + assert.deepStrictEqual(actual.local.updated, {}); + assert.deepStrictEqual(actual.local.removed, []); + assert.deepStrictEqual(actual.conflicts, ['html.json']); + assert.deepStrictEqual(actual.remote.added, {}); + assert.deepStrictEqual(actual.remote.updated, {}); + assert.deepStrictEqual(actual.remote.removed, []); }); test('merge when the entry is removed in remote but updated in local and a new entry is added in remote', async () => { @@ -373,13 +373,13 @@ suite('SnippetsMerge', () => { const actual = merge(local, remote, base); - assert.deepEqual(actual.local.added, { 'typescript.json': tsSnippet1 }); - assert.deepEqual(actual.local.updated, {}); - assert.deepEqual(actual.local.removed, []); - assert.deepEqual(actual.conflicts, ['html.json']); - assert.deepEqual(actual.remote.added, {}); - assert.deepEqual(actual.remote.updated, {}); - assert.deepEqual(actual.remote.removed, []); + assert.deepStrictEqual(actual.local.added, { 'typescript.json': tsSnippet1 }); + assert.deepStrictEqual(actual.local.updated, {}); + assert.deepStrictEqual(actual.local.removed, []); + assert.deepStrictEqual(actual.conflicts, ['html.json']); + assert.deepStrictEqual(actual.remote.added, {}); + assert.deepStrictEqual(actual.remote.updated, {}); + assert.deepStrictEqual(actual.remote.removed, []); }); test('merge with single entry and local is empty', async () => { @@ -389,13 +389,13 @@ suite('SnippetsMerge', () => { const actual = merge(local, remote, base); - assert.deepEqual(actual.local.added, { 'html.json': htmlSnippet2 }); - assert.deepEqual(actual.local.updated, {}); - assert.deepEqual(actual.local.removed, []); - assert.deepEqual(actual.conflicts, []); - assert.deepEqual(actual.remote.added, {}); - assert.deepEqual(actual.remote.updated, {}); - assert.deepEqual(actual.remote.removed, []); + assert.deepStrictEqual(actual.local.added, { 'html.json': htmlSnippet2 }); + assert.deepStrictEqual(actual.local.updated, {}); + assert.deepStrictEqual(actual.local.removed, []); + assert.deepStrictEqual(actual.conflicts, []); + assert.deepStrictEqual(actual.remote.added, {}); + assert.deepStrictEqual(actual.remote.updated, {}); + assert.deepStrictEqual(actual.remote.removed, []); }); test('merge when local and remote has moved forwareded with conflicts', async () => { @@ -405,13 +405,13 @@ suite('SnippetsMerge', () => { const actual = merge(local, remote, base); - assert.deepEqual(actual.local.added, { 'typescript.json': tsSnippet2 }); - assert.deepEqual(actual.local.updated, {}); - assert.deepEqual(actual.local.removed, []); - assert.deepEqual(actual.conflicts, ['html.json']); - assert.deepEqual(actual.remote.added, { 'c.json': cSnippet }); - assert.deepEqual(actual.remote.updated, {}); - assert.deepEqual(actual.remote.removed, []); + assert.deepStrictEqual(actual.local.added, { 'typescript.json': tsSnippet2 }); + assert.deepStrictEqual(actual.local.updated, {}); + assert.deepStrictEqual(actual.local.removed, []); + assert.deepStrictEqual(actual.conflicts, ['html.json']); + assert.deepStrictEqual(actual.remote.added, { 'c.json': cSnippet }); + assert.deepStrictEqual(actual.remote.updated, {}); + assert.deepStrictEqual(actual.remote.removed, []); }); test('merge when local and remote has moved forwareded with multiple conflicts', async () => { @@ -421,13 +421,13 @@ suite('SnippetsMerge', () => { const actual = merge(local, remote, base); - assert.deepEqual(actual.local.added, {}); - assert.deepEqual(actual.local.updated, {}); - assert.deepEqual(actual.local.removed, []); - assert.deepEqual(actual.conflicts, ['html.json', 'typescript.json']); - assert.deepEqual(actual.remote.added, {}); - assert.deepEqual(actual.remote.updated, {}); - assert.deepEqual(actual.remote.removed, []); + assert.deepStrictEqual(actual.local.added, {}); + assert.deepStrictEqual(actual.local.updated, {}); + assert.deepStrictEqual(actual.local.removed, []); + assert.deepStrictEqual(actual.conflicts, ['html.json', 'typescript.json']); + assert.deepStrictEqual(actual.remote.added, {}); + assert.deepStrictEqual(actual.remote.updated, {}); + assert.deepStrictEqual(actual.remote.removed, []); }); }); diff --git a/src/vs/platform/userDataSync/test/common/snippetsSync.test.ts b/src/vs/platform/userDataSync/test/common/snippetsSync.test.ts index 262fcf95..21899e94 100644 --- a/src/vs/platform/userDataSync/test/common/snippetsSync.test.ts +++ b/src/vs/platform/userDataSync/test/common/snippetsSync.test.ts @@ -171,31 +171,31 @@ suite('SnippetsSync', () => { const fileService = testClient.instantiationService.get(IFileService); const snippetsResource = testClient.instantiationService.get(IEnvironmentService).snippetsHome; - assert.deepEqual(await testObject.getLastSyncUserData(), null); + assert.deepStrictEqual(await testObject.getLastSyncUserData(), null); let manifest = await testClient.manifest(); server.reset(); await testObject.sync(manifest); - assert.deepEqual(server.requests, [ + assert.deepStrictEqual(server.requests, [ { type: 'GET', url: `${server.url}/v1/resource/${testObject.resource}/latest`, headers: {} }, ]); assert.ok(!await fileService.exists(snippetsResource)); const lastSyncUserData = await testObject.getLastSyncUserData(); const remoteUserData = await testObject.getRemoteUserData(null); - assert.deepEqual(lastSyncUserData!.ref, remoteUserData.ref); - assert.deepEqual(lastSyncUserData!.syncData, remoteUserData.syncData); - assert.equal(lastSyncUserData!.syncData, null); + assert.deepStrictEqual(lastSyncUserData!.ref, remoteUserData.ref); + assert.deepStrictEqual(lastSyncUserData!.syncData, remoteUserData.syncData); + assert.strictEqual(lastSyncUserData!.syncData, null); manifest = await testClient.manifest(); server.reset(); await testObject.sync(manifest); - assert.deepEqual(server.requests, []); + assert.deepStrictEqual(server.requests, []); manifest = await testClient.manifest(); server.reset(); await testObject.sync(manifest); - assert.deepEqual(server.requests, []); + assert.deepStrictEqual(server.requests, []); }); test('when snippet is created after first sync', async () => { @@ -207,15 +207,15 @@ suite('SnippetsSync', () => { server.reset(); await testObject.sync(manifest); - assert.deepEqual(server.requests, [ + assert.deepStrictEqual(server.requests, [ { type: 'POST', url: `${server.url}/v1/resource/${testObject.resource}`, headers: { 'If-Match': lastSyncUserData?.ref } }, ]); lastSyncUserData = await testObject.getLastSyncUserData(); const remoteUserData = await testObject.getRemoteUserData(null); - assert.deepEqual(lastSyncUserData!.ref, remoteUserData.ref); - assert.deepEqual(lastSyncUserData!.syncData, remoteUserData.syncData); - assert.deepEqual(lastSyncUserData!.syncData!.content, JSON.stringify({ 'html.json': htmlSnippet1 })); + assert.deepStrictEqual(lastSyncUserData!.ref, remoteUserData.ref); + assert.deepStrictEqual(lastSyncUserData!.syncData, remoteUserData.syncData); + assert.deepStrictEqual(lastSyncUserData!.syncData!.content, JSON.stringify({ 'html.json': htmlSnippet1 })); }); test('first time sync - outgoing to server (no snippets)', async () => { @@ -223,13 +223,13 @@ suite('SnippetsSync', () => { await updateSnippet('typescript.json', tsSnippet1, testClient); await testObject.sync(await testClient.manifest()); - assert.equal(testObject.status, SyncStatus.Idle); - assert.deepEqual(testObject.conflicts, []); + assert.strictEqual(testObject.status, SyncStatus.Idle); + assert.deepStrictEqual(testObject.conflicts, []); const { content } = await testClient.read(testObject.resource); assert.ok(content !== null); const actual = parseSnippets(content!); - assert.deepEqual(actual, { 'html.json': htmlSnippet1, 'typescript.json': tsSnippet1 }); + assert.deepStrictEqual(actual, { 'html.json': htmlSnippet1, 'typescript.json': tsSnippet1 }); }); test('first time sync - incoming from server (no snippets)', async () => { @@ -238,13 +238,13 @@ suite('SnippetsSync', () => { await client2.sync(); await testObject.sync(await testClient.manifest()); - assert.equal(testObject.status, SyncStatus.Idle); - assert.deepEqual(testObject.conflicts, []); + assert.strictEqual(testObject.status, SyncStatus.Idle); + assert.deepStrictEqual(testObject.conflicts, []); const actual1 = await readSnippet('html.json', testClient); - assert.equal(actual1, htmlSnippet1); + assert.strictEqual(actual1, htmlSnippet1); const actual2 = await readSnippet('typescript.json', testClient); - assert.equal(actual2, tsSnippet1); + assert.strictEqual(actual2, tsSnippet1); }); test('first time sync when snippets exists', async () => { @@ -253,18 +253,18 @@ suite('SnippetsSync', () => { await updateSnippet('typescript.json', tsSnippet1, testClient); await testObject.sync(await testClient.manifest()); - assert.equal(testObject.status, SyncStatus.Idle); - assert.deepEqual(testObject.conflicts, []); + assert.strictEqual(testObject.status, SyncStatus.Idle); + assert.deepStrictEqual(testObject.conflicts, []); const actual1 = await readSnippet('html.json', testClient); - assert.equal(actual1, htmlSnippet1); + assert.strictEqual(actual1, htmlSnippet1); const actual2 = await readSnippet('typescript.json', testClient); - assert.equal(actual2, tsSnippet1); + assert.strictEqual(actual2, tsSnippet1); const { content } = await testClient.read(testObject.resource); assert.ok(content !== null); const actual = parseSnippets(content!); - assert.deepEqual(actual, { 'html.json': htmlSnippet1, 'typescript.json': tsSnippet1 }); + assert.deepStrictEqual(actual, { 'html.json': htmlSnippet1, 'typescript.json': tsSnippet1 }); }); test('first time sync when snippets exists - has conflicts', async () => { @@ -274,7 +274,7 @@ suite('SnippetsSync', () => { await updateSnippet('html.json', htmlSnippet2, testClient); await testObject.sync(await testClient.manifest()); - assert.equal(testObject.status, SyncStatus.HasConflicts); + assert.strictEqual(testObject.status, SyncStatus.HasConflicts); const environmentService = testClient.instantiationService.get(IEnvironmentService); const local = joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'html.json'); assertPreviews(testObject.conflicts, [local]); @@ -290,16 +290,16 @@ suite('SnippetsSync', () => { await testObject.accept(conflicts[0].previewResource, htmlSnippet1); await testObject.apply(false); - assert.equal(testObject.status, SyncStatus.Idle); - assert.deepEqual(testObject.conflicts, []); + assert.strictEqual(testObject.status, SyncStatus.Idle); + assert.deepStrictEqual(testObject.conflicts, []); const actual1 = await readSnippet('html.json', testClient); - assert.equal(actual1, htmlSnippet1); + assert.strictEqual(actual1, htmlSnippet1); const { content } = await testClient.read(testObject.resource); assert.ok(content !== null); const actual = parseSnippets(content!); - assert.deepEqual(actual, { 'html.json': htmlSnippet1 }); + assert.deepStrictEqual(actual, { 'html.json': htmlSnippet1 }); }); test('first time sync when snippets exists - has multiple conflicts', async () => { @@ -311,7 +311,7 @@ suite('SnippetsSync', () => { await updateSnippet('typescript.json', tsSnippet2, testClient); await testObject.sync(await testClient.manifest()); - assert.equal(testObject.status, SyncStatus.HasConflicts); + assert.strictEqual(testObject.status, SyncStatus.HasConflicts); const environmentService = testClient.instantiationService.get(IEnvironmentService); const local1 = joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'html.json'); const local2 = joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'typescript.json'); @@ -331,7 +331,7 @@ suite('SnippetsSync', () => { await testObject.accept(conflicts[0].previewResource, htmlSnippet2); conflicts = testObject.conflicts; - assert.equal(testObject.status, SyncStatus.HasConflicts); + assert.strictEqual(testObject.status, SyncStatus.HasConflicts); const environmentService = testClient.instantiationService.get(IEnvironmentService); const local = joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'typescript.json'); assertPreviews(testObject.conflicts, [local]); @@ -351,18 +351,18 @@ suite('SnippetsSync', () => { await testObject.accept(conflicts[1].previewResource, tsSnippet1); await testObject.apply(false); - assert.equal(testObject.status, SyncStatus.Idle); - assert.deepEqual(testObject.conflicts, []); + assert.strictEqual(testObject.status, SyncStatus.Idle); + assert.deepStrictEqual(testObject.conflicts, []); const actual1 = await readSnippet('html.json', testClient); - assert.equal(actual1, htmlSnippet2); + assert.strictEqual(actual1, htmlSnippet2); const actual2 = await readSnippet('typescript.json', testClient); - assert.equal(actual2, tsSnippet1); + assert.strictEqual(actual2, tsSnippet1); const { content } = await testClient.read(testObject.resource); assert.ok(content !== null); const actual = parseSnippets(content!); - assert.deepEqual(actual, { 'html.json': htmlSnippet2, 'typescript.json': tsSnippet1 }); + assert.deepStrictEqual(actual, { 'html.json': htmlSnippet2, 'typescript.json': tsSnippet1 }); }); test('sync adding a snippet', async () => { @@ -371,18 +371,18 @@ suite('SnippetsSync', () => { await updateSnippet('typescript.json', tsSnippet1, testClient); await testObject.sync(await testClient.manifest()); - assert.equal(testObject.status, SyncStatus.Idle); - assert.deepEqual(testObject.conflicts, []); + assert.strictEqual(testObject.status, SyncStatus.Idle); + assert.deepStrictEqual(testObject.conflicts, []); const actual1 = await readSnippet('html.json', testClient); - assert.equal(actual1, htmlSnippet1); + assert.strictEqual(actual1, htmlSnippet1); const actual2 = await readSnippet('typescript.json', testClient); - assert.equal(actual2, tsSnippet1); + assert.strictEqual(actual2, tsSnippet1); const { content } = await testClient.read(testObject.resource); assert.ok(content !== null); const actual = parseSnippets(content!); - assert.deepEqual(actual, { 'html.json': htmlSnippet1, 'typescript.json': tsSnippet1 }); + assert.deepStrictEqual(actual, { 'html.json': htmlSnippet1, 'typescript.json': tsSnippet1 }); }); test('sync adding a snippet - accept', async () => { @@ -394,13 +394,13 @@ suite('SnippetsSync', () => { await client2.sync(); await testObject.sync(await testClient.manifest()); - assert.equal(testObject.status, SyncStatus.Idle); - assert.deepEqual(testObject.conflicts, []); + assert.strictEqual(testObject.status, SyncStatus.Idle); + assert.deepStrictEqual(testObject.conflicts, []); const actual1 = await readSnippet('html.json', testClient); - assert.equal(actual1, htmlSnippet1); + assert.strictEqual(actual1, htmlSnippet1); const actual2 = await readSnippet('typescript.json', testClient); - assert.equal(actual2, tsSnippet1); + assert.strictEqual(actual2, tsSnippet1); }); test('sync updating a snippet', async () => { @@ -409,16 +409,16 @@ suite('SnippetsSync', () => { await updateSnippet('html.json', htmlSnippet2, testClient); await testObject.sync(await testClient.manifest()); - assert.equal(testObject.status, SyncStatus.Idle); - assert.deepEqual(testObject.conflicts, []); + assert.strictEqual(testObject.status, SyncStatus.Idle); + assert.deepStrictEqual(testObject.conflicts, []); const actual1 = await readSnippet('html.json', testClient); - assert.equal(actual1, htmlSnippet2); + assert.strictEqual(actual1, htmlSnippet2); const { content } = await testClient.read(testObject.resource); assert.ok(content !== null); const actual = parseSnippets(content!); - assert.deepEqual(actual, { 'html.json': htmlSnippet2 }); + assert.deepStrictEqual(actual, { 'html.json': htmlSnippet2 }); }); test('sync updating a snippet - accept', async () => { @@ -430,11 +430,11 @@ suite('SnippetsSync', () => { await client2.sync(); await testObject.sync(await testClient.manifest()); - assert.equal(testObject.status, SyncStatus.Idle); - assert.deepEqual(testObject.conflicts, []); + assert.strictEqual(testObject.status, SyncStatus.Idle); + assert.deepStrictEqual(testObject.conflicts, []); const actual1 = await readSnippet('html.json', testClient); - assert.equal(actual1, htmlSnippet2); + assert.strictEqual(actual1, htmlSnippet2); }); test('sync updating a snippet - conflict', async () => { @@ -447,7 +447,7 @@ suite('SnippetsSync', () => { await updateSnippet('html.json', htmlSnippet3, testClient); await testObject.sync(await testClient.manifest()); - assert.equal(testObject.status, SyncStatus.HasConflicts); + assert.strictEqual(testObject.status, SyncStatus.HasConflicts); const environmentService = testClient.instantiationService.get(IEnvironmentService); const local = joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'html.json'); assertPreviews(testObject.conflicts, [local]); @@ -466,16 +466,16 @@ suite('SnippetsSync', () => { await testObject.accept(testObject.conflicts[0].previewResource, htmlSnippet2); await testObject.apply(false); - assert.equal(testObject.status, SyncStatus.Idle); - assert.deepEqual(testObject.conflicts, []); + assert.strictEqual(testObject.status, SyncStatus.Idle); + assert.deepStrictEqual(testObject.conflicts, []); const actual1 = await readSnippet('html.json', testClient); - assert.equal(actual1, htmlSnippet2); + assert.strictEqual(actual1, htmlSnippet2); const { content } = await testClient.read(testObject.resource); assert.ok(content !== null); const actual = parseSnippets(content!); - assert.deepEqual(actual, { 'html.json': htmlSnippet2 }); + assert.deepStrictEqual(actual, { 'html.json': htmlSnippet2 }); }); test('sync removing a snippet', async () => { @@ -485,18 +485,18 @@ suite('SnippetsSync', () => { await removeSnippet('html.json', testClient); await testObject.sync(await testClient.manifest()); - assert.equal(testObject.status, SyncStatus.Idle); - assert.deepEqual(testObject.conflicts, []); + assert.strictEqual(testObject.status, SyncStatus.Idle); + assert.deepStrictEqual(testObject.conflicts, []); const actual1 = await readSnippet('typescript.json', testClient); - assert.equal(actual1, tsSnippet1); + assert.strictEqual(actual1, tsSnippet1); const actual2 = await readSnippet('html.json', testClient); - assert.equal(actual2, null); + assert.strictEqual(actual2, null); const { content } = await testClient.read(testObject.resource); assert.ok(content !== null); const actual = parseSnippets(content!); - assert.deepEqual(actual, { 'typescript.json': tsSnippet1 }); + assert.deepStrictEqual(actual, { 'typescript.json': tsSnippet1 }); }); test('sync removing a snippet - accept', async () => { @@ -509,13 +509,13 @@ suite('SnippetsSync', () => { await client2.sync(); await testObject.sync(await testClient.manifest()); - assert.equal(testObject.status, SyncStatus.Idle); - assert.deepEqual(testObject.conflicts, []); + assert.strictEqual(testObject.status, SyncStatus.Idle); + assert.deepStrictEqual(testObject.conflicts, []); const actual1 = await readSnippet('typescript.json', testClient); - assert.equal(actual1, tsSnippet1); + assert.strictEqual(actual1, tsSnippet1); const actual2 = await readSnippet('html.json', testClient); - assert.equal(actual2, null); + assert.strictEqual(actual2, null); }); test('sync removing a snippet locally and updating it remotely', async () => { @@ -530,13 +530,13 @@ suite('SnippetsSync', () => { await removeSnippet('html.json', testClient); await testObject.sync(await testClient.manifest()); - assert.equal(testObject.status, SyncStatus.Idle); - assert.deepEqual(testObject.conflicts, []); + assert.strictEqual(testObject.status, SyncStatus.Idle); + assert.deepStrictEqual(testObject.conflicts, []); const actual1 = await readSnippet('typescript.json', testClient); - assert.equal(actual1, tsSnippet1); + assert.strictEqual(actual1, tsSnippet1); const actual2 = await readSnippet('html.json', testClient); - assert.equal(actual2, htmlSnippet2); + assert.strictEqual(actual2, htmlSnippet2); }); test('sync removing a snippet - conflict', async () => { @@ -551,7 +551,7 @@ suite('SnippetsSync', () => { await updateSnippet('html.json', htmlSnippet2, testClient); await testObject.sync(await testClient.manifest()); - assert.equal(testObject.status, SyncStatus.HasConflicts); + assert.strictEqual(testObject.status, SyncStatus.HasConflicts); const environmentService = testClient.instantiationService.get(IEnvironmentService); const local = joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'html.json'); assertPreviews(testObject.conflicts, [local]); @@ -571,18 +571,18 @@ suite('SnippetsSync', () => { await testObject.accept(testObject.conflicts[0].previewResource, htmlSnippet3); await testObject.apply(false); - assert.equal(testObject.status, SyncStatus.Idle); - assert.deepEqual(testObject.conflicts, []); + assert.strictEqual(testObject.status, SyncStatus.Idle); + assert.deepStrictEqual(testObject.conflicts, []); const actual1 = await readSnippet('typescript.json', testClient); - assert.equal(actual1, tsSnippet1); + assert.strictEqual(actual1, tsSnippet1); const actual2 = await readSnippet('html.json', testClient); - assert.equal(actual2, htmlSnippet3); + assert.strictEqual(actual2, htmlSnippet3); const { content } = await testClient.read(testObject.resource); assert.ok(content !== null); const actual = parseSnippets(content!); - assert.deepEqual(actual, { 'typescript.json': tsSnippet1, 'html.json': htmlSnippet3 }); + assert.deepStrictEqual(actual, { 'typescript.json': tsSnippet1, 'html.json': htmlSnippet3 }); }); test('sync removing a snippet - resolve conflict by removing', async () => { @@ -599,18 +599,18 @@ suite('SnippetsSync', () => { await testObject.accept(testObject.conflicts[0].previewResource, null); await testObject.apply(false); - assert.equal(testObject.status, SyncStatus.Idle); - assert.deepEqual(testObject.conflicts, []); + assert.strictEqual(testObject.status, SyncStatus.Idle); + assert.deepStrictEqual(testObject.conflicts, []); const actual1 = await readSnippet('typescript.json', testClient); - assert.equal(actual1, tsSnippet1); + assert.strictEqual(actual1, tsSnippet1); const actual2 = await readSnippet('html.json', testClient); - assert.equal(actual2, null); + assert.strictEqual(actual2, null); const { content } = await testClient.read(testObject.resource); assert.ok(content !== null); const actual = parseSnippets(content!); - assert.deepEqual(actual, { 'typescript.json': tsSnippet1 }); + assert.deepStrictEqual(actual, { 'typescript.json': tsSnippet1 }); }); test('sync global and language snippet', async () => { @@ -619,18 +619,18 @@ suite('SnippetsSync', () => { await client2.sync(); await testObject.sync(await testClient.manifest()); - assert.equal(testObject.status, SyncStatus.Idle); - assert.deepEqual(testObject.conflicts, []); + assert.strictEqual(testObject.status, SyncStatus.Idle); + assert.deepStrictEqual(testObject.conflicts, []); const actual1 = await readSnippet('html.json', testClient); - assert.equal(actual1, htmlSnippet1); + assert.strictEqual(actual1, htmlSnippet1); const actual2 = await readSnippet('global.code-snippets', testClient); - assert.equal(actual2, globalSnippet); + assert.strictEqual(actual2, globalSnippet); const { content } = await testClient.read(testObject.resource); assert.ok(content !== null); const actual = parseSnippets(content!); - assert.deepEqual(actual, { 'html.json': htmlSnippet1, 'global.code-snippets': globalSnippet }); + assert.deepStrictEqual(actual, { 'html.json': htmlSnippet1, 'global.code-snippets': globalSnippet }); }); test('sync should ignore non snippets', async () => { @@ -640,20 +640,20 @@ suite('SnippetsSync', () => { await client2.sync(); await testObject.sync(await testClient.manifest()); - assert.equal(testObject.status, SyncStatus.Idle); - assert.deepEqual(testObject.conflicts, []); + assert.strictEqual(testObject.status, SyncStatus.Idle); + assert.deepStrictEqual(testObject.conflicts, []); const actual1 = await readSnippet('typescript.json', testClient); - assert.equal(actual1, tsSnippet1); + assert.strictEqual(actual1, tsSnippet1); const actual2 = await readSnippet('global.code-snippets', testClient); - assert.equal(actual2, globalSnippet); + assert.strictEqual(actual2, globalSnippet); const actual3 = await readSnippet('html.html', testClient); - assert.equal(actual3, null); + assert.strictEqual(actual3, null); const { content } = await testClient.read(testObject.resource); assert.ok(content !== null); const actual = parseSnippets(content!); - assert.deepEqual(actual, { 'typescript.json': tsSnippet1, 'global.code-snippets': globalSnippet }); + assert.deepStrictEqual(actual, { 'typescript.json': tsSnippet1, 'global.code-snippets': globalSnippet }); }); test('previews are reset after all conflicts resolved', async () => { @@ -679,23 +679,23 @@ suite('SnippetsSync', () => { await updateSnippet('typescript.json', tsSnippet2, testClient); let preview = await testObject.preview(await testClient.manifest()); - assert.equal(testObject.status, SyncStatus.Syncing); + assert.strictEqual(testObject.status, SyncStatus.Syncing); assertPreviews(preview!.resourcePreviews, [ joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'html.json'), joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'typescript.json'), ]); - assert.deepEqual(testObject.conflicts, []); + assert.deepStrictEqual(testObject.conflicts, []); preview = await testObject.merge(preview!.resourcePreviews[0].localResource); - assert.equal(testObject.status, SyncStatus.Syncing); + assert.strictEqual(testObject.status, SyncStatus.Syncing); assertPreviews(preview!.resourcePreviews, [ joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'html.json'), joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'typescript.json'), ]); - assert.deepEqual(testObject.conflicts, []); + assert.deepStrictEqual(testObject.conflicts, []); }); test('merge when there are multiple snippets and all snippets are merged', async () => { @@ -705,24 +705,24 @@ suite('SnippetsSync', () => { await updateSnippet('typescript.json', tsSnippet2, testClient); let preview = await testObject.preview(await testClient.manifest()); - assert.equal(testObject.status, SyncStatus.Syncing); + assert.strictEqual(testObject.status, SyncStatus.Syncing); assertPreviews(preview!.resourcePreviews, [ joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'html.json'), joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'typescript.json'), ]); - assert.deepEqual(testObject.conflicts, []); + assert.deepStrictEqual(testObject.conflicts, []); preview = await testObject.merge(preview!.resourcePreviews[0].localResource); preview = await testObject.merge(preview!.resourcePreviews[1].localResource); - assert.equal(testObject.status, SyncStatus.Syncing); + assert.strictEqual(testObject.status, SyncStatus.Syncing); assertPreviews(preview!.resourcePreviews, [ joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'html.json'), joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'typescript.json'), ]); - assert.deepEqual(testObject.conflicts, []); + assert.deepStrictEqual(testObject.conflicts, []); }); test('merge when there are multiple snippets and all snippets are merged and applied', async () => { @@ -732,21 +732,21 @@ suite('SnippetsSync', () => { await updateSnippet('typescript.json', tsSnippet2, testClient); let preview = await testObject.preview(await testClient.manifest()); - assert.equal(testObject.status, SyncStatus.Syncing); + assert.strictEqual(testObject.status, SyncStatus.Syncing); assertPreviews(preview!.resourcePreviews, [ joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'html.json'), joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'typescript.json'), ]); - assert.deepEqual(testObject.conflicts, []); + assert.deepStrictEqual(testObject.conflicts, []); preview = await testObject.merge(preview!.resourcePreviews[0].localResource); preview = await testObject.merge(preview!.resourcePreviews[1].localResource); preview = await testObject.apply(false); - assert.equal(testObject.status, SyncStatus.Idle); - assert.equal(preview, null); - assert.deepEqual(testObject.conflicts, []); + assert.strictEqual(testObject.status, SyncStatus.Idle); + assert.strictEqual(preview, null); + assert.deepStrictEqual(testObject.conflicts, []); }); test('merge when there are multiple snippets and one snippet has no changes and one snippet is merged', async () => { @@ -759,23 +759,23 @@ suite('SnippetsSync', () => { await updateSnippet('typescript.json', tsSnippet2, testClient); let preview = await testObject.preview(await testClient.manifest()); - assert.equal(testObject.status, SyncStatus.Syncing); + assert.strictEqual(testObject.status, SyncStatus.Syncing); assertPreviews(preview!.resourcePreviews, [ joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'typescript.json'), joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'html.json'), ]); - assert.deepEqual(testObject.conflicts, []); + assert.deepStrictEqual(testObject.conflicts, []); preview = await testObject.merge(preview!.resourcePreviews[0].localResource); - assert.equal(testObject.status, SyncStatus.Syncing); + assert.strictEqual(testObject.status, SyncStatus.Syncing); assertPreviews(preview!.resourcePreviews, [ joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'typescript.json'), joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'html.json'), ]); - assert.deepEqual(testObject.conflicts, []); + assert.deepStrictEqual(testObject.conflicts, []); }); test('merge when there are multiple snippets and one snippet has no changes and one snippet is merged and applied', async () => { @@ -788,20 +788,20 @@ suite('SnippetsSync', () => { await updateSnippet('typescript.json', tsSnippet2, testClient); let preview = await testObject.preview(await testClient.manifest()); - assert.equal(testObject.status, SyncStatus.Syncing); + assert.strictEqual(testObject.status, SyncStatus.Syncing); assertPreviews(preview!.resourcePreviews, [ joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'typescript.json'), joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'html.json'), ]); - assert.deepEqual(testObject.conflicts, []); + assert.deepStrictEqual(testObject.conflicts, []); preview = await testObject.merge(preview!.resourcePreviews[0].localResource); preview = await testObject.apply(false); - assert.equal(testObject.status, SyncStatus.Idle); - assert.equal(preview, null); - assert.deepEqual(testObject.conflicts, []); + assert.strictEqual(testObject.status, SyncStatus.Idle); + assert.strictEqual(preview, null); + assert.deepStrictEqual(testObject.conflicts, []); }); test('merge when there are multiple snippets with conflicts and only one snippet is merged', async () => { @@ -815,17 +815,17 @@ suite('SnippetsSync', () => { await updateSnippet('typescript.json', tsSnippet2, testClient); let preview = await testObject.preview(await testClient.manifest()); - assert.equal(testObject.status, SyncStatus.Syncing); + assert.strictEqual(testObject.status, SyncStatus.Syncing); assertPreviews(preview!.resourcePreviews, [ joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'html.json'), joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'typescript.json'), ]); - assert.deepEqual(testObject.conflicts, []); + assert.deepStrictEqual(testObject.conflicts, []); preview = await testObject.merge(preview!.resourcePreviews[0].previewResource); - assert.equal(testObject.status, SyncStatus.HasConflicts); + assert.strictEqual(testObject.status, SyncStatus.HasConflicts); assertPreviews(preview!.resourcePreviews, [ joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'html.json'), @@ -848,18 +848,18 @@ suite('SnippetsSync', () => { await updateSnippet('typescript.json', tsSnippet2, testClient); let preview = await testObject.preview(await testClient.manifest()); - assert.equal(testObject.status, SyncStatus.Syncing); + assert.strictEqual(testObject.status, SyncStatus.Syncing); assertPreviews(preview!.resourcePreviews, [ joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'html.json'), joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'typescript.json'), ]); - assert.deepEqual(testObject.conflicts, []); + assert.deepStrictEqual(testObject.conflicts, []); preview = await testObject.merge(preview!.resourcePreviews[0].previewResource); preview = await testObject.merge(preview!.resourcePreviews[1].previewResource); - assert.equal(testObject.status, SyncStatus.HasConflicts); + assert.strictEqual(testObject.status, SyncStatus.HasConflicts); assertPreviews(preview!.resourcePreviews, [ joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'html.json'), @@ -883,23 +883,23 @@ suite('SnippetsSync', () => { await updateSnippet('typescript.json', tsSnippet2, testClient); let preview = await testObject.preview(await testClient.manifest()); - assert.equal(testObject.status, SyncStatus.Syncing); + assert.strictEqual(testObject.status, SyncStatus.Syncing); assertPreviews(preview!.resourcePreviews, [ joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'html.json'), joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'typescript.json'), ]); - assert.deepEqual(testObject.conflicts, []); + assert.deepStrictEqual(testObject.conflicts, []); preview = await testObject.accept(preview!.resourcePreviews[0].previewResource, htmlSnippet2); - assert.equal(testObject.status, SyncStatus.Syncing); + assert.strictEqual(testObject.status, SyncStatus.Syncing); assertPreviews(preview!.resourcePreviews, [ joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'html.json'), joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'typescript.json'), ]); - assert.deepEqual(testObject.conflicts, []); + assert.deepStrictEqual(testObject.conflicts, []); }); test('accept when there are multiple snippets with conflicts and all snippets are accepted', async () => { @@ -913,24 +913,24 @@ suite('SnippetsSync', () => { await updateSnippet('typescript.json', tsSnippet2, testClient); let preview = await testObject.preview(await testClient.manifest()); - assert.equal(testObject.status, SyncStatus.Syncing); + assert.strictEqual(testObject.status, SyncStatus.Syncing); assertPreviews(preview!.resourcePreviews, [ joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'html.json'), joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'typescript.json'), ]); - assert.deepEqual(testObject.conflicts, []); + assert.deepStrictEqual(testObject.conflicts, []); preview = await testObject.accept(preview!.resourcePreviews[0].previewResource, htmlSnippet2); preview = await testObject.accept(preview!.resourcePreviews[1].previewResource, tsSnippet2); - assert.equal(testObject.status, SyncStatus.Syncing); + assert.strictEqual(testObject.status, SyncStatus.Syncing); assertPreviews(preview!.resourcePreviews, [ joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'html.json'), joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'typescript.json'), ]); - assert.deepEqual(testObject.conflicts, []); + assert.deepStrictEqual(testObject.conflicts, []); }); test('accept when there are multiple snippets with conflicts and all snippets are accepted and applied', async () => { @@ -944,21 +944,21 @@ suite('SnippetsSync', () => { await updateSnippet('typescript.json', tsSnippet2, testClient); let preview = await testObject.preview(await testClient.manifest()); - assert.equal(testObject.status, SyncStatus.Syncing); + assert.strictEqual(testObject.status, SyncStatus.Syncing); assertPreviews(preview!.resourcePreviews, [ joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'html.json'), joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'typescript.json'), ]); - assert.deepEqual(testObject.conflicts, []); + assert.deepStrictEqual(testObject.conflicts, []); preview = await testObject.accept(preview!.resourcePreviews[0].previewResource, htmlSnippet2); preview = await testObject.accept(preview!.resourcePreviews[1].previewResource, tsSnippet2); preview = await testObject.apply(false); - assert.equal(testObject.status, SyncStatus.Idle); - assert.equal(preview, null); - assert.deepEqual(testObject.conflicts, []); + assert.strictEqual(testObject.status, SyncStatus.Idle); + assert.strictEqual(preview, null); + assert.deepStrictEqual(testObject.conflicts, []); }); function parseSnippets(content: string): IStringDictionary { @@ -992,7 +992,7 @@ suite('SnippetsSync', () => { } function assertPreviews(actual: IResourcePreview[], expected: URI[]) { - assert.deepEqual(actual.map(({ previewResource }) => previewResource.toString()), expected.map(uri => uri.toString())); + assert.deepStrictEqual(actual.map(({ previewResource }) => previewResource.toString()), expected.map(uri => uri.toString())); } }); diff --git a/src/vs/platform/userDataSync/test/common/synchronizer.test.ts b/src/vs/platform/userDataSync/test/common/synchronizer.test.ts index 24739111..ee64a1cf 100644 --- a/src/vs/platform/userDataSync/test/common/synchronizer.test.ts +++ b/src/vs/platform/userDataSync/test/common/synchronizer.test.ts @@ -28,20 +28,20 @@ class TestSynchroniser extends AbstractSynchroniser { onDoSyncCall: Emitter = this._register(new Emitter()); failWhenGettingLatestRemoteUserData: boolean = false; - readonly resource: SyncResource = SyncResource.Settings; + override readonly resource: SyncResource = SyncResource.Settings; protected readonly version: number = 1; private cancelled: boolean = false; readonly localResource = joinPath(this.environmentService.userRoamingDataHome, 'testResource.json'); - protected getLatestRemoteUserData(manifest: IUserDataManifest | null, lastSyncUserData: IRemoteUserData | null): Promise { + protected override getLatestRemoteUserData(manifest: IUserDataManifest | null, lastSyncUserData: IRemoteUserData | null): Promise { if (this.failWhenGettingLatestRemoteUserData) { throw new Error(); } return super.getLatestRemoteUserData(manifest, lastSyncUserData); } - protected async doSync(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, apply: boolean): Promise { + protected override async doSync(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, apply: boolean): Promise { this.cancelled = false; this.onDoSyncCall.fire(); await this.syncBarrier.wait(); @@ -145,18 +145,18 @@ class TestSynchroniser extends AbstractSynchroniser { await this.updateLastSyncUserData(remoteUserData); } - async stop(): Promise { + override async stop(): Promise { this.cancelled = true; this.syncBarrier.open(); super.stop(); } - async triggerLocalChange(): Promise { + override async triggerLocalChange(): Promise { super.triggerLocalChange(); } onDidTriggerLocalChangeCall: Emitter = this._register(new Emitter()); - protected async doTriggerLocalChange(): Promise { + protected override async doTriggerLocalChange(): Promise { await super.doTriggerLocalChange(); this.onDidTriggerLocalChangeCall.fire(); } @@ -191,8 +191,8 @@ suite('TestSynchronizer - Auto Sync', () => { testObject.sync(await client.manifest()); await promise; - assert.deepEqual(actual, [SyncStatus.Syncing]); - assert.deepEqual(testObject.status, SyncStatus.Syncing); + assert.deepStrictEqual(actual, [SyncStatus.Syncing]); + assert.deepStrictEqual(testObject.status, SyncStatus.Syncing); testObject.stop(); }); @@ -205,8 +205,8 @@ suite('TestSynchronizer - Auto Sync', () => { disposableStore.add(testObject.onDidChangeStatus(status => actual.push(status))); await testObject.sync(await client.manifest()); - assert.deepEqual(actual, [SyncStatus.Syncing, SyncStatus.Idle]); - assert.deepEqual(testObject.status, SyncStatus.Idle); + assert.deepStrictEqual(actual, [SyncStatus.Syncing, SyncStatus.Idle]); + assert.deepStrictEqual(testObject.status, SyncStatus.Idle); }); test('status is set correctly when sync has errors', async () => { @@ -221,8 +221,8 @@ suite('TestSynchronizer - Auto Sync', () => { await testObject.sync(await client.manifest()); assert.fail('Should fail'); } catch (e) { - assert.deepEqual(actual, [SyncStatus.Syncing, SyncStatus.Idle]); - assert.deepEqual(testObject.status, SyncStatus.Idle); + assert.deepStrictEqual(actual, [SyncStatus.Syncing, SyncStatus.Idle]); + assert.deepStrictEqual(testObject.status, SyncStatus.Idle); } }); @@ -233,7 +233,7 @@ suite('TestSynchronizer - Auto Sync', () => { await testObject.sync(await client.manifest()); - assert.deepEqual(testObject.status, SyncStatus.HasConflicts); + assert.deepStrictEqual(testObject.status, SyncStatus.HasConflicts); assertConflicts(testObject.conflicts, [testObject.localResource]); }); @@ -248,8 +248,8 @@ suite('TestSynchronizer - Auto Sync', () => { disposableStore.add(testObject.onDidChangeStatus(status => actual.push(status))); await testObject.sync(await client.manifest()); - assert.deepEqual(actual, []); - assert.deepEqual(testObject.status, SyncStatus.Syncing); + assert.deepStrictEqual(actual, []); + assert.deepStrictEqual(testObject.status, SyncStatus.Syncing); await testObject.stop(); }); @@ -263,8 +263,8 @@ suite('TestSynchronizer - Auto Sync', () => { await testObject.sync(await client.manifest()); - assert.deepEqual(actual, []); - assert.deepEqual(testObject.status, SyncStatus.Idle); + assert.deepStrictEqual(actual, []); + assert.deepStrictEqual(testObject.status, SyncStatus.Idle); }); test('sync should not run if there are conflicts', async () => { @@ -277,8 +277,8 @@ suite('TestSynchronizer - Auto Sync', () => { disposableStore.add(testObject.onDidChangeStatus(status => actual.push(status))); await testObject.sync(await client.manifest()); - assert.deepEqual(actual, []); - assert.deepEqual(testObject.status, SyncStatus.HasConflicts); + assert.deepStrictEqual(actual, []); + assert.deepStrictEqual(testObject.status, SyncStatus.HasConflicts); }); test('accept preview during conflicts', async () => { @@ -287,16 +287,16 @@ suite('TestSynchronizer - Auto Sync', () => { testObject.syncBarrier.open(); await testObject.sync(await client.manifest()); - assert.deepEqual(testObject.status, SyncStatus.HasConflicts); + assert.deepStrictEqual(testObject.status, SyncStatus.HasConflicts); await testObject.accept(testObject.conflicts[0].previewResource); - assert.deepEqual(testObject.status, SyncStatus.Syncing); + assert.deepStrictEqual(testObject.status, SyncStatus.Syncing); assertConflicts(testObject.conflicts, []); await testObject.apply(false); - assert.deepEqual(testObject.status, SyncStatus.Idle); + assert.deepStrictEqual(testObject.status, SyncStatus.Idle); const fileService = client.instantiationService.get(IFileService); - assert.equal((await testObject.getRemoteUserData(null)).syncData?.content, (await fileService.readFile(testObject.localResource)).value.toString()); + assert.strictEqual((await testObject.getRemoteUserData(null)).syncData?.content, (await fileService.readFile(testObject.localResource)).value.toString()); }); test('accept remote during conflicts', async () => { @@ -310,16 +310,16 @@ suite('TestSynchronizer - Auto Sync', () => { testObject.syncResult = { hasConflicts: true, hasError: false }; await testObject.sync(await client.manifest()); - assert.deepEqual(testObject.status, SyncStatus.HasConflicts); + assert.deepStrictEqual(testObject.status, SyncStatus.HasConflicts); await testObject.accept(testObject.conflicts[0].remoteResource); - assert.deepEqual(testObject.status, SyncStatus.Syncing); + assert.deepStrictEqual(testObject.status, SyncStatus.Syncing); assertConflicts(testObject.conflicts, []); await testObject.apply(false); - assert.deepEqual(testObject.status, SyncStatus.Idle); - assert.equal((await testObject.getRemoteUserData(null)).syncData?.content, currentRemoteContent); - assert.equal((await fileService.readFile(testObject.localResource)).value.toString(), currentRemoteContent); + assert.deepStrictEqual(testObject.status, SyncStatus.Idle); + assert.strictEqual((await testObject.getRemoteUserData(null)).syncData?.content, currentRemoteContent); + assert.strictEqual((await fileService.readFile(testObject.localResource)).value.toString(), currentRemoteContent); }); test('accept local during conflicts', async () => { @@ -332,16 +332,16 @@ suite('TestSynchronizer - Auto Sync', () => { testObject.syncResult = { hasConflicts: true, hasError: false }; await testObject.sync(await client.manifest()); - assert.deepEqual(testObject.status, SyncStatus.HasConflicts); + assert.deepStrictEqual(testObject.status, SyncStatus.HasConflicts); await testObject.accept(testObject.conflicts[0].localResource); - assert.deepEqual(testObject.status, SyncStatus.Syncing); + assert.deepStrictEqual(testObject.status, SyncStatus.Syncing); assertConflicts(testObject.conflicts, []); await testObject.apply(false); - assert.deepEqual(testObject.status, SyncStatus.Idle); - assert.equal((await testObject.getRemoteUserData(null)).syncData?.content, newLocalContent); - assert.equal((await fileService.readFile(testObject.localResource)).value.toString(), newLocalContent); + assert.deepStrictEqual(testObject.status, SyncStatus.Idle); + assert.strictEqual((await testObject.getRemoteUserData(null)).syncData?.content, newLocalContent); + assert.strictEqual((await fileService.readFile(testObject.localResource)).value.toString(), newLocalContent); }); test('accept new content during conflicts', async () => { @@ -354,17 +354,17 @@ suite('TestSynchronizer - Auto Sync', () => { testObject.syncResult = { hasConflicts: true, hasError: false }; await testObject.sync(await client.manifest()); - assert.deepEqual(testObject.status, SyncStatus.HasConflicts); + assert.deepStrictEqual(testObject.status, SyncStatus.HasConflicts); const mergeContent = 'newContent'; await testObject.accept(testObject.conflicts[0].previewResource, mergeContent); - assert.deepEqual(testObject.status, SyncStatus.Syncing); + assert.deepStrictEqual(testObject.status, SyncStatus.Syncing); assertConflicts(testObject.conflicts, []); await testObject.apply(false); - assert.deepEqual(testObject.status, SyncStatus.Idle); - assert.equal((await testObject.getRemoteUserData(null)).syncData?.content, mergeContent); - assert.equal((await fileService.readFile(testObject.localResource)).value.toString(), mergeContent); + assert.deepStrictEqual(testObject.status, SyncStatus.Idle); + assert.strictEqual((await testObject.getRemoteUserData(null)).syncData?.content, mergeContent); + assert.strictEqual((await fileService.readFile(testObject.localResource)).value.toString(), mergeContent); }); test('accept delete during conflicts', async () => { @@ -377,15 +377,15 @@ suite('TestSynchronizer - Auto Sync', () => { testObject.syncResult = { hasConflicts: true, hasError: false }; await testObject.sync(await client.manifest()); - assert.deepEqual(testObject.status, SyncStatus.HasConflicts); + assert.deepStrictEqual(testObject.status, SyncStatus.HasConflicts); await testObject.accept(testObject.conflicts[0].previewResource, null); - assert.deepEqual(testObject.status, SyncStatus.Syncing); + assert.deepStrictEqual(testObject.status, SyncStatus.Syncing); assertConflicts(testObject.conflicts, []); await testObject.apply(false); - assert.deepEqual(testObject.status, SyncStatus.Idle); - assert.equal((await testObject.getRemoteUserData(null)).syncData?.content, ''); + assert.deepStrictEqual(testObject.status, SyncStatus.Idle); + assert.strictEqual((await testObject.getRemoteUserData(null)).syncData?.content, ''); assert.ok(!(await fileService.exists(testObject.localResource))); }); @@ -398,15 +398,15 @@ suite('TestSynchronizer - Auto Sync', () => { testObject.syncResult = { hasConflicts: true, hasError: false }; await testObject.sync(await client.manifest()); - assert.deepEqual(testObject.status, SyncStatus.HasConflicts); + assert.deepStrictEqual(testObject.status, SyncStatus.HasConflicts); await testObject.accept(testObject.conflicts[0].localResource); - assert.deepEqual(testObject.status, SyncStatus.Syncing); + assert.deepStrictEqual(testObject.status, SyncStatus.Syncing); assertConflicts(testObject.conflicts, []); await testObject.apply(false); - assert.deepEqual(testObject.status, SyncStatus.Idle); - assert.equal((await testObject.getRemoteUserData(null)).syncData?.content, ''); + assert.deepStrictEqual(testObject.status, SyncStatus.Idle); + assert.strictEqual((await testObject.getRemoteUserData(null)).syncData?.content, ''); assert.ok(!(await fileService.exists(testObject.localResource))); }); @@ -418,15 +418,15 @@ suite('TestSynchronizer - Auto Sync', () => { testObject.syncResult = { hasConflicts: true, hasError: false }; await testObject.sync(await client.manifest()); - assert.deepEqual(testObject.status, SyncStatus.HasConflicts); + assert.deepStrictEqual(testObject.status, SyncStatus.HasConflicts); await testObject.accept(testObject.conflicts[0].remoteResource); - assert.deepEqual(testObject.status, SyncStatus.Syncing); + assert.deepStrictEqual(testObject.status, SyncStatus.Syncing); assertConflicts(testObject.conflicts, []); await testObject.apply(false); - assert.deepEqual(testObject.status, SyncStatus.Idle); - assert.equal((await testObject.getRemoteUserData(null)).syncData, null); + assert.deepStrictEqual(testObject.status, SyncStatus.Idle); + assert.strictEqual((await testObject.getRemoteUserData(null)).syncData, null); assert.ok(!(await fileService.exists(testObject.localResource))); }); @@ -450,7 +450,7 @@ suite('TestSynchronizer - Auto Sync', () => { const ref = manifest!.latest![testObject.resource]; await testObject.sync(await client.manifest()); - assert.deepEqual(server.requests, [ + assert.deepStrictEqual(server.requests, [ { type: 'POST', url: `${server.url}/v1/resource/${testObject.resource}`, headers: { 'If-Match': ref } }, { type: 'GET', url: `${server.url}/v1/resource/${testObject.resource}/latest`, headers: {} }, { type: 'POST', url: `${server.url}/v1/resource/${testObject.resource}`, headers: { 'If-Match': `${parseInt(ref) + 1}` } }, @@ -467,7 +467,7 @@ suite('TestSynchronizer - Auto Sync', () => { await testObject.triggerLocalChange(); await promise; - assert.deepEqual(server.requests, []); + assert.deepStrictEqual(server.requests, []); }); test('status is reset when getting latest remote data fails', async () => { @@ -480,7 +480,7 @@ suite('TestSynchronizer - Auto Sync', () => { } catch (error) { } - assert.equal(testObject.status, SyncStatus.Idle); + assert.strictEqual(testObject.status, SyncStatus.Idle); }); }); @@ -508,7 +508,7 @@ suite('TestSynchronizer - Manual Sync', () => { const preview = await testObject.preview(await client.manifest()); - assert.deepEqual(testObject.status, SyncStatus.Syncing); + assert.deepStrictEqual(testObject.status, SyncStatus.Syncing); assertPreviews(preview!.resourcePreviews, [testObject.localResource]); assertConflicts(testObject.conflicts, []); }); @@ -521,9 +521,9 @@ suite('TestSynchronizer - Manual Sync', () => { let preview = await testObject.preview(await client.manifest()); preview = await testObject.merge(preview!.resourcePreviews[0].previewResource); - assert.deepEqual(testObject.status, SyncStatus.Syncing); + assert.deepStrictEqual(testObject.status, SyncStatus.Syncing); assertPreviews(preview!.resourcePreviews, [testObject.localResource]); - assert.equal(preview!.resourcePreviews[0].mergeState, MergeState.Accepted); + assert.strictEqual(preview!.resourcePreviews[0].mergeState, MergeState.Accepted); assertConflicts(testObject.conflicts, []); }); @@ -535,9 +535,9 @@ suite('TestSynchronizer - Manual Sync', () => { let preview = await testObject.preview(await client.manifest()); preview = await testObject.accept(preview!.resourcePreviews[0].previewResource); - assert.deepEqual(testObject.status, SyncStatus.Syncing); + assert.deepStrictEqual(testObject.status, SyncStatus.Syncing); assertPreviews(preview!.resourcePreviews, [testObject.localResource]); - assert.equal(preview!.resourcePreviews[0].mergeState, MergeState.Accepted); + assert.strictEqual(preview!.resourcePreviews[0].mergeState, MergeState.Accepted); assertConflicts(testObject.conflicts, []); }); @@ -550,9 +550,9 @@ suite('TestSynchronizer - Manual Sync', () => { preview = await testObject.merge(preview!.resourcePreviews[0].previewResource); preview = await testObject.accept(preview!.resourcePreviews[0].localResource); - assert.deepEqual(testObject.status, SyncStatus.Syncing); + assert.deepStrictEqual(testObject.status, SyncStatus.Syncing); assertPreviews(preview!.resourcePreviews, [testObject.localResource]); - assert.equal(preview!.resourcePreviews[0].mergeState, MergeState.Accepted); + assert.strictEqual(preview!.resourcePreviews[0].mergeState, MergeState.Accepted); assertConflicts(testObject.conflicts, []); }); @@ -567,13 +567,13 @@ suite('TestSynchronizer - Manual Sync', () => { preview = await testObject.merge(preview!.resourcePreviews[0].previewResource); preview = await testObject.apply(false); - assert.deepEqual(testObject.status, SyncStatus.Idle); - assert.equal(preview, null); + assert.deepStrictEqual(testObject.status, SyncStatus.Idle); + assert.strictEqual(preview, null); assertConflicts(testObject.conflicts, []); const expectedContent = manifest!.latest![testObject.resource]; - assert.equal((await testObject.getRemoteUserData(null)).syncData?.content, expectedContent); - assert.equal((await client.instantiationService.get(IFileService).readFile(testObject.localResource)).value.toString(), expectedContent); + assert.strictEqual((await testObject.getRemoteUserData(null)).syncData?.content, expectedContent); + assert.strictEqual((await client.instantiationService.get(IFileService).readFile(testObject.localResource)).value.toString(), expectedContent); }); test('preview -> accept -> apply', async () => { @@ -588,12 +588,12 @@ suite('TestSynchronizer - Manual Sync', () => { preview = await testObject.accept(preview!.resourcePreviews[0].previewResource); preview = await testObject.apply(false); - assert.deepEqual(testObject.status, SyncStatus.Idle); - assert.equal(preview, null); + assert.deepStrictEqual(testObject.status, SyncStatus.Idle); + assert.strictEqual(preview, null); assertConflicts(testObject.conflicts, []); - assert.equal((await testObject.getRemoteUserData(null)).syncData?.content, expectedContent); - assert.equal((await client.instantiationService.get(IFileService).readFile(testObject.localResource)).value.toString(), expectedContent); + assert.strictEqual((await testObject.getRemoteUserData(null)).syncData?.content, expectedContent); + assert.strictEqual((await client.instantiationService.get(IFileService).readFile(testObject.localResource)).value.toString(), expectedContent); }); test('preview -> merge -> accept -> apply', async () => { @@ -608,12 +608,12 @@ suite('TestSynchronizer - Manual Sync', () => { preview = await testObject.accept(preview!.resourcePreviews[0].localResource); preview = await testObject.apply(false); - assert.deepEqual(testObject.status, SyncStatus.Idle); - assert.equal(preview, null); + assert.deepStrictEqual(testObject.status, SyncStatus.Idle); + assert.strictEqual(preview, null); assertConflicts(testObject.conflicts, []); - assert.equal((await testObject.getRemoteUserData(null)).syncData?.content, expectedContent); - assert.equal(!(await client.instantiationService.get(IFileService).readFile(testObject.localResource)).value.toString(), expectedContent); + assert.strictEqual((await testObject.getRemoteUserData(null)).syncData?.content, expectedContent); + assert.strictEqual((await client.instantiationService.get(IFileService).readFile(testObject.localResource)).value.toString(), expectedContent); }); test('preview -> accept', async () => { @@ -624,7 +624,7 @@ suite('TestSynchronizer - Manual Sync', () => { let preview = await testObject.preview(await client.manifest()); preview = await testObject.accept(preview!.resourcePreviews[0].previewResource); - assert.deepEqual(testObject.status, SyncStatus.Syncing); + assert.deepStrictEqual(testObject.status, SyncStatus.Syncing); assertPreviews(preview!.resourcePreviews, [testObject.localResource]); assertConflicts(testObject.conflicts, []); }); @@ -641,12 +641,12 @@ suite('TestSynchronizer - Manual Sync', () => { preview = await testObject.accept(preview!.resourcePreviews[0].previewResource); preview = await testObject.apply(false); - assert.deepEqual(testObject.status, SyncStatus.Idle); - assert.equal(preview, null); + assert.deepStrictEqual(testObject.status, SyncStatus.Idle); + assert.strictEqual(preview, null); assertConflicts(testObject.conflicts, []); - assert.equal((await testObject.getRemoteUserData(null)).syncData?.content, expectedContent); - assert.equal((await client.instantiationService.get(IFileService).readFile(testObject.localResource)).value.toString(), expectedContent); + assert.strictEqual((await testObject.getRemoteUserData(null)).syncData?.content, expectedContent); + assert.strictEqual((await client.instantiationService.get(IFileService).readFile(testObject.localResource)).value.toString(), expectedContent); }); test('preivew -> merge -> discard', async () => { @@ -658,9 +658,9 @@ suite('TestSynchronizer - Manual Sync', () => { preview = await testObject.merge(preview!.resourcePreviews[0].previewResource); preview = await testObject.discard(preview!.resourcePreviews[0].previewResource); - assert.deepEqual(testObject.status, SyncStatus.Syncing); + assert.deepStrictEqual(testObject.status, SyncStatus.Syncing); assertPreviews(preview!.resourcePreviews, [testObject.localResource]); - assert.equal(preview!.resourcePreviews[0].mergeState, MergeState.Preview); + assert.strictEqual(preview!.resourcePreviews[0].mergeState, MergeState.Preview); assertConflicts(testObject.conflicts, []); }); @@ -674,9 +674,9 @@ suite('TestSynchronizer - Manual Sync', () => { preview = await testObject.discard(preview!.resourcePreviews[0].previewResource); preview = await testObject.accept(preview!.resourcePreviews[0].remoteResource); - assert.deepEqual(testObject.status, SyncStatus.Syncing); + assert.deepStrictEqual(testObject.status, SyncStatus.Syncing); assertPreviews(preview!.resourcePreviews, [testObject.localResource]); - assert.equal(preview!.resourcePreviews[0].mergeState, MergeState.Accepted); + assert.strictEqual(preview!.resourcePreviews[0].mergeState, MergeState.Accepted); assertConflicts(testObject.conflicts, []); }); @@ -689,9 +689,9 @@ suite('TestSynchronizer - Manual Sync', () => { preview = await testObject.accept(preview!.resourcePreviews[0].previewResource); preview = await testObject.discard(preview!.resourcePreviews[0].previewResource); - assert.deepEqual(testObject.status, SyncStatus.Syncing); + assert.deepStrictEqual(testObject.status, SyncStatus.Syncing); assertPreviews(preview!.resourcePreviews, [testObject.localResource]); - assert.equal(preview!.resourcePreviews[0].mergeState, MergeState.Preview); + assert.strictEqual(preview!.resourcePreviews[0].mergeState, MergeState.Preview); assertConflicts(testObject.conflicts, []); }); @@ -705,9 +705,9 @@ suite('TestSynchronizer - Manual Sync', () => { preview = await testObject.discard(preview!.resourcePreviews[0].previewResource); preview = await testObject.accept(preview!.resourcePreviews[0].remoteResource); - assert.deepEqual(testObject.status, SyncStatus.Syncing); + assert.deepStrictEqual(testObject.status, SyncStatus.Syncing); assertPreviews(preview!.resourcePreviews, [testObject.localResource]); - assert.equal(preview!.resourcePreviews[0].mergeState, MergeState.Accepted); + assert.strictEqual(preview!.resourcePreviews[0].mergeState, MergeState.Accepted); assertConflicts(testObject.conflicts, []); }); @@ -721,9 +721,9 @@ suite('TestSynchronizer - Manual Sync', () => { preview = await testObject.discard(preview!.resourcePreviews[0].previewResource); preview = await testObject.merge(preview!.resourcePreviews[0].remoteResource); - assert.deepEqual(testObject.status, SyncStatus.Syncing); + assert.deepStrictEqual(testObject.status, SyncStatus.Syncing); assertPreviews(preview!.resourcePreviews, [testObject.localResource]); - assert.equal(preview!.resourcePreviews[0].mergeState, MergeState.Accepted); + assert.strictEqual(preview!.resourcePreviews[0].mergeState, MergeState.Accepted); assertConflicts(testObject.conflicts, []); }); @@ -737,9 +737,9 @@ suite('TestSynchronizer - Manual Sync', () => { preview = await testObject.accept(preview!.resourcePreviews[0].remoteResource); preview = await testObject.discard(preview!.resourcePreviews[0].previewResource); - assert.deepEqual(testObject.status, SyncStatus.Syncing); + assert.deepStrictEqual(testObject.status, SyncStatus.Syncing); assertPreviews(preview!.resourcePreviews, [testObject.localResource]); - assert.equal(preview!.resourcePreviews[0].mergeState, MergeState.Preview); + assert.strictEqual(preview!.resourcePreviews[0].mergeState, MergeState.Preview); assertConflicts(testObject.conflicts, []); }); @@ -756,11 +756,11 @@ suite('TestSynchronizer - Manual Sync', () => { preview = await testObject.accept(preview!.resourcePreviews[0].localResource); preview = await testObject.apply(false); - assert.deepEqual(testObject.status, SyncStatus.Idle); - assert.equal(preview, null); + assert.deepStrictEqual(testObject.status, SyncStatus.Idle); + assert.strictEqual(preview, null); assertConflicts(testObject.conflicts, []); - assert.equal((await testObject.getRemoteUserData(null)).syncData?.content, expectedContent); - assert.equal(!(await client.instantiationService.get(IFileService).readFile(testObject.localResource)).value.toString(), expectedContent); + assert.strictEqual((await testObject.getRemoteUserData(null)).syncData?.content, expectedContent); + assert.strictEqual((await client.instantiationService.get(IFileService).readFile(testObject.localResource)).value.toString(), expectedContent); }); test('preivew -> accept -> discard -> accept -> apply', async () => { @@ -777,11 +777,11 @@ suite('TestSynchronizer - Manual Sync', () => { preview = await testObject.accept(preview!.resourcePreviews[0].localResource); preview = await testObject.apply(false); - assert.deepEqual(testObject.status, SyncStatus.Idle); - assert.equal(preview, null); + assert.deepStrictEqual(testObject.status, SyncStatus.Idle); + assert.strictEqual(preview, null); assertConflicts(testObject.conflicts, []); - assert.equal((await testObject.getRemoteUserData(null)).syncData?.content, expectedContent); - assert.equal(!(await client.instantiationService.get(IFileService).readFile(testObject.localResource)).value.toString(), expectedContent); + assert.strictEqual((await testObject.getRemoteUserData(null)).syncData?.content, expectedContent); + assert.strictEqual((await client.instantiationService.get(IFileService).readFile(testObject.localResource)).value.toString(), expectedContent); }); test('preivew -> accept -> discard -> merge -> apply', async () => { @@ -799,12 +799,12 @@ suite('TestSynchronizer - Manual Sync', () => { preview = await testObject.merge(preview!.resourcePreviews[0].localResource); preview = await testObject.apply(false); - assert.deepEqual(testObject.status, SyncStatus.Idle); - assert.equal(preview, null); + assert.deepStrictEqual(testObject.status, SyncStatus.Idle); + assert.strictEqual(preview, null); assertConflicts(testObject.conflicts, []); - assert.equal((await testObject.getRemoteUserData(null)).syncData?.content, expectedContent); - assert.equal((await client.instantiationService.get(IFileService).readFile(testObject.localResource)).value.toString(), expectedContent); + assert.strictEqual((await testObject.getRemoteUserData(null)).syncData?.content, expectedContent); + assert.strictEqual((await client.instantiationService.get(IFileService).readFile(testObject.localResource)).value.toString(), expectedContent); }); test('conflicts: preview', async () => { @@ -814,7 +814,7 @@ suite('TestSynchronizer - Manual Sync', () => { const preview = await testObject.preview(await client.manifest()); - assert.deepEqual(testObject.status, SyncStatus.Syncing); + assert.deepStrictEqual(testObject.status, SyncStatus.Syncing); assertPreviews(preview!.resourcePreviews, [testObject.localResource]); assertConflicts(testObject.conflicts, []); }); @@ -827,9 +827,9 @@ suite('TestSynchronizer - Manual Sync', () => { let preview = await testObject.preview(await client.manifest()); preview = await testObject.merge(preview!.resourcePreviews[0].previewResource); - assert.deepEqual(testObject.status, SyncStatus.HasConflicts); + assert.deepStrictEqual(testObject.status, SyncStatus.HasConflicts); assertPreviews(preview!.resourcePreviews, [testObject.localResource]); - assert.equal(preview!.resourcePreviews[0].mergeState, MergeState.Conflict); + assert.strictEqual(preview!.resourcePreviews[0].mergeState, MergeState.Conflict); assertConflicts(testObject.conflicts, [preview!.resourcePreviews[0].localResource]); }); @@ -842,9 +842,9 @@ suite('TestSynchronizer - Manual Sync', () => { await testObject.merge(preview!.resourcePreviews[0].previewResource); await testObject.discard(preview!.resourcePreviews[0].previewResource); - assert.deepEqual(testObject.status, SyncStatus.Syncing); + assert.deepStrictEqual(testObject.status, SyncStatus.Syncing); assertPreviews(preview!.resourcePreviews, [testObject.localResource]); - assert.equal(preview!.resourcePreviews[0].mergeState, MergeState.Preview); + assert.strictEqual(preview!.resourcePreviews[0].mergeState, MergeState.Preview); assertConflicts(testObject.conflicts, []); }); @@ -858,9 +858,9 @@ suite('TestSynchronizer - Manual Sync', () => { const content = await testObject.resolveContent(preview!.resourcePreviews[0].previewResource); preview = await testObject.accept(preview!.resourcePreviews[0].previewResource, content); - assert.deepEqual(testObject.status, SyncStatus.Syncing); + assert.deepStrictEqual(testObject.status, SyncStatus.Syncing); assertPreviews(preview!.resourcePreviews, [testObject.localResource]); - assert.deepEqual(testObject.conflicts, []); + assert.deepStrictEqual(testObject.conflicts, []); }); test('conflicts: preview -> merge -> accept -> apply', async () => { @@ -878,12 +878,12 @@ suite('TestSynchronizer - Manual Sync', () => { preview = await testObject.accept(preview!.resourcePreviews[0].previewResource); preview = await testObject.apply(false); - assert.deepEqual(testObject.status, SyncStatus.Idle); - assert.equal(preview, null); + assert.deepStrictEqual(testObject.status, SyncStatus.Idle); + assert.strictEqual(preview, null); assertConflicts(testObject.conflicts, []); - assert.equal((await testObject.getRemoteUserData(null)).syncData?.content, expectedContent); - assert.equal((await client.instantiationService.get(IFileService).readFile(testObject.localResource)).value.toString(), expectedContent); + assert.strictEqual((await testObject.getRemoteUserData(null)).syncData?.content, expectedContent); + assert.strictEqual((await client.instantiationService.get(IFileService).readFile(testObject.localResource)).value.toString(), expectedContent); }); test('conflicts: preview -> accept', async () => { @@ -895,7 +895,7 @@ suite('TestSynchronizer - Manual Sync', () => { const content = await testObject.resolveContent(preview!.resourcePreviews[0].previewResource); preview = await testObject.accept(preview!.resourcePreviews[0].previewResource, content); - assert.deepEqual(testObject.status, SyncStatus.Syncing); + assert.deepStrictEqual(testObject.status, SyncStatus.Syncing); assertPreviews(preview!.resourcePreviews, [testObject.localResource]); assertConflicts(testObject.conflicts, []); }); @@ -914,12 +914,12 @@ suite('TestSynchronizer - Manual Sync', () => { preview = await testObject.accept(preview!.resourcePreviews[0].previewResource); preview = await testObject.apply(false); - assert.deepEqual(testObject.status, SyncStatus.Idle); - assert.equal(preview, null); + assert.deepStrictEqual(testObject.status, SyncStatus.Idle); + assert.strictEqual(preview, null); assertConflicts(testObject.conflicts, []); - assert.equal((await testObject.getRemoteUserData(null)).syncData?.content, expectedContent); - assert.equal((await client.instantiationService.get(IFileService).readFile(testObject.localResource)).value.toString(), expectedContent); + assert.strictEqual((await testObject.getRemoteUserData(null)).syncData?.content, expectedContent); + assert.strictEqual((await client.instantiationService.get(IFileService).readFile(testObject.localResource)).value.toString(), expectedContent); }); test('conflicts: preivew -> merge -> discard', async () => { @@ -931,9 +931,9 @@ suite('TestSynchronizer - Manual Sync', () => { preview = await testObject.merge(preview!.resourcePreviews[0].previewResource); preview = await testObject.discard(preview!.resourcePreviews[0].previewResource); - assert.deepEqual(testObject.status, SyncStatus.Syncing); + assert.deepStrictEqual(testObject.status, SyncStatus.Syncing); assertPreviews(preview!.resourcePreviews, [testObject.localResource]); - assert.equal(preview!.resourcePreviews[0].mergeState, MergeState.Preview); + assert.strictEqual(preview!.resourcePreviews[0].mergeState, MergeState.Preview); assertConflicts(testObject.conflicts, []); }); @@ -947,9 +947,9 @@ suite('TestSynchronizer - Manual Sync', () => { preview = await testObject.discard(preview!.resourcePreviews[0].previewResource); preview = await testObject.accept(preview!.resourcePreviews[0].remoteResource); - assert.deepEqual(testObject.status, SyncStatus.Syncing); + assert.deepStrictEqual(testObject.status, SyncStatus.Syncing); assertPreviews(preview!.resourcePreviews, [testObject.localResource]); - assert.equal(preview!.resourcePreviews[0].mergeState, MergeState.Accepted); + assert.strictEqual(preview!.resourcePreviews[0].mergeState, MergeState.Accepted); assertConflicts(testObject.conflicts, []); }); @@ -962,9 +962,9 @@ suite('TestSynchronizer - Manual Sync', () => { preview = await testObject.accept(preview!.resourcePreviews[0].previewResource); preview = await testObject.discard(preview!.resourcePreviews[0].previewResource); - assert.deepEqual(testObject.status, SyncStatus.Syncing); + assert.deepStrictEqual(testObject.status, SyncStatus.Syncing); assertPreviews(preview!.resourcePreviews, [testObject.localResource]); - assert.equal(preview!.resourcePreviews[0].mergeState, MergeState.Preview); + assert.strictEqual(preview!.resourcePreviews[0].mergeState, MergeState.Preview); assertConflicts(testObject.conflicts, []); }); @@ -978,9 +978,9 @@ suite('TestSynchronizer - Manual Sync', () => { preview = await testObject.discard(preview!.resourcePreviews[0].previewResource); preview = await testObject.accept(preview!.resourcePreviews[0].remoteResource); - assert.deepEqual(testObject.status, SyncStatus.Syncing); + assert.deepStrictEqual(testObject.status, SyncStatus.Syncing); assertPreviews(preview!.resourcePreviews, [testObject.localResource]); - assert.equal(preview!.resourcePreviews[0].mergeState, MergeState.Accepted); + assert.strictEqual(preview!.resourcePreviews[0].mergeState, MergeState.Accepted); assertConflicts(testObject.conflicts, []); }); @@ -994,9 +994,9 @@ suite('TestSynchronizer - Manual Sync', () => { preview = await testObject.discard(preview!.resourcePreviews[0].previewResource); preview = await testObject.merge(preview!.resourcePreviews[0].remoteResource); - assert.deepEqual(testObject.status, SyncStatus.HasConflicts); + assert.deepStrictEqual(testObject.status, SyncStatus.HasConflicts); assertPreviews(preview!.resourcePreviews, [testObject.localResource]); - assert.equal(preview!.resourcePreviews[0].mergeState, MergeState.Conflict); + assert.strictEqual(preview!.resourcePreviews[0].mergeState, MergeState.Conflict); assertConflicts(testObject.conflicts, [preview!.resourcePreviews[0].localResource]); }); @@ -1010,9 +1010,9 @@ suite('TestSynchronizer - Manual Sync', () => { preview = await testObject.discard(preview!.resourcePreviews[0].previewResource); preview = await testObject.merge(preview!.resourcePreviews[0].remoteResource); - assert.deepEqual(testObject.status, SyncStatus.HasConflicts); + assert.deepStrictEqual(testObject.status, SyncStatus.HasConflicts); assertPreviews(preview!.resourcePreviews, [testObject.localResource]); - assert.equal(preview!.resourcePreviews[0].mergeState, MergeState.Conflict); + assert.strictEqual(preview!.resourcePreviews[0].mergeState, MergeState.Conflict); assertConflicts(testObject.conflicts, [preview!.resourcePreviews[0].localResource]); }); @@ -1026,9 +1026,9 @@ suite('TestSynchronizer - Manual Sync', () => { preview = await testObject.accept(preview!.resourcePreviews[0].remoteResource); preview = await testObject.discard(preview!.resourcePreviews[0].previewResource); - assert.deepEqual(testObject.status, SyncStatus.Syncing); + assert.deepStrictEqual(testObject.status, SyncStatus.Syncing); assertPreviews(preview!.resourcePreviews, [testObject.localResource]); - assert.equal(preview!.resourcePreviews[0].mergeState, MergeState.Preview); + assert.strictEqual(preview!.resourcePreviews[0].mergeState, MergeState.Preview); assertConflicts(testObject.conflicts, []); }); @@ -1045,11 +1045,11 @@ suite('TestSynchronizer - Manual Sync', () => { preview = await testObject.accept(preview!.resourcePreviews[0].localResource); preview = await testObject.apply(false); - assert.deepEqual(testObject.status, SyncStatus.Idle); - assert.equal(preview, null); + assert.deepStrictEqual(testObject.status, SyncStatus.Idle); + assert.strictEqual(preview, null); assertConflicts(testObject.conflicts, []); - assert.equal((await testObject.getRemoteUserData(null)).syncData?.content, expectedContent); - assert.equal(!(await client.instantiationService.get(IFileService).readFile(testObject.localResource)).value.toString(), expectedContent); + assert.strictEqual((await testObject.getRemoteUserData(null)).syncData?.content, expectedContent); + assert.strictEqual((await client.instantiationService.get(IFileService).readFile(testObject.localResource)).value.toString(), expectedContent); }); test('conflicts: preivew -> accept -> discard -> accept -> apply', async () => { @@ -1066,19 +1066,19 @@ suite('TestSynchronizer - Manual Sync', () => { preview = await testObject.accept(preview!.resourcePreviews[0].localResource); preview = await testObject.apply(false); - assert.deepEqual(testObject.status, SyncStatus.Idle); - assert.equal(preview, null); + assert.deepStrictEqual(testObject.status, SyncStatus.Idle); + assert.strictEqual(preview, null); assertConflicts(testObject.conflicts, []); - assert.equal((await testObject.getRemoteUserData(null)).syncData?.content, expectedContent); - assert.equal(!(await client.instantiationService.get(IFileService).readFile(testObject.localResource)).value.toString(), expectedContent); + assert.strictEqual((await testObject.getRemoteUserData(null)).syncData?.content, expectedContent); + assert.strictEqual((await client.instantiationService.get(IFileService).readFile(testObject.localResource)).value.toString(), expectedContent); }); }); function assertConflicts(actual: IBaseResourcePreview[], expected: URI[]) { - assert.deepEqual(actual.map(({ localResource }) => localResource.toString()), expected.map(uri => uri.toString())); + assert.deepStrictEqual(actual.map(({ localResource }) => localResource.toString()), expected.map(uri => uri.toString())); } function assertPreviews(actual: IBaseResourcePreview[], expected: URI[]) { - assert.deepEqual(actual.map(({ localResource }) => localResource.toString()), expected.map(uri => uri.toString())); + assert.deepStrictEqual(actual.map(({ localResource }) => localResource.toString()), expected.map(uri => uri.toString())); } diff --git a/src/vs/platform/userDataSync/test/common/userDataAutoSyncService.test.ts b/src/vs/platform/userDataSync/test/common/userDataAutoSyncService.test.ts index a12085fe..a015b118 100644 --- a/src/vs/platform/userDataSync/test/common/userDataAutoSyncService.test.ts +++ b/src/vs/platform/userDataSync/test/common/userDataAutoSyncService.test.ts @@ -16,8 +16,8 @@ import { joinPath } from 'vs/base/common/resources'; import { IUserDataSyncMachinesService } from 'vs/platform/userDataSync/common/userDataSyncMachines'; class TestUserDataAutoSyncService extends UserDataAutoSyncService { - protected startAutoSync(): boolean { return false; } - protected getSyncTriggerDelayTime(): number { return 50; } + protected override startAutoSync(): boolean { return false; } + protected override getSyncTriggerDelayTime(): number { return 50; } sync(): Promise { return this.triggerSync(['sync'], false, false); @@ -49,7 +49,7 @@ suite('UserDataAutoSyncService', () => { const actual = target.requests.filter(request => !request.url.startsWith(`${target.url}/v1/resource/machines`)); // Make sure only one manifest request is made - assert.deepEqual(actual, [{ type: 'GET', url: `${target.url}/v1/manifest`, headers: {} }]); + assert.deepStrictEqual(actual, [{ type: 'GET', url: `${target.url}/v1/manifest`, headers: {} }]); }); test('test auto sync with sync resource change triggers sync for every change', async () => { @@ -72,7 +72,7 @@ suite('UserDataAutoSyncService', () => { // Filter out machine requests const actual = target.requests.filter(request => !request.url.startsWith(`${target.url}/v1/resource/machines`)); - assert.deepEqual(actual, [ + assert.deepStrictEqual(actual, [ { type: 'GET', url: `${target.url}/v1/manifest`, headers: {} }, { type: 'GET', url: `${target.url}/v1/manifest`, headers: {} } ]); @@ -97,7 +97,7 @@ suite('UserDataAutoSyncService', () => { const actual = target.requests.filter(request => !request.url.startsWith(`${target.url}/v1/resource/machines`)); // Make sure only one manifest request is made - assert.deepEqual(actual, [{ type: 'GET', url: `${target.url}/v1/manifest`, headers: {} }]); + assert.deepStrictEqual(actual, [{ type: 'GET', url: `${target.url}/v1/manifest`, headers: {} }]); }); test('test auto sync with non sync resource change does not trigger continuous syncs', async () => { @@ -121,7 +121,7 @@ suite('UserDataAutoSyncService', () => { const actual = target.requests.filter(request => !request.url.startsWith(`${target.url}/v1/resource/machines`)); // Make sure only one manifest request is made - assert.deepEqual(actual, [{ type: 'GET', url: `${target.url}/v1/manifest`, headers: {} }]); + assert.deepStrictEqual(actual, [{ type: 'GET', url: `${target.url}/v1/manifest`, headers: {} }]); }); test('test first auto sync requests', async () => { @@ -133,7 +133,7 @@ suite('UserDataAutoSyncService', () => { await testObject.sync(); - assert.deepEqual(target.requests, [ + assert.deepStrictEqual(target.requests, [ // Manifest { type: 'GET', url: `${target.url}/v1/manifest`, headers: {} }, // Machines @@ -173,7 +173,7 @@ suite('UserDataAutoSyncService', () => { await testObject.sync(); - assert.deepEqual(target.requests, [ + assert.deepStrictEqual(target.requests, [ // Manifest { type: 'GET', url: `${target.url}/v1/manifest`, headers: {} } ]); @@ -200,7 +200,7 @@ suite('UserDataAutoSyncService', () => { await fileService.writeFile(environmentService.argvResource, VSBuffer.fromString(JSON.stringify({ 'locale': 'de' }))); await testObject.sync(); - assert.deepEqual(target.requests, [ + assert.deepStrictEqual(target.requests, [ // Manifest { type: 'GET', url: `${target.url}/v1/manifest`, headers: {} }, // Settings @@ -264,8 +264,8 @@ suite('UserDataAutoSyncService', () => { const e = await errorPromise; assert.ok(e instanceof UserDataAutoSyncError); - assert.deepEqual((e).code, UserDataSyncErrorCode.TurnedOff); - assert.deepEqual(target.requests, [ + assert.deepStrictEqual((e).code, UserDataSyncErrorCode.TurnedOff); + assert.deepStrictEqual(target.requests, [ // Manifest { type: 'GET', url: `${target.url}/v1/manifest`, headers: {} }, // Machine @@ -295,8 +295,8 @@ suite('UserDataAutoSyncService', () => { const e = await errorPromise; assert.ok(e instanceof UserDataAutoSyncError); - assert.deepEqual((e).code, UserDataSyncErrorCode.TurnedOff); - assert.deepEqual(target.requests, [ + assert.deepStrictEqual((e).code, UserDataSyncErrorCode.TurnedOff); + assert.deepStrictEqual(target.requests, [ // Manifest { type: 'GET', url: `${target.url}/v1/manifest`, headers: {} }, // Machine @@ -320,7 +320,7 @@ suite('UserDataAutoSyncService', () => { target.reset(); await testObject.sync(); - assert.deepEqual(target.requests, [ + assert.deepStrictEqual(target.requests, [ // Manifest { type: 'GET', url: `${target.url}/v1/manifest`, headers: {} }, // Machine @@ -356,8 +356,8 @@ suite('UserDataAutoSyncService', () => { const e = await errorPromise; assert.ok(e instanceof UserDataAutoSyncError); - assert.deepEqual((e).code, UserDataSyncErrorCode.SessionExpired); - assert.deepEqual(target.requests, [ + assert.deepStrictEqual((e).code, UserDataSyncErrorCode.SessionExpired); + assert.deepStrictEqual(target.requests, [ // Manifest { type: 'GET', url: `${target.url}/v1/manifest`, headers: {} }, // Machine @@ -380,7 +380,7 @@ suite('UserDataAutoSyncService', () => { const e = await errorPromise; assert.ok(e instanceof UserDataSyncStoreError); - assert.deepEqual((e).code, UserDataSyncErrorCode.TooManyRequests); + assert.deepStrictEqual((e).code, UserDataSyncErrorCode.TooManyRequests); }); test('test auto sync is suspended when server donot accepts requests', async () => { @@ -398,7 +398,7 @@ suite('UserDataAutoSyncService', () => { target.reset(); await testObject.sync(); - assert.deepEqual(target.requests, []); + assert.deepStrictEqual(target.requests, []); }); test('test cache control header with no cache is sent when triggered with disable cache option', async () => { @@ -410,7 +410,7 @@ suite('UserDataAutoSyncService', () => { const testObject: TestUserDataAutoSyncService = disposableStore.add(testClient.instantiationService.createInstance(TestUserDataAutoSyncService)); await testObject.triggerSync(['some reason'], true, true); - assert.equal(target.requestsWithAllHeaders[0].headers!['Cache-Control'], 'no-cache'); + assert.strictEqual(target.requestsWithAllHeaders[0].headers!['Cache-Control'], 'no-cache'); }); test('test cache control header is not sent when triggered without disable cache option', async () => { @@ -422,7 +422,7 @@ suite('UserDataAutoSyncService', () => { const testObject: TestUserDataAutoSyncService = disposableStore.add(testClient.instantiationService.createInstance(TestUserDataAutoSyncService)); await testObject.triggerSync(['some reason'], true, false); - assert.equal(target.requestsWithAllHeaders[0].headers!['Cache-Control'], undefined); + assert.strictEqual(target.requestsWithAllHeaders[0].headers!['Cache-Control'], undefined); }); }); diff --git a/src/vs/platform/userDataSync/test/common/userDataSyncService.test.ts b/src/vs/platform/userDataSync/test/common/userDataSyncService.test.ts index 9a88da0a..62fddee8 100644 --- a/src/vs/platform/userDataSync/test/common/userDataSyncService.test.ts +++ b/src/vs/platform/userDataSync/test/common/userDataSyncService.test.ts @@ -28,7 +28,7 @@ suite('UserDataSyncService', () => { // Sync for first time await (await testObject.createSyncTask()).run(); - assert.deepEqual(target.requests, [ + assert.deepStrictEqual(target.requests, [ // Manifest { type: 'GET', url: `${target.url}/v1/manifest`, headers: {} }, // Settings @@ -59,7 +59,7 @@ suite('UserDataSyncService', () => { // Sync for first time await (await testObject.createSyncTask()).run(); - assert.deepEqual(target.requests, [ + assert.deepStrictEqual(target.requests, [ // Manifest { type: 'GET', url: `${target.url}/v1/manifest`, headers: {} }, // Settings @@ -93,7 +93,7 @@ suite('UserDataSyncService', () => { target.reset(); await (await testObject.createSyncTask()).run(); - assert.deepEqual(target.requests, [ + assert.deepStrictEqual(target.requests, [ { type: 'GET', url: `${target.url}/v1/manifest`, headers: {} }, { type: 'GET', url: `${target.url}/v1/resource/settings/latest`, headers: {} }, { type: 'GET', url: `${target.url}/v1/resource/keybindings/latest`, headers: {} }, @@ -127,7 +127,7 @@ suite('UserDataSyncService', () => { target.reset(); await (await testObject.createSyncTask()).run(); - assert.deepEqual(target.requests, [ + assert.deepStrictEqual(target.requests, [ { type: 'GET', url: `${target.url}/v1/manifest`, headers: {} }, { type: 'GET', url: `${target.url}/v1/resource/settings/latest`, headers: {} }, { type: 'POST', url: `${target.url}/v1/resource/settings`, headers: { 'If-Match': '1' } }, @@ -154,7 +154,7 @@ suite('UserDataSyncService', () => { target.reset(); await (await testObject.createSyncTask()).run(); - assert.deepEqual(target.requests, [ + assert.deepStrictEqual(target.requests, [ // Manifest { type: 'GET', url: `${target.url}/v1/manifest`, headers: {} }, ]); @@ -181,7 +181,7 @@ suite('UserDataSyncService', () => { // Sync from the client await (await testObject.createSyncTask()).run(); - assert.deepEqual(target.requests, [ + assert.deepStrictEqual(target.requests, [ // Manifest { type: 'GET', url: `${target.url}/v1/manifest`, headers: {} }, // Settings @@ -222,7 +222,7 @@ suite('UserDataSyncService', () => { target.reset(); await (await testObject.createSyncTask()).run(); - assert.deepEqual(target.requests, [ + assert.deepStrictEqual(target.requests, [ // Manifest { type: 'GET', url: `${target.url}/v1/manifest`, headers: {} }, // Settings @@ -250,7 +250,7 @@ suite('UserDataSyncService', () => { target.reset(); await testObject.reset(); - assert.deepEqual(target.requests, [ + assert.deepStrictEqual(target.requests, [ // Manifest { type: 'DELETE', url: `${target.url}/v1/resource`, headers: {} }, ]); @@ -273,7 +273,7 @@ suite('UserDataSyncService', () => { target.reset(); await (await testObject.createSyncTask()).run(); - assert.deepEqual(target.requests, [ + assert.deepStrictEqual(target.requests, [ // Manifest { type: 'GET', url: `${target.url}/v1/manifest`, headers: {} }, // Settings @@ -308,7 +308,7 @@ suite('UserDataSyncService', () => { await (await testObject.createSyncTask()).run(); disposable.dispose(); - assert.deepEqual(actualStatuses, [SyncStatus.Syncing, SyncStatus.Idle, SyncStatus.Syncing, SyncStatus.Idle, SyncStatus.Syncing, SyncStatus.Idle, SyncStatus.Syncing, SyncStatus.Idle, SyncStatus.Syncing, SyncStatus.Idle]); + assert.deepStrictEqual(actualStatuses, [SyncStatus.Syncing, SyncStatus.Idle, SyncStatus.Syncing, SyncStatus.Idle, SyncStatus.Syncing, SyncStatus.Idle, SyncStatus.Syncing, SyncStatus.Idle, SyncStatus.Syncing, SyncStatus.Idle]); }); test('test sync conflicts status', async () => { @@ -333,8 +333,8 @@ suite('UserDataSyncService', () => { // sync from the client await (await testObject.createSyncTask()).run(); - assert.deepEqual(testObject.status, SyncStatus.HasConflicts); - assert.deepEqual(testObject.conflicts.map(([syncResource]) => syncResource), [SyncResource.Settings]); + assert.deepStrictEqual(testObject.status, SyncStatus.HasConflicts); + assert.deepStrictEqual(testObject.conflicts.map(([syncResource]) => syncResource), [SyncResource.Settings]); }); test('test sync will sync other non conflicted areas', async () => { @@ -368,10 +368,10 @@ suite('UserDataSyncService', () => { await (await testObject.createSyncTask()).run(); disposable.dispose(); - assert.deepEqual(actualStatuses, []); - assert.deepEqual(testObject.status, SyncStatus.HasConflicts); + assert.deepStrictEqual(actualStatuses, []); + assert.deepStrictEqual(testObject.status, SyncStatus.HasConflicts); - assert.deepEqual(target.requests, [ + assert.deepStrictEqual(target.requests, [ // Manifest { type: 'GET', url: `${target.url}/v1/manifest`, headers: {} }, // Keybindings @@ -403,8 +403,8 @@ suite('UserDataSyncService', () => { syncTask.run().then(null, () => null /* ignore error */); await syncTask.stop(); - assert.deepEqual(testObject.status, SyncStatus.Idle); - assert.deepEqual(testObject.conflicts, []); + assert.deepStrictEqual(testObject.status, SyncStatus.Idle); + assert.deepStrictEqual(testObject.conflicts, []); }); test('test sync send execution id header', async () => { diff --git a/src/vs/platform/userDataSync/test/common/userDataSyncStoreService.test.ts b/src/vs/platform/userDataSync/test/common/userDataSyncStoreService.test.ts index 345fe859..79631bb5 100644 --- a/src/vs/platform/userDataSync/test/common/userDataSyncStoreService.test.ts +++ b/src/vs/platform/userDataSync/test/common/userDataSyncStoreService.test.ts @@ -7,7 +7,8 @@ import * as assert from 'assert'; import { IUserDataSyncStoreService, SyncResource, UserDataSyncErrorCode, UserDataSyncStoreError, IUserDataSyncStoreManagementService, IUserDataSyncStore } from 'vs/platform/userDataSync/common/userDataSync'; import { UserDataSyncClient, UserDataSyncTestServer } from 'vs/platform/userDataSync/test/common/userDataSyncClient'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import { IProductService, ConfigurationSyncStore } from 'vs/platform/product/common/productService'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { ConfigurationSyncStore } from 'vs/base/common/product'; import { isWeb } from 'vs/base/common/platform'; import { RequestsSession, UserDataSyncStoreService, UserDataSyncStoreManagementService } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -61,9 +62,9 @@ suite('UserDataSyncStoreManagementService', () => { const testObject: IUserDataSyncStoreManagementService = disposableStore.add(client.instantiationService.createInstance(UserDataSyncStoreManagementService)); - assert.equal(testObject.userDataSyncStore?.url.toString(), expected.url.toString()); - assert.equal(testObject.userDataSyncStore?.defaultUrl.toString(), expected.defaultUrl.toString()); - assert.deepEqual(testObject.userDataSyncStore?.authenticationProviders, expected.authenticationProviders); + assert.strictEqual(testObject.userDataSyncStore?.url.toString(), expected.url.toString()); + assert.strictEqual(testObject.userDataSyncStore?.defaultUrl.toString(), expected.defaultUrl.toString()); + assert.deepStrictEqual(testObject.userDataSyncStore?.authenticationProviders, expected.authenticationProviders); }); }); @@ -84,12 +85,12 @@ suite('UserDataSyncStoreService', () => { await testObject.manifest(); - assert.equal(target.requestsWithAllHeaders.length, 1); - assert.equal(target.requestsWithAllHeaders[0].headers!['X-Client-Name'], `${productService.applicationName}${isWeb ? '-web' : ''}`); - assert.equal(target.requestsWithAllHeaders[0].headers!['X-Client-Version'], productService.version); - assert.notEqual(target.requestsWithAllHeaders[0].headers!['X-Machine-Id'], undefined); - assert.notEqual(target.requestsWithAllHeaders[0].headers!['X-Machine-Session-Id'], undefined); - assert.equal(target.requestsWithAllHeaders[0].headers!['X-User-Session-Id'], undefined); + assert.strictEqual(target.requestsWithAllHeaders.length, 1); + assert.strictEqual(target.requestsWithAllHeaders[0].headers!['X-Client-Name'], `${productService.applicationName}${isWeb ? '-web' : ''}`); + assert.strictEqual(target.requestsWithAllHeaders[0].headers!['X-Client-Version'], productService.version); + assert.notStrictEqual(target.requestsWithAllHeaders[0].headers!['X-Machine-Id'], undefined); + assert.notStrictEqual(target.requestsWithAllHeaders[0].headers!['X-Machine-Session-Id'], undefined); + assert.strictEqual(target.requestsWithAllHeaders[0].headers!['X-User-Session-Id'], undefined); }); test('test read manifest for the second time when session is not yet created', async () => { @@ -105,9 +106,9 @@ suite('UserDataSyncStoreService', () => { target.reset(); await testObject.manifest(); - assert.equal(target.requestsWithAllHeaders.length, 1); - assert.equal(target.requestsWithAllHeaders[0].headers!['X-Machine-Session-Id'], machineSessionId); - assert.equal(target.requestsWithAllHeaders[0].headers!['X-User-Session-Id'], undefined); + assert.strictEqual(target.requestsWithAllHeaders.length, 1); + assert.strictEqual(target.requestsWithAllHeaders[0].headers!['X-Machine-Session-Id'], machineSessionId); + assert.strictEqual(target.requestsWithAllHeaders[0].headers!['X-User-Session-Id'], undefined); }); test('test session id header is not set in the first manifest request after session is created', async () => { @@ -124,9 +125,9 @@ suite('UserDataSyncStoreService', () => { target.reset(); await testObject.manifest(); - assert.equal(target.requestsWithAllHeaders.length, 1); - assert.equal(target.requestsWithAllHeaders[0].headers!['X-Machine-Session-Id'], machineSessionId); - assert.equal(target.requestsWithAllHeaders[0].headers!['X-User-Session-Id'], undefined); + assert.strictEqual(target.requestsWithAllHeaders.length, 1); + assert.strictEqual(target.requestsWithAllHeaders[0].headers!['X-Machine-Session-Id'], machineSessionId); + assert.strictEqual(target.requestsWithAllHeaders[0].headers!['X-User-Session-Id'], undefined); }); test('test session id header is set from the second manifest request after session is created', async () => { @@ -144,9 +145,9 @@ suite('UserDataSyncStoreService', () => { target.reset(); await testObject.manifest(); - assert.equal(target.requestsWithAllHeaders.length, 1); - assert.equal(target.requestsWithAllHeaders[0].headers!['X-Machine-Session-Id'], machineSessionId); - assert.notEqual(target.requestsWithAllHeaders[0].headers!['X-User-Session-Id'], undefined); + assert.strictEqual(target.requestsWithAllHeaders.length, 1); + assert.strictEqual(target.requestsWithAllHeaders[0].headers!['X-Machine-Session-Id'], machineSessionId); + assert.notStrictEqual(target.requestsWithAllHeaders[0].headers!['X-User-Session-Id'], undefined); }); test('test headers are send for write request', async () => { @@ -165,9 +166,9 @@ suite('UserDataSyncStoreService', () => { target.reset(); await testObject.write(SyncResource.Settings, 'some content', null); - assert.equal(target.requestsWithAllHeaders.length, 1); - assert.equal(target.requestsWithAllHeaders[0].headers!['X-Machine-Session-Id'], machineSessionId); - assert.notEqual(target.requestsWithAllHeaders[0].headers!['X-User-Session-Id'], undefined); + assert.strictEqual(target.requestsWithAllHeaders.length, 1); + assert.strictEqual(target.requestsWithAllHeaders[0].headers!['X-Machine-Session-Id'], machineSessionId); + assert.notStrictEqual(target.requestsWithAllHeaders[0].headers!['X-User-Session-Id'], undefined); }); test('test headers are send for read request', async () => { @@ -186,9 +187,9 @@ suite('UserDataSyncStoreService', () => { target.reset(); await testObject.read(SyncResource.Settings, null); - assert.equal(target.requestsWithAllHeaders.length, 1); - assert.equal(target.requestsWithAllHeaders[0].headers!['X-Machine-Session-Id'], machineSessionId); - assert.notEqual(target.requestsWithAllHeaders[0].headers!['X-User-Session-Id'], undefined); + assert.strictEqual(target.requestsWithAllHeaders.length, 1); + assert.strictEqual(target.requestsWithAllHeaders[0].headers!['X-Machine-Session-Id'], machineSessionId); + assert.notStrictEqual(target.requestsWithAllHeaders[0].headers!['X-User-Session-Id'], undefined); }); test('test headers are reset after session is cleared ', async () => { @@ -208,10 +209,10 @@ suite('UserDataSyncStoreService', () => { target.reset(); await testObject.manifest(); - assert.equal(target.requestsWithAllHeaders.length, 1); - assert.notEqual(target.requestsWithAllHeaders[0].headers!['X-Machine-Session-Id'], undefined); - assert.notEqual(target.requestsWithAllHeaders[0].headers!['X-Machine-Session-Id'], machineSessionId); - assert.equal(target.requestsWithAllHeaders[0].headers!['X-User-Session-Id'], undefined); + assert.strictEqual(target.requestsWithAllHeaders.length, 1); + assert.notStrictEqual(target.requestsWithAllHeaders[0].headers!['X-Machine-Session-Id'], undefined); + assert.notStrictEqual(target.requestsWithAllHeaders[0].headers!['X-Machine-Session-Id'], machineSessionId); + assert.strictEqual(target.requestsWithAllHeaders[0].headers!['X-User-Session-Id'], undefined); }); test('test old headers are sent after session is changed on server ', async () => { @@ -239,11 +240,11 @@ suite('UserDataSyncStoreService', () => { target.reset(); await testObject.manifest(); - assert.equal(target.requestsWithAllHeaders.length, 1); - assert.notEqual(target.requestsWithAllHeaders[0].headers!['X-Machine-Session-Id'], undefined); - assert.equal(target.requestsWithAllHeaders[0].headers!['X-Machine-Session-Id'], machineSessionId); - assert.notEqual(target.requestsWithAllHeaders[0].headers!['X-User-Session-Id'], undefined); - assert.equal(target.requestsWithAllHeaders[0].headers!['X-User-Session-Id'], userSessionId); + assert.strictEqual(target.requestsWithAllHeaders.length, 1); + assert.notStrictEqual(target.requestsWithAllHeaders[0].headers!['X-Machine-Session-Id'], undefined); + assert.strictEqual(target.requestsWithAllHeaders[0].headers!['X-Machine-Session-Id'], machineSessionId); + assert.notStrictEqual(target.requestsWithAllHeaders[0].headers!['X-User-Session-Id'], undefined); + assert.strictEqual(target.requestsWithAllHeaders[0].headers!['X-User-Session-Id'], userSessionId); }); test('test old headers are reset from second request after session is changed on server ', async () => { @@ -272,11 +273,11 @@ suite('UserDataSyncStoreService', () => { target.reset(); await testObject.manifest(); - assert.equal(target.requestsWithAllHeaders.length, 1); - assert.notEqual(target.requestsWithAllHeaders[0].headers!['X-Machine-Session-Id'], undefined); - assert.notEqual(target.requestsWithAllHeaders[0].headers!['X-Machine-Session-Id'], machineSessionId); - assert.notEqual(target.requestsWithAllHeaders[0].headers!['X-User-Session-Id'], undefined); - assert.notEqual(target.requestsWithAllHeaders[0].headers!['X-User-Session-Id'], userSessionId); + assert.strictEqual(target.requestsWithAllHeaders.length, 1); + assert.notStrictEqual(target.requestsWithAllHeaders[0].headers!['X-Machine-Session-Id'], undefined); + assert.notStrictEqual(target.requestsWithAllHeaders[0].headers!['X-Machine-Session-Id'], machineSessionId); + assert.notStrictEqual(target.requestsWithAllHeaders[0].headers!['X-User-Session-Id'], undefined); + assert.notStrictEqual(target.requestsWithAllHeaders[0].headers!['X-User-Session-Id'], userSessionId); }); test('test old headers are sent after session is cleared from another server ', async () => { @@ -303,11 +304,11 @@ suite('UserDataSyncStoreService', () => { target.reset(); await testObject.manifest(); - assert.equal(target.requestsWithAllHeaders.length, 1); - assert.notEqual(target.requestsWithAllHeaders[0].headers!['X-Machine-Session-Id'], undefined); - assert.equal(target.requestsWithAllHeaders[0].headers!['X-Machine-Session-Id'], machineSessionId); - assert.notEqual(target.requestsWithAllHeaders[0].headers!['X-User-Session-Id'], undefined); - assert.equal(target.requestsWithAllHeaders[0].headers!['X-User-Session-Id'], userSessionId); + assert.strictEqual(target.requestsWithAllHeaders.length, 1); + assert.notStrictEqual(target.requestsWithAllHeaders[0].headers!['X-Machine-Session-Id'], undefined); + assert.strictEqual(target.requestsWithAllHeaders[0].headers!['X-Machine-Session-Id'], machineSessionId); + assert.notStrictEqual(target.requestsWithAllHeaders[0].headers!['X-User-Session-Id'], undefined); + assert.strictEqual(target.requestsWithAllHeaders[0].headers!['X-User-Session-Id'], userSessionId); }); test('test headers are reset after session is cleared from another server ', async () => { @@ -334,10 +335,10 @@ suite('UserDataSyncStoreService', () => { target.reset(); await testObject.manifest(); - assert.equal(target.requestsWithAllHeaders.length, 1); - assert.notEqual(target.requestsWithAllHeaders[0].headers!['X-Machine-Session-Id'], undefined); - assert.notEqual(target.requestsWithAllHeaders[0].headers!['X-Machine-Session-Id'], machineSessionId); - assert.equal(target.requestsWithAllHeaders[0].headers!['X-User-Session-Id'], undefined); + assert.strictEqual(target.requestsWithAllHeaders.length, 1); + assert.notStrictEqual(target.requestsWithAllHeaders[0].headers!['X-Machine-Session-Id'], undefined); + assert.notStrictEqual(target.requestsWithAllHeaders[0].headers!['X-Machine-Session-Id'], machineSessionId); + assert.strictEqual(target.requestsWithAllHeaders[0].headers!['X-User-Session-Id'], undefined); }); test('test headers are reset after session is cleared from another server - started syncing again', async () => { @@ -367,11 +368,11 @@ suite('UserDataSyncStoreService', () => { target.reset(); await testObject.manifest(); - assert.equal(target.requestsWithAllHeaders.length, 1); - assert.notEqual(target.requestsWithAllHeaders[0].headers!['X-Machine-Session-Id'], undefined); - assert.notEqual(target.requestsWithAllHeaders[0].headers!['X-Machine-Session-Id'], machineSessionId); - assert.notEqual(target.requestsWithAllHeaders[0].headers!['X-User-Session-Id'], userSessionId); - assert.notEqual(target.requestsWithAllHeaders[0].headers!['X-User-Session-Id'], undefined); + assert.strictEqual(target.requestsWithAllHeaders.length, 1); + assert.notStrictEqual(target.requestsWithAllHeaders[0].headers!['X-Machine-Session-Id'], undefined); + assert.notStrictEqual(target.requestsWithAllHeaders[0].headers!['X-Machine-Session-Id'], machineSessionId); + assert.notStrictEqual(target.requestsWithAllHeaders[0].headers!['X-User-Session-Id'], userSessionId); + assert.notStrictEqual(target.requestsWithAllHeaders[0].headers!['X-User-Session-Id'], undefined); }); test('test rate limit on server with retry after', async () => { @@ -388,7 +389,7 @@ suite('UserDataSyncStoreService', () => { assert.fail('should fail'); } catch (e) { assert.ok(e instanceof UserDataSyncStoreError); - assert.deepEqual((e).code, UserDataSyncErrorCode.TooManyRequestsAndRetryAfter); + assert.deepStrictEqual((e).code, UserDataSyncErrorCode.TooManyRequestsAndRetryAfter); await promise; assert.ok(!!testObject.donotMakeRequestsUntil); } @@ -421,7 +422,7 @@ suite('UserDataSyncStoreService', () => { } catch (e) { } const target = disposableStore.add(client.instantiationService.createInstance(UserDataSyncStoreService)); - assert.equal(target.donotMakeRequestsUntil?.getTime(), testObject.donotMakeRequestsUntil?.getTime()); + assert.strictEqual(target.donotMakeRequestsUntil?.getTime(), testObject.donotMakeRequestsUntil?.getTime()); }); test('test donotMakeRequestsUntil is checked and reset after retreived', async () => { @@ -450,7 +451,7 @@ suite('UserDataSyncStoreService', () => { const expected = await testObject.read(SyncResource.Settings, null); const actual = await testObject.read(SyncResource.Settings, expected); - assert.equal(actual, expected); + assert.strictEqual(actual, expected); }); }); @@ -471,7 +472,7 @@ suite('UserDataSyncRequestsSession', () => { await testObject.request('url', {}, CancellationToken.None); } catch (error) { assert.ok(error instanceof UserDataSyncStoreError); - assert.equal((error).code, UserDataSyncErrorCode.LocalTooManyRequests); + assert.strictEqual((error).code, UserDataSyncErrorCode.LocalTooManyRequests); return; } assert.fail('Should fail with limit exceeded'); @@ -494,7 +495,7 @@ suite('UserDataSyncRequestsSession', () => { await testObject.request('url', {}, CancellationToken.None); } catch (error) { assert.ok(error instanceof UserDataSyncStoreError); - assert.equal((error).code, UserDataSyncErrorCode.LocalTooManyRequests); + assert.strictEqual((error).code, UserDataSyncErrorCode.LocalTooManyRequests); return; } assert.fail('Should fail with limit exceeded'); diff --git a/src/vs/platform/webview/common/webviewManagerService.ts b/src/vs/platform/webview/common/webviewManagerService.ts index 98fe61bf..a2fac061 100644 --- a/src/vs/platform/webview/common/webviewManagerService.ts +++ b/src/vs/platform/webview/common/webviewManagerService.ts @@ -3,14 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { VSBuffer } from 'vs/base/common/buffer'; -import { UriComponents } from 'vs/base/common/uri'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IRemoteConnectionData } from 'vs/platform/remote/common/remoteAuthorityResolver'; -import { IWebviewPortMapping } from 'vs/platform/webview/common/webviewPortMapping'; export const IWebviewManagerService = createDecorator('webviewManagerService'); +export const webviewPartitionId = 'webview'; + export interface WebviewWebContentsId { readonly webContentsId: number; } @@ -19,32 +17,8 @@ export interface WebviewWindowId { readonly windowId: number; } -export type WebviewManagerDidLoadResourceResponse = - VSBuffer - | 'not-modified' - | 'access-denied' - | 'not-found'; - -export interface WebviewManagerDidLoadResourceResponseDetails { - readonly etag?: string; -} - export interface IWebviewManagerService { _serviceBrand: unknown; - registerWebview(id: string, windowId: number, metadata: RegisterWebviewMetadata): Promise; - unregisterWebview(id: string): Promise; - updateWebviewMetadata(id: string, metadataDelta: Partial): Promise; - - /** Note: the VSBuffer must be a top level argument so that it can be serialized and deserialized properly */ - didLoadResource(requestId: number, response: WebviewManagerDidLoadResourceResponse, responseDetails?: WebviewManagerDidLoadResourceResponseDetails): void; - setIgnoreMenuShortcuts(id: WebviewWebContentsId | WebviewWindowId, enabled: boolean): Promise; } - -export interface RegisterWebviewMetadata { - readonly extensionLocation: UriComponents | undefined; - readonly localResourceRoots: readonly UriComponents[]; - readonly remoteConnectionData: IRemoteConnectionData | null; - readonly portMappings: readonly IWebviewPortMapping[]; -} diff --git a/src/vs/platform/webview/electron-main/webviewMainService.ts b/src/vs/platform/webview/electron-main/webviewMainService.ts index 549dda62..1665474e 100644 --- a/src/vs/platform/webview/electron-main/webviewMainService.ts +++ b/src/vs/platform/webview/electron-main/webviewMainService.ts @@ -5,14 +5,8 @@ import { session, WebContents, webContents } from 'electron'; import { Disposable } from 'vs/base/common/lifecycle'; -import { URI } from 'vs/base/common/uri'; -import { IFileService } from 'vs/platform/files/common/files'; -import { ILogService } from 'vs/platform/log/common/log'; import { ITunnelService } from 'vs/platform/remote/common/tunnel'; -import { IRequestService } from 'vs/platform/request/common/request'; -import { webviewPartitionId } from 'vs/platform/webview/common/resourceLoader'; -import { IWebviewManagerService, RegisterWebviewMetadata, WebviewManagerDidLoadResourceResponse, WebviewManagerDidLoadResourceResponseDetails, WebviewWebContentsId, WebviewWindowId } from 'vs/platform/webview/common/webviewManagerService'; -import { WebviewPortMappingProvider } from 'vs/platform/webview/electron-main/webviewPortMappingProvider'; +import { IWebviewManagerService, webviewPartitionId, WebviewWebContentsId, WebviewWindowId } from 'vs/platform/webview/common/webviewManagerService'; import { WebviewProtocolProvider } from 'vs/platform/webview/electron-main/webviewProtocolProvider'; import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows'; @@ -20,19 +14,12 @@ export class WebviewMainService extends Disposable implements IWebviewManagerSer declare readonly _serviceBrand: undefined; - private readonly protocolProvider: WebviewProtocolProvider; - private readonly portMappingProvider: WebviewPortMappingProvider; - constructor( - @IFileService fileService: IFileService, - @ILogService logService: ILogService, - @IRequestService requestService: IRequestService, @ITunnelService tunnelService: ITunnelService, @IWindowsMainService private readonly windowsMainService: IWindowsMainService, ) { super(); - this.protocolProvider = this._register(new WebviewProtocolProvider(fileService, logService, requestService, windowsMainService)); - this.portMappingProvider = this._register(new WebviewPortMappingProvider(tunnelService)); + this._register(new WebviewProtocolProvider()); const sess = session.fromPartition(webviewPartitionId); sess.setPermissionRequestHandler((_webContents, permission, callback) => { @@ -48,43 +35,6 @@ export class WebviewMainService extends Disposable implements IWebviewManagerSer }); } - public async registerWebview(id: string, windowId: number, metadata: RegisterWebviewMetadata): Promise { - const extensionLocation = metadata.extensionLocation ? URI.from(metadata.extensionLocation) : undefined; - - this.protocolProvider.registerWebview(id, { - ...metadata, - windowId: windowId, - extensionLocation, - localResourceRoots: metadata.localResourceRoots.map(x => URI.from(x)) - }); - - this.portMappingProvider.registerWebview(id, { - extensionLocation, - mappings: metadata.portMappings, - resolvedAuthority: metadata.remoteConnectionData, - }); - } - - public async unregisterWebview(id: string): Promise { - this.protocolProvider.unregisterWebview(id); - this.portMappingProvider.unregisterWebview(id); - } - - public async updateWebviewMetadata(id: string, metaDataDelta: Partial): Promise { - const extensionLocation = metaDataDelta.extensionLocation ? URI.from(metaDataDelta.extensionLocation) : undefined; - - this.protocolProvider.updateWebviewMetadata(id, { - ...metaDataDelta, - extensionLocation, - localResourceRoots: metaDataDelta.localResourceRoots?.map(x => URI.from(x)), - }); - - this.portMappingProvider.updateWebviewMetadata(id, { - ...metaDataDelta, - extensionLocation, - }); - } - public async setIgnoreMenuShortcuts(id: WebviewWebContentsId | WebviewWindowId, enabled: boolean): Promise { let contents: WebContents | undefined; @@ -107,12 +57,4 @@ export class WebviewMainService extends Disposable implements IWebviewManagerSer contents.setIgnoreMenuShortcuts(enabled); } } - - public async didLoadResource( - requestId: number, - response: WebviewManagerDidLoadResourceResponse, - responseDetails?: WebviewManagerDidLoadResourceResponseDetails, - ): Promise { - this.protocolProvider.didLoadResource(requestId, response, responseDetails); - } } diff --git a/src/vs/platform/webview/electron-main/webviewPortMappingProvider.ts b/src/vs/platform/webview/electron-main/webviewPortMappingProvider.ts deleted file mode 100644 index f97a8bc0..00000000 --- a/src/vs/platform/webview/electron-main/webviewPortMappingProvider.ts +++ /dev/null @@ -1,103 +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 { OnBeforeRequestListenerDetails, session, webContents } from 'electron'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { Schemas } from 'vs/base/common/network'; -import { URI } from 'vs/base/common/uri'; -import { IAddress } from 'vs/platform/remote/common/remoteAgentConnection'; -import { ITunnelService } from 'vs/platform/remote/common/tunnel'; -import { webviewPartitionId } from 'vs/platform/webview/common/resourceLoader'; -import { IWebviewPortMapping, WebviewPortMappingManager } from 'vs/platform/webview/common/webviewPortMapping'; - -interface OnBeforeRequestListenerDetails_Extended extends OnBeforeRequestListenerDetails { - readonly lastCommittedOrigin?: string; -} - -interface PortMappingData { - readonly extensionLocation: URI | undefined; - readonly mappings: readonly IWebviewPortMapping[]; - readonly resolvedAuthority: IAddress | null | undefined; -} - -interface WebviewData { - readonly manager: WebviewPortMappingManager; - readonly metadata: PortMappingData; -} - -export class WebviewPortMappingProvider extends Disposable { - - private readonly _webviewData = new Map(); - - constructor( - @ITunnelService private readonly _tunnelService: ITunnelService, - ) { - super(); - - const sess = session.fromPartition(webviewPartitionId); - sess.webRequest.onBeforeRequest({ - urls: [ - '*://localhost:*/*', - '*://127.0.0.1:*/*', - '*://0.0.0.0:*/*', - ] - }, async (details: OnBeforeRequestListenerDetails_Extended, callback) => { - let webviewId: string | undefined; - try { - if (details.lastCommittedOrigin) { - const origin = URI.parse(details.lastCommittedOrigin); - webviewId = origin.authority; - } else if (typeof details.webContentsId === 'number') { - const contents = webContents.fromId(details.webContentsId); - const url = URI.parse(contents.getURL()); - if (url.scheme === Schemas.vscodeWebview) { - webviewId = url.authority; - } - } - } catch { - return callback({}); - } - - if (!webviewId) { - return callback({}); - } - - const entry = this._webviewData.get(webviewId); - if (!entry) { - return callback({}); - } - - const redirect = await entry.manager.getRedirect(entry.metadata.resolvedAuthority, details.url); - return callback(redirect ? { redirectURL: redirect } : {}); - }); - } - - public async registerWebview(id: string, metadata: PortMappingData): Promise { - const manager = new WebviewPortMappingManager( - () => this._webviewData.get(id)?.metadata.extensionLocation, - () => this._webviewData.get(id)?.metadata.mappings || [], - this._tunnelService); - - this._webviewData.set(id, { metadata, manager }); - } - - public unregisterWebview(id: string): void { - const existing = this._webviewData.get(id); - if (existing) { - existing.manager.dispose(); - this._webviewData.delete(id); - } - } - - public async updateWebviewMetadata(id: string, metadataDelta: Partial): Promise { - const entry = this._webviewData.get(id); - if (entry) { - this._webviewData.set(id, { - ...entry, - ...metadataDelta, - }); - } - } -} diff --git a/src/vs/platform/webview/electron-main/webviewProtocolProvider.ts b/src/vs/platform/webview/electron-main/webviewProtocolProvider.ts index c25169b9..db8288cb 100644 --- a/src/vs/platform/webview/electron-main/webviewProtocolProvider.ts +++ b/src/vs/platform/webview/electron-main/webviewProtocolProvider.ts @@ -4,53 +4,24 @@ *--------------------------------------------------------------------------------------------*/ import { protocol, session } from 'electron'; -import { Readable } from 'stream'; -import { bufferToStream, VSBufferReadableStream } from 'vs/base/common/buffer'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; +import { Disposable } from 'vs/base/common/lifecycle'; import { FileAccess, Schemas } from 'vs/base/common/network'; -import { listenStream } from 'vs/base/common/stream'; import { URI } from 'vs/base/common/uri'; -import { FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files'; -import { ILogService } from 'vs/platform/log/common/log'; -import { IRemoteConnectionData } from 'vs/platform/remote/common/remoteAuthorityResolver'; -import { IRequestService } from 'vs/platform/request/common/request'; -import { loadLocalResource, readFileStream, WebviewFileReadResponse, webviewPartitionId, WebviewResourceFileReader, WebviewResourceResponse } from 'vs/platform/webview/common/resourceLoader'; -import { WebviewManagerDidLoadResourceResponse, WebviewManagerDidLoadResourceResponseDetails } from 'vs/platform/webview/common/webviewManagerService'; -import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows'; +import { webviewPartitionId } from 'vs/platform/webview/common/webviewManagerService'; -interface WebviewMetadata { - readonly windowId: number; - readonly extensionLocation: URI | undefined; - readonly localResourceRoots: readonly URI[]; - readonly remoteConnectionData: IRemoteConnectionData | null; -} - -interface PendingResourceResult { - readonly response: WebviewManagerDidLoadResourceResponse; - readonly responseDetails?: WebviewManagerDidLoadResourceResponseDetails; -} export class WebviewProtocolProvider extends Disposable { private static validWebviewFilePaths = new Map([ ['/index.html', 'index.html'], - ['/electron-browser/index.html', 'index.html'], + ['/fake.html', 'fake.html'], + ['/electron-browser-index.html', 'index.html'], ['/main.js', 'main.js'], ['/host.js', 'host.js'], + ['/service-worker.js', 'service-worker.js'], ]); - private readonly webviewMetadata = new Map(); - - private requestIdPool = 1; - private readonly pendingResourceReads = new Map void }>(); - - constructor( - @IFileService private readonly fileService: IFileService, - @ILogService private readonly logService: ILogService, - @IRequestService private readonly requestService: IRequestService, - @IWindowsMainService private readonly windowsMainService: IWindowsMainService, - ) { + constructor() { super(); const sess = session.fromPartition(webviewPartitionId); @@ -59,79 +30,6 @@ export class WebviewProtocolProvider extends Disposable { const webviewHandler = this.handleWebviewRequest.bind(this); protocol.registerFileProtocol(Schemas.vscodeWebview, webviewHandler); sess.protocol.registerFileProtocol(Schemas.vscodeWebview, webviewHandler); - - // Register the protocol loading webview resources both inside the webview and at the top level - const webviewResourceHandler = this.handleWebviewResourceRequest.bind(this); - protocol.registerStreamProtocol(Schemas.vscodeWebviewResource, webviewResourceHandler); - sess.protocol.registerStreamProtocol(Schemas.vscodeWebviewResource, webviewResourceHandler); - - this._register(toDisposable(() => { - protocol.unregisterProtocol(Schemas.vscodeWebviewResource); - sess.protocol.unregisterProtocol(Schemas.vscodeWebviewResource); - protocol.unregisterProtocol(Schemas.vscodeWebview); - sess.protocol.unregisterProtocol(Schemas.vscodeWebview); - })); - } - - private streamToNodeReadable(stream: VSBufferReadableStream): Readable { - return new class extends Readable { - private listening = false; - - _read(size?: number): void { - if (!this.listening) { - this.listening = true; - - listenStream(stream, { - onData: data => { - try { - if (!this.push(data.buffer)) { - stream.pause(); // pause the stream if we should not push anymore - } - } catch (error) { - this.emit(error); - } - }, - onError: error => { - this.emit('error', error); - }, - onEnd: () => { - try { - this.push(null); // signal EOS - } catch (error) { - this.emit(error); - } - } - }); - } - - // ensure the stream is flowing - stream.resume(); - } - - _destroy(error: Error | null, callback: (error: Error | null) => void): void { - stream.destroy(); - - callback(null); - } - }; - } - - public async registerWebview(id: string, metadata: WebviewMetadata): Promise { - this.webviewMetadata.set(id, metadata); - } - - public unregisterWebview(id: string): void { - this.webviewMetadata.delete(id); - } - - public async updateWebviewMetadata(id: string, metadataDelta: Partial): Promise { - const entry = this.webviewMetadata.get(id); - if (entry) { - this.webviewMetadata.set(id, { - ...entry, - ...metadataDelta, - }); - } } private async handleWebviewRequest( @@ -154,164 +52,4 @@ export class WebviewProtocolProvider extends Disposable { } callback({ error: -10 /* ACCESS_DENIED - https://cs.chromium.org/chromium/src/net/base/net_error_list.h?l=32 */ }); } - - private async handleWebviewResourceRequest( - request: Electron.ProtocolRequest, - callback: (stream: NodeJS.ReadableStream | Electron.ProtocolResponse) => void - ) { - try { - const uri = URI.parse(request.url); - const ifNoneMatch = request.headers['If-None-Match']; - - const id = uri.authority; - const metadata = this.webviewMetadata.get(id); - if (metadata) { - - // Try to further rewrite remote uris so that they go to the resolved server on the main thread - let rewriteUri: undefined | ((uri: URI) => URI); - if (metadata.remoteConnectionData) { - rewriteUri = (uri) => { - if (metadata.remoteConnectionData) { - if (uri.scheme === Schemas.vscodeRemote || (metadata.extensionLocation?.scheme === Schemas.vscodeRemote)) { - let host = metadata.remoteConnectionData.host; - if (host && host.indexOf(':') !== -1) { // IPv6 address - host = `[${host}]`; - } - return URI.parse(`http://${host}:${metadata.remoteConnectionData.port}`).with({ - path: '/vscode-remote-resource', - query: `tkn=${metadata.remoteConnectionData.connectionToken}&path=${encodeURIComponent(uri.path)}`, - }); - } - } - return uri; - }; - } - - const fileReader: WebviewResourceFileReader = { - readFileStream: async (resource: URI, etag: string | undefined): Promise => { - if (resource.scheme === Schemas.file) { - return readFileStream(this.fileService, resource, etag); - } - - // Unknown uri scheme. Try delegating the file read back to the renderer - // process which should have a file system provider registered for the uri. - - const window = this.windowsMainService.getWindowById(metadata.windowId); - if (!window) { - throw new FileOperationError('Could not find window for resource', FileOperationResult.FILE_NOT_FOUND); - } - - const requestId = this.requestIdPool++; - const p = new Promise(resolve => { - this.pendingResourceReads.set(requestId, { resolve }); - }); - - window.send(`vscode:loadWebviewResource-${id}`, requestId, uri, etag); - - const result = await p; - switch (result.response) { - case 'access-denied': - throw new FileOperationError('Could not read file', FileOperationResult.FILE_PERMISSION_DENIED); - - case 'not-found': - throw new FileOperationError('Could not read file', FileOperationResult.FILE_NOT_FOUND); - - case 'not-modified': - return WebviewFileReadResponse.NotModified; - - default: - return new WebviewFileReadResponse.StreamSuccess(bufferToStream(result.response), result.responseDetails?.etag); - } - } - }; - - const result = await loadLocalResource(uri, ifNoneMatch, { - extensionLocation: metadata.extensionLocation, - roots: metadata.localResourceRoots, - remoteConnectionData: metadata.remoteConnectionData, - rewriteUri, - }, fileReader, this.requestService, this.logService, CancellationToken.None); - - switch (result.type) { - case WebviewResourceResponse.Type.Success: - { - const cacheHeaders: Record = result.etag ? { - 'ETag': result.etag, - 'Cache-Control': 'no-cache' - } : {}; - - const ifNoneMatch = request.headers['If-None-Match']; - if (ifNoneMatch && result.etag === ifNoneMatch) { - /* - * Note that the server generating a 304 response MUST - * generate any of the following header fields that would - * have been sent in a 200 (OK) response to the same request: - * Cache-Control, Content-Location, Date, ETag, Expires, and Vary. - * (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-None-Match) - */ - return callback({ - statusCode: 304, // not modified - data: undefined, // The request fails if `data` is not set - headers: { - 'Content-Type': result.mimeType, - 'Access-Control-Allow-Origin': '*', - ...cacheHeaders - } - }); - } - - return callback({ - statusCode: 200, - data: this.streamToNodeReadable(result.stream), - headers: { - 'Content-Type': result.mimeType, - 'Access-Control-Allow-Origin': '*', - ...cacheHeaders - } - }); - } - case WebviewResourceResponse.Type.NotModified: - { - /* - * Note that the server generating a 304 response MUST - * generate any of the following header fields that would - * have been sent in a 200 (OK) response to the same request: - * Cache-Control, Content-Location, Date, ETag, Expires, and Vary. - * (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-None-Match) - */ - return callback({ - statusCode: 304, // not modified - data: undefined, // The request fails if `data` is not set - headers: { - 'Content-Type': result.mimeType, - 'Access-Control-Allow-Origin': '*', - } - }); - } - case WebviewResourceResponse.Type.AccessDenied: - { - console.error('Webview: Cannot load resource outside of protocol root'); - return callback({ data: undefined, statusCode: 401 }); - } - } - } - } catch { - // noop - } - - return callback({ data: undefined, statusCode: 404 }); - } - - public didLoadResource( - requestId: number, - response: WebviewManagerDidLoadResourceResponse, - responseDetails?: WebviewManagerDidLoadResourceResponseDetails, - ) { - const pendingRead = this.pendingResourceReads.get(requestId); - if (!pendingRead) { - throw new Error('Unknown request'); - } - this.pendingResourceReads.delete(requestId); - pendingRead.resolve({ response, responseDetails }); - } } diff --git a/src/vs/platform/windows/common/windows.ts b/src/vs/platform/windows/common/windows.ts index f835d479..9fb6e838 100644 --- a/src/vs/platform/windows/common/windows.ts +++ b/src/vs/platform/windows/common/windows.ts @@ -3,13 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { isMacintosh, isLinux, isWeb, IProcessEnvironment, isNative } from 'vs/base/common/platform'; +import { isMacintosh, isLinux, isWeb, isNative } from 'vs/base/common/platform'; import { URI, UriComponents } from 'vs/base/common/uri'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; import { LogLevel } from 'vs/platform/log/common/log'; import { PerformanceMark } from 'vs/base/common/performance'; +import { ISandboxConfiguration } from 'vs/base/parts/sandbox/common/sandboxTypes'; export const WindowMinimumSize = { WIDTH: 400, @@ -218,8 +219,6 @@ export interface IColorScheme { } export interface IWindowConfiguration { - sessionId: string; - remoteAuthority?: string; colorScheme: IColorScheme; @@ -231,32 +230,34 @@ export interface IWindowConfiguration { export interface IOSConfiguration { readonly release: string; + readonly hostname: string; } -export interface INativeWindowConfiguration extends IWindowConfiguration, NativeParsedArgs { +export interface INativeWindowConfiguration extends IWindowConfiguration, NativeParsedArgs, ISandboxConfiguration { mainPid: number; - windowId: number; machineId: string; - appRoot: string; execPath: string; backupPath?: string; - nodeCachedDataDir?: string; + homeDir: string; + tmpDir: string; + userDataDir: string; + partsSplashPath: string; workspace?: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier; isInitialStartup?: boolean; logLevel: LogLevel; - zoomLevel?: number; + fullscreen?: boolean; maximized?: boolean; accessibilitySupport?: boolean; + perfMarks: PerformanceMark[]; - userEnv: IProcessEnvironment; filesToWait?: IPathsToWaitFor; os: IOSConfiguration; diff --git a/src/vs/platform/windows/electron-main/window.ts b/src/vs/platform/windows/electron-main/window.ts index 78d2e286..103f6a6d 100644 --- a/src/vs/platform/windows/electron-main/window.ts +++ b/src/vs/platform/windows/electron-main/window.ts @@ -3,17 +3,15 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { release } from 'os'; import { join } from 'vs/base/common/path'; import { localize } from 'vs/nls'; import { getMarks, mark } from 'vs/base/common/performance'; import { Emitter } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; -import { screen, BrowserWindow, systemPreferences, app, TouchBar, nativeImage, Rectangle, Display, TouchBarSegmentedControl, NativeImage, BrowserWindowConstructorOptions, SegmentedControlSegment, nativeTheme, Event, RenderProcessGoneDetails } from 'electron'; +import { screen, BrowserWindow, systemPreferences, app, TouchBar, nativeImage, Rectangle, Display, TouchBarSegmentedControl, NativeImage, BrowserWindowConstructorOptions, SegmentedControlSegment, Event, RenderProcessGoneDetails, WebFrameMain } from 'electron'; import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService'; import { ILogService } from 'vs/platform/log/common/log'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv'; import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; import { IProductService } from 'vs/platform/product/common/productService'; import { WindowMinimumSize, IWindowSettings, MenuBarVisibility, getTitleBarStyle, getMenuBarVisibility, zoomLevelToZoomFactor, INativeWindowConfiguration } from 'vs/platform/windows/common/windows'; @@ -32,11 +30,12 @@ import { mnemonicButtonLabel } from 'vs/base/common/labels'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { IStorageMainService } from 'vs/platform/storage/electron-main/storageMainService'; -import { ByteSize, IFileService } from 'vs/platform/files/common/files'; +import { IFileService } from 'vs/platform/files/common/files'; import { FileAccess, Schemas } from 'vs/base/common/network'; import { isLaunchedFromCli } from 'vs/platform/environment/node/argvHelper'; import { CancellationToken } from 'vs/base/common/cancellation'; import { INativeHostMainService } from 'vs/platform/native/electron-main/nativeHostMainService'; +import { IProtocolMainService } from 'vs/platform/protocol/electron-main/protocol'; export interface IWindowCreationOptions { state: IWindowState; @@ -48,6 +47,11 @@ interface ITouchBarSegment extends SegmentedControlSegment { id: string; } +interface ILoadOptions { + isReload?: boolean; + disableExtensions?: boolean; +} + const enum ReadyState { /** @@ -73,7 +77,7 @@ const enum ReadyState { export class CodeWindow extends Disposable implements ICodeWindow { - private static readonly MAX_URL_LENGTH = 2 * ByteSize.MB; // https://cs.chromium.org/chromium/src/url/url_constants.cc?l=32 + //#region Events private readonly _onWillLoad = this._register(new Emitter()); readonly onWillLoad = this._onWillLoad.event; @@ -87,6 +91,8 @@ export class CodeWindow extends Disposable implements ICodeWindow { private readonly _onDidDestroy = this._register(new Emitter()); readonly onDidDestroy = this._onDidDestroy.event; + //#endregion + private hiddenTitleBarStyle: boolean | undefined; private showTimeoutHandle: NodeJS.Timeout | undefined; private windowState: IWindowState; @@ -104,6 +110,36 @@ export class CodeWindow extends Disposable implements ICodeWindow { private currentHttpProxy: string | undefined = undefined; private currentNoProxy: string | undefined = undefined; + private _id: number; + get id(): number { return this._id; } + + private _win: BrowserWindow; + get win(): BrowserWindow | null { return this._win; } + + private _lastFocusTime = -1; + get lastFocusTime(): number { return this._lastFocusTime; } + + get backupPath(): string | undefined { return this.currentConfig?.backupPath; } + + get openedWorkspace(): IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | undefined { return this.currentConfig?.workspace; } + + get remoteAuthority(): string | undefined { return this.currentConfig?.remoteAuthority; } + + private pendingLoadConfig: INativeWindowConfiguration | undefined; + + private currentConfig: INativeWindowConfiguration | undefined; + get config(): INativeWindowConfiguration | undefined { return this.currentConfig; } + + private readonly configObjectUrl = this._register(this.protocolMainService.createIPCObjectUrl()); + + get hasHiddenTitleBarStyle(): boolean { return !!this.hiddenTitleBarStyle; } + + get isExtensionDevelopmentHost(): boolean { return !!(this.currentConfig?.extensionDevelopmentPath); } + + get isExtensionTestHost(): boolean { return !!(this.currentConfig?.extensionTestsPath); } + + get isExtensionDevelopmentTestFromCli(): boolean { return this.isExtensionDevelopmentHost && this.isExtensionTestHost && !this.currentConfig?.debugId; } + constructor( config: IWindowCreationOptions, @ILogService private readonly logService: ILogService, @@ -118,7 +154,8 @@ export class CodeWindow extends Disposable implements ICodeWindow { @IDialogMainService private readonly dialogMainService: IDialogMainService, @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, @INativeHostMainService private readonly nativeHostMainService: INativeHostMainService, - @IProductService private readonly productService: IProductService + @IProductService private readonly productService: IProductService, + @IProtocolMainService private readonly protocolMainService: IProtocolMainService ) { super(); @@ -132,7 +169,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { // in case we are maximized or fullscreen, only show later after the call to maximize/fullscreen (see below) const isFullscreenOrMaximized = (this.windowState.mode === WindowMode.Maximized || this.windowState.mode === WindowMode.Fullscreen); - const windowConfig = this.configurationService.getValue('window'); + const windowSettings = this.configurationService.getValue('window'); const options: BrowserWindowConstructorOptions = { width: this.windowState.width, @@ -146,30 +183,35 @@ export class CodeWindow extends Disposable implements ICodeWindow { title: this.productService.nameLong, webPreferences: { preload: FileAccess.asFileUri('vs/base/parts/sandbox/electron-browser/preload.js', require).fsPath, + additionalArguments: this.environmentMainService.sandbox ? + [`--vscode-window-config=${this.configObjectUrl.resource.toString()}`, '--context-isolation' /* TODO@bpasero: Use process.contextIsolateed when 13-x-y is adopted (https://github.com/electron/electron/pull/28030) */] : + [`--vscode-window-config=${this.configObjectUrl.resource.toString()}`], v8CacheOptions: browserCodeLoadingCacheStrategy, enableWebSQL: false, enableRemoteModule: false, spellcheck: false, nativeWindowOpen: true, webviewTag: true, - zoomFactor: zoomLevelToZoomFactor(windowConfig?.zoomLevel), + zoomFactor: zoomLevelToZoomFactor(windowSettings?.zoomLevel), ...this.environmentMainService.sandbox ? // Sandbox { - sandbox: true, - contextIsolation: true + sandbox: true } : // No Sandbox { - nodeIntegration: true + nodeIntegration: true, + contextIsolation: false } } }; if (browserCodeLoadingCacheStrategy) { - this.logService.info(`window#ctor: using vscode-file protocol and V8 cache options: ${browserCodeLoadingCacheStrategy}`); + this.logService.info(`window#ctor: using vscode-file:// protocol and V8 cache options: ${browserCodeLoadingCacheStrategy}`); + } else { + this.logService.trace(`window#ctor: vscode-file:// protocol is explicitly disabled`); } // Apply icon to window @@ -188,12 +230,12 @@ export class CodeWindow extends Disposable implements ICodeWindow { if (isMacintosh) { options.acceptFirstMouse = true; // enabled by default - if (windowConfig?.clickThroughInactive === false) { + if (windowSettings?.clickThroughInactive === false) { options.acceptFirstMouse = false; } } - const useNativeTabs = isMacintosh && windowConfig?.nativeTabs === true; + const useNativeTabs = isMacintosh && windowSettings?.nativeTabs === true; if (useNativeTabs) { options.tabbingIdentifier = this.productService.nameShort; // this opts in to sierra tabs } @@ -207,8 +249,11 @@ export class CodeWindow extends Disposable implements ICodeWindow { } } - // Create the browser window. + // Create the browser window + mark('code/willCreateCodeBrowserWindow'); this._win = new BrowserWindow(options); + mark('code/didCreateCodeBrowserWindow'); + this._id = this._win.id; // Open devtools if instructed from command line args @@ -240,6 +285,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { } if (isFullscreenOrMaximized) { + mark('code/willMaximizeCodeWindow'); this._win.maximize(); if (this.windowState.mode === WindowMode.Fullscreen) { @@ -249,6 +295,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { if (!this._win.isVisible()) { this._win.show(); // to reduce flicker from the default window size to maximize, we only show after maximize } + mark('code/didMaximizeCodeWindow'); } this._lastFocusTime = Date.now(); // since we show directly, we need to set the last focus time too @@ -271,25 +318,6 @@ export class CodeWindow extends Disposable implements ICodeWindow { this.registerListeners(); } - private pendingLoadConfig: INativeWindowConfiguration | undefined; - - private currentConfig: INativeWindowConfiguration | undefined; - get config(): INativeWindowConfiguration | undefined { return this.currentConfig; } - - private _id: number; - get id(): number { return this._id; } - - private _win: BrowserWindow; - get win(): BrowserWindow | null { return this._win; } - - get hasHiddenTitleBarStyle(): boolean { return !!this.hiddenTitleBarStyle; } - - get isExtensionDevelopmentHost(): boolean { return !!(this.currentConfig?.extensionDevelopmentPath); } - - get isExtensionTestHost(): boolean { return !!(this.currentConfig?.extensionTestsPath); } - - get isExtensionDevelopmentTestFromCli(): boolean { return this.isExtensionDevelopmentHost && this.isExtensionTestHost && !this.currentConfig?.debugId; } - setRepresentedFilename(filename: string): void { if (isMacintosh) { this._win.setRepresentedFilename(filename); @@ -345,15 +373,6 @@ export class CodeWindow extends Disposable implements ICodeWindow { this._win.focus(); } - private _lastFocusTime = -1; - get lastFocusTime(): number { return this._lastFocusTime; } - - get backupPath(): string | undefined { return this.currentConfig?.backupPath; } - - get openedWorkspace(): IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | undefined { return this.currentConfig?.workspace; } - - get remoteAuthority(): string | undefined { return this.currentConfig?.remoteAuthority; } - private readyState = ReadyState.NONE; setReady(): void { @@ -400,7 +419,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { private registerListeners(): void { - // Crashes & Unrsponsive & Failed to load + // Crashes & Unresponsive & Failed to load this._win.on('unresponsive', () => this.onWindowError(WindowError.UNRESPONSIVE)); this._win.webContents.on('render-process-gone', (event, details) => this.onWindowError(WindowError.CRASHED, details)); this._win.webContents.on('did-fail-load', (event, errorCode, errorDescription) => this.onWindowError(WindowError.LOAD, errorDescription)); @@ -413,15 +432,25 @@ export class CodeWindow extends Disposable implements ICodeWindow { }); // Block all SVG requests from unsupported origins - const svgFileSchemes = new Set([Schemas.file, Schemas.vscodeFileResource, Schemas.vscodeRemoteResource, 'devtools']); + const supportedSvgSchemes = new Set([Schemas.file, Schemas.vscodeFileResource, Schemas.vscodeRemoteResource, 'devtools']); // TODO: handle webview origin + + // But allow them if the are made from inside an webview + const isSafeFrame = (requestFrame: WebFrameMain | undefined): boolean => { + for (let frame: WebFrameMain | null | undefined = requestFrame; frame; frame = frame.parent) { + if (frame.url.startsWith(`${Schemas.vscodeWebview}://`)) { + return true; + } + } + return false; + }; + this._win.webContents.session.webRequest.onBeforeRequest((details, callback) => { const uri = URI.parse(details.url); - - // Prevent loading of remote svgs if (uri.path.endsWith('.svg')) { - const safeScheme = svgFileSchemes.has(uri.scheme) || uri.path.includes(Schemas.vscodeRemoteResource); - if (!safeScheme) { - return callback({ cancel: true }); + const isSafeResourceUrl = supportedSvgSchemes.has(uri.scheme) || uri.path.includes(Schemas.vscodeRemoteResource); + if (!isSafeResourceUrl) { + const isSafeContext = isSafeFrame(details.frame); + return callback({ cancel: !isSafeContext }); } } @@ -429,17 +458,15 @@ export class CodeWindow extends Disposable implements ICodeWindow { }); // Configure SVG header content type properly + // https://github.com/microsoft/vscode/issues/97564 this._win.webContents.session.webRequest.onHeadersReceived((details, callback) => { const responseHeaders = details.responseHeaders as Record; const contentTypes = (responseHeaders['content-type'] || responseHeaders['Content-Type']); if (contentTypes && Array.isArray(contentTypes)) { const uri = URI.parse(details.url); - - // https://github.com/microsoft/vscode/issues/97564 - // ensure local svg files have Content-Type image/svg+xml if (uri.path.endsWith('.svg')) { - if (svgFileSchemes.has(uri.scheme)) { + if (supportedSvgSchemes.has(uri.scheme)) { responseHeaders['Content-Type'] = ['image/svg+xml']; return callback({ cancel: false, responseHeaders }); @@ -449,7 +476,8 @@ export class CodeWindow extends Disposable implements ICodeWindow { // remote extension schemes have the following format // http://127.0.0.1:/vscode-remote-resource?path= if (!uri.path.includes(Schemas.vscodeRemoteResource) && contentTypes.some(contentType => contentType.toLowerCase().includes('image/svg'))) { - return callback({ cancel: true }); + const isSafeContext = isSafeFrame(details.frame); + return callback({ cancel: !isSafeContext }); } } @@ -680,58 +708,17 @@ export class CodeWindow extends Disposable implements ICodeWindow { } } - load(config: INativeWindowConfiguration, { isReload, disableExtensions }: { isReload?: boolean, disableExtensions?: boolean } = Object.create(null)): void { - - // If this window was loaded before from the command line - // (as indicated by VSCODE_CLI environment), make sure to - // preserve that user environment in subsequent loads, - // unless the new configuration context was also a CLI - // (for https://github.com/microsoft/vscode/issues/108571) - const currentUserEnv = (this.currentConfig ?? this.pendingLoadConfig)?.userEnv; - if (currentUserEnv && isLaunchedFromCli(currentUserEnv) && !isLaunchedFromCli(config.userEnv)) { - config.userEnv = { ...currentUserEnv, ...config.userEnv }; // still allow to override certain environment as passed in - } - - // If named pipe was instantiated for the crashpad_handler process, reuse the same - // pipe for new app instances connecting to the original app instance. - // Ref: https://github.com/microsoft/vscode/issues/115874 - if (process.env['CHROME_CRASHPAD_PIPE_NAME']) { - Object.assign(config.userEnv, { - CHROME_CRASHPAD_PIPE_NAME: process.env['CHROME_CRASHPAD_PIPE_NAME'] - }); - } - - // If this is the first time the window is loaded, we associate the paths - // directly with the window because we assume the loading will just work - if (this.readyState === ReadyState.NONE) { - this.currentConfig = config; - } - - // Otherwise, the window is currently showing a folder and if there is an - // unload handler preventing the load, we cannot just associate the paths - // because the loading might be vetoed. Instead we associate it later when - // the window load event has fired. - else { - this.pendingLoadConfig = config; - this.readyState = ReadyState.NAVIGATING; - } - - // Add disable-extensions to the config, but do not preserve it on currentConfig or - // pendingLoadConfig so that it is applied only on this load - const configuration = { ...config }; - if (disableExtensions !== undefined) { - configuration['disable-extensions'] = disableExtensions; - } + load(configuration: INativeWindowConfiguration, options: ILoadOptions = Object.create(null)): void { // Clear Document Edited if needed if (this.isDocumentEdited()) { - if (!isReload || !this.backupMainService.isHotExitEnabled()) { + if (!options.isReload || !this.backupMainService.isHotExitEnabled()) { this.setDocumentEdited(false); } } // Clear Title and Filename if needed - if (!isReload) { + if (!options.isReload) { if (this.getRepresentedFilename()) { this.setRepresentedFilename(''); } @@ -739,9 +726,30 @@ export class CodeWindow extends Disposable implements ICodeWindow { this._win.setTitle(this.productService.nameLong); } + // Update configuration values based on our window context + // and set it into the config object URL for usage. + this.updateConfiguration(configuration, options); + + // If this is the first time the window is loaded, we associate the paths + // directly with the window because we assume the loading will just work + if (this.readyState === ReadyState.NONE) { + this.currentConfig = configuration; + } + + // Otherwise, the window is currently showing a folder and if there is an + // unload handler preventing the load, we cannot just associate the paths + // because the loading might be vetoed. Instead we associate it later when + // the window load event has fired. + else { + this.pendingLoadConfig = configuration; + this.readyState = ReadyState.NAVIGATING; + } + // Load URL - mark('code/willOpenNewWindow'); - this._win.loadURL(this.getUrl(configuration)); + this._win.loadURL(FileAccess.asBrowserUri(this.environmentMainService.sandbox ? + 'vs/code/electron-sandbox/workbench/workbench.html' : + 'vs/code/electron-browser/workbench/workbench.html', require + ).toString(true)); // Make window visible if it did not open in N seconds because this indicates an error // Only do this when running out of sources and not when running tests @@ -759,6 +767,45 @@ export class CodeWindow extends Disposable implements ICodeWindow { this._onWillLoad.fire({ workspace: configuration.workspace }); } + private updateConfiguration(configuration: INativeWindowConfiguration, options: ILoadOptions): void { + + // If this window was loaded before from the command line + // (as indicated by VSCODE_CLI environment), make sure to + // preserve that user environment in subsequent loads, + // unless the new configuration context was also a CLI + // (for https://github.com/microsoft/vscode/issues/108571) + const currentUserEnv = (this.currentConfig ?? this.pendingLoadConfig)?.userEnv; + if (currentUserEnv && isLaunchedFromCli(currentUserEnv) && !isLaunchedFromCli(configuration.userEnv)) { + configuration.userEnv = { ...currentUserEnv, ...configuration.userEnv }; // still allow to override certain environment as passed in + } + + // If named pipe was instantiated for the crashpad_handler process, reuse the same + // pipe for new app instances connecting to the original app instance. + // Ref: https://github.com/microsoft/vscode/issues/115874 + if (process.env['CHROME_CRASHPAD_PIPE_NAME']) { + Object.assign(configuration.userEnv, { + CHROME_CRASHPAD_PIPE_NAME: process.env['CHROME_CRASHPAD_PIPE_NAME'] + }); + } + + // Add disable-extensions to the config, but do not preserve it on currentConfig or + // pendingLoadConfig so that it is applied only on this load + if (options.disableExtensions !== undefined) { + configuration['disable-extensions'] = options.disableExtensions; + } + + // Update window related properties + configuration.fullscreen = this.isFullScreen; + configuration.maximized = this._win.isMaximized(); + + // Update with latest perf marks + mark('code/willOpenNewWindow'); + configuration.perfMarks = getMarks(); + + // Update in config object URL for usage in renderer + this.configObjectUrl.update(configuration); + } + async reload(cli?: NativeParsedArgs): Promise { // Copy our current config for reuse @@ -816,87 +863,6 @@ export class CodeWindow extends Disposable implements ICodeWindow { return configuration.workspace; } - private getUrl(windowConfiguration: INativeWindowConfiguration): string { - - // Set window ID - windowConfiguration.windowId = this._win.id; - windowConfiguration.sessionId = `window:${this._win.id}`; - windowConfiguration.logLevel = this.logService.getLevel(); - windowConfiguration.logsPath = this.environmentMainService.logsPath; - - // Set zoomlevel - const windowConfig = this.configurationService.getValue('window'); - const zoomLevel = windowConfig?.zoomLevel; - if (typeof zoomLevel === 'number') { - windowConfiguration.zoomLevel = zoomLevel; - } - - // Set fullscreen state - windowConfiguration.fullscreen = this.isFullScreen; - - // Set Accessibility Config - windowConfiguration.colorScheme = { - dark: nativeTheme.shouldUseDarkColors, - highContrast: nativeTheme.shouldUseInvertedColorScheme || nativeTheme.shouldUseHighContrastColors - }; - windowConfiguration.autoDetectHighContrast = windowConfig?.autoDetectHighContrast ?? true; - windowConfiguration.accessibilitySupport = app.accessibilitySupportEnabled; - - // Title style related - windowConfiguration.maximized = this._win.isMaximized(); - - // Dump Perf Counters - windowConfiguration.perfMarks = getMarks(); - - // Parts splash - windowConfiguration.partsSplashPath = join(this.environmentMainService.userDataPath, 'rapid_render.json'); - - // OS Info - windowConfiguration.os = { - release: release() - }; - - // Config (combination of process.argv and window configuration) - const environment = parseArgs(process.argv, OPTIONS); - const config = Object.assign(environment, windowConfiguration) as unknown as { [key: string]: unknown }; - for (const key in config) { - const configValue = config[key]; - if (configValue === undefined || configValue === null || configValue === '' || configValue === false) { - delete config[key]; // only send over properties that have a true value - } - } - - // In the unlikely event of the URL becoming larger than 2MB, remove parts of - // it that are not under our control. Mainly, the user environment can be very - // large depending on user configuration, so we can only remove it in that case. - let configUrl = this.doGetUrl(config); - if (configUrl.length > CodeWindow.MAX_URL_LENGTH) { - this.logService.warn('Application URL exceeds maximum of 2MB and was shortened.'); - - configUrl = this.doGetUrl({ ...config, userEnv: undefined }); - - if (configUrl.length > CodeWindow.MAX_URL_LENGTH) { - this.logService.error('Application URL exceeds maximum of 2MB and cannot be loaded.'); - } - } - - return configUrl; - } - - private doGetUrl(config: object): string { - let workbench: string; - if (this.environmentMainService.sandbox) { - workbench = 'vs/code/electron-sandbox/workbench/workbench.html'; - } else { - workbench = 'vs/code/electron-browser/workbench/workbench.html'; - } - - return FileAccess - .asBrowserUri(workbench, require) - .with({ query: `config=${encodeURIComponent(JSON.stringify(config))}` }) - .toString(true); - } - serializeWindowState(): IWindowState { if (!this._win) { return defaultWindowState(); @@ -969,6 +935,8 @@ export class CodeWindow extends Disposable implements ICodeWindow { } private restoreWindowState(state?: IWindowState): [IWindowState, boolean? /* has multiple displays */] { + mark('code/willRestoreCodeWindowState'); + let hasMultipleDisplays = false; if (state) { try { @@ -981,6 +949,8 @@ export class CodeWindow extends Disposable implements ICodeWindow { } } + mark('code/didRestoreCodeWindowState'); + return [state || defaultWindowState(), hasMultipleDisplays]; } @@ -1382,7 +1352,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { return segments; } - dispose(): void { + override dispose(): void { super.dispose(); if (this.showTimeoutHandle) { diff --git a/src/vs/platform/windows/electron-main/windowsMainService.ts b/src/vs/platform/windows/electron-main/windowsMainService.ts index 283bbc93..4f66b0be 100644 --- a/src/vs/platform/windows/electron-main/windowsMainService.ts +++ b/src/vs/platform/windows/electron-main/windowsMainService.ts @@ -4,6 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import { statSync } from 'fs'; +import { release, hostname } from 'os'; +import product from 'vs/platform/product/common/product'; +import { mark, getMarks } from 'vs/base/common/performance'; import { basename, normalize, join, posix } from 'vs/base/common/path'; import { localize } from 'vs/nls'; import { coalesce, distinct, firstOrDefault } from 'vs/base/common/arrays'; @@ -13,13 +16,13 @@ import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/e import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; import { IStateService } from 'vs/platform/state/node/state'; import { CodeWindow } from 'vs/platform/windows/electron-main/window'; -import { BrowserWindow, MessageBoxOptions, WebContents } from 'electron'; -import { ILifecycleMainService, UnloadReason, LifecycleMainPhase } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; +import { app, BrowserWindow, MessageBoxOptions, nativeTheme, WebContents } from 'electron'; +import { ILifecycleMainService, UnloadReason } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; 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 { Event, Emitter } from 'vs/base/common/event'; 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'; @@ -33,7 +36,7 @@ import { getRemoteAuthority } from 'vs/platform/remote/common/remoteHosts'; import { IWindowState, WindowsStateHandler } from 'vs/platform/windows/electron-main/windowsStateHandler'; import { getSingleFolderWorkspaceIdentifier, getWorkspaceIdentifier, IWorkspacesManagementMainService } from 'vs/platform/workspaces/electron-main/workspacesManagementMainService'; import { once } from 'vs/base/common/functional'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IDialogMainService } from 'vs/platform/dialogs/electron-main/dialogMainService'; import { assertIsDefined, withNullAsUndefined } from 'vs/base/common/types'; import { isWindowsDriveLetter, toSlashes, parseLineAndColumnAware, sanitizeFilePath } from 'vs/base/common/extpath'; @@ -42,6 +45,7 @@ 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'; +import { IProtocolMainService } from 'vs/platform/protocol/electron-main/protocol'; //#region Helper Interfaces @@ -151,17 +155,38 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic @IInstantiationService private readonly instantiationService: IInstantiationService, @IDialogMainService private readonly dialogMainService: IDialogMainService, @IFileService private readonly fileService: IFileService, - @IProductService private readonly productService: IProductService + @IProductService private readonly productService: IProductService, + @IProtocolMainService private readonly protocolMainService: IProtocolMainService ) { super(); - this.lifecycleMainService.when(LifecycleMainPhase.Ready).then(() => this.registerListeners()); + this.registerListeners(); } private registerListeners(): void { // Signal a window is ready after having entered a workspace this._register(this.workspacesManagementMainService.onDidEnterWorkspace(event => this._onDidSignalReadyWindow.fire(event.window))); + + // Update valid roots in protocol service for extension dev windows + this._register(this.onDidSignalReadyWindow(window => { + if (window.config?.extensionDevelopmentPath || window.config?.extensionTestsPath) { + const disposables = new DisposableStore(); + disposables.add(Event.any(window.onDidClose, window.onDidDestroy)(() => disposables.dispose())); + + // Allow access to extension development path + if (window.config.extensionDevelopmentPath) { + for (const extensionDevelopmentPath of window.config.extensionDevelopmentPath) { + disposables.add(this.protocolMainService.addValidFileRoot(URI.file(extensionDevelopmentPath))); + } + } + + // Allow access to extension tests path + if (window.config.extensionTestsPath) { + disposables.add(this.protocolMainService.addValidFileRoot(URI.file(window.config.extensionTestsPath))); + } + } + })); } openEmptyWindow(openConfig: IOpenEmptyConfiguration, options?: IOpenEmptyWindowOptions): ICodeWindow[] { @@ -316,7 +341,15 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic // process can continue. We do this by deleting the waitMarkerFilePath. const waitMarkerFileURI = openConfig.waitMarkerFileURI; if (openConfig.context === OpenContext.CLI && waitMarkerFileURI && usedWindows.length === 1 && usedWindows[0]) { - usedWindows[0].whenClosedOrLoaded.then(() => this.fileService.del(waitMarkerFileURI), () => undefined); + (async () => { + await usedWindows[0].whenClosedOrLoaded; + + try { + await this.fileService.del(waitMarkerFileURI); + } catch (error) { + // ignore - could have been deleted from the window already + } + })(); } return usedWindows; @@ -719,7 +752,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic // folder or file paths const cliPaths = cli._; for (const cliPath of cliPaths) { - const path = cli.remote ? this.doResolvePathRemote(cliPath, cli.remote) : this.doResolveFilePath(cliPath, pathResolveOptions); + const path = pathResolveOptions.remoteAuthority ? this.doResolvePathRemote(cliPath, pathResolveOptions) : this.doResolveFilePath(cliPath, pathResolveOptions); if (path) { pathsToOpen.push(path); } @@ -878,11 +911,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic let columnNumber: number | undefined; if (options.gotoLineMode) { - const parsedPath = parseLineAndColumnAware(path); - lineNumber = parsedPath.line; - columnNumber = parsedPath.column; - - path = parsedPath.path; + ({ path, line: lineNumber, column: columnNumber } = parseLineAndColumnAware(path)); } // Ensure the path is normalized and absolute @@ -925,8 +954,17 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic return undefined; } - private doResolvePathRemote(path: string, remoteAuthority: string, forceOpenWorkspaceAsFile?: boolean): IPathToOpen | undefined { + private doResolvePathRemote(path: string, options: IPathResolveOptions): IPathToOpen | undefined { const first = path.charCodeAt(0); + const remoteAuthority = options.remoteAuthority; + + // Extract line/col information from path + let lineNumber: number | undefined; + let columnNumber: number | undefined; + + if (options.gotoLineMode) { + ({ path, line: lineNumber, column: columnNumber } = parseLineAndColumnAware(path)); + } // make absolute if (first !== CharCode.Slash) { @@ -941,21 +979,21 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic // guess the file type: // - if it ends with a slash it's a folder - // - if it has a file extension, it's a file or a workspace + // - if in goto line mode or if it has a file extension, it's a file or a workspace // - by defaults it's a folder if (path.charCodeAt(path.length - 1) !== CharCode.Slash) { // file name ends with .code-workspace if (hasWorkspaceFileExtension(path)) { - if (forceOpenWorkspaceAsFile) { - return { fileUri: uri, remoteAuthority }; + if (options.forceOpenWorkspaceAsFile) { + return { fileUri: uri, lineNumber, columnNumber, remoteAuthority: options.remoteAuthority }; } return { workspace: getWorkspaceIdentifier(uri), remoteAuthority }; } // file name starts with a dot or has an file extension - else if (posix.basename(path).indexOf('.') !== -1) { - return { fileUri: uri, remoteAuthority }; + else if (options.gotoLineMode || posix.basename(path).indexOf('.') !== -1) { + return { fileUri: uri, lineNumber, columnNumber, remoteAuthority }; } } @@ -1112,33 +1150,60 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic } private openInBrowserWindow(options: IOpenBrowserWindowOptions): ICodeWindow { + const windowConfig = this.configurationService.getValue('window'); - // Build `INativeWindowConfiguration` from config and options - const configuration = { ...options.cli } as INativeWindowConfiguration; - configuration.appRoot = this.environmentMainService.appRoot; - configuration.machineId = this.machineId; - configuration.nodeCachedDataDir = this.environmentMainService.nodeCachedDataDir; - configuration.mainPid = process.pid; - configuration.execPath = process.execPath; - configuration.userEnv = { ...this.initialUserEnv, ...options.userEnv }; - configuration.isInitialStartup = options.initialStartup; - configuration.workspace = options.workspace; - configuration.remoteAuthority = options.remoteAuthority; + // Build up the window configuration from provided options, config and environment + const configuration: INativeWindowConfiguration = { - const filesToOpen = options.filesToOpen; - if (filesToOpen) { - configuration.filesToOpenOrCreate = filesToOpen.filesToOpenOrCreate; - configuration.filesToDiff = filesToOpen.filesToDiff; - configuration.filesToWait = filesToOpen.filesToWait; - } + // Inherit CLI arguments from environment and/or + // the specific properties from this launch if provided + ...this.environmentMainService.args, + ...options.cli, - // if we know the backup folder upfront (for empty windows to restore), we can set it - // directly here which helps for restoring UI state associated with that window. - // For all other cases we first call into registerEmptyWindowBackupSync() to set it before - // loading the window. - if (options.emptyWindowBackupInfo) { - configuration.backupPath = join(this.environmentMainService.backupHome, options.emptyWindowBackupInfo.backupFolder); - } + machineId: this.machineId, + + windowId: -1, // Will be filled in by the window once loaded later + + mainPid: process.pid, + + appRoot: this.environmentMainService.appRoot, + execPath: process.execPath, + nodeCachedDataDir: this.environmentMainService.nodeCachedDataDir, + partsSplashPath: join(this.environmentMainService.userDataPath, 'rapid_render.json'), + // If we know the backup folder upfront (for empty windows to restore), we can set it + // directly here which helps for restoring UI state associated with that window. + // For all other cases we first call into registerEmptyWindowBackupSync() to set it before + // loading the window. + backupPath: options.emptyWindowBackupInfo ? join(this.environmentMainService.backupHome, options.emptyWindowBackupInfo.backupFolder) : undefined, + + homeDir: this.environmentMainService.userHome.fsPath, + tmpDir: this.environmentMainService.tmpDir.fsPath, + userDataDir: this.environmentMainService.userDataPath, + + remoteAuthority: options.remoteAuthority, + workspace: options.workspace, + userEnv: { ...this.initialUserEnv, ...options.userEnv }, + + filesToOpenOrCreate: options.filesToOpen?.filesToOpenOrCreate, + filesToDiff: options.filesToOpen?.filesToDiff, + filesToWait: options.filesToOpen?.filesToWait, + + logLevel: this.logService.getLevel(), + logsPath: this.environmentMainService.logsPath, + + product, + isInitialStartup: options.initialStartup, + perfMarks: getMarks(), + os: { release: release(), hostname: hostname() }, + zoomLevel: typeof windowConfig?.zoomLevel === 'number' ? windowConfig.zoomLevel : undefined, + + autoDetectHighContrast: windowConfig?.autoDetectHighContrast ?? true, + accessibilitySupport: app.accessibilitySupportEnabled, + colorScheme: { + dark: nativeTheme.shouldUseDarkColors, + highContrast: nativeTheme.shouldUseInvertedColorScheme || nativeTheme.shouldUseHighContrastColors + } + }; let window: ICodeWindow | undefined; if (!options.forceNewWindow && !options.forceNewTabbedWindow) { @@ -1153,11 +1218,13 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic const state = this.windowsStateHandler.getNewWindowState(configuration); // Create the window + mark('code/willCreateCodeWindow'); const createdWindow = window = this.instantiationService.createInstance(CodeWindow, { state, extensionDevelopmentPath: configuration.extensionDevelopmentPath, isExtensionTestHost: !!configuration.extensionTestsPath }); + mark('code/didCreateCodeWindow'); // Add as window tab if configured (macOS only) if (options.forceNewTabbedWindow) { @@ -1205,6 +1272,10 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic } } + // Update window identifier and session now + // that we have the window object in hand. + configuration.windowId = window.id; + // If the window was already loaded, make sure to unload it // first and only load the new configuration if that was // not vetoed diff --git a/src/vs/platform/workspace/common/workspace.ts b/src/vs/platform/workspace/common/workspace.ts index 38a11c92..4d60487c 100644 --- a/src/vs/platform/workspace/common/workspace.ts +++ b/src/vs/platform/workspace/common/workspace.ts @@ -27,6 +27,11 @@ export interface IWorkspaceContextService extends IWorkspaceFolderProvider { */ readonly onDidChangeWorkspaceName: Event; + /** + * An event which fires before workspace folders change. + */ + readonly onWillChangeWorkspaceFolders: Event; + /** * An event which fires on workspace folders change. */ @@ -75,6 +80,12 @@ export const enum WorkbenchState { WORKSPACE } +export interface IWorkspaceFoldersWillChangeEvent { + join(promise: Promise): void; + readonly changes: IWorkspaceFoldersChangeEvent; + readonly fromCache: boolean; +} + export interface IWorkspaceFoldersChangeEvent { added: IWorkspaceFolder[]; removed: IWorkspaceFolder[]; diff --git a/src/vs/platform/workspace/common/workspaceTrust.ts b/src/vs/platform/workspace/common/workspaceTrust.ts index d01d2742..1a303f14 100644 --- a/src/vs/platform/workspace/common/workspaceTrust.ts +++ b/src/vs/platform/workspace/common/workspaceTrust.ts @@ -13,88 +13,63 @@ export enum WorkspaceTrustScope { Remote = 1 } -export enum WorkspaceTrustState { - Untrusted = 0, - Trusted = 1, - Unknown = 2 -} - -export function workspaceTrustStateToString(trustState: WorkspaceTrustState) { - switch (trustState) { - case WorkspaceTrustState.Trusted: - return localize('trusted', "Trusted"); - case WorkspaceTrustState.Untrusted: - return localize('untrusted', "Untrusted"); - case WorkspaceTrustState.Unknown: - default: - return localize('unknown', "Unknown"); +export function workspaceTrustToString(trustState: boolean) { + if (trustState) { + return localize('trusted', "Trusted"); + } else { + return localize('untrusted', "Restricted Mode"); } } -export interface IWorkspaceTrustModel { - - readonly onDidChangeTrustState: Event; - - setFolderTrustState(folder: URI, trustState: WorkspaceTrustState): void; - getFolderTrustStateInfo(folder: URI): IWorkspaceTrustFolderInfo; - - setTrustedFolders(folders: URI[]): void; - setUntrustedFolders(folders: URI[]): void; - - getTrustStateInfo(): IWorkspaceTrustStateInfo; -} - export interface WorkspaceTrustRequestButton { - label: string; - type: 'ContinueWithTrust' | 'ContinueWithoutTrust' | 'Manage' | 'Cancel' + readonly label: string; + readonly type: 'ContinueWithTrust' | 'ContinueWithoutTrust' | 'Manage' | 'Cancel' } export interface WorkspaceTrustRequestOptions { - buttons?: WorkspaceTrustRequestButton[]; - message?: string; - modal: boolean; + readonly buttons?: WorkspaceTrustRequestButton[]; + readonly message?: string; + readonly modal: boolean; } -export interface IWorkspaceTrustRequestModel { - readonly trustRequestOptions: WorkspaceTrustRequestOptions | undefined; +export type WorkspaceTrustChangeEvent = Event; +export const IWorkspaceTrustManagementService = createDecorator('workspaceTrustManagementService'); - readonly onDidInitiateRequest: Event; - readonly onDidCompleteRequest: Event; - readonly onDidCancelRequest: Event; - - initiateRequest(options?: WorkspaceTrustRequestOptions): void; - completeRequest(trustState?: WorkspaceTrustState): void; - cancelRequest(): void; -} - -export interface WorkspaceTrustStateChangeEvent { - previousTrustState: WorkspaceTrustState; - currentTrustState: WorkspaceTrustState; -} - -export type WorkspaceTrustChangeEvent = Event; - -export const IWorkspaceTrustService = createDecorator('workspaceTrustService'); - -export interface IWorkspaceTrustService { +export interface IWorkspaceTrustManagementService { readonly _serviceBrand: undefined; - readonly requestModel: IWorkspaceTrustRequestModel; + onDidChangeTrust: WorkspaceTrustChangeEvent; + onDidChangeTrustedFolders: Event; - onDidChangeTrustState: WorkspaceTrustChangeEvent; - getWorkspaceTrustState(): WorkspaceTrustState; - isWorkspaceTrustEnabled(): boolean; - requireWorkspaceTrust(options?: WorkspaceTrustRequestOptions): Promise; + isWorkpaceTrusted(): boolean; + canSetParentFolderTrust(): boolean; + setParentFolderTrust(trusted: boolean): void; + canSetWorkspaceTrust(): boolean; + setWorkspaceTrust(trusted: boolean): void; + getFolderTrustInfo(folder: URI): IWorkspaceTrustUriInfo; + setFoldersTrust(folders: URI[], trusted: boolean): void; + getTrustedFolders(): URI[]; + setTrustedFolders(folders: URI[]): void; } -export interface IWorkspaceTrustFolderInfo { - uri: string, - trustState: WorkspaceTrustState +export const IWorkspaceTrustRequestService = createDecorator('workspaceTrustRequestService'); + +export interface IWorkspaceTrustRequestService { + readonly _serviceBrand: undefined; + + readonly onDidInitiateWorkspaceTrustRequest: Event; + readonly onDidCompleteWorkspaceTrustRequest: Event; + + cancelRequest(): void; + completeRequest(trusted?: boolean): void; + requestWorkspaceTrust(options?: WorkspaceTrustRequestOptions): Promise; } -export interface IWorkspaceTrustStateInfo { - localFolders: IWorkspaceTrustFolderInfo[] - - // Removing complexity of remote items - //trustedRemoteItems: { uri: string }[] +export interface IWorkspaceTrustUriInfo { + uri: URI, + trusted: boolean +} + +export interface IWorkspaceTrustInfo { + uriTrustInfo: IWorkspaceTrustUriInfo[] } 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 5f1eb19f..1b524310 100644 --- a/src/vs/platform/workspaces/test/electron-main/workspacesManagementMainService.test.ts +++ b/src/vs/platform/workspaces/test/electron-main/workspacesManagementMainService.test.ts @@ -102,7 +102,7 @@ flakySuite('WorkspacesManagementMainService', () => { super(parseArgs(process.argv, OPTIONS), productService); } - get untitledWorkspacesHome(): URI { + override get untitledWorkspacesHome(): URI { return URI.file(untitledWorkspacesHomePath); } }; diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index c751676a..ad38c32f 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -1665,6 +1665,12 @@ declare module 'vscode' { * Options to configure the behavior of the quick pick UI. */ export interface QuickPickOptions { + + /** + * An optional string that represents the title of the quick pick. + */ + title?: string; + /** * An optional flag to include the description when filtering the picks. */ @@ -1847,6 +1853,11 @@ declare module 'vscode' { */ export interface InputBoxOptions { + /** + * An optional string that represents the title of the input box. + */ + title?: string; + /** * The value to prefill in the input box. */ @@ -2157,11 +2168,34 @@ declare module 'vscode' { contains(other: CodeActionKind): boolean; } + /** + * 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 = 2, + } + /** * Contains additional diagnostic information about the context in which * a [code action](#CodeActionProvider.provideCodeActions) is run. */ export interface CodeActionContext { + /** + * The reason why code actions were requested. + */ + readonly triggerKind: CodeActionTriggerKind; + /** * An array of diagnostics. */ @@ -2419,13 +2453,13 @@ declare module 'vscode' { /** * Information about where a symbol is defined. * - * Provides additional metadata over normal [location](#Location) definitions, including the range of + * Provides additional metadata over normal {@link Location location} definitions, including the range of * the defining symbol */ export type DefinitionLink = LocationLink; /** - * The definition of a symbol represented as one or many [locations](#Location). + * The definition of a symbol represented as one or many {@link Location locations}. * For most programming languages there is only one location at which a symbol is * defined. */ @@ -9277,7 +9311,7 @@ declare module 'vscode' { /** * Object with environment variables that will be added to the VS Code process. */ - env?: { [key: string]: string | null }; + env?: { [key: string]: string | null | undefined }; /** * Whether the terminal process environment should be exactly as provided in @@ -10663,6 +10697,16 @@ declare module 'vscode' { * @return A [disposable](#Disposable) that unregisters this provider when being disposed. */ export function registerFileSystemProvider(scheme: string, provider: FileSystemProvider, options?: { readonly isCaseSensitive?: boolean, readonly isReadonly?: boolean }): Disposable; + + /** + * When true, the user has explicitly trusted the contents of the workspace. + */ + export const isTrusted: boolean; + + /** + * Event that fires when the current workspace has been trusted. + */ + export const onDidGrantWorkspaceTrust: Event; } /** diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 45be959a..50c3f114 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -80,8 +80,16 @@ declare module 'vscode' { constructor(host: string, port: number, connectionToken?: string); } + export enum RemoteTrustOption { + Unknown = 0, + DisableTrust = 1, + MachineTrusted = 2 + } + export interface ResolvedOptions { extensionHostEnv?: { [key: string]: string | null; }; + + trust?: RemoteTrustOption; } export interface TunnelOptions { @@ -376,6 +384,14 @@ declare module 'vscode' { afterContext?: number; } + /** + * Represents the severiry of a TextSearchComplete message. + */ + export enum TextSearchCompleteMessageType { + Information = 1, + Warning = 2, + } + /** * Information collected when text search is complete. */ @@ -388,6 +404,15 @@ declare module 'vscode' { * - If search hits an internal limit which is less than `maxResults`, this should be true. */ limitHit?: boolean; + + /** + * Additional information regarding the state of the completed search. + * + * Messages with "Information" tyle support links in markdown syntax: + * - Click to [run a command](command:workbench.action.OpenQuickPick) + * - Click to [open a website](https://aka.ms) + */ + message?: { text: string, type: TextSearchCompleteMessageType } | { text: string, type: TextSearchCompleteMessageType }[]; } /** @@ -727,11 +752,16 @@ declare module 'vscode' { */ export interface SourceControlInputBox { + /** + * Shows a transient contextual message on the input. + */ + showValidationMessage(message: string, type: SourceControlInputBoxValidationType): void; + /** * A validation function for the input box. It's possible to change * the validation provider simply by setting this property to a different function. */ - validateInput?(value: string, cursorPosition: number): ProviderResult; + validateInput?(value: string, cursorPosition: number): ProviderResult; } //#endregion @@ -812,6 +842,30 @@ declare module 'vscode' { //#endregion + //#region Terminal initial text https://github.com/microsoft/vscode/issues/120368 + + export interface TerminalOptions { + /** + * A message to write to the terminal on first launch, note that this is not sent to the + * process but, rather written directly to the terminal. This supports escape sequences such + * a setting text style. + */ + readonly message?: string; + } + + //#endregion + + //#region Terminal icon https://github.com/microsoft/vscode/issues/120538 + + export interface TerminalOptions { + /** + * A codicon ID to associate with this terminal. + */ + readonly icon?: string; + } + + //#endregion + // eslint-disable-next-line vscode-dts-region-comments //#region @jrieken -> exclusive document filters @@ -924,69 +978,53 @@ declare module 'vscode' { //#endregion - //#region allow title property to QuickPickOptions/InputBoxOptions: https://github.com/microsoft/vscode/issues/77423 - - interface QuickPickOptions { - /** - * An optional string that represents the tile of the quick pick. - */ - title?: string; - } - - interface InputBoxOptions { - /** - * An optional string that represents the tile of the input box. - */ - title?: string; - } - - //#endregion - //#region https://github.com/microsoft/vscode/issues/106744, Notebooks (misc) export enum NotebookCellKind { + // todo@API rename/rethink as "Markup" cell Markdown = 1, Code = 2 } export class NotebookCellMetadata { - /** - * Controls whether a cell's editor is editable/readonly. - */ - readonly editable?: boolean; - /** - * Controls if the cell has a margin to support the breakpoint UI. - * This metadata is ignored for markdown cell. - */ - readonly breakpointMargin?: boolean; /** * Whether a code cell's editor is collapsed */ - readonly outputCollapsed?: boolean; + readonly inputCollapsed?: boolean; + /** * Whether a code cell's outputs are collapsed */ - readonly inputCollapsed?: boolean; + readonly outputCollapsed?: boolean; + /** * Additional attributes of a cell metadata. */ - readonly custom?: Record; + readonly [key: string]: any; - // todo@API duplicates status bar API - readonly statusMessage?: string; + /** + * Create a new notebook cell metadata. + * + * @param inputCollapsed Whether a code cell's editor is collapsed + * @param outputCollapsed Whether a code cell's outputs are collapsed + */ + constructor(inputCollapsed?: boolean, outputCollapsed?: boolean); - // run related API, will be removed - readonly hasExecutionOrder?: boolean; - - constructor(editable?: boolean, breakpointMargin?: boolean, hasExecutionOrder?: boolean, statusMessage?: string, lastRunDuration?: number, inputCollapsed?: boolean, outputCollapsed?: boolean, custom?: Record) - - with(change: { editable?: boolean | null, breakpointMargin?: boolean | null, hasExecutionOrder?: boolean | null, statusMessage?: string | null, lastRunDuration?: number | null, inputCollapsed?: boolean | null, outputCollapsed?: boolean | null, custom?: Record | null, }): NotebookCellMetadata; + /** + * Derived a new cell metadata from this metadata. + * + * @param change An object that describes a change to this NotebookCellMetadata. + * @return A new NotebookCellMetadata that reflects the given change. Will return `this` NotebookCellMetadata if the change + * is not changing anything. + */ + with(change: { inputCollapsed?: boolean | null, outputCollapsed?: boolean | null, [key: string]: any }): NotebookCellMetadata; } export interface NotebookCellExecutionSummary { - executionOrder?: number; - success?: boolean; - duration?: number; + readonly executionOrder?: number; + readonly success?: boolean; + readonly startTime?: number; + readonly endTime?: number; } // todo@API support ids https://github.com/jupyter/enhancement-proposals/blob/master/62-cell-id/cell-id.md @@ -997,37 +1035,37 @@ declare module 'vscode' { readonly document: TextDocument; readonly metadata: NotebookCellMetadata readonly outputs: ReadonlyArray; + + // todo@API maybe just executionSummary or lastExecutionSummary? readonly latestExecutionSummary: NotebookCellExecutionSummary | undefined; } export class NotebookDocumentMetadata { - - /** - * Controls if users can add or delete cells - * Defaults to true - */ - readonly editable: boolean; - /** - * Default value for [cell editable metadata](#NotebookCellMetadata.editable). - * Defaults to true. - */ - readonly cellEditable: boolean; - /** - * Additional attributes of the document metadata. - */ - readonly custom: { [key: string]: any; }; /** * Whether the document is trusted, default to true * When false, insecure outputs like HTML, JavaScript, SVG will not be rendered. */ readonly trusted: boolean; - // todo@API is this a kernel property? - readonly cellHasExecutionOrder: boolean; + /** + * Additional attributes of the document metadata. + */ + readonly [key: string]: any; - constructor(editable?: boolean, cellEditable?: boolean, cellHasExecutionOrder?: boolean, custom?: { [key: string]: any; }, trusted?: boolean); + /** + * Create a new notebook document metadata + * @param trusted Whether the document metadata is trusted. + */ + constructor(trusted?: boolean); - with(change: { editable?: boolean | null, cellEditable?: boolean | null, cellHasExecutionOrder?: boolean | null, custom?: { [key: string]: any; } | null, trusted?: boolean | null, }): NotebookDocumentMetadata + /** + * Derived a new document metadata from this metadata. + * + * @param change An object that describes a change to this NotebookDocumentMetadata. + * @return A new NotebookDocumentMetadata that reflects the given change. Will return `this` NotebookDocumentMetadata if the change + * is not changing anything. + */ + with(change: { trusted?: boolean | null, [key: string]: any }): NotebookDocumentMetadata } export interface NotebookDocumentContentOptions { @@ -1035,31 +1073,90 @@ declare module 'vscode' { * Controls if outputs change will trigger notebook document content change and if it will be used in the diff editor * Default to false. If the content provider doesn't persisit the outputs in the file document, this should be set to true. */ - transientOutputs: boolean; + transientOutputs?: boolean; /** - * Controls if a meetadata property change will trigger notebook document content change and if it will be used in the diff editor + * Controls if a cell metadata property change will trigger notebook document content change and if it will be used in the diff editor * Default to false. If the content provider doesn't persisit a metadata property in the file document, it should be set to true. */ - transientMetadata: { [K in keyof NotebookCellMetadata]?: boolean }; + transientCellMetadata?: { [K in keyof NotebookCellMetadata]?: boolean }; + + /** + * Controls if a document metadata property change will trigger notebook document content change and if it will be used in the diff editor + * Default to false. If the content provider doesn't persisit a metadata property in the file document, it should be set to true. + */ + transientDocumentMetadata?: { [K in keyof NotebookDocumentMetadata]?: boolean }; } + /** + * Represents a notebook. Notebooks are composed of cells and metadata. + */ export interface NotebookDocument { + + /** + * The associated uri for this notebook. + * + * *Note* that most notebooks use the `file`-scheme, which means they are files on disk. However, **not** all notebooks are + * saved on disk and therefore the `scheme` must be checked before trying to access the underlying file or siblings on disk. + * + * @see [FileSystemProvider](#FileSystemProvider) + * @see [TextDocumentContentProvider](#TextDocumentContentProvider) + */ readonly uri: Uri; - readonly version: number; - - // todo@API don't have this... - readonly fileName: string; - - readonly isDirty: boolean; - readonly isUntitled: boolean; - readonly cells: ReadonlyArray; - - readonly metadata: NotebookDocumentMetadata; // todo@API should we really expose this? + // todo@API should this be called `notebookType` or `notebookKind` readonly viewType: string; + /** + * The version number of this notebook (it will strictly increase after each + * change, including undo/redo). + */ + readonly version: number; + + /** + * `true` if there are unpersisted changes. + */ + readonly isDirty: boolean; + + /** + * Is this notebook representing an untitled file which has not been saved yet. + */ + readonly isUntitled: boolean; + + /** + * `true` if the notebook has been closed. A closed notebook isn't synchronized anymore + * and won't be re-used when the same resource is opened again. + */ + readonly isClosed: boolean; + + /** + * The [metadata](#NotebookDocumentMetadata) for this notebook. + */ + readonly metadata: NotebookDocumentMetadata; + + /** + * The number of cells in the notebook. + */ + readonly cellCount: number; + + /** + * Return the cell at the specified index. The index will be adjusted to the notebook. + * + * @param index - The index of the cell to retrieve. + * @return A [cell](#NotebookCell). + */ + cellAt(index: number): NotebookCell; + + /** + * Get the cells of this notebook. A subset can be retrieved by providing + * a range. The range will be adjuset to the notebook. + * + * @param range A notebook range. + * @returns The cells contained by the range or all cells. + */ + getCells(range?: NotebookRange): NotebookCell[]; + /** * Save the document. The saving will be handled by the corresponding content provider * @@ -1070,17 +1167,44 @@ declare module 'vscode' { save(): Thenable; } - // todo@API maybe have a NotebookCellPosition sibling - export class NotebookCellRange { - readonly start: number; + /** + * A notebook range represents on ordered pair of two cell indicies. + * It is guaranteed that start is less than or equal to end. + */ + export class NotebookRange { + /** - * exclusive + * The zero-based start index of this range. + */ + readonly start: number; + + /** + * The exclusive end index of this range (zero-based). */ readonly end: number; + /** + * `true` if `start` and `end` are equal. + */ readonly isEmpty: boolean; + /** + * Create a new notebook range. If `start` is not + * before or equal to `end`, the values will be swapped. + * + * @param start start index + * @param end end index. + */ constructor(start: number, end: number); + + /** + * Derive a new range for this range. + * + * @param change An object that describes a change to this range. + * @return A range that reflects the given change. Will return `this` range if the change + * is not changing anything. + */ + with(change: { start?: number, end?: number }): NotebookRange; } export enum NotebookEditorRevealType { @@ -1088,6 +1212,7 @@ declare module 'vscode' { * The range will be revealed with as little scrolling as possible. */ Default = 0, + /** * The range will always be revealed in the center of the viewport. */ @@ -1112,96 +1237,92 @@ declare module 'vscode' { readonly document: NotebookDocument; /** - * @deprecated - */ - // todo@API should not be undefined, rather a default - readonly selection?: NotebookCell; - - /** - * todo@API should replace selection * The selections on this notebook editor. * * The primary selection (or focused range) is `selections[0]`. When the document has no cells, the primary selection is empty `{ start: 0, end: 0 }`; */ - readonly selections: NotebookCellRange[]; + readonly selections: NotebookRange[]; /** * The current visible ranges in the editor (vertically). */ - readonly visibleRanges: NotebookCellRange[]; + readonly visibleRanges: NotebookRange[]; - revealRange(range: NotebookCellRange, revealType?: NotebookEditorRevealType): void; + /** + * Scroll as indicated by `revealType` in order to reveal the given range. + * + * @param range A range. + * @param revealType The scrolling strategy for revealing `range`. + */ + revealRange(range: NotebookRange, revealType?: NotebookEditorRevealType): void; /** * The column in which this editor shows. */ - // @jrieken - // this is not implemented... readonly viewColumn?: ViewColumn; - - /** - * @deprecated - */ - // @rebornix REMOVE/REplace NotebookCommunication - // todo@API fishy? notebooks are public objects, there should be a "global" events for this - readonly onDidDispose: Event; } export interface NotebookDocumentMetadataChangeEvent { + /** + * The [notebook document](#NotebookDocument) for which the document metadata have changed. + */ readonly document: NotebookDocument; } export interface NotebookCellsChangeData { readonly start: number; + // todo@API end? Use NotebookCellRange instead? readonly deletedCount: number; + // todo@API removedCells, deletedCells? readonly deletedItems: NotebookCell[]; + // todo@API addedCells, insertedCells, newCells? readonly items: NotebookCell[]; } export interface NotebookCellsChangeEvent { - /** - * The affected document. + * The [notebook document](#NotebookDocument) for which the cells have changed. */ readonly document: NotebookDocument; readonly changes: ReadonlyArray; } export interface NotebookCellOutputsChangeEvent { - /** - * The affected document. + * The [notebook document](#NotebookDocument) for which the cell outputs have changed. */ readonly document: NotebookDocument; readonly cells: NotebookCell[]; } - export interface NotebookCellLanguageChangeEvent { - - /** - * The affected document. - */ - readonly document: NotebookDocument; - readonly cell: NotebookCell; - readonly language: string; - } - export interface NotebookCellMetadataChangeEvent { + /** + * The [notebook document](#NotebookDocument) for which the cell metadata have changed. + */ readonly document: NotebookDocument; readonly cell: NotebookCell; } export interface NotebookEditorSelectionChangeEvent { + /** + * The [notebook editor](#NotebookEditor) for which the selections have changed. + */ readonly notebookEditor: NotebookEditor; - readonly selections: ReadonlyArray + readonly selections: ReadonlyArray } export interface NotebookEditorVisibleRangesChangeEvent { + /** + * The [notebook editor](#NotebookEditor) for which the visible ranges have changed. + */ readonly notebookEditor: NotebookEditor; - readonly visibleRanges: ReadonlyArray; + readonly visibleRanges: ReadonlyArray; } export interface NotebookCellExecutionStateChangeEvent { + /** + * The [notebook document](#NotebookDocument) for which the cell execution state has changed. + */ readonly document: NotebookDocument; readonly cell: NotebookCell; readonly executionState: NotebookCellExecutionState; @@ -1212,10 +1333,11 @@ declare module 'vscode' { kind: NotebookCellKind; // todo@API better names: value? text? source: string; - // todo@API how does language and MD relate? + // todo@API languageId (as in TextDocument) language: string; outputs?: NotebookCellOutput[]; metadata?: NotebookCellMetadata; + // todo@API just executionSummary or lastExecutionSummary latestExecutionSummary?: NotebookCellExecutionSummary; constructor(kind: NotebookCellKind, source: string, language: string, outputs?: NotebookCellOutput[], metadata?: NotebookCellMetadata, latestExecutionSummary?: NotebookCellExecutionSummary); } @@ -1226,49 +1348,11 @@ declare module 'vscode' { constructor(cells: NotebookCellData[], metadata?: NotebookDocumentMetadata); } - /** - * Communication object passed to the {@link NotebookContentProvider} and - * {@link NotebookOutputRenderer} to communicate with the webview. - */ - export interface NotebookCommunication { - /** - * ID of the editor this object communicates with. A single notebook - * document can have multiple attached webviews and editors, when the - * notebook is split for instance. The editor ID lets you differentiate - * between them. - */ - readonly editorId: string; - - /** - * Fired when the output hosting webview posts a message. - */ - readonly onDidReceiveMessage: Event; - /** - * Post a message to the output hosting webview. - * - * Messages are only delivered if the editor is live. - * - * @param message Body of the message. This must be a string or other json serializable object. - */ - postMessage(message: any): Thenable; - - /** - * Convert a uri for the local file system to one that can be used inside outputs webview. - */ - asWebviewUri(localResource: Uri): Uri; - - // @rebornix - // readonly onDidDispose: Event; - } - - // export function registerNotebookKernel(selector: string, kernel: NotebookKernel): Disposable; - - export interface NotebookDocumentShowOptions { viewColumn?: ViewColumn; preserveFocus?: boolean; preview?: boolean; - selection?: NotebookCellRange; + selections?: NotebookRange[]; } export namespace notebook { @@ -1286,8 +1370,11 @@ declare module 'vscode' { export const notebookDocuments: ReadonlyArray; export const onDidChangeNotebookDocumentMetadata: Event; export const onDidChangeNotebookCells: Event; + + // todo@API add onDidChangeNotebookCellOutputs export const onDidChangeCellOutputs: Event; + // todo@API add onDidChangeNotebookCellMetadata export const onDidChangeCellMetadata: Event; } @@ -1319,23 +1406,19 @@ declare module 'vscode' { // static textplain(value:string): NotebookCellOutputItem; // static errortrace(value:any): NotebookCellOutputItem; - readonly mime: string; - readonly value: unknown; - readonly metadata?: Record; + mime: string; + value: unknown; + metadata?: Record; constructor(mime: string, value: unknown, metadata?: Record); } - // @jrieken - // todo@API think about readonly... - //TODO@API add execution count to cell output? + // @jrieken transient export class NotebookCellOutput { - readonly id: string; - readonly outputs: NotebookCellOutputItem[]; - readonly metadata?: Record; - + id: string; + outputs: NotebookCellOutputItem[]; + metadata?: Record; constructor(outputs: NotebookCellOutputItem[], metadata?: Record); - constructor(outputs: NotebookCellOutputItem[], id: string, metadata?: Record); } @@ -1343,26 +1426,32 @@ declare module 'vscode' { //#region https://github.com/microsoft/vscode/issues/106744, NotebookEditorEdit + // todo@API add NotebookEdit-type which handles all these cases? + // export class NotebookEdit { + // range: NotebookRange; + // newCells: NotebookCellData[]; + // newMetadata?: NotebookDocumentMetadata; + // constructor(range: NotebookRange, newCells: NotebookCellData) + // } + + // export class NotebookCellEdit { + // newMetadata?: NotebookCellMetadata; + // } + + // export interface WorkspaceEdit { + // set(uri: Uri, edits: TextEdit[] | NotebookEdit[]): void + // } + export interface WorkspaceEdit { + // todo@API add NotebookEdit-type which handles all these cases? replaceNotebookMetadata(uri: Uri, value: NotebookDocumentMetadata): void; - - // todo@API use NotebookCellRange - replaceNotebookCells(uri: Uri, start: number, end: number, cells: NotebookCellData[], metadata?: WorkspaceEditEntryMetadata): void; + replaceNotebookCells(uri: Uri, range: NotebookRange, cells: NotebookCellData[], metadata?: WorkspaceEditEntryMetadata): void; replaceNotebookCellMetadata(uri: Uri, index: number, cellMetadata: NotebookCellMetadata, metadata?: WorkspaceEditEntryMetadata): void; - - replaceNotebookCellOutput(uri: Uri, index: number, outputs: NotebookCellOutput[], metadata?: WorkspaceEditEntryMetadata): void; - appendNotebookCellOutput(uri: Uri, index: number, outputs: NotebookCellOutput[], metadata?: WorkspaceEditEntryMetadata): void; - - // TODO@api - // https://jupyter-protocol.readthedocs.io/en/latest/messaging.html#update-display-data - replaceNotebookCellOutputItems(uri: Uri, index: number, outputId: string, items: NotebookCellOutputItem[], metadata?: WorkspaceEditEntryMetadata): void; - appendNotebookCellOutputItems(uri: Uri, index: number, outputId: string, items: NotebookCellOutputItem[], metadata?: WorkspaceEditEntryMetadata): void; } export interface NotebookEditorEdit { replaceMetadata(value: NotebookDocumentMetadata): void; replaceCells(start: number, end: number, cells: NotebookCellData[]): void; - replaceCellOutput(index: number, outputs: NotebookCellOutput[]): void; replaceCellMetadata(index: number, metadata: NotebookCellMetadata): void; } @@ -1385,16 +1474,218 @@ declare module 'vscode' { //#region https://github.com/microsoft/vscode/issues/106744, NotebookSerializer + /** + * The notebook serializer enables the editor to open notebook files. + * + * At its core the editor only knows a [notebook data structure](#NotebookData) but not + * how that data structure is written to a file, nor how it is read from a file. The + * notebook serializer bridges this gap by deserializing bytes into notebook data and + * vice versa. + */ export interface NotebookSerializer { - dataToNotebook(data: Uint8Array): NotebookData | Thenable; - notebookToData(data: NotebookData): Uint8Array | Thenable; + + /** + * Deserialize contents of a notebook file into the notebook data structure. + * + * @param content Contents of a notebook file. + * @param token A cancellation token. + * @return Notebook data or a thenable that resolves to such. + */ + deserializeNotebook(content: Uint8Array, token: CancellationToken): NotebookData | Thenable; + + /** + * Serialize notebook data into file contents. + * + * @param data A notebook data structure. + * @param token A cancellation token. + * @returns An array of bytes or a thenable that resolves to such. + */ + serializeNotebook(data: NotebookData, token: CancellationToken): Uint8Array | Thenable; } export namespace notebook { - // TODO@api use NotebookDocumentFilter instead of just notebookType:string? - // TODO@API options duplicates the more powerful variant on NotebookContentProvider - export function registerNotebookSerializer(notebookType: string, provider: NotebookSerializer, options?: NotebookDocumentContentOptions): Disposable; + // todo@API remove output when notebook marks that as transient, same for metadata + /** + * Register a [notebook serializer](#NotebookSerializer). + * + * @param notebookType A notebook. + * @param serializer A notebook serialzier. + * @param options Optional context options that define what parts of a notebook should be persisted + * @return A [disposable](#Disposable) that unregisters this serializer when being disposed. + */ + export function registerNotebookSerializer(notebookType: string, serializer: NotebookSerializer, options?: NotebookDocumentContentOptions): Disposable; + } + + //#endregion + + //#region https://github.com/microsoft/vscode/issues/119949 + + + export interface NotebookFilter { + readonly viewType?: string; + readonly scheme?: string; + readonly pattern?: GlobPattern; + } + + export type NotebookSelector = NotebookFilter | string | ReadonlyArray; + + export interface NotebookExecuteHandler { + /** + * @param cells The notebook cells to execute. + * @param notebook The notebook for which the execute handler is being called. + * @param controller The controller that the handler is attached to + */ + (this: NotebookController, cells: NotebookCell[], notebook: NotebookDocument, controller: NotebookController): void | Thenable + } + + export interface NotebookInterruptHandler { + /** + * @param notebook The notebook for which the interrupt handler is being called. + */ + (this: NotebookController, notebook: NotebookDocument): void | Thenable; + } + + export interface NotebookController { + + /** + * The identifier of this notebook controller. + */ + readonly id: string; + + /** + * The notebook view type this controller is for. + */ + readonly viewType: string; + + /** + * An array of language identifiers that are supported by this + * controller. Any language identifier from [`getLanguages`](#languages.getLanguages) + * is possible. When falsy all languages are supported. + * + * Samples: + * ```js + * // support JavaScript and TypeScript + * myController.supportedLanguages = ['javascript', 'typescript'] + * + * // support all languages + * myController.supportedLanguages = undefined; // falsy + * myController.supportedLanguages = []; // falsy + * ``` + */ + supportedLanguages?: string[]; + + /** + * The human-readable label of this notebook controller. + */ + label: string; + + /** + * The human-readable description which is rendered less prominent. + */ + description?: string; + + /** + * The human-readable detail which is rendered less prominent. + */ + detail?: string; + + /** + * Whether this controller supports execution order so that the + * editor can render placeholders for them. + */ + // todo@API rename to supportsExecutionOrder, usesExecutionOrder + hasExecutionOrder?: boolean; + + /** + * The execute handler is invoked when the run gestures in the UI are selected, e.g Run Cell, Run All, + * Run Selection etc. + */ + executeHandler: NotebookExecuteHandler; + + /** + * The interrupt handler is invoked the interrupt all execution. This is contrary to cancellation (available via + * [`NotebookCellExecutionTask#token`](NotebookCellExecutionTask#token)) and should only be used when + * execution-level cancellation is supported + */ + interruptHandler?: NotebookInterruptHandler + + /** + * Dispose and free associated resources. + */ + dispose(): void; + + /** + * A kernel can apply to one or many notebook documents but a notebook has only one active + * kernel. This event fires whenever a notebook has been associated to a kernel or when + * that association has been removed. + */ + readonly onDidChangeNotebookAssociation: Event<{ notebook: NotebookDocument, selected: boolean }>; + + /** + * Create a cell execution task. + * + * This should be used in response to the [execution handler](#NotebookController.executeHandler) + * being calleed or when cell execution has been started else, e.g when a cell was already + * executing or when cell execution was triggered from another source. + * + * @param cell The notebook cell for which to create the execution. + * @returns A notebook cell execution. + */ + createNotebookCellExecutionTask(cell: NotebookCell): NotebookCellExecutionTask; + + // todo@API find a better name than "preloads" + // todo@API allow add, not remove + // ipc + readonly preloads: NotebookKernelPreload[]; + + /** + * An event that fires when a renderer (see `preloads`) has send a message to the controller. + */ + readonly onDidReceiveMessage: Event<{ editor: NotebookEditor, message: any }>; + + /** + * Send a message to the renderer of notebook editors. + * + * Note that only editors showing documents that are bound to this controller + * are receiving the message. + * + * @param message The message to send. + * @param editor A specific editor to send the message to. When `undefined` all applicable editors are receiving the message. + * @returns A promise that resolves to a boolean indicating if the message has been send or not. + */ + postMessage(message: any, editor?: NotebookEditor): Thenable; + + //todo@API validate this works + asWebviewUri(localResource: Uri): Uri; + + /** + * A controller can set affinities for specific notebook documents. This allows a controller + * to be more important for some notebooks. + * + * @param notebook The notebook for which a priority is set. + * @param affinity A controller affinity + */ + updateNotebookAffinity(notebook: NotebookDocument, affinity: NotebookControllerAffinity): void; + } + + export enum NotebookControllerAffinity { + Default = 1, + Preferred = 2 + } + + export namespace notebook { + + /** + * Creates a new notebook controller. + * + * @param id Extension-unique identifier of the controller + * @param viewType A notebook type for which this controller is for. + * @param label The label of the controller + * @param handler + * @param preloads + */ + export function createNotebookController(id: string, viewType: string, label: string, handler?: NotebookExecuteHandler, preloads?: NotebookKernelPreload[]): NotebookController; } //#endregion @@ -1435,88 +1726,51 @@ declare module 'vscode' { readonly options?: NotebookDocumentContentOptions; 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; - /** * Content providers should always use [file system providers](#FileSystemProvider) to * resolve the raw content for `uri` as the resouce is not necessarily a file on disk. */ openNotebook(uri: Uri, openContext: NotebookDocumentOpenContext, token: CancellationToken): NotebookData | Thenable; + // todo@API use NotebookData instead saveNotebook(document: NotebookDocument, token: CancellationToken): Thenable; + // todo@API use NotebookData instead saveNotebookAs(targetResource: Uri, document: NotebookDocument, token: CancellationToken): Thenable; + // todo@API use NotebookData instead backupNotebook(document: NotebookDocument, context: NotebookDocumentBackupContext, token: CancellationToken): Thenable; } + export interface NotebookDocumentContentOptions { + /** + * Not ready for production or development use yet. + */ + viewOptions?: { + displayName: string; + filenamePattern: (GlobPattern | { include: GlobPattern; exclude: GlobPattern; })[]; + exclusive?: boolean; + }; + } + export namespace notebook { // TODO@api use NotebookDocumentFilter instead of just notebookType:string? // TODO@API options duplicates the more powerful variant on NotebookContentProvider - export function registerNotebookContentProvider(notebookType: string, provider: NotebookContentProvider, - options?: NotebookDocumentContentOptions & { - /** - * Not ready for production or development use yet. - */ - viewOptions?: { - displayName: string; - filenamePattern: NotebookFilenamePattern[]; - exclusive?: boolean; - }; - } - ): Disposable; + export function registerNotebookContentProvider(notebookType: string, provider: NotebookContentProvider, options?: NotebookDocumentContentOptions): Disposable; } //#endregion //#region https://github.com/microsoft/vscode/issues/106744, NotebookKernel - export interface NotebookKernel { - - // todo@API make this mandatory? - readonly id?: string; - - label: string; - description?: string; - detail?: string; - isPreferred?: boolean; - - // todo@API is this maybe an output property? - preloads?: Uri[]; - - /** - * languages supported by kernel - * - first is preferred - * - `undefined` means all languages available in the editor - */ - supportedLanguages?: string[]; - - // todo@API kernel updating itself - // fired when properties like the supported languages etc change - // onDidChangeProperties?: Event - - /** - * A kernel can optionally implement this which will be called when any "cancel" button is clicked in the document. - */ - interrupt?(document: NotebookDocument): void; - - /** - * Called when the user triggers execution of a cell by clicking the run button for a cell, multiple cells, - * or full notebook. The cell will be put into the Pending state when this method is called. If - * createNotebookCellExecutionTask has not been called by the time the promise returned by this method is - * resolved, the cell will be put back into the Idle state. - */ - executeCellsRequest(document: NotebookDocument, ranges: NotebookCellRange[]): Thenable; + // todo@API make class? + export interface NotebookKernelPreload { + provides?: string | string[]; + uri: Uri; } export interface NotebookCellExecuteStartContext { - // TODO@roblou are we concerned about clock issues with this absolute time? /** * The time that execution began, in milliseconds in the Unix epoch. Used to drive the clock * that shows for how long a cell has been running. If not given, the clock won't be shown. @@ -1532,9 +1786,9 @@ declare module 'vscode' { success?: boolean; /** - * The total execution time in milliseconds. + * The time that execution finished, in milliseconds in the Unix epoch. */ - duration?: number; + endTime?: number; } /** @@ -1572,54 +1826,18 @@ declare module 'vscode' { } export namespace notebook { - /** - * Creates a [`NotebookCellExecutionTask`](#NotebookCellExecutionTask). Should only be called by a kernel. Returns undefined unless requested by the active kernel. - * @param uri The [uri](#Uri) of the notebook document. - * @param index The index of the cell. - * @param kernelId The id of the kernel requesting this run task. If this kernel is not the current active kernel, `undefined` is returned. - */ + /** @deprecated use NotebookController */ export function createNotebookCellExecutionTask(uri: Uri, index: number, kernelId: string): NotebookCellExecutionTask | undefined; export const onDidChangeCellExecutionState: Event; } - export type NotebookFilenamePattern = GlobPattern | { include: GlobPattern; exclude: GlobPattern; }; - - // todo@API why not for NotebookContentProvider? - export interface NotebookDocumentFilter { - viewType?: string | string[]; - filenamePattern?: NotebookFilenamePattern; - } - - // todo@API very unclear, provider MUST not return alive object but only data object - // todo@API unclear how the flow goes - export interface NotebookKernelProvider { - onDidChangeKernels?: Event; - provideKernels(document: NotebookDocument, token: CancellationToken): ProviderResult; - resolveKernel?(kernel: T, document: NotebookDocument, webview: NotebookCommunication, token: CancellationToken): ProviderResult; - } - - export interface NotebookEditor { - /** - * Active kernel used in the editor - */ - // todo@API unsure about that - // kernel, kernel selection, kernel provider - readonly kernel?: NotebookKernel; - } - - export namespace notebook { - export const onDidChangeActiveNotebookKernel: Event<{ document: NotebookDocument, kernel: NotebookKernel | undefined; }>; - - export function registerNotebookKernelProvider(selector: NotebookDocumentFilter, provider: NotebookKernelProvider): Disposable; - } - //#endregion //#region https://github.com/microsoft/vscode/issues/106744, NotebookEditorDecorationType export interface NotebookEditor { - setDecorations(decorationType: NotebookEditorDecorationType, range: NotebookCellRange): void; + setDecorations(decorationType: NotebookEditorDecorationType, range: NotebookRange): void; } export interface NotebookDecorationRenderOptions { @@ -1657,32 +1875,31 @@ declare module 'vscode' { Right = 2 } - export interface NotebookCellStatusBarItem { - readonly cell: NotebookCell; - readonly alignment: NotebookCellStatusBarAlignment; - readonly priority?: number; + export class NotebookCellStatusBarItem { text: string; - tooltip: string | undefined; - command: string | Command | undefined; + alignment: NotebookCellStatusBarAlignment; + command?: string | Command; + tooltip?: string; + priority?: number; accessibilityInformation?: AccessibilityInformation; - show(): void; - hide(): void; - dispose(): void; + + constructor(text: string, alignment: NotebookCellStatusBarAlignment, command?: string | Command, tooltip?: string, priority?: number, accessibilityInformation?: AccessibilityInformation); + } + + interface NotebookCellStatusBarItemProvider { + /** + * Implement and fire this event to signal that statusbar items have changed. The provide method will be called again. + */ + onDidChangeCellStatusBarItems?: Event; + + /** + * The provider will be called when the cell scrolls into view, when its content, outputs, language, or metadata change, and when it changes execution state. + */ + provideCellStatusBarItems(cell: NotebookCell, token: CancellationToken): ProviderResult; } export namespace notebook { - /** - * Creates a notebook cell status bar [item](#NotebookCellStatusBarItem). - * It will be disposed automatically when the notebook document is closed or the cell is deleted. - * - * @param cell The cell on which this item should be shown. - * @param alignment The alignment of the item. - * @param priority The priority of the item. Higher values mean the item should be shown more to the left. - * @return A new status bar item. - */ - // @roblourens - // todo@API this should be a provider, https://github.com/microsoft/vscode/issues/105809 - export function createCellStatusBarItem(cell: NotebookCell, alignment?: NotebookCellStatusBarAlignment, priority?: number): NotebookCellStatusBarItem; + export function registerNotebookCellStatusBarItemProvider(selector: NotebookSelector, provider: NotebookCellStatusBarItemProvider): Disposable; } //#endregion @@ -1697,17 +1914,16 @@ declare module 'vscode' { * @param notebook * @param selector */ - // @jrieken REMOVE. p_never // todo@API really needed? we didn't find a user here export function createConcatTextDocument(notebook: NotebookDocument, selector?: DocumentSelector): NotebookConcatTextDocument; } export interface NotebookConcatTextDocument { - uri: Uri; - isClosed: boolean; + readonly uri: Uri; + readonly isClosed: boolean; dispose(): void; - onDidChange: Event; - version: number; + readonly onDidChange: Event; + readonly version: number; getText(): string; getText(range: Range): string; @@ -2047,77 +2263,92 @@ declare module 'vscode' { //#endregion //#region https://github.com/microsoft/vscode/issues/107467 - /* - General activation events: - - `onLanguage:*` most test extensions will want to activate when their - language is opened to provide code lenses. - - `onTests:*` new activation event very simiular to `workspaceContains`, - but only fired when the user wants to run tests or opens the test explorer. - */ export namespace test { /** - * Registers a provider that discovers and runs tests. + * Registers a controller that can discover and + * run tests in workspaces and documents. */ - export function registerTestProvider(testProvider: TestProvider): Disposable; + export function registerTestController(testController: TestController): Disposable; /** - * Runs tests. The "run" contains the list of tests to run as well as a - * method that can be used to update their state. At the point in time - * that "run" is called, all tests given in the run have their state - * automatically set to {@link TestRunState.Queued}. + * Requests that tests be run by their controller. + * @param run Run options to use + * @param token Cancellation token for the test run */ - export function runTests(run: TestRunOptions, cancellationToken?: CancellationToken): Thenable; + export function runTests(run: TestRunRequest, token?: CancellationToken): Thenable; /** * Returns an observer that retrieves tests in the given workspace folder. + * @stability experimental */ export function createWorkspaceTestObserver(workspaceFolder: WorkspaceFolder): TestObserver; /** * Returns an observer that retrieves tests in the given text document. + * @stability experimental */ export function createDocumentTestObserver(document: TextDocument): TestObserver; /** - * Inserts custom test results into the VS Code UI. The results are - * inserted and sorted based off the `completedAt` timestamp. If the - * results are being read from a file, for example, the `completedAt` - * time should generally be the modified time of the file if not more - * specific time is available. + * Creates a {@link TestRun}. This should be called by the + * {@link TestRunner} when a request is made to execute tests, and may also + * be called if a test run is detected externally. Once created, tests + * that are included in the results will be moved into the + * {@link TestResultState.Pending} state. * - * This will no-op if the inserted results are deeply equal to an - * existing result. - * - * @param results test results - * @param persist whether the test results should be saved by VS Code - * and persisted across reloads. Defaults to true. + * @param request Test run request. Only tests inside the `include` may be + * modified, and tests in its `exclude` are ignored. + * @param name The human-readable name of the run. This can be used to + * disambiguate multiple sets of results in a test run. It is useful if + * tests are run across multiple platforms, for example. + * @param persist Whether the results created by the run should be + * persisted in VS Code. This may be false if the results are coming from + * a file already saved externally, such as a coverage information file. */ - export function publishTestResult(results: TestResults, persist?: boolean): void; + export function createTestRun(request: TestRunRequest, name?: string, persist?: boolean): TestRun; /** - * List of test results stored by VS Code, sorted in descnding - * order by their `completedAt` time. - */ - export const testResults: ReadonlyArray; + * Creates a new managed {@link TestItem} instance. + * @param options Initial/required options for the item + * @param data Custom data to be stored in {@link TestItem.data} + */ + export function createTestItem(options: TestItemOptions, data: T): TestItem; /** - * Event that fires when the {@link testResults} array is updated. - */ + * Creates a new managed {@link TestItem} instance. + * @param options Initial/required options for the item + */ + export function createTestItem(options: TestItemOptions): TestItem; + + /** + * List of test results stored by VS Code, sorted in descnding + * order by their `completedAt` time. + * @stability experimental + */ + export const testResults: ReadonlyArray; + + /** + * Event that fires when the {@link testResults} array is updated. + * @stability experimental + */ export const onDidChangeTestResults: Event; } + /** + * @stability experimental + */ export interface TestObserver { /** * List of tests returned by test provider for files in the workspace. */ - readonly tests: ReadonlyArray; + readonly tests: ReadonlyArray>; /** * An event that fires when an existing test in the collection changes, or * null if a top-level test was added or removed. When fired, the consumer * should check the test item and all its children for changes. */ - readonly onDidChangeTest: Event; + readonly onDidChangeTest: Event; /** * An event that fires when all test providers have signalled that the tests @@ -2136,57 +2367,43 @@ declare module 'vscode' { dispose(): void; } - export interface TestChangeEvent { + /** + * @stability experimental + */ + export interface TestsChangeEvent { /** * List of all tests that are newly added. */ - readonly added: ReadonlyArray; + readonly added: ReadonlyArray>; /** * List of existing tests that have updated. */ - readonly updated: ReadonlyArray; + readonly updated: ReadonlyArray>; /** * List of existing tests that have been removed. */ - readonly removed: ReadonlyArray; + readonly removed: ReadonlyArray>; } /** - * Tree of tests returned from the provide methods in the {@link TestProvider}. + * Interface to discover and execute tests. */ - export interface TestHierarchy { - /** - * Root node for tests. The root instance must not be replaced over - * the lifespan of the TestHierarchy, since you will need to reference it - * in {@link onDidChangeTest} when a test is added or removed. - */ - readonly root: T; - } - - /** - * Discovers and provides tests. - * - * Additionally, the UI may request it to discover tests for the workspace - * via `addWorkspaceTests`. - * - * @todo rename from provider - */ - export interface TestProvider { + export interface TestController { /** * Requests that tests be provided for the given workspace. This will * be called when tests need to be enumerated for the workspace, such as * when the user opens the test explorer. * * It's guaranteed that this method will not be called again while - * there is a previous uncancelled hierarchy for the given workspace folder. + * there is a previous uncancelled call for the given workspace folder. * * @param workspace The workspace in which to observe tests * @param cancellationToken Token that signals the used asked to abort the test run. * @returns the root test item for the workspace */ - provideWorkspaceTestRoot(workspace: WorkspaceFolder, token: CancellationToken): ProviderResult; + createWorkspaceTestRoot(workspace: WorkspaceFolder, token: CancellationToken): ProviderResult>; /** * Requests that tests be provided for the given document. This will be @@ -2194,8 +2411,8 @@ declare module 'vscode' { * instance by code lens UI. * * It's suggested that the provider listen to change events for the text - * document to provide information for test that might not yet be - * saved, if possible. + * document to provide information for tests that might not yet be + * saved. * * If the test system is not able to provide or estimate for tests on a * per-file basis, this method may not be implemented. In that case, the @@ -2203,99 +2420,139 @@ declare module 'vscode' { * * @param document The document in which to observe tests * @param cancellationToken Token that signals the used asked to abort the test run. - * @returns the root test item for the workspace + * @returns the root test item for the document */ - provideDocumentTestRoot?(document: TextDocument, token: CancellationToken): ProviderResult; + createDocumentTestRoot?(document: TextDocument, token: CancellationToken): ProviderResult>; /** - * @todo this will move out of the provider soon - * @todo this will eventually need to be able to return a summary report, coverage for example. + * Starts a test run. When called, the controller should call + * {@link vscode.test.createTestRun}. All tasks associated with the + * run should be created before the function returns or the reutrned + * promise is resolved. * - * Starts a test run. This should cause {@link onDidChangeTest} to - * fire with update test states during the run. * @param options Options for this test run * @param cancellationToken Token that signals the used asked to abort the test run. */ - // eslint-disable-next-line vscode-dts-provider-naming - runTests(options: TestRun, token: CancellationToken): ProviderResult; + runTests(options: TestRunRequest, token: CancellationToken): Thenable | void; } /** * Options given to {@link test.runTests}. */ - export interface TestRunOptions { + export interface TestRunRequest { /** - * Array of specific tests to run. The {@link TestProvider.testRoot} may - * be provided as an indication to run all tests. + * Array of specific tests to run. The controllers should run all of the + * given tests and all children of the given tests, excluding any tests + * that appear in {@link TestRunRequest.exclude}. */ - tests: T[]; + tests: TestItem[]; /** * An array of tests the user has marked as excluded in VS Code. May be - * omitted if no exclusions were requested. Test providers should not run + * omitted if no exclusions were requested. Test controllers should not run * excluded tests or any children of excluded tests. */ - exclude?: T[]; + exclude?: TestItem[]; /** - * Whether or not tests in this run should be debugged. + * Whether tests in this run should be debugged. */ debug: boolean; } /** - * Options given to `TestProvider.runTests` + * Options given to {@link TestController.runTests} */ - export interface TestRun extends TestRunOptions { + export interface TestRun { /** - * Updates the state of the test in the run. By default, all tests involved - * in the run will have a "queued" state until they are updated by this method. - * - * Calling with method with nodes outside the {@link tests} or in the - * {@link exclude} array will no-op. + * The human-readable name of the run. This can be used to + * disambiguate multiple sets of results in a test run. It is useful if + * tests are run across multiple platforms, for example. + */ + readonly name?: string; + + /** + * Updates the state of the test in the run. Calling with method with nodes + * outside the {@link TestRunRequest.tests} or in the + * {@link TestRunRequest.exclude} array will no-op. * * @param test The test to update * @param state The state to assign to the test + * @param duration Optionally sets how long the test took to run */ - setState(test: T, state: TestState): void; + setState(test: TestItem, state: TestResultState, duration?: number): void; + + /** + * Appends a message, such as an assertion error, to the test item. + * + * Calling with method with nodes outside the {@link TestRunRequest.tests} + * or in the {@link TestRunRequest.exclude} array will no-op. + * + * @param test The test to update + * @param state The state to assign to the test + * + */ + appendMessage(test: TestItem, message: TestMessage): void; + + /** + * Appends raw output from the test runner. On the user's request, the + * output will be displayed in a terminal. ANSI escape sequences, + * such as colors and text styles, are supported. + * + * @param output Output text to append + * @param associateTo Optionally, associate the given segment of output + */ + appendOutput(output: string): void; + + /** + * Signals that the end of the test run. Any tests whose states have not + * been updated will be moved into the {@link TestResultState.Unset} state. + */ + end(): void; } - export interface TestChildrenCollection extends Iterable { + /** + * Indicates the the activity state of the {@link TestItem}. + */ + export enum TestItemStatus { /** - * Gets the number of children in the collection. + * All children of the test item, if any, have been discovered. */ - readonly size: number; + Resolved = 1, /** - * Gets an existing TestItem by its ID, if it exists. - * @param id ID of the test. - * @returns the TestItem instance if it exists. + * The test item may have children who have not been discovered yet. */ - get(id: string): T | undefined; + Pending = 0, + } + + /** + * Options initially passed into `vscode.test.createTestItem` + */ + export interface TestItemOptions { + /** + * Unique identifier for the TestItem. This is used to correlate + * test results and tests in the document with those in the workspace + * (test explorer). This cannot change for the lifetime of the TestItem. + */ + id: string; /** - * Adds a new child test item. No-ops if the test was already a child. - * @param child The test item to add. + * URI this TestItem is associated with. May be a file or directory. */ - add(child: T): void; + uri: Uri; /** - * Removes the child test item by reference or ID from the collection. - * @param child Child ID or instance to remove. + * Display name describing the test item. */ - delete(child: T | string): void; - - /** - * Removes all children from the collection. - */ - clear(): void; + label: string; } /** * A test item is an item shown in the "test explorer" view. It encompasses * both a suite and a test, since they have almost or identical capabilities. */ - export class TestItem { + export interface TestItem { /** * Unique identifier for the TestItem. This is used to correlate * test results and tests in the document with those in the workspace @@ -2304,15 +2561,33 @@ declare module 'vscode' { readonly id: string; /** - * URI this TestItem is associated with. May be a file or file. + * URI this TestItem is associated with. May be a file or directory. */ readonly uri: Uri; /** - * A set of children this item has. You can add new children to it, which - * will propagate to the editor UI. + * A mapping of children by ID to the associated TestItem instances. */ - readonly children: TestChildrenCollection; + readonly children: ReadonlyMap>; + + /** + * The parent of this item, if any. Assigned automatically when calling + * {@link TestItem.addChild}. + */ + readonly parent?: TestItem; + + /** + * Indicates the state of the test item's children. The editor will show + * TestItems in the `Pending` state and with a `resolveHandler` as being + * expandable, and will call the `resolveHandler` to request items. + * + * A TestItem in the `Resolved` state is assumed to have discovered and be + * watching for changes in its children if applicable. TestItems are in the + * `Resolved` state when initially created; if the editor should call + * the `resolveHandler` to discover children, set the state to `Pending` + * after creating the item. + */ + status: TestItemStatus; /** * Display name describing the test case. @@ -2331,42 +2606,34 @@ declare module 'vscode' { range?: Range; /** - * Whether this test item can be run individually, defaults to `true`. - * - * In some cases, like Go's tests, test can have children but these - * children cannot be run independently. + * May be set to an error associated with loading the test. Note that this + * is not a test result and should only be used to represent errors in + * discovery, such as syntax errors. + */ + error?: string | MarkdownString; + + /** + * Whether this test item can be run by providing it in the + * {@link TestRunRequest.tests} array. Defaults to `true`. */ runnable: boolean; /** - * Whether this test item can be debugged individually, defaults to `false`. - * - * In some cases, like Go's tests, test can have children but these - * children cannot be run independently. + * Whether this test item can be debugged by providing it in the + * {@link TestRunRequest.tests} array. Defaults to `false`. */ debuggable: boolean; /** - * Whether this test item can be expanded in the tree view, implying it - * has (or may have) children. If this is given, the item may be - * passed to the {@link TestHierarchy.getChildren} method. + * Custom extension data on the item. This data will never be serialized + * or shared outside the extenion who created the item. */ - expandable: boolean; - - /** - * Creates a new TestItem instance. - * @param id Value of the "id" property - * @param label Value of the "label" property. - * @param uri Value of the "uri" property. - * @param parent Parent of this item. This should only be defined for the - * test root. - */ - constructor(id: string, label: string, uri: Uri, expandable: boolean); + data: T; /** * Marks the test as outdated. This can happen as a result of file changes, * for example. In "auto run" mode, tests that are outdated will be - * automatically re-run after a short delay. Invoking this on a + * automatically rerun after a short delay. Invoking this on a * test with children will mark the entire subtree as outdated. * * Extensions should generally not override this method. @@ -2374,17 +2641,18 @@ declare module 'vscode' { invalidate(): void; /** - * Requests the children of the test item. Extensions should override this - * method for any test that can discover children. + * A function provided by the extension that the editor may call to request + * children of the item, if the {@link TestItem.status} is `Pending`. * - * When called, the item should discover tests and update its's `children`. - * The provider will be marked as 'busy' when this method is called, and - * the provider should report `{ busy: false }` to {@link Progress.report} - * once discovery is complete. + * When called, the item should discover tests and call {@link TestItem.addChild}. + * The items should set its {@link TestItem.status} to `Resolved` when + * discovery is finished. * * The item should continue watching for changes to the children and * firing updates until the token is cancelled. The process of watching - * the tests may involve creating a file watcher, for example. + * the tests may involve creating a file watcher, for example. After the + * token is cancelled and watching stops, the TestItem should set its + * {@link TestItem.status} back to `Pending`. * * The editor will only call this method when it's interested in refreshing * the children of the item, and will not call it again while there's an @@ -2392,15 +2660,26 @@ declare module 'vscode' { * * @param token Cancellation for the request. Cancellation will be * requested if the test changes before the previous call completes. - * @returns a provider result of child test items */ - discoverChildren(progress: Progress<{ busy: boolean }>, token: CancellationToken): void; + resolveHandler?: (token: CancellationToken) => void; + + /** + * Attaches a child, created from the {@link test.createTestItem} function, + * to this item. A `TestItem` may be a child of at most one other item. + */ + addChild(child: TestItem): void; + + /** + * Removes the test and its children from the tree. Any tokens passed to + * child `resolveHandler` methods will be cancelled. + */ + dispose(): void; } /** * Possible states of tests in a test run. */ - export enum TestResult { + export enum TestResultState { // Initial state Unset = 0, // Test will be run, but is not currently running. @@ -2417,32 +2696,6 @@ declare module 'vscode' { Errored = 6 } - /** - * TestState associated with a test in its results. - */ - export class TestState { - /** - * Current state of the test. - */ - readonly state: TestResult; - - /** - * Optional duration of the test run, in milliseconds. - */ - duration?: number; - - /** - * Associated test run message. Can, for example, contain assertion - * failure information if the test fails. - */ - messages: TestMessage[]; - - /** - * Creates a new TestState instance. - */ - constructor(state: TestResult); - } - /** * Represents the severity of test messages. */ @@ -2499,21 +2752,27 @@ declare module 'vscode' { } /** - * TestResults can be provided to VS Code, or read from it. + * TestResults can be provided to VS Code in {@link test.publishTestResult}, + * or read from it in {@link test.testResults}. * * The results contain a 'snapshot' of the tests at the point when the test - * run is complete. Therefore, information such as {@link Location} instances - * may be out of date. If the test still exists in the workspace, consumers - * can use its `id` to correlate the result instance with the living test. + * run is complete. Therefore, information such as its {@link Range} may be + * out of date. If the test still exists in the workspace, consumers can use + * its `id` to correlate the result instance with the living test. * * @todo coverage and other info may eventually be provided here */ - export interface TestResults { + export interface TestRunResult { /** - * Unix milliseconds timestamp at which the tests were completed. + * Unix milliseconds timestamp at which the test run was completed. */ completedAt: number; + /** + * Optional raw output from the test run. + */ + output?: string; + /** * List of test results. The items in this array are the items that * were passed in the {@link test.runTests} method. @@ -2555,9 +2814,10 @@ declare module 'vscode' { readonly range?: Range; /** - * Current result of the test. + * State of the test in each task. In the common case, a test will only + * be executed in a single task and the length of this array will be 1. */ - readonly result: TestState; + readonly taskStates: ReadonlyArray; /** * Optional list of nested tests for this item. @@ -2565,6 +2825,25 @@ declare module 'vscode' { readonly children: Readonly[]; } + export interface TestSnapshoptTaskState { + /** + * Current result of the test. + */ + readonly state: TestResultState; + + /** + * The number of milliseconds the test took to run. This is set once the + * `state` is `Passed`, `Failed`, or `Errored`. + */ + readonly duration?: number; + + /** + * Associated test run message. Can, for example, contain assertion + * failure information if the test fails. + */ + readonly messages: ReadonlyArray; + } + //#endregion //#region Opener service (https://github.com/microsoft/vscode/issues/109277) @@ -2742,100 +3021,51 @@ declare module 'vscode' { //#endregion - //#region https://github.com/microsoft/vscode/issues/106488 - - export enum WorkspaceTrustState { - /** - * The workspace is untrusted, and it will have limited functionality. - */ - Untrusted = 0, - - /** - * The workspace is trusted, and all functionality will be available. - */ - Trusted = 1, - - /** - * The initial state of the workspace. - * - * If trust will be required, users will be prompted to make a choice. - */ - Unknown = 2 - } - - /** - * The event data that is fired when the trust state of the workspace changes - */ - export interface WorkspaceTrustStateChangeEvent { - /** - * Previous trust state of the workspace - */ - previousTrustState: WorkspaceTrustState; - - /** - * Current trust state of the workspace - */ - currentTrustState: WorkspaceTrustState; - } - + //#region https://github.com/microsoft/vscode/issues/120173 /** * The object describing the properties of the workspace trust request */ export interface WorkspaceTrustRequestOptions { /** * When true, a modal dialog will be used to request workspace trust. - * When false, a badge will be displayed on the Setting activity bar item + * When false, a badge will be displayed on the settings gear activity bar item. */ - modal: boolean; + readonly modal: boolean; } export namespace workspace { - /** - * The trust state of the current workspace - */ - export const trustState: WorkspaceTrustState; - /** * Prompt the user to chose whether to trust the current workspace * @param options Optional object describing the properties of the - * workspace trust request + * workspace trust request. Defaults to { modal: false } + * When using a non-modal request, the promise will return immediately. + * Any time trust is not given, it is recommended to use the + * `onDidGrantWorkspaceTrust` event to listen for trust changes. */ - export function requireWorkspaceTrust(options?: WorkspaceTrustRequestOptions): Thenable; - - /** - * Event that fires when the trust state of the current workspace changes - */ - export const onDidChangeWorkspaceTrustState: Event; + export function requestWorkspaceTrust(options?: WorkspaceTrustRequestOptions): Thenable; } //#endregion - //#region https://github.com/microsoft/vscode/issues/118084 + //#region https://github.com/microsoft/vscode/issues/115807 - /** - * The reason why code actions were requested. - */ - export enum CodeActionTriggerKind { + export interface Webview { /** - * Code actions were explicitly requested by the user or by an extension. - */ - Invoke = 1, - - /** - * Code actions were requested automatically. + * @param message A json serializable message to send to the webview. * - * This typically happens when current selection in a file changes, but can - * also be triggered when file content changes. + * For older versions of vscode, if an `ArrayBuffer` is included in `message`, + * it will not be serialized properly and will not be received by the webview. + * Similarly any TypedArrays, such as a `Uint8Array`, will be very inefficiently + * serialized and will also not be recreated as a typed array inside the webview. + * + * However if your extension targets vscode 1.56+ in the `engines` field of its + * `package.json` any `ArrayBuffer` values that appear in `message` will be more + * efficiently transferred to the webview and will also be recreated inside of + * the webview. */ - Automatic = 2, + postMessage(message: any): Thenable; } - export interface CodeActionContext { - /** - * The reason why code actions were requested. - */ - readonly triggerKind: CodeActionTriggerKind; - } //#endregion //#region https://github.com/microsoft/vscode/issues/115616 @alexr00 @@ -2854,11 +3084,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. + * Provides attributes for the given port. For ports that your extension doesn't know about, simply + * return undefined. For example, if `providePortAttributes` is called with ports 3000 but your + * extension doesn't know anything about 3000 you should return undefined. */ - providePortAttributes(ports: number[], pid: number | undefined, commandLine: string | undefined, token: CancellationToken): ProviderResult; + providePortAttributes(port: number, pid: number | undefined, commandLine: string | undefined, token: CancellationToken): ProviderResult; } export namespace workspace { @@ -2869,11 +3099,24 @@ declare module 'vscode' { * ignored, since they don't need to be user facing. * * @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. + * know the range of ports or the pid of your process. All properties of a the portSelector must be true for your + * provider to get called. * The `portRange` is start inclusive and end exclusive. * @param provider The PortAttributesProvider */ - export function registerPortAttributesProvider(portSelector: { pid?: number, portRange?: [number, number] }, provider: PortAttributesProvider): Disposable; + export function registerPortAttributesProvider(portSelector: { pid?: number, portRange?: [number, number], commandMatcher?: RegExp }, provider: PortAttributesProvider): Disposable; } //#endregion + + // region https://github.com/microsoft/vscode/issues/119904 @eamodio + + export interface SourceControlInputBox { + + /** + * Sets focus to the input. + */ + focus(): void; + } + + //#endregion } diff --git a/src/vs/workbench/api/browser/apiCommands.ts b/src/vs/workbench/api/browser/apiCommands.ts new file mode 100644 index 00000000..a41b0c30 --- /dev/null +++ b/src/vs/workbench/api/browser/apiCommands.ts @@ -0,0 +1,20 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { isWeb } from 'vs/base/common/platform'; + +if (isWeb) { + CommandsRegistry.registerCommand('_workbench.fetchJSON', async function (accessor: ServicesAccessor, url: string, method: string) { + const result = await fetch(url, { method, headers: { Accept: 'application/json' } }); + + if (result.ok) { + return result.json(); + } else { + throw new Error(result.statusText); + } + }); +} diff --git a/src/vs/workbench/api/browser/extensionHost.contribution.ts b/src/vs/workbench/api/browser/extensionHost.contribution.ts index 583887ec..524697c3 100644 --- a/src/vs/workbench/api/browser/extensionHost.contribution.ts +++ b/src/vs/workbench/api/browser/extensionHost.contribution.ts @@ -63,6 +63,8 @@ import './mainThreadWebviewManager'; import './mainThreadWorkspace'; import './mainThreadComments'; import './mainThreadNotebook'; +import './mainThreadNotebookKernels'; +import './mainThreadNotebookDocumentsAndEditors'; import './mainThreadTask'; import './mainThreadLabelService'; import './mainThreadTunnelService'; @@ -71,6 +73,7 @@ import './mainThreadTimeline'; import './mainThreadTesting'; import './mainThreadSecretState'; import 'vs/workbench/api/common/apiCommands'; +import 'vs/workbench/api/browser/apiCommands'; export class ExtensionPoints implements IWorkbenchContribution { diff --git a/src/vs/workbench/api/browser/mainThreadBulkEdits.ts b/src/vs/workbench/api/browser/mainThreadBulkEdits.ts index 8583555b..8b4e10f8 100644 --- a/src/vs/workbench/api/browser/mainThreadBulkEdits.ts +++ b/src/vs/workbench/api/browser/mainThreadBulkEdits.ts @@ -6,6 +6,7 @@ import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; import { IExtHostContext, IWorkspaceEditDto, MainThreadBulkEditsShape, MainContext } from 'vs/workbench/api/common/extHost.protocol'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { reviveWorkspaceEditDto2 } from 'vs/workbench/api/browser/mainThreadEditors'; +import { ILogService } from 'vs/platform/log/common/log'; @extHostNamedCustomer(MainContext.MainThreadBulkEdits) export class MainThreadBulkEdits implements MainThreadBulkEditsShape { @@ -13,12 +14,16 @@ export class MainThreadBulkEdits implements MainThreadBulkEditsShape { constructor( _extHostContext: IExtHostContext, @IBulkEditService private readonly _bulkEditService: IBulkEditService, + @ILogService private readonly _logService: ILogService, ) { } dispose(): void { } $tryApplyWorkspaceEdit(dto: IWorkspaceEditDto, undoRedoGroupId?: number): Promise { const edits = reviveWorkspaceEditDto2(dto); - return this._bulkEditService.apply(edits, { undoRedoGroupId }).then(() => true, _err => false); + return this._bulkEditService.apply(edits, { undoRedoGroupId }).then(() => true, err => { + this._logService.warn('IGNORING workspace edit', err); + return false; + }); } } diff --git a/src/vs/workbench/api/browser/mainThreadCLICommands.ts b/src/vs/workbench/api/browser/mainThreadCLICommands.ts index 0976a8ab..62002c1f 100644 --- a/src/vs/workbench/api/browser/mainThreadCLICommands.ts +++ b/src/vs/workbench/api/browser/mainThreadCLICommands.ts @@ -7,7 +7,7 @@ import { Schemas } from 'vs/base/common/network'; import { isString } from 'vs/base/common/types'; import { URI, UriComponents } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; -import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { CLIOutput, IExtensionGalleryService, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { ExtensionManagementCLIService } from 'vs/platform/extensionManagement/common/extensionManagementCLIService'; @@ -17,9 +17,10 @@ import { ServiceCollection } from 'vs/platform/instantiation/common/serviceColle import { ILabelService } from 'vs/platform/label/common/label'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IProductService } from 'vs/platform/product/common/productService'; +import { IOpenWindowOptions, IWindowOpenable } from 'vs/platform/windows/common/windows'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IExtensionManagementServerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; -import { ExtensionKindController } from 'vs/workbench/services/extensions/common/extensionsUtil'; +import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService'; import { IExtensionManifest } from 'vs/workbench/workbench.web.api'; @@ -27,7 +28,17 @@ import { IExtensionManifest } from 'vs/workbench/workbench.web.api'; CommandsRegistry.registerCommand('_remoteCLI.openExternal', function (accessor: ServicesAccessor, uri: UriComponents | string) { const openerService = accessor.get(IOpenerService); - openerService.open(isString(uri) ? uri : URI.revive(uri), { openExternal: true, allowTunneling: true }); + return openerService.open(isString(uri) ? uri : URI.revive(uri), { openExternal: true, allowTunneling: true }); +}); + +CommandsRegistry.registerCommand('_remoteCLI.windowOpen', function (accessor: ServicesAccessor, toOpen: IWindowOpenable[], options?: IOpenWindowOptions) { + const commandService = accessor.get(ICommandService); + return commandService.executeCommand('_files.windowOpen', toOpen, options); +}); + +CommandsRegistry.registerCommand('_remoteCLI.getSystemStatus', function (accessor: ServicesAccessor) { + const commandService = accessor.get(ICommandService); + return commandService.executeCommand('_issues.getSystemStatus'); }); interface ManageExtensionsArgs { @@ -77,30 +88,27 @@ class RemoteExtensionCLIManagementService extends ExtensionManagementCLIService private _location: string | undefined; - private readonly _extensionKindController: ExtensionKindController; - constructor( @IExtensionManagementService extensionManagementService: IExtensionManagementService, @IProductService productService: IProductService, @IConfigurationService configurationService: IConfigurationService, @IExtensionGalleryService extensionGalleryService: IExtensionGalleryService, @ILabelService labelService: ILabelService, - @IWorkbenchEnvironmentService envService: IWorkbenchEnvironmentService + @IWorkbenchEnvironmentService envService: IWorkbenchEnvironmentService, + @IExtensionManifestPropertiesService private readonly _extensionManifestPropertiesService: IExtensionManifestPropertiesService, ) { super(extensionManagementService, extensionGalleryService); const remoteAuthority = envService.remoteAuthority; this._location = remoteAuthority ? labelService.getHostLabel(Schemas.vscodeRemote, remoteAuthority) : undefined; - - this._extensionKindController = new ExtensionKindController(productService, configurationService); } - protected get location(): string | undefined { + protected override get location(): string | undefined { return this._location; } - protected validateExtensionKind(manifest: IExtensionManifest, output: CLIOutput): boolean { - if (!this._extensionKindController.canExecuteOnWorkspace(manifest)) { + protected override validateExtensionKind(manifest: IExtensionManifest, output: CLIOutput): boolean { + if (!this._extensionManifestPropertiesService.canExecuteOnWorkspace(manifest)) { output.log(localize('cannot be installed', "Cannot install the '{0}' extension because it is declared to not run in this setup.", getExtensionId(manifest.publisher, manifest.name))); return false; } diff --git a/src/vs/workbench/api/browser/mainThreadCodeInsets.ts b/src/vs/workbench/api/browser/mainThreadCodeInsets.ts index 140fa60e..746d3146 100644 --- a/src/vs/workbench/api/browser/mainThreadCodeInsets.ts +++ b/src/vs/workbench/api/browser/mainThreadCodeInsets.ts @@ -105,7 +105,7 @@ export class MainThreadEditorInsets implements MainThreadEditorInsetsShape { disposables.add(editor.onDidDispose(remove)); disposables.add(webviewZone); disposables.add(webview); - disposables.add(webview.onMessage(msg => this._proxy.$onDidReceiveMessage(handle, msg))); + disposables.add(webview.onMessage(msg => this._proxy.$onDidReceiveMessage(handle, msg.message))); this._insets.set(handle, webviewZone); } diff --git a/src/vs/workbench/api/browser/mainThreadComments.ts b/src/vs/workbench/api/browser/mainThreadComments.ts index 64b607f7..c866d7f7 100644 --- a/src/vs/workbench/api/browser/mainThreadComments.ts +++ b/src/vs/workbench/api/browser/mainThreadComments.ts @@ -535,7 +535,7 @@ export class MainThreadComments extends Disposable implements MainThreadComments this._commentService.updateComments(providerId, event); } - dispose(): void { + override dispose(): void { super.dispose(); this._workspaceProviders.forEach(value => dispose(value)); this._workspaceProviders.clear(); diff --git a/src/vs/workbench/api/browser/mainThreadCustomEditors.ts b/src/vs/workbench/api/browser/mainThreadCustomEditors.ts index 6693da4f..00e05927 100644 --- a/src/vs/workbench/api/browser/mainThreadCustomEditors.ts +++ b/src/vs/workbench/api/browser/mainThreadCustomEditors.ts @@ -31,13 +31,13 @@ import { CustomTextEditorModel } from 'vs/workbench/contrib/customEditor/common/ import { WebviewExtensionDescription } from 'vs/workbench/contrib/webview/browser/webview'; import { WebviewInput } from 'vs/workbench/contrib/webviewPanel/browser/webviewEditorInput'; import { IWebviewWorkbenchService } from 'vs/workbench/contrib/webviewPanel/browser/webviewWorkbenchService'; -import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IPathService } from 'vs/workbench/services/path/common/pathService'; import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; -import { IWorkingCopy, IWorkingCopyBackup, IWorkingCopyService, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { IWorkingCopy, IWorkingCopyBackup, NO_TYPE_ID, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopy'; const enum CustomEditorModelType { Custom, @@ -60,8 +60,7 @@ export class MainThreadCustomEditors extends Disposable implements extHostProtoc @ICustomEditorService private readonly _customEditorService: ICustomEditorService, @IEditorGroupsService private readonly _editorGroupService: IEditorGroupsService, @IWebviewWorkbenchService private readonly _webviewWorkbenchService: IWebviewWorkbenchService, - @IInstantiationService private readonly _instantiationService: IInstantiationService, - @IBackupFileService private readonly _backupService: IBackupFileService, + @IInstantiationService private readonly _instantiationService: IInstantiationService ) { super(); @@ -92,19 +91,19 @@ export class MainThreadCustomEditors extends Disposable implements extHostProtoc })); } - dispose() { + override dispose() { super.dispose(); dispose(this._editorProviders.values()); this._editorProviders.clear(); } - public $registerTextEditorProvider(extensionData: extHostProtocol.WebviewExtensionDescription, viewType: string, options: extHostProtocol.IWebviewPanelOptions, capabilities: extHostProtocol.CustomTextEditorCapabilities): void { - this.registerEditorProvider(CustomEditorModelType.Text, reviveWebviewExtension(extensionData), viewType, options, capabilities, true); + public $registerTextEditorProvider(extensionData: extHostProtocol.WebviewExtensionDescription, viewType: string, options: extHostProtocol.IWebviewPanelOptions, capabilities: extHostProtocol.CustomTextEditorCapabilities, serializeBuffersForPostMessage: boolean): void { + this.registerEditorProvider(CustomEditorModelType.Text, reviveWebviewExtension(extensionData), viewType, options, capabilities, true, serializeBuffersForPostMessage); } - public $registerCustomEditorProvider(extensionData: extHostProtocol.WebviewExtensionDescription, viewType: string, options: extHostProtocol.IWebviewPanelOptions, supportsMultipleEditorsPerDocument: boolean): void { - this.registerEditorProvider(CustomEditorModelType.Custom, reviveWebviewExtension(extensionData), viewType, options, {}, supportsMultipleEditorsPerDocument); + public $registerCustomEditorProvider(extensionData: extHostProtocol.WebviewExtensionDescription, viewType: string, options: extHostProtocol.IWebviewPanelOptions, supportsMultipleEditorsPerDocument: boolean, serializeBuffersForPostMessage: boolean): void { + this.registerEditorProvider(CustomEditorModelType.Custom, reviveWebviewExtension(extensionData), viewType, options, {}, supportsMultipleEditorsPerDocument, serializeBuffersForPostMessage); } private registerEditorProvider( @@ -114,6 +113,7 @@ export class MainThreadCustomEditors extends Disposable implements extHostProtoc options: extHostProtocol.IWebviewPanelOptions, capabilities: extHostProtocol.CustomTextEditorCapabilities, supportsMultipleEditorsPerDocument: boolean, + serializeBuffersForPostMessage: boolean, ): void { if (this._editorProviders.has(viewType)) { throw new Error(`Provider for ${viewType} already registered`); @@ -133,7 +133,7 @@ export class MainThreadCustomEditors extends Disposable implements extHostProtoc const handle = webviewInput.id; const resource = webviewInput.resource; - this.mainThreadWebviewPanels.addWebviewInput(handle, webviewInput); + this.mainThreadWebviewPanels.addWebviewInput(handle, webviewInput, { serializeBuffersForPostMessage }); webviewInput.webview.options = options; webviewInput.webview.extension = extension; @@ -228,7 +228,7 @@ export class MainThreadCustomEditors extends Disposable implements extHostProtoc const model = MainThreadCustomEditorModel.create(this._instantiationService, this._proxyCustomEditors, viewType, resource, options, () => { return Array.from(this.mainThreadWebviewPanels.webviewInputs) .filter(editor => editor instanceof CustomEditorInput && isEqual(editor.resource, resource)) as CustomEditorInput[]; - }, cancellation, this._backupService); + }, cancellation); return this._customEditorService.models.add(resource, viewType, model); } } @@ -295,6 +295,18 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod private readonly _onDidChangeOrphaned = this._register(new Emitter()); public readonly onDidChangeOrphaned = this._onDidChangeOrphaned.event; + // TODO@mjbvz consider to enable a `typeId` that is specific for custom + // editors. Using a distinct `typeId` allows the working copy to have + // any resource (including file based resources) even if other working + // copies exist with the same resource. + // + // IMPORTANT: changing the `typeId` has an impact on backups for this + // working copy. Any value that is not the empty string will be used + // as seed to the backup. Only change the `typeId` if you have implemented + // a fallback solution to resolve any existing backups that do not have + // this seed. + readonly typeId = NO_TYPE_ID; + public static async create( instantiationService: IInstantiationService, proxy: extHostProtocol.ExtHostCustomEditorsShape, @@ -303,7 +315,6 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod options: { backupId?: string }, getEditors: () => CustomEditorInput[], cancellation: CancellationToken, - _backupFileService: IBackupFileService, ): Promise { const editors = getEditors(); let untitledDocumentData: VSBuffer | undefined; @@ -344,7 +355,7 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod return this._editorResource; } - dispose() { + override dispose() { this.#isDisposed = true; if (this._editable) { @@ -651,23 +662,25 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod } const primaryEditor = editors[0]; - const backupData: IWorkingCopyBackup = { - meta: { - viewType: this.viewType, - editorResource: this._editorResource, - backupId: '', - extension: primaryEditor.extension ? { - id: primaryEditor.extension.id.value, - location: primaryEditor.extension.location, - } : undefined, - webview: { - id: primaryEditor.id, - options: primaryEditor.webview.options, - state: primaryEditor.webview.state, - } + const backupMeta: CustomDocumentBackupData = { + viewType: this.viewType, + editorResource: this._editorResource, + backupId: '', + extension: primaryEditor.extension ? { + id: primaryEditor.extension.id.value, + location: primaryEditor.extension.location, + } : undefined, + webview: { + id: primaryEditor.id, + options: primaryEditor.webview.options, + state: primaryEditor.webview.state, } }; + const backupData: IWorkingCopyBackup = { + meta: backupMeta + }; + if (!this._editable) { return backupData; } diff --git a/src/vs/workbench/api/browser/mainThreadDocuments.ts b/src/vs/workbench/api/browser/mainThreadDocuments.ts index 1ac417c8..5b010249 100644 --- a/src/vs/workbench/api/browser/mainThreadDocuments.ts +++ b/src/vs/workbench/api/browser/mainThreadDocuments.ts @@ -157,10 +157,12 @@ export class MainThreadDocuments extends Disposable implements MainThreadDocumen })); this._register(workingCopyFileService.onDidRunWorkingCopyFileOperation(e => { - if (e.operation === FileOperation.MOVE || e.operation === FileOperation.DELETE) { - for (const { source } of e.files) { - if (source) { - this._modelReferenceCollection.remove(source); + const isMove = e.operation === FileOperation.MOVE; + if (isMove || e.operation === FileOperation.DELETE) { + for (const pair of e.files) { + const removed = isMove ? pair.source : pair.target; + if (removed) { + this._modelReferenceCollection.remove(removed); } } } @@ -169,7 +171,7 @@ export class MainThreadDocuments extends Disposable implements MainThreadDocumen this._modelTrackers = Object.create(null); } - public dispose(): void { + public override dispose(): void { Object.keys(this._modelTrackers).forEach((modelUrl) => { this._modelTrackers[modelUrl].dispose(); }); diff --git a/src/vs/workbench/api/browser/mainThreadEditor.ts b/src/vs/workbench/api/browser/mainThreadEditor.ts index 37d4d932..23f062a3 100644 --- a/src/vs/workbench/api/browser/mainThreadEditor.ts +++ b/src/vs/workbench/api/browser/mainThreadEditor.ts @@ -518,7 +518,7 @@ export class MainThreadTextEditor { const snippetController = SnippetController2.get(this._codeEditor); - // // cancel previous snippet mode + // cancel previous snippet mode // snippetController.leaveSnippet(); // set selection, focus editor diff --git a/src/vs/workbench/api/browser/mainThreadExtensionService.ts b/src/vs/workbench/api/browser/mainThreadExtensionService.ts index 67d63452..1a5a495c 100644 --- a/src/vs/workbench/api/browser/mainThreadExtensionService.ts +++ b/src/vs/workbench/api/browser/mainThreadExtensionService.ts @@ -7,7 +7,7 @@ import { SerializedError } from 'vs/base/common/errors'; import Severity from 'vs/base/common/severity'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { IExtHostContext, MainContext, MainThreadExtensionServiceShape } from 'vs/workbench/api/common/extHost.protocol'; -import { IExtensionService, ExtensionActivationError, ExtensionHostKind } from 'vs/workbench/services/extensions/common/extensions'; +import { IExtensionService, ExtensionHostKind, MissingExtensionDependency } from 'vs/workbench/services/extensions/common/extensions'; import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { localize } from 'vs/nls'; @@ -20,6 +20,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; import { ExtensionActivationReason } from 'vs/workbench/api/common/extHostExtensionActivator'; import { ITimerService } from 'vs/workbench/services/timer/browser/timerService'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @extHostNamedCustomer(MainContext.MainThreadExtensionService) export class MainThreadExtensionService implements MainThreadExtensionServiceShape { @@ -34,6 +35,7 @@ export class MainThreadExtensionService implements MainThreadExtensionServiceSha @IHostService private readonly _hostService: IHostService, @IWorkbenchExtensionEnablementService private readonly _extensionEnablementService: IWorkbenchExtensionEnablementService, @ITimerService private readonly _timerService: ITimerService, + @IWorkbenchEnvironmentService protected readonly _environmentService: IWorkbenchEnvironmentService, ) { this._extensionHostKind = extHostContext.extensionHostKind; } @@ -59,25 +61,36 @@ export class MainThreadExtensionService implements MainThreadExtensionServiceSha console.error(`[${extensionId}]${error.message}`); console.error(error.stack); } - async $onExtensionActivationError(extensionId: ExtensionIdentifier, activationError: ExtensionActivationError): Promise { - if (typeof activationError === 'string') { - this._extensionService._logOrShowMessage(Severity.Error, activationError); - } else { - this._handleMissingDependency(extensionId, activationError.dependency); - } - } + async $onExtensionActivationError(extensionId: ExtensionIdentifier, data: SerializedError, missingExtensionDependency: MissingExtensionDependency | null): Promise { + const error = new Error(); + error.name = data.name; + error.message = data.message; + error.stack = data.stack; - private async _handleMissingDependency(extensionId: ExtensionIdentifier, missingDependency: string): Promise { - const extension = await this._extensionService.getExtension(extensionId.value); - if (extension) { - const local = await this._extensionsWorkbenchService.queryLocal(); - const installedDependency = local.filter(i => areSameExtensions(i.identifier, { id: missingDependency }))[0]; - if (installedDependency) { - await this._handleMissingInstalledDependency(extension, installedDependency.local!); - } else { - await this._handleMissingNotInstalledDependency(extension, missingDependency); + this._extensionService._onDidActivateExtensionError(extensionId, error); + + if (missingExtensionDependency) { + const extension = await this._extensionService.getExtension(extensionId.value); + if (extension) { + const local = await this._extensionsWorkbenchService.queryLocal(); + const installedDependency = local.filter(i => areSameExtensions(i.identifier, { id: missingExtensionDependency.dependency }))[0]; + if (installedDependency) { + await this._handleMissingInstalledDependency(extension, installedDependency.local!); + return; + } else { + await this._handleMissingNotInstalledDependency(extension, missingExtensionDependency.dependency); + return; + } } } + + const isDev = !this._environmentService.isBuilt || this._environmentService.isExtensionDevelopment; + if (isDev) { + this._notificationService.error(error); + return; + } + + console.error(error.message); } private async _handleMissingInstalledDependency(extension: IExtensionDescription, missingInstalledDependency: ILocalExtension): Promise { diff --git a/src/vs/workbench/api/browser/mainThreadMessageService.ts b/src/vs/workbench/api/browser/mainThreadMessageService.ts index 995d6532..f61b2cd5 100644 --- a/src/vs/workbench/api/browser/mainThreadMessageService.ts +++ b/src/vs/workbench/api/browser/mainThreadMessageService.ts @@ -121,7 +121,7 @@ export class MainThreadMessageService implements MainThreadMessageServiceShape { cancelId = buttons.length - 1; } - const { choice } = await this._dialogService.show(severity, message, buttons, { cancelId, useCustom }); + const { choice } = await this._dialogService.show(severity, message, buttons, { cancelId, custom: useCustom }); return choice === commands.length ? undefined : commands[choice].handle; } } diff --git a/src/vs/workbench/api/browser/mainThreadNotebook.ts b/src/vs/workbench/api/browser/mainThreadNotebook.ts index f287b19b..acc83786 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebook.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebook.ts @@ -5,100 +5,16 @@ import { VSBuffer } from 'vs/base/common/buffer'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { diffMaps, diffSets } from 'vs/base/common/collections'; import { Emitter } from 'vs/base/common/event'; import { IRelativePattern } from 'vs/base/common/glob'; import { DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle'; -import { ResourceMap } from 'vs/base/common/map'; -import { Schemas } from 'vs/base/common/network'; -import { isEqual } from 'vs/base/common/resources'; -import { URI, UriComponents } from 'vs/base/common/uri'; -import { EditorActivation, EditorOverride } from 'vs/platform/editor/common/editor'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { ILogService } from 'vs/platform/log/common/log'; -import { BoundModelReferenceCollection } from 'vs/workbench/api/browser/mainThreadDocuments'; +import { URI } from 'vs/base/common/uri'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; -import { getNotebookEditorFromEditorPane, IActiveNotebookEditor, INotebookEditor, NotebookEditorOptions } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; -import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/browser/notebookEditorInput'; -import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/notebookEditorService'; -import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; -import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { INotebookCellStatusBarService } from 'vs/workbench/contrib/notebook/common/notebookCellStatusBarService'; -import { ICellEditOperation, ICellRange, IImmediateCellEditOperation, IMainCellDto, INotebookDecorationRenderOptions, INotebookDocumentFilter, INotebookExclusiveDocumentFilter, INotebookKernel, NotebookCellsChangeType, NotebookDataDto, TransientMetadata, TransientOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { INotebookEditorModelResolverService } from 'vs/workbench/contrib/notebook/common/notebookEditorModelResolverService'; -import { IMainNotebookController, INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; -import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; -import { ExtHostContext, ExtHostNotebookShape, IExtHostContext, INotebookCellStatusBarEntryDto, INotebookDocumentsAndEditorsDelta, INotebookDocumentShowOptions, INotebookEditorAddData, INotebookModelAddedData, MainContext, MainThreadNotebookShape, NotebookEditorRevealType, NotebookExtensionDescription } from '../common/extHost.protocol'; - -class NotebookAndEditorState { - static compute(before: NotebookAndEditorState | undefined, after: NotebookAndEditorState): INotebookDocumentsAndEditorsDelta { - if (!before) { - return { - addedDocuments: [...after.documents].map(NotebookAndEditorState._asModelAddData), - addedEditors: [...after.textEditors.values()].map(NotebookAndEditorState._asEditorAddData), - visibleEditors: [...after.visibleEditors].map(editor => editor[0]) - }; - } - const documentDelta = diffSets(before.documents, after.documents); - const editorDelta = diffMaps(before.textEditors, after.textEditors); - const addedAPIEditors = editorDelta.added.map(NotebookAndEditorState._asEditorAddData); - - const removedAPIEditors = editorDelta.removed.map(removed => removed.getId()); - const newActiveEditor = before.activeEditor !== after.activeEditor ? after.activeEditor : undefined; - const visibleEditorDelta = diffMaps(before.visibleEditors, after.visibleEditors); - - return { - addedDocuments: documentDelta.added.map(NotebookAndEditorState._asModelAddData), - removedDocuments: documentDelta.removed.map(e => e.uri), - addedEditors: addedAPIEditors, - removedEditors: removedAPIEditors, - newActiveEditor: newActiveEditor, - visibleEditors: visibleEditorDelta.added.length === 0 && visibleEditorDelta.removed.length === 0 - ? undefined - : [...after.visibleEditors].map(editor => editor[0]) - }; - } - - constructor( - readonly documents: Set, - readonly textEditors: Map, - readonly activeEditor: string | null | undefined, - readonly visibleEditors: Map - ) { - // - } - - private static _asModelAddData(e: NotebookTextModel): INotebookModelAddedData { - return { - viewType: e.viewType, - uri: e.uri, - metadata: e.metadata, - versionId: e.versionId, - cells: e.cells.map(cell => ({ - handle: cell.handle, - uri: cell.uri, - source: cell.textBuffer.getLinesContent(), - eol: cell.textBuffer.getEOL(), - language: cell.language, - cellKind: cell.cellKind, - outputs: cell.outputs, - metadata: cell.metadata - })) - }; - } - - private static _asEditorAddData(add: IActiveNotebookEditor): INotebookEditorAddData { - return { - id: add.getId(), - documentUri: add.viewModel.uri, - selections: add.getSelections(), - visibleRanges: add.visibleRanges, - viewColumn: undefined - }; - } -} +import { NotebookSelector } from 'vs/workbench/contrib/notebook/common/notebookSelector'; +import { INotebookCellStatusBarItemProvider, INotebookExclusiveDocumentFilter, NotebookDataDto, TransientCellMetadata, TransientDocumentMetadata, TransientOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookContentProvider, INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; +import { ExtHostContext, ExtHostNotebookShape, IExtHostContext, MainContext, MainThreadNotebookShape, NotebookExtensionDescription } from '../common/extHost.protocol'; @extHostNamedCustomer(MainContext.MainThreadNotebook) export class MainThreadNotebooks implements MainThreadNotebookShape { @@ -106,311 +22,42 @@ export class MainThreadNotebooks implements MainThreadNotebookShape { private readonly _disposables = new DisposableStore(); private readonly _proxy: ExtHostNotebookShape; - private readonly _notebookProviders = new Map(); + private readonly _notebookProviders = new Map(); private readonly _notebookSerializer = new Map(); - private readonly _notebookKernelProviders = new Map, provider: IDisposable }>(); - private readonly _editorEventListenersMapping = new Map(); - private readonly _documentEventListenersMapping = new ResourceMap(); - private readonly _cellStatusBarEntries = new Map(); - private readonly _modelReferenceCollection: BoundModelReferenceCollection; - - private _currentState?: NotebookAndEditorState; + private readonly _notebookCellStatusBarRegistrations = new Map(); constructor( extHostContext: IExtHostContext, - @IInstantiationService private readonly _instantiationService: IInstantiationService, - @IWorkingCopyService private readonly _workingCopyService: IWorkingCopyService, @INotebookService private readonly _notebookService: INotebookService, - @INotebookEditorService private readonly _notebookEditorService: INotebookEditorService, - @IEditorService private readonly _editorService: IEditorService, - @ILogService private readonly _logService: ILogService, @INotebookCellStatusBarService private readonly _cellStatusBarService: INotebookCellStatusBarService, - @INotebookEditorModelResolverService private readonly _notebookEditorModelResolverService: INotebookEditorModelResolverService, - @IUriIdentityService private readonly _uriIdentityService: IUriIdentityService ) { this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostNotebook); - this._modelReferenceCollection = new BoundModelReferenceCollection(this._uriIdentityService.extUri); - this._registerListeners(); } dispose(): void { this._disposables.dispose(); - - this._modelReferenceCollection.dispose(); - // remove all notebook providers for (const item of this._notebookProviders.values()) { item.disposable.dispose(); } - - // remove all kernel providers - for (const item of this._notebookKernelProviders.values()) { - item.emitter.dispose(); - item.provider.dispose(); - } dispose(this._notebookSerializer.values()); - dispose(this._editorEventListenersMapping.values()); - dispose(this._documentEventListenersMapping.values()); - dispose(this._cellStatusBarEntries.values()); - } - - async $tryApplyEdits(_viewType: string, resource: UriComponents, modelVersionId: number, cellEdits: ICellEditOperation[]): Promise { - const textModel = this._notebookService.getNotebookTextModel(URI.from(resource)); - if (!textModel) { - return false; - } - if (textModel.versionId !== modelVersionId) { - return false; - } - return textModel.applyEdits(cellEdits, true, undefined, () => undefined, undefined); - } - - async $applyEdits(resource: UriComponents, cellEdits: IImmediateCellEditOperation[], computeUndoRedo = true): Promise { - const textModel = this._notebookService.getNotebookTextModel(URI.from(resource)); - if (!textModel) { - throw new Error(`Can't apply edits to unknown notebook model: ${resource}`); - } - - textModel.applyEdits(cellEdits, true, undefined, () => undefined, undefined, computeUndoRedo); - } - - private _registerListeners(): void { - - // forward changes to dirty state - // todo@rebornix todo@mjbvz this seem way too complicated... is there an easy way to - // the actual resource from a working copy? - this._disposables.add(this._workingCopyService.onDidChangeDirty(e => { - if (e.resource.scheme !== Schemas.vscodeNotebook) { - return; - } - for (const notebook of this._notebookService.getNotebookTextModels()) { - if (isEqual(notebook.uri.with({ scheme: Schemas.vscodeNotebook }), e.resource)) { - this._proxy.$acceptDirtyStateChanged(notebook.uri, e.isDirty()); - break; - } - } - })); - - - this._disposables.add(this._editorService.onDidActiveEditorChange(e => { - this._updateState(); - })); - - this._disposables.add(this._editorService.onDidVisibleEditorsChange(e => { - if (this._notebookProviders.size > 0) { // TODO@rebornix propably wrong, what about providers from another host - if (!this._currentState) { - // no current state means we didn't even create editors in ext host yet. - return; - } - - // we can't simply update visibleEditors as we need to check if we should create editors first. - this._updateState(); - } - })); - - const handleNotebookEditorAdded = (editor: INotebookEditor) => { - if (this._editorEventListenersMapping.has(editor.getId())) { - //todo@jrieken a bug when this happens? - return; - } - const disposableStore = new DisposableStore(); - disposableStore.add(editor.onDidChangeVisibleRanges(() => { - this._proxy.$acceptEditorPropertiesChanged(editor.getId(), { visibleRanges: { ranges: editor.visibleRanges } }); - })); - - disposableStore.add(editor.onDidChangeSelection(() => { - this._proxy.$acceptEditorPropertiesChanged(editor.getId(), { selections: { selections: editor.getSelections() } }); - })); - - disposableStore.add(editor.onDidChangeKernel(() => { - if (!editor.hasModel()) { - return; - } - this._proxy.$acceptNotebookActiveKernelChange({ - uri: editor.viewModel.uri, - providerHandle: editor.activeKernel?.providerHandle, - kernelFriendlyId: editor.activeKernel?.friendlyId - }); - })); - - disposableStore.add(editor.onDidChangeModel(() => this._updateState())); - disposableStore.add(editor.onDidFocusEditorWidget(() => this._updateState(editor))); - - this._editorEventListenersMapping.set(editor.getId(), disposableStore); - - const activeNotebookEditor = getNotebookEditorFromEditorPane(this._editorService.activeEditorPane); - this._updateState(activeNotebookEditor); - }; - - this._notebookEditorService.listNotebookEditors().forEach(handleNotebookEditorAdded); - this._disposables.add(this._notebookEditorService.onDidAddNotebookEditor(handleNotebookEditorAdded)); - - this._disposables.add(this._notebookEditorService.onDidRemoveNotebookEditor(editor => { - this._editorEventListenersMapping.get(editor.getId())?.dispose(); - this._editorEventListenersMapping.delete(editor.getId()); - this._updateState(); - })); - - - const cellToDto = (cell: NotebookCellTextModel): IMainCellDto => { - return { - handle: cell.handle, - uri: cell.uri, - source: cell.textBuffer.getLinesContent(), - eol: cell.textBuffer.getEOL(), - language: cell.language, - cellKind: cell.cellKind, - outputs: cell.outputs, - metadata: cell.metadata - }; - }; - - - const handleNotebookDocumentAdded = (textModel: NotebookTextModel) => { - if (this._documentEventListenersMapping.has(textModel.uri)) { - //todo@jrieken a bug when this happens? - return; - } - const disposableStore = new DisposableStore(); - disposableStore.add(textModel!.onDidChangeContent(event => { - const dto = event.rawEvents.map(e => { - const data = - e.kind === NotebookCellsChangeType.ModelChange || e.kind === NotebookCellsChangeType.Initialize - ? { - kind: e.kind, - versionId: event.versionId, - changes: e.changes.map(diff => [diff[0], diff[1], diff[2].map(cell => cellToDto(cell as NotebookCellTextModel))] as [number, number, IMainCellDto[]]) - } - : ( - e.kind === NotebookCellsChangeType.Move - ? { - kind: e.kind, - index: e.index, - length: e.length, - newIdx: e.newIdx, - versionId: event.versionId, - cells: e.cells.map(cell => cellToDto(cell as NotebookCellTextModel)) - } - : e - ); - - return data; - }); - - /** - * TODO@rebornix, @jrieken - * When a document is modified, it will trigger onDidChangeContent events. - * The first event listener is this one, which doesn't know if the text model is dirty or not. It can ask `workingCopyService` but get the wrong result - * The second event listener is `NotebookEditorModel`, which will then set `isDirty` to `true`. - * Since `e.transient` decides if the model should be dirty or not, we will use the same logic here. - */ - const hasNonTransientEvent = event.rawEvents.find(e => !e.transient); - this._proxy.$acceptModelChanged(textModel.uri, { - rawEvents: dto, - versionId: event.versionId - }, !!hasNonTransientEvent); - - const hasDocumentMetadataChangeEvent = event.rawEvents.find(e => e.kind === NotebookCellsChangeType.ChangeDocumentMetadata); - if (!!hasDocumentMetadataChangeEvent) { - this._proxy.$acceptDocumentPropertiesChanged(textModel.uri, { metadata: textModel.metadata }); - } - })); - this._documentEventListenersMapping.set(textModel!.uri, disposableStore); - }; - - this._notebookService.listNotebookDocuments().forEach(handleNotebookDocumentAdded); - this._disposables.add(this._notebookService.onDidAddNotebookDocument(document => { - handleNotebookDocumentAdded(document); - this._updateState(); - })); - - this._disposables.add(this._notebookService.onDidRemoveNotebookDocument(uri => { - this._documentEventListenersMapping.get(uri)?.dispose(); - this._documentEventListenersMapping.delete(uri); - this._updateState(); - })); - - this._disposables.add(this._notebookService.onDidChangeNotebookActiveKernel(e => { - this._proxy.$acceptNotebookActiveKernelChange(e); - })); - - this._disposables.add(this._notebookEditorModelResolverService.onDidSaveNotebook(e => { - this._proxy.$acceptModelSaved(e); - })); - - const notebookEditor = getNotebookEditorFromEditorPane(this._editorService.activeEditorPane); - this._updateState(notebookEditor); - } - - private _updateState(focusedNotebookEditor?: INotebookEditor): void { - - const activeNotebookEditor = getNotebookEditorFromEditorPane(this._editorService.activeEditorPane); - let activeEditor = activeNotebookEditor?.hasModel() ? activeNotebookEditor.getId() : null; - - const editors = new Map(); - const visibleEditorsMap = new Map(); - - for (const editor of this._notebookEditorService.listNotebookEditors()) { - if (editor.hasModel()) { - editors.set(editor.getId(), editor); - } - } - - this._editorService.visibleEditorPanes.forEach(editorPane => { - const notebookEditor = getNotebookEditorFromEditorPane(editorPane); - if (notebookEditor?.hasModel() && editors.has(notebookEditor.getId())) { - visibleEditorsMap.set(notebookEditor.getId(), notebookEditor); - } - }); - - if (!activeEditor && focusedNotebookEditor?.textModel) { - activeEditor = focusedNotebookEditor.getId(); - } - - const newState = new NotebookAndEditorState(new Set(this._notebookService.listNotebookDocuments()), editors, activeEditor, visibleEditorsMap); - const delta = NotebookAndEditorState.compute(this._currentState, newState); - - this._currentState = newState; - if (!this._isDeltaEmpty(delta)) { - return this._proxy.$acceptDocumentAndEditorsDelta(delta); - } - } - - private _isDeltaEmpty(delta: INotebookDocumentsAndEditorsDelta): boolean { - if (delta.addedDocuments !== undefined && delta.addedDocuments.length > 0) { - return false; - } - if (delta.removedDocuments !== undefined && delta.removedDocuments.length > 0) { - return false; - } - if (delta.addedEditors !== undefined && delta.addedEditors.length > 0) { - return false; - } - if (delta.removedEditors !== undefined && delta.removedEditors.length > 0) { - return false; - } - if (delta.visibleEditors !== undefined && delta.visibleEditors.length > 0) { - return false; - } - if (delta.newActiveEditor !== undefined) { - return false; - } - return true; } async $registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string, options: { transientOutputs: boolean; - transientMetadata: TransientMetadata; + transientCellMetadata: TransientCellMetadata; + transientDocumentMetadata: TransientDocumentMetadata; viewOptions?: { displayName: string; filenamePattern: (string | IRelativePattern | INotebookExclusiveDocumentFilter)[]; exclusive: boolean; }; }): Promise { - let contentOptions = { transientOutputs: options.transientOutputs, transientMetadata: options.transientMetadata }; + let contentOptions = { transientOutputs: options.transientOutputs, transientCellMetadata: options.transientCellMetadata, transientDocumentMetadata: options.transientDocumentMetadata }; - const controller: IMainNotebookController = { + const controller: INotebookContentProvider = { get options() { return contentOptions; }, set options(newOptions) { - contentOptions.transientMetadata = newOptions.transientMetadata; + contentOptions.transientCellMetadata = newOptions.transientCellMetadata; + contentOptions.transientDocumentMetadata = newOptions.transientDocumentMetadata; contentOptions.transientOutputs = newOptions.transientOutputs; }, viewOptions: options.viewOptions, @@ -421,12 +68,6 @@ export class MainThreadNotebooks implements MainThreadNotebookShape { transientOptions: contentOptions }; }, - resolveNotebookEditor: async (viewType: string, uri: URI, editorId: string) => { - await this._proxy.$resolveNotebookEditor(viewType, uri, editorId); - }, - onDidReceiveMessage: (editorId: string, rendererType: string | undefined, message: unknown) => { - this._proxy.$onDidReceiveMessage(editorId, rendererType, message); - }, save: async (uri: URI, token: CancellationToken) => { return this._proxy.$saveNotebook(viewType, uri, token); }, @@ -442,7 +83,7 @@ export class MainThreadNotebooks implements MainThreadNotebookShape { this._notebookProviders.set(viewType, { controller, disposable }); } - async $updateNotebookProviderOptions(viewType: string, options?: { transientOutputs: boolean; transientMetadata: TransientMetadata; }): Promise { + async $updateNotebookProviderOptions(viewType: string, options?: { transientOutputs: boolean; transientCellMetadata: TransientCellMetadata; transientDocumentMetadata: TransientDocumentMetadata; }): Promise { const provider = this._notebookProviders.get(viewType); if (provider && options) { @@ -467,10 +108,10 @@ export class MainThreadNotebooks implements MainThreadNotebookShape { const registration = this._notebookService.registerNotebookSerializer(viewType, extension, { options, dataToNotebook: (data: VSBuffer): Promise => { - return this._proxy.$dataToNotebook(handle, data); + return this._proxy.$dataToNotebook(handle, data, CancellationToken.None); }, notebookToData: (data: NotebookDataDto): Promise => { - return this._proxy.$notebookToData(handle, data); + return this._proxy.$notebookToData(handle, data, CancellationToken.None); } }); this._notebookSerializer.set(handle, registration); @@ -481,174 +122,51 @@ export class MainThreadNotebooks implements MainThreadNotebookShape { this._notebookSerializer.delete(handle); } - async $registerNotebookKernelProvider(extension: NotebookExtensionDescription, handle: number, documentFilter: INotebookDocumentFilter): Promise { - const emitter = new Emitter(); + $emitCellStatusBarEvent(eventHandle: number): void { + const emitter = this._notebookCellStatusBarRegistrations.get(eventHandle); + if (emitter instanceof Emitter) { + emitter.fire(undefined); + } + } + + async $registerNotebookCellStatusBarItemProvider(handle: number, eventHandle: number | undefined, selector: NotebookSelector): Promise { const that = this; - - const provider = this._notebookService.registerNotebookKernelProvider({ - providerExtensionId: extension.id.value, - providerDescription: extension.description, - onDidChangeKernels: emitter.event, - selector: documentFilter, - provideKernels: async (uri: URI, token: CancellationToken): Promise => { - const result: INotebookKernel[] = []; - const kernelsDto = await that._proxy.$provideNotebookKernels(handle, uri, token); - for (const dto of kernelsDto) { - result.push({ - id: dto.id, - friendlyId: dto.friendlyId, - label: dto.label, - extension: dto.extension, - extensionLocation: URI.revive(dto.extensionLocation), - providerHandle: dto.providerHandle, - description: dto.description, - detail: dto.detail, - isPreferred: dto.isPreferred, - preloads: dto.preloads?.map(u => URI.revive(u)), - supportedLanguages: dto.supportedLanguages, - implementsInterrupt: dto.implementsInterrupt, - resolve: (uri: URI, editorId: string, token: CancellationToken): Promise => { - this._logService.debug('MainthreadNotebooks.resolveNotebookKernel', uri.path, dto.friendlyId); - return this._proxy.$resolveNotebookKernel(handle, editorId, uri, dto.friendlyId, token); - }, - executeNotebookCellsRequest: (uri: URI, cellRanges: ICellRange[]): Promise => { - this._logService.debug('MainthreadNotebooks.executeNotebookCell', uri.path, dto.friendlyId, cellRanges); - return this._proxy.$executeNotebookKernelFromProvider(handle, uri, dto.friendlyId, cellRanges); - }, - cancelNotebookCellExecution: (uri: URI, cellRanges: ICellRange[]): Promise => { - this._logService.debug('MainthreadNotebooks.cancelNotebookCellExecution', uri.path, dto.friendlyId, cellRanges); - return this._proxy.$cancelNotebookCellExecution(handle, uri, dto.friendlyId, cellRanges); + const provider: INotebookCellStatusBarItemProvider = { + async provideCellStatusBarItems(uri: URI, index: number, token: CancellationToken) { + const result = await that._proxy.$provideNotebookCellStatusBarItems(handle, uri, index, token); + return { + items: result?.items ?? [], + dispose() { + if (result) { + that._proxy.$releaseNotebookCellStatusBarItems(result.cacheId); } - }); - } - return result; - } - }); - this._notebookKernelProviders.set(handle, { extension, emitter, provider }); - return; - } - - async $unregisterNotebookKernelProvider(handle: number): Promise { - const entry = this._notebookKernelProviders.get(handle); - - if (entry) { - entry.emitter.dispose(); - entry.provider.dispose(); - this._notebookKernelProviders.delete(handle); - } - } - - $onNotebookKernelChange(handle: number, uriComponents: UriComponents): void { - const entry = this._notebookKernelProviders.get(handle); - - entry?.emitter.fire(uriComponents ? URI.revive(uriComponents) : undefined); - } - - async $postMessage(id: string, forRendererId: string | undefined, value: any): Promise { - const editor = this._notebookEditorService.getNotebookEditor(id); - if (!editor) { - return false; - } - editor.postMessage(forRendererId, value); - return true; - } - - async $tryRevealRange(id: string, range: ICellRange, revealType: NotebookEditorRevealType): Promise { - const editor = this._notebookEditorService.getNotebookEditor(id); - if (!editor) { - return; - } - const notebookEditor = editor as INotebookEditor; - if (!notebookEditor.hasModel()) { - return; - } - const viewModel = notebookEditor.viewModel; - const cell = viewModel.viewCells[range.start]; - if (!cell) { - return; - } - - switch (revealType) { - case NotebookEditorRevealType.Default: - return notebookEditor.revealCellRangeInView(range); - case NotebookEditorRevealType.InCenter: - return notebookEditor.revealInCenter(cell); - case NotebookEditorRevealType.InCenterIfOutsideViewport: - return notebookEditor.revealInCenterIfOutsideViewport(cell); - case NotebookEditorRevealType.AtTop: - return notebookEditor.revealInViewAtTop(cell); - } - } - - $registerNotebookEditorDecorationType(key: string, options: INotebookDecorationRenderOptions): void { - this._notebookEditorService.registerEditorDecorationType(key, options); - } - - $removeNotebookEditorDecorationType(key: string): void { - this._notebookEditorService.removeEditorDecorationType(key); - } - - $trySetDecorations(id: string, range: ICellRange, key: string): void { - const editor = this._notebookEditorService.getNotebookEditor(id); - if (editor) { - const notebookEditor = editor as INotebookEditor; - notebookEditor.setEditorDecorations(key, range); - } - } - - async $setStatusBarEntry(id: number, rawStatusBarEntry: INotebookCellStatusBarEntryDto): Promise { - const statusBarEntry = { - ...rawStatusBarEntry, - ...{ cellResource: URI.revive(rawStatusBarEntry.cellResource) } + } + }; + }, + selector: selector }; - const existingEntry = this._cellStatusBarEntries.get(id); - if (existingEntry) { - existingEntry.dispose(); + if (typeof eventHandle === 'number') { + const emitter = new Emitter(); + this._notebookCellStatusBarRegistrations.set(eventHandle, emitter); + provider.onDidChangeStatusBarItems = emitter.event; } - if (statusBarEntry.visible) { - this._cellStatusBarEntries.set(id, this._cellStatusBarService.addEntry(statusBarEntry)); - } + const disposable = this._cellStatusBarService.registerCellStatusBarItemProvider(provider); + this._notebookCellStatusBarRegistrations.set(handle, disposable); } - - async $tryOpenDocument(uriComponents: UriComponents): Promise { - const uri = URI.revive(uriComponents); - const ref = await this._notebookEditorModelResolverService.resolve(uri, undefined); - this._modelReferenceCollection.add(uri, ref); - return uri; - } - - async $trySaveDocument(uriComponents: UriComponents) { - const uri = URI.revive(uriComponents); - - const ref = await this._notebookEditorModelResolverService.resolve(uri); - const saveResult = await ref.object.save(); - ref.dispose(); - return saveResult; - } - - async $tryShowNotebookDocument(resource: UriComponents, viewType: string, options: INotebookDocumentShowOptions): Promise { - const editorOptions = new NotebookEditorOptions({ - cellSelections: options.selection && [options.selection], - preserveFocus: options.preserveFocus, - pinned: options.pinned, - // selection: options.selection, - // preserve pre 1.38 behaviour to not make group active when preserveFocus: true - // but make sure to restore the editor to fix https://github.com/microsoft/vscode/issues/79633 - activation: options.preserveFocus ? EditorActivation.RESTORE : undefined, - override: EditorOverride.DISABLED, - }); - - const input = NotebookEditorInput.create(this._instantiationService, URI.revive(resource), viewType); - const editorPane = await this._editorService.openEditor(input, editorOptions, options.position); - const notebookEditor = getNotebookEditorFromEditorPane(editorPane); - - if (notebookEditor) { - return notebookEditor.getId(); - } else { - throw new Error(`Notebook Editor creation failure for documenet ${resource}`); + async $unregisterNotebookCellStatusBarItemProvider(handle: number, eventHandle: number | undefined): Promise { + const unregisterThing = (handle: number) => { + const entry = this._notebookCellStatusBarRegistrations.get(handle); + if (entry) { + this._notebookCellStatusBarRegistrations.get(handle)?.dispose(); + this._notebookCellStatusBarRegistrations.delete(handle); + } + }; + unregisterThing(handle); + if (typeof eventHandle === 'number') { + unregisterThing(eventHandle); } } } diff --git a/src/vs/workbench/api/browser/mainThreadNotebookDocuments.ts b/src/vs/workbench/api/browser/mainThreadNotebookDocuments.ts new file mode 100644 index 00000000..d2bf4a13 --- /dev/null +++ b/src/vs/workbench/api/browser/mainThreadNotebookDocuments.ts @@ -0,0 +1,151 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { DisposableStore, dispose } from 'vs/base/common/lifecycle'; +import { ResourceMap } from 'vs/base/common/map'; +import { URI, UriComponents } from 'vs/base/common/uri'; +import { BoundModelReferenceCollection } from 'vs/workbench/api/browser/mainThreadDocuments'; +import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; +import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; +import { IImmediateCellEditOperation, IMainCellDto, NotebookCellsChangeType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookEditorModelResolverService } from 'vs/workbench/contrib/notebook/common/notebookEditorModelResolverService'; +import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; +import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; +import { ExtHostContext, ExtHostNotebookShape, IExtHostContext, MainThreadNotebookDocumentsShape } from '../common/extHost.protocol'; +import { MainThreadNotebooksAndEditors } from 'vs/workbench/api/browser/mainThreadNotebookDocumentsAndEditors'; +import { onUnexpectedError } from 'vs/base/common/errors'; + +export class MainThreadNotebookDocuments implements MainThreadNotebookDocumentsShape { + + private readonly _disposables = new DisposableStore(); + + private readonly _proxy: ExtHostNotebookShape; + private readonly _documentEventListenersMapping = new ResourceMap(); + private readonly _modelReferenceCollection: BoundModelReferenceCollection; + + constructor( + extHostContext: IExtHostContext, + notebooksAndEditors: MainThreadNotebooksAndEditors, + @INotebookService private readonly _notebookService: INotebookService, + @INotebookEditorModelResolverService private readonly _notebookEditorModelResolverService: INotebookEditorModelResolverService, + @IUriIdentityService private readonly _uriIdentityService: IUriIdentityService + ) { + this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostNotebook); + this._modelReferenceCollection = new BoundModelReferenceCollection(this._uriIdentityService.extUri); + + notebooksAndEditors.onDidAddNotebooks(this._handleNotebooksAdded, this, this._disposables); + notebooksAndEditors.onDidRemoveNotebooks(this._handleNotebooksRemoved, this, this._disposables); + + // forward dirty and save events + this._disposables.add(this._notebookEditorModelResolverService.onDidChangeDirty(model => this._proxy.$acceptDirtyStateChanged(model.resource, model.isDirty()))); + this._disposables.add(this._notebookEditorModelResolverService.onDidSaveNotebook(e => this._proxy.$acceptModelSaved(e))); + } + + dispose(): void { + this._disposables.dispose(); + this._modelReferenceCollection.dispose(); + dispose(this._documentEventListenersMapping.values()); + + } + + private _handleNotebooksAdded(notebooks: readonly NotebookTextModel[]): void { + + for (const textModel of notebooks) { + const disposableStore = new DisposableStore(); + disposableStore.add(textModel.onDidChangeContent(event => { + const dto = event.rawEvents.map(e => { + const data = + e.kind === NotebookCellsChangeType.ModelChange || e.kind === NotebookCellsChangeType.Initialize + ? { + kind: e.kind, + versionId: event.versionId, + changes: e.changes.map(diff => [diff[0], diff[1], diff[2].map(cell => MainThreadNotebookDocuments._cellToDto(cell as NotebookCellTextModel))] as [number, number, IMainCellDto[]]) + } + : ( + e.kind === NotebookCellsChangeType.Move + ? { + kind: e.kind, + index: e.index, + length: e.length, + newIdx: e.newIdx, + versionId: event.versionId, + cells: e.cells.map(cell => MainThreadNotebookDocuments._cellToDto(cell as NotebookCellTextModel)) + } + : e + ); + + return data; + }); + + // using the model resolver service to know if the model is dirty or not. + // assuming this is the first listener it can mean that at first the model + // is marked as dirty and that another event is fired + this._proxy.$acceptModelChanged( + textModel.uri, + { rawEvents: dto, versionId: event.versionId }, + this._notebookEditorModelResolverService.isDirty(textModel.uri) + ); + + const hasDocumentMetadataChangeEvent = event.rawEvents.find(e => e.kind === NotebookCellsChangeType.ChangeDocumentMetadata); + if (hasDocumentMetadataChangeEvent) { + this._proxy.$acceptDocumentPropertiesChanged(textModel.uri, { metadata: textModel.metadata }); + } + })); + + this._documentEventListenersMapping.set(textModel.uri, disposableStore); + } + } + + private _handleNotebooksRemoved(uris: URI[]): void { + for (const uri of uris) { + this._documentEventListenersMapping.get(uri)?.dispose(); + this._documentEventListenersMapping.delete(uri); + } + } + + private static _cellToDto(cell: NotebookCellTextModel): IMainCellDto { + return { + handle: cell.handle, + uri: cell.uri, + source: cell.textBuffer.getLinesContent(), + eol: cell.textBuffer.getEOL(), + language: cell.language, + cellKind: cell.cellKind, + outputs: cell.outputs, + metadata: cell.metadata + }; + } + + async $tryOpenDocument(uriComponents: UriComponents): Promise { + const uri = URI.revive(uriComponents); + const ref = await this._notebookEditorModelResolverService.resolve(uri, undefined); + this._modelReferenceCollection.add(uri, ref); + return uri; + } + + async $trySaveDocument(uriComponents: UriComponents) { + const uri = URI.revive(uriComponents); + + const ref = await this._notebookEditorModelResolverService.resolve(uri); + const saveResult = await ref.object.save(); + ref.dispose(); + return saveResult; + } + + async $applyEdits(resource: UriComponents, cellEdits: IImmediateCellEditOperation[], computeUndoRedo = true): Promise { + const textModel = this._notebookService.getNotebookTextModel(URI.from(resource)); + if (!textModel) { + throw new Error(`Can't apply edits to unknown notebook model: ${URI.revive(resource).toString()}`); + } + + try { + textModel.applyEdits(cellEdits, true, undefined, () => undefined, undefined, computeUndoRedo); + } catch (e) { + // Clearing outputs at the same time as the EH calling append/replaceOutputItems is an expected race, and it should be a no-op. + // And any other failure should not throw back to the extension. + onUnexpectedError(e); + } + } +} diff --git a/src/vs/workbench/api/browser/mainThreadNotebookDocumentsAndEditors.ts b/src/vs/workbench/api/browser/mainThreadNotebookDocumentsAndEditors.ts new file mode 100644 index 00000000..c0f0aff2 --- /dev/null +++ b/src/vs/workbench/api/browser/mainThreadNotebookDocumentsAndEditors.ts @@ -0,0 +1,254 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { diffMaps, diffSets } from 'vs/base/common/collections'; +import { Emitter, Event } from 'vs/base/common/event'; +import { combinedDisposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; +import { URI } from 'vs/base/common/uri'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { MainThreadNotebookDocuments } from 'vs/workbench/api/browser/mainThreadNotebookDocuments'; +import { MainThreadNotebookEditors } from 'vs/workbench/api/browser/mainThreadNotebookEditors'; +import { extHostCustomer } from 'vs/workbench/api/common/extHostCustomers'; +import { editorGroupToViewColumn } from 'vs/workbench/common/editor'; +import { getNotebookEditorFromEditorPane, IActiveNotebookEditor, INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/notebookEditorService'; +import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; +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'; +import { ExtHostContext, ExtHostNotebookShape, IExtHostContext, INotebookDocumentsAndEditorsDelta, INotebookEditorAddData, INotebookModelAddedData, MainContext } from '../common/extHost.protocol'; + +interface INotebookAndEditorDelta { + removedDocuments: URI[]; + addedDocuments: NotebookTextModel[]; + removedEditors: string[]; + addedEditors: IActiveNotebookEditor[]; + newActiveEditor?: string | null; + visibleEditors?: string[]; +} + +class NotebookAndEditorState { + static compute(before: NotebookAndEditorState | undefined, after: NotebookAndEditorState): INotebookAndEditorDelta { + if (!before) { + return { + addedDocuments: [...after.documents], + removedDocuments: [], + addedEditors: [...after.textEditors.values()], + removedEditors: [], + visibleEditors: [...after.visibleEditors].map(editor => editor[0]) + }; + } + const documentDelta = diffSets(before.documents, after.documents); + const editorDelta = diffMaps(before.textEditors, after.textEditors); + + const newActiveEditor = before.activeEditor !== after.activeEditor ? after.activeEditor : undefined; + const visibleEditorDelta = diffMaps(before.visibleEditors, after.visibleEditors); + + return { + addedDocuments: documentDelta.added, + removedDocuments: documentDelta.removed.map(e => e.uri), + addedEditors: editorDelta.added, + removedEditors: editorDelta.removed.map(removed => removed.getId()), + newActiveEditor: newActiveEditor, + visibleEditors: visibleEditorDelta.added.length === 0 && visibleEditorDelta.removed.length === 0 + ? undefined + : [...after.visibleEditors].map(editor => editor[0]) + }; + } + + constructor( + readonly documents: Set, + readonly textEditors: Map, + readonly activeEditor: string | null | undefined, + readonly visibleEditors: Map + ) { + // + } +} + +@extHostCustomer +export class MainThreadNotebooksAndEditors { + + private readonly _onDidAddNotebooks = new Emitter(); + private readonly _onDidRemoveNotebooks = new Emitter(); + private readonly _onDidAddEditors = new Emitter(); + private readonly _onDidRemoveEditors = new Emitter(); + + readonly onDidAddNotebooks: Event = this._onDidAddNotebooks.event; + readonly onDidRemoveNotebooks: Event = this._onDidRemoveNotebooks.event; + readonly onDidAddEditors: Event = this._onDidAddEditors.event; + readonly onDidRemoveEditors: Event = this._onDidRemoveEditors.event; + + private readonly _proxy: Pick; + private readonly _disposables = new DisposableStore(); + + private readonly _editorListeners = new Map(); + + private _currentState?: NotebookAndEditorState; + + private readonly _mainThreadNotebooks: MainThreadNotebookDocuments; + private readonly _mainThreadEditors: MainThreadNotebookEditors; + + constructor( + extHostContext: IExtHostContext, + @IInstantiationService instantiationService: IInstantiationService, + @INotebookService private readonly _notebookService: INotebookService, + @INotebookEditorService private readonly _notebookEditorService: INotebookEditorService, + @IEditorService private readonly _editorService: IEditorService, + @IEditorGroupsService private readonly _editorGroupService: IEditorGroupsService, + ) { + this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostNotebook); + + this._mainThreadNotebooks = instantiationService.createInstance(MainThreadNotebookDocuments, extHostContext, this); + this._mainThreadEditors = instantiationService.createInstance(MainThreadNotebookEditors, extHostContext, this); + + extHostContext.set(MainContext.MainThreadNotebookDocuments, this._mainThreadNotebooks); + extHostContext.set(MainContext.MainThreadNotebookEditors, this._mainThreadEditors); + + this._notebookService.onDidCreateNotebookDocument(() => this._updateState(), this, this._disposables); + this._notebookService.onDidRemoveNotebookDocument(() => this._updateState(), this, this._disposables); + this._editorService.onDidActiveEditorChange(() => this._updateState(), this, this._disposables); + this._editorService.onDidVisibleEditorsChange(() => this._updateState(), this, this._disposables); + this._notebookEditorService.onDidAddNotebookEditor(this._handleEditorAdd, this, this._disposables); + this._notebookEditorService.onDidRemoveNotebookEditor(this._handleEditorRemove, this, this._disposables); + this._updateState(); + } + + dispose() { + this._mainThreadNotebooks.dispose(); + this._mainThreadEditors.dispose(); + this._onDidAddEditors.dispose(); + this._onDidRemoveEditors.dispose(); + this._onDidAddNotebooks.dispose(); + this._onDidRemoveNotebooks.dispose(); + this._disposables.dispose(); + } + + private _handleEditorAdd(editor: INotebookEditor): void { + this._editorListeners.set(editor.getId(), combinedDisposable( + editor.onDidChangeModel(() => this._updateState()), + editor.onDidFocusEditorWidget(() => this._updateState(editor)), + )); + this._updateState(); + } + + private _handleEditorRemove(editor: INotebookEditor): void { + this._editorListeners.get(editor.getId())?.dispose(); + this._editorListeners.delete(editor.getId()); + this._updateState(); + } + + private _updateState(focusedEditor?: INotebookEditor): void { + + const editors = new Map(); + const visibleEditorsMap = new Map(); + + for (const editor of this._notebookEditorService.listNotebookEditors()) { + if (editor.hasModel()) { + editors.set(editor.getId(), editor); + } + } + + const activeNotebookEditor = getNotebookEditorFromEditorPane(this._editorService.activeEditorPane); + let activeEditor: string | null = null; + if (activeNotebookEditor) { + activeEditor = activeNotebookEditor.getId(); + } else if (focusedEditor?.textModel) { + activeEditor = focusedEditor.getId(); + } + if (activeEditor && !editors.has(activeEditor)) { + activeEditor = null; + } + + for (const editorPane of this._editorService.visibleEditorPanes) { + const notebookEditor = getNotebookEditorFromEditorPane(editorPane); + if (notebookEditor?.hasModel() && editors.has(notebookEditor.getId())) { + visibleEditorsMap.set(notebookEditor.getId(), notebookEditor); + } + } + + const newState = new NotebookAndEditorState(new Set(this._notebookService.listNotebookDocuments()), editors, activeEditor, visibleEditorsMap); + this._onDelta(NotebookAndEditorState.compute(this._currentState, newState)); + this._currentState = newState; + } + + private _onDelta(delta: INotebookAndEditorDelta): void { + if (MainThreadNotebooksAndEditors._isDeltaEmpty(delta)) { + return; + } + + const dto: INotebookDocumentsAndEditorsDelta = { + removedDocuments: delta.removedDocuments, + removedEditors: delta.removedEditors, + newActiveEditor: delta.newActiveEditor, + visibleEditors: delta.visibleEditors, + addedDocuments: delta.addedDocuments.map(MainThreadNotebooksAndEditors._asModelAddData), + addedEditors: delta.addedEditors.map(this._asEditorAddData, this), + }; + + // send to extension FIRST + this._proxy.$acceptDocumentAndEditorsDelta(dto); + + // handle internally + this._onDidRemoveEditors.fire(delta.removedEditors); + this._onDidRemoveNotebooks.fire(delta.removedDocuments); + this._onDidAddNotebooks.fire(delta.addedDocuments); + this._onDidAddEditors.fire(delta.addedEditors); + } + + private static _isDeltaEmpty(delta: INotebookAndEditorDelta): boolean { + if (delta.addedDocuments !== undefined && delta.addedDocuments.length > 0) { + return false; + } + if (delta.removedDocuments !== undefined && delta.removedDocuments.length > 0) { + return false; + } + if (delta.addedEditors !== undefined && delta.addedEditors.length > 0) { + return false; + } + if (delta.removedEditors !== undefined && delta.removedEditors.length > 0) { + return false; + } + if (delta.visibleEditors !== undefined && delta.visibleEditors.length > 0) { + return false; + } + if (delta.newActiveEditor !== undefined) { + return false; + } + return true; + } + + private static _asModelAddData(e: NotebookTextModel): INotebookModelAddedData { + return { + viewType: e.viewType, + uri: e.uri, + metadata: e.metadata, + versionId: e.versionId, + cells: e.cells.map(cell => ({ + handle: cell.handle, + uri: cell.uri, + source: cell.textBuffer.getLinesContent(), + eol: cell.textBuffer.getEOL(), + language: cell.language, + cellKind: cell.cellKind, + outputs: cell.outputs, + metadata: cell.metadata + })) + }; + } + + private _asEditorAddData(add: IActiveNotebookEditor): INotebookEditorAddData { + + const pane = this._editorService.visibleEditorPanes.find(pane => getNotebookEditorFromEditorPane(pane) === add); + + return { + id: add.getId(), + documentUri: add.viewModel.uri, + selections: add.getSelections(), + visibleRanges: add.visibleRanges, + viewColumn: pane && editorGroupToViewColumn(this._editorGroupService, pane.group) + }; + } +} diff --git a/src/vs/workbench/api/browser/mainThreadNotebookEditors.ts b/src/vs/workbench/api/browser/mainThreadNotebookEditors.ts new file mode 100644 index 00000000..a8ca5a5a --- /dev/null +++ b/src/vs/workbench/api/browser/mainThreadNotebookEditors.ts @@ -0,0 +1,191 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { DisposableStore, dispose } from 'vs/base/common/lifecycle'; +import { getNotebookEditorFromEditorPane, INotebookEditor, NotebookEditorOptions } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/notebookEditorService'; +import { ExtHostContext, ExtHostNotebookShape, IExtHostContext, INotebookDocumentShowOptions, INotebookEditorViewColumnInfo, MainThreadNotebookEditorsShape, NotebookEditorRevealType } from '../common/extHost.protocol'; +import { MainThreadNotebooksAndEditors } from 'vs/workbench/api/browser/mainThreadNotebookDocumentsAndEditors'; +import { ICellEditOperation, INotebookDecorationRenderOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; +import { ILogService } from 'vs/platform/log/common/log'; +import { URI, UriComponents } from 'vs/base/common/uri'; +import { EditorActivation, EditorOverride } from 'vs/platform/editor/common/editor'; +import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { editorGroupToViewColumn } from 'vs/workbench/common/editor'; +import { equals } from 'vs/base/common/objects'; + +class MainThreadNotebook { + + constructor( + readonly editor: INotebookEditor, + readonly disposables: DisposableStore + ) { } + + dispose() { + this.disposables.dispose(); + } +} + +export class MainThreadNotebookEditors implements MainThreadNotebookEditorsShape { + + private readonly _disposables = new DisposableStore(); + + private readonly _proxy: ExtHostNotebookShape; + private readonly _mainThreadEditors = new Map(); + + private _currentViewColumnInfo?: INotebookEditorViewColumnInfo; + + constructor( + extHostContext: IExtHostContext, + notebooksAndEditors: MainThreadNotebooksAndEditors, + @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IEditorService private readonly _editorService: IEditorService, + @ILogService private readonly _logService: ILogService, + @INotebookEditorService private readonly _notebookEditorService: INotebookEditorService, + @IEditorGroupsService private readonly _editorGroupService: IEditorGroupsService + ) { + this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostNotebook); + + notebooksAndEditors.onDidAddEditors(this._handleEditorsAdded, this, this._disposables); + notebooksAndEditors.onDidRemoveEditors(this._handleEditorsRemoved, this, this._disposables); + + this._editorService.onDidActiveEditorChange(() => this._updateEditorViewColumns(), this, this._disposables); + this._editorGroupService.onDidRemoveGroup(() => this._updateEditorViewColumns(), this, this._disposables); + this._editorGroupService.onDidMoveGroup(() => this._updateEditorViewColumns(), this, this._disposables); + } + + dispose(): void { + this._disposables.dispose(); + dispose(this._mainThreadEditors.values()); + } + + private _handleEditorsAdded(editors: readonly INotebookEditor[]): void { + + for (const editor of editors) { + + const editorDisposables = new DisposableStore(); + editorDisposables.add(editor.onDidChangeVisibleRanges(() => { + this._proxy.$acceptEditorPropertiesChanged(editor.getId(), { visibleRanges: { ranges: editor.visibleRanges } }); + })); + + editorDisposables.add(editor.onDidChangeSelection(() => { + this._proxy.$acceptEditorPropertiesChanged(editor.getId(), { selections: { selections: editor.getSelections() } }); + })); + + const wrapper = new MainThreadNotebook(editor, editorDisposables); + this._mainThreadEditors.set(editor.getId(), wrapper); + } + } + + private _handleEditorsRemoved(editorIds: readonly string[]): void { + for (const id of editorIds) { + this._mainThreadEditors.get(id)?.dispose(); + this._mainThreadEditors.delete(id); + } + } + + private _updateEditorViewColumns(): void { + const result: INotebookEditorViewColumnInfo = Object.create(null); + for (let editorPane of this._editorService.visibleEditorPanes) { + const candidate = getNotebookEditorFromEditorPane(editorPane); + if (candidate && this._mainThreadEditors.has(candidate.getId())) { + result[candidate.getId()] = editorGroupToViewColumn(this._editorGroupService, editorPane.group); + } + } + if (!equals(result, this._currentViewColumnInfo)) { + this._currentViewColumnInfo = result; + this._proxy.$acceptEditorViewColumns(result); + } + } + + async $tryApplyEdits(editorId: string, modelVersionId: number, cellEdits: ICellEditOperation[]): Promise { + const wrapper = this._mainThreadEditors.get(editorId); + if (!wrapper) { + return false; + } + const { editor } = wrapper; + if (!editor.textModel) { + this._logService.warn('Notebook editor has NO model', editorId); + return false; + } + if (editor.textModel.versionId !== modelVersionId) { + return false; + } + //todo@jrieken use proper selection logic! + return editor.textModel.applyEdits(cellEdits, true, undefined, () => undefined, undefined); + } + + + + async $tryShowNotebookDocument(resource: UriComponents, viewType: string, options: INotebookDocumentShowOptions): Promise { + const editorOptions = new NotebookEditorOptions({ + cellSelections: options.selections, + preserveFocus: options.preserveFocus, + pinned: options.pinned, + // selection: options.selection, + // preserve pre 1.38 behaviour to not make group active when preserveFocus: true + // but make sure to restore the editor to fix https://github.com/microsoft/vscode/issues/79633 + activation: options.preserveFocus ? EditorActivation.RESTORE : undefined, + override: EditorOverride.DISABLED, + }); + + const input = NotebookEditorInput.create(this._instantiationService, URI.revive(resource), viewType); + const editorPane = await this._editorService.openEditor(input, editorOptions, options.position); + const notebookEditor = getNotebookEditorFromEditorPane(editorPane); + + if (notebookEditor) { + return notebookEditor.getId(); + } else { + throw new Error(`Notebook Editor creation failure for documenet ${resource}`); + } + } + + async $tryRevealRange(id: string, range: ICellRange, revealType: NotebookEditorRevealType): Promise { + const editor = this._notebookEditorService.getNotebookEditor(id); + if (!editor) { + return; + } + const notebookEditor = editor as INotebookEditor; + if (!notebookEditor.hasModel()) { + return; + } + const viewModel = notebookEditor.viewModel; + const cell = viewModel.cellAt(range.start); + if (!cell) { + return; + } + + switch (revealType) { + case NotebookEditorRevealType.Default: + return notebookEditor.revealCellRangeInView(range); + case NotebookEditorRevealType.InCenter: + return notebookEditor.revealInCenter(cell); + case NotebookEditorRevealType.InCenterIfOutsideViewport: + return notebookEditor.revealInCenterIfOutsideViewport(cell); + case NotebookEditorRevealType.AtTop: + return notebookEditor.revealInViewAtTop(cell); + } + } + + $registerNotebookEditorDecorationType(key: string, options: INotebookDecorationRenderOptions): void { + this._notebookEditorService.registerEditorDecorationType(key, options); + } + + $removeNotebookEditorDecorationType(key: string): void { + this._notebookEditorService.removeEditorDecorationType(key); + } + + $trySetDecorations(id: string, range: ICellRange, key: string): void { + const editor = this._notebookEditorService.getNotebookEditor(id); + if (editor) { + const notebookEditor = editor as INotebookEditor; + notebookEditor.setEditorDecorations(key, range); + } + } +} diff --git a/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts b/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts new file mode 100644 index 00000000..308e0964 --- /dev/null +++ b/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts @@ -0,0 +1,225 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { flatten, isNonEmptyArray } from 'vs/base/common/arrays'; +import { Emitter, Event } from 'vs/base/common/event'; +import { combinedDisposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; +import { URI, UriComponents } from 'vs/base/common/uri'; +import { IModeService } from 'vs/editor/common/services/modeService'; +import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; +import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/notebookEditorService'; +import { INotebookKernel, INotebookKernelChangeEvent } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { ExtHostContext, ExtHostNotebookKernelsShape, IExtHostContext, INotebookKernelDto2, MainContext, MainThreadNotebookKernelsShape } from '../common/extHost.protocol'; + +abstract class MainThreadKernel implements INotebookKernel { + + private readonly _onDidChange = new Emitter(); + private readonly preloads: { uri: URI, provides: string[] }[]; + readonly onDidChange: Event = this._onDidChange.event; + + readonly id: string; + readonly viewType: string; + readonly extension: ExtensionIdentifier; + + implementsInterrupt: boolean; + label: string; + description?: string; + detail?: string; + supportedLanguages: string[]; + implementsExecutionOrder: boolean; + localResourceRoot: URI; + + public get preloadUris() { + return this.preloads.map(p => p.uri); + } + + public get preloadProvides() { + return flatten(this.preloads.map(p => p.provides)); + } + + constructor(data: INotebookKernelDto2, private _modeService: IModeService) { + this.id = data.id; + this.viewType = data.viewType; + this.extension = data.extensionId; + + this.implementsInterrupt = data.supportsInterrupt ?? false; + this.label = data.label; + this.description = data.description; + this.detail = data.detail; + this.supportedLanguages = isNonEmptyArray(data.supportedLanguages) ? data.supportedLanguages : _modeService.getRegisteredModes(); + this.implementsExecutionOrder = data.hasExecutionOrder ?? false; + this.localResourceRoot = URI.revive(data.extensionLocation); + this.preloads = data.preloads?.map(u => ({ uri: URI.revive(u.uri), provides: u.provides })) ?? []; + } + + + update(data: Partial) { + + const event: INotebookKernelChangeEvent = Object.create(null); + if (data.label !== undefined) { + this.label = data.label; + event.label = true; + } + if (data.description !== undefined) { + this.description = data.description; + event.description = true; + } + if (data.detail !== undefined) { + this.detail = data.detail; + event.detail = true; + } + if (data.supportedLanguages !== undefined) { + this.supportedLanguages = isNonEmptyArray(data.supportedLanguages) ? data.supportedLanguages : this._modeService.getRegisteredModes(); + event.supportedLanguages = true; + } + if (data.hasExecutionOrder !== undefined) { + this.implementsExecutionOrder = data.hasExecutionOrder; + event.hasExecutionOrder = true; + } + this._onDidChange.fire(event); + } + + abstract executeNotebookCellsRequest(uri: URI, cellHandles: number[]): Promise; + abstract cancelNotebookCellExecution(uri: URI, cellHandles: number[]): Promise; +} + +@extHostNamedCustomer(MainContext.MainThreadNotebookKernels) +export class MainThreadNotebookKernels implements MainThreadNotebookKernelsShape { + + private readonly _editors = new Map(); + private readonly _disposables = new DisposableStore(); + + private readonly _kernels = new Map(); + private readonly _proxy: ExtHostNotebookKernelsShape; + + constructor( + extHostContext: IExtHostContext, + @IModeService private readonly _modeService: IModeService, + @INotebookKernelService private readonly _notebookKernelService: INotebookKernelService, + @INotebookEditorService notebookEditorService: INotebookEditorService + ) { + this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostNotebookKernels); + + notebookEditorService.listNotebookEditors().forEach(this._onEditorAdd, this); + notebookEditorService.onDidAddNotebookEditor(this._onEditorAdd, this, this._disposables); + notebookEditorService.onDidRemoveNotebookEditor(this._onEditorRemove, this, this._disposables); + } + + dispose(): void { + this._disposables.dispose(); + for (let [, registration] of this._kernels.values()) { + registration.dispose(); + } + } + + // --- kernel ipc + + private _onEditorAdd(editor: INotebookEditor) { + + const ipcListener = editor.onDidReceiveMessage(e => { + if (e.forRenderer) { + return; + } + if (!editor.hasModel()) { + return; + } + const { selected } = this._notebookKernelService.getMatchingKernel(editor.viewModel.notebookDocument); + if (!selected) { + return; + } + for (let [handle, candidate] of this._kernels) { + if (candidate[0] === selected) { + this._proxy.$acceptRendererMessage(handle, editor.getId(), e.message); + break; + } + } + }); + this._editors.set(editor, ipcListener); + } + + private _onEditorRemove(editor: INotebookEditor) { + this._editors.get(editor)?.dispose(); + this._editors.delete(editor); + } + + async $postMessage(handle: number, editorId: string | undefined, message: any): Promise { + const tuple = this._kernels.get(handle); + if (!tuple) { + throw new Error('kernel already disposed'); + } + const [kernel] = tuple; + let didSend = false; + for (const [editor] of this._editors) { + if (!editor.hasModel()) { + continue; + } + if (this._notebookKernelService.getMatchingKernel(editor.viewModel.notebookDocument).selected !== kernel) { + // different kernel + continue; + } + if (editorId === undefined) { + // all editors + editor.postMessage(undefined, message); + didSend = true; + } else if (editor.getId() === editorId) { + // selected editors + editor.postMessage(undefined, message); + didSend = true; + break; + } + } + return didSend; + } + + // --- kernel adding/updating/removal + + async $addKernel(handle: number, data: INotebookKernelDto2): Promise { + const that = this; + const kernel = new class extends MainThreadKernel { + async executeNotebookCellsRequest(uri: URI, handles: number[]): Promise { + await that._proxy.$executeCells(handle, uri, handles); + } + async cancelNotebookCellExecution(uri: URI, handles: number[]): Promise { + await that._proxy.$cancelCells(handle, uri, handles); + } + }(data, this._modeService); + const registration = this._notebookKernelService.registerKernel(kernel); + + const listener = this._notebookKernelService.onDidChangeNotebookKernelBinding(e => { + if (e.oldKernel === kernel.id) { + this._proxy.$acceptSelection(handle, e.notebook, false); + } else if (e.newKernel === kernel.id) { + this._proxy.$acceptSelection(handle, e.notebook, true); + } + }); + + this._kernels.set(handle, [kernel, combinedDisposable(listener, registration)]); + } + + $updateKernel(handle: number, data: Partial): void { + const tuple = this._kernels.get(handle); + if (tuple) { + tuple[0].update(data); + } + } + + $removeKernel(handle: number): void { + const tuple = this._kernels.get(handle); + if (tuple) { + tuple[1].dispose(); + this._kernels.delete(handle); + } + } + + $updateNotebookPriority(handle: number, notebook: UriComponents, value: number | undefined): void { + const tuple = this._kernels.get(handle); + if (tuple) { + this._notebookKernelService.updateKernelNotebookAffinity(tuple[0], URI.revive(notebook), value); + } + } +} diff --git a/src/vs/workbench/api/browser/mainThreadSCM.ts b/src/vs/workbench/api/browser/mainThreadSCM.ts index 0c23ea4a..791f1d0c 100644 --- a/src/vs/workbench/api/browser/mainThreadSCM.ts +++ b/src/vs/workbench/api/browser/mainThreadSCM.ts @@ -6,7 +6,7 @@ import { URI, UriComponents } from 'vs/base/common/uri'; import { Event, Emitter } from 'vs/base/common/event'; import { IDisposable, DisposableStore, combinedDisposable } from 'vs/base/common/lifecycle'; -import { ISCMService, ISCMRepository, ISCMProvider, ISCMResource, ISCMResourceGroup, ISCMResourceDecorations, IInputValidation, ISCMViewService } from 'vs/workbench/contrib/scm/common/scm'; +import { ISCMService, ISCMRepository, ISCMProvider, ISCMResource, ISCMResourceGroup, ISCMResourceDecorations, IInputValidation, ISCMViewService, InputValidationType } from 'vs/workbench/contrib/scm/common/scm'; import { ExtHostContext, MainThreadSCMShape, ExtHostSCMShape, SCMProviderFeatures, SCMRawResourceSplices, SCMGroupFeatures, MainContext, IExtHostContext } from '../common/extHost.protocol'; import { Command } from 'vs/editor/common/modes'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; @@ -424,6 +424,24 @@ export class MainThreadSCM implements MainThreadSCMShape { repository.input.visible = visible; } + $setInputBoxFocus(sourceControlHandle: number): void { + const repository = this._repositories.get(sourceControlHandle); + if (!repository) { + return; + } + + repository.input.setFocus(); + } + + $showValidationMessage(sourceControlHandle: number, message: string, type: InputValidationType) { + const repository = this._repositories.get(sourceControlHandle); + if (!repository) { + return; + } + + repository.input.showValidationMessage(message, type); + } + $setValidationProviderIsEnabled(sourceControlHandle: number, enabled: boolean): void { const repository = this._repositories.get(sourceControlHandle); diff --git a/src/vs/workbench/api/browser/mainThreadSearch.ts b/src/vs/workbench/api/browser/mainThreadSearch.ts index 9e9ef51c..ac7014a8 100644 --- a/src/vs/workbench/api/browser/mainThreadSearch.ts +++ b/src/vs/workbench/api/browser/mainThreadSearch.ts @@ -138,7 +138,7 @@ class RemoteSearchProvider implements ISearchResultProvider, IDisposable { return Promise.resolve(searchP).then((result: ISearchCompleteStats) => { this._searches.delete(search.id); - return { results: Array.from(search.matches.values()), stats: result.stats, limitHit: result.limitHit }; + return { results: Array.from(search.matches.values()), stats: result.stats, limitHit: result.limitHit, messages: result.messages }; }, err => { this._searches.delete(search.id); return Promise.reject(err); diff --git a/src/vs/workbench/api/browser/mainThreadStatusBar.ts b/src/vs/workbench/api/browser/mainThreadStatusBar.ts index 246982f9..3ba38718 100644 --- a/src/vs/workbench/api/browser/mainThreadStatusBar.ts +++ b/src/vs/workbench/api/browser/mainThreadStatusBar.ts @@ -10,12 +10,12 @@ import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { dispose } from 'vs/base/common/lifecycle'; import { Command } from 'vs/editor/common/modes'; import { IAccessibilityInformation } from 'vs/platform/accessibility/common/accessibility'; +import { getCodiconAriaLabel } from 'vs/base/common/codicons'; @extHostNamedCustomer(MainContext.MainThreadStatusBar) export class MainThreadStatusBar implements MainThreadStatusBarShape { private readonly entries: Map = new Map(); - private static readonly CODICON_REGEXP = /\$\((.*?)\)/g; constructor( _extHostContext: IExtHostContext, @@ -35,7 +35,7 @@ export class MainThreadStatusBar implements MainThreadStatusBarShape { ariaLabel = accessibilityInformation.label; role = accessibilityInformation.role; } else { - ariaLabel = text ? text.replace(MainThreadStatusBar.CODICON_REGEXP, (_match, codiconName) => codiconName) : ''; + ariaLabel = getCodiconAriaLabel(text); } const entry: IStatusbarEntry = { text, tooltip, command, color, backgroundColor, ariaLabel, role }; diff --git a/src/vs/workbench/api/browser/mainThreadTask.ts b/src/vs/workbench/api/browser/mainThreadTask.ts index 3cc31f08..c88061cf 100644 --- a/src/vs/workbench/api/browser/mainThreadTask.ts +++ b/src/vs/workbench/api/browser/mainThreadTask.ts @@ -422,7 +422,7 @@ export class MainThreadTask implements MainThreadTaskShape { if (execution.task?.execution && CustomExecutionDTO.is(execution.task.execution) && event.resolvedVariables) { const dictionary: IStringDictionary = {}; Array.from(event.resolvedVariables.entries()).forEach(entry => dictionary[entry[0]] = entry[1]); - resolvedDefinition = await this._configurationResolverService.resolveAny(task.getWorkspaceFolder(), + resolvedDefinition = await this._configurationResolverService.resolveAnyAsync(task.getWorkspaceFolder(), execution.task.definition, dictionary); } this._proxy.$onDidStartTask(execution, event.terminalId!, resolvedDefinition); diff --git a/src/vs/workbench/api/browser/mainThreadTerminalService.ts b/src/vs/workbench/api/browser/mainThreadTerminalService.ts index 93c639e5..428f2412 100644 --- a/src/vs/workbench/api/browser/mainThreadTerminalService.ts +++ b/src/vs/workbench/api/browser/mainThreadTerminalService.ts @@ -8,11 +8,12 @@ import { StopWatch } from 'vs/base/common/stopwatch'; import { URI } from 'vs/base/common/uri'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; -import { IShellLaunchConfig, ITerminalDimensions } from 'vs/platform/terminal/common/terminal'; +import { IShellLaunchConfig, IShellLaunchConfigDto, ITerminalDimensions } from 'vs/platform/terminal/common/terminal'; import { TerminalDataBufferer } from 'vs/platform/terminal/common/terminalDataBuffering'; -import { ExtHostContext, ExtHostTerminalServiceShape, IExtHostContext, IShellLaunchConfigDto, ITerminalDimensionsDto, MainContext, MainThreadTerminalServiceShape, TerminalIdentifier, TerminalLaunchConfig } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostContext, ExtHostTerminalServiceShape, IExtHostContext, ITerminalDimensionsDto, MainContext, MainThreadTerminalServiceShape, TerminalIdentifier, TerminalLaunchConfig } from 'vs/workbench/api/common/extHost.protocol'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { ITerminalExternalLinkProvider, ITerminalInstance, ITerminalInstanceService, ITerminalLink, ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { TerminalProcessExtHostProxy } from 'vs/workbench/contrib/terminal/browser/terminalProcessExtHostProxy'; import { IEnvironmentVariableService, ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable'; import { deserializeEnvironmentVariableCollection, serializeEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariableShared'; import { IAvailableProfilesRequest as IAvailableProfilesRequest, IDefaultShellAndArgsRequest, IStartExtensionTerminalRequest, ITerminalProcessExtHostProxy } from 'vs/workbench/contrib/terminal/common/terminal'; @@ -69,7 +70,6 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape this._toDispose.add(_terminalService.onInstanceRequestStartExtensionTerminal(e => this._onRequestStartExtensionTerminal(e))); 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.onRequestAvailableProfiles(e => this._onRequestAvailableProfiles(e))); // ITerminalInstanceService listeners @@ -100,9 +100,6 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape public dispose(): void { this._toDispose.dispose(); this._linkProvider?.dispose(); - - // TODO@Daniel: Should all the previously created terminals be disposed - // when the extension host process goes down ? } private _getTerminalId(id: TerminalIdentifier): number | undefined { @@ -126,12 +123,16 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape executable: launchConfig.shellPath, args: launchConfig.shellArgs, cwd: typeof launchConfig.cwd === 'string' ? launchConfig.cwd : URI.revive(launchConfig.cwd), + icon: launchConfig.icon, + initialText: launchConfig.initialText, waitOnExit: launchConfig.waitOnExit, ignoreConfigurationCwd: true, env: launchConfig.env, strictEnv: launchConfig.strictEnv, hideFromUser: launchConfig.hideFromUser, - isExtensionCustomPtyTerminal: launchConfig.isExtensionCustomPtyTerminal, + customPtyImplementation: launchConfig.isExtensionCustomPtyTerminal + ? (id, cols, rows) => new TerminalProcessExtHostProxy(id, cols, rows, this._terminalService) + : undefined, extHostTerminalId: extHostTerminalId, isFeatureTerminal: launchConfig.isFeatureTerminal, isExtensionOwnedTerminal: launchConfig.isExtensionOwnedTerminal @@ -157,17 +158,11 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape } public $dispose(id: TerminalIdentifier): void { - const terminalInstance = this._getTerminalInstance(id); - if (terminalInstance) { - terminalInstance.dispose(); - } + this._getTerminalInstance(id)?.dispose(); } public $sendText(id: TerminalIdentifier, text: string, addNewLine: boolean): void { - const terminalInstance = this._getTerminalInstance(id); - if (terminalInstance) { - terminalInstance.sendText(text, addNewLine); - } + this._getTerminalInstance(id)?.sendText(text, addNewLine); } public $startSendingDataEvents(): void { @@ -183,10 +178,8 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape } public $stopSendingDataEvents(): void { - if (this._dataEventTracker) { - this._dataEventTracker.dispose(); - this._dataEventTracker = undefined; - } + this._dataEventTracker?.dispose(); + this._dataEventTracker = undefined; } public $startLinkProvider(): void { @@ -215,10 +208,6 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape this._proxy.$acceptTerminalTitleChange(terminalId, name); } - private _onWorkspacePermissionsChanged(isAllowed: boolean): void { - this._proxy.$acceptWorkspacePermissionsChanged(isAllowed); - } - private _onTerminalDisposed(terminalInstance: ITerminalInstance): void { this._proxy.$acceptTerminalClosed(terminalInstance.instanceId, terminalInstance.exitCode); } @@ -276,60 +265,35 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape } public $sendProcessTitle(terminalId: number, title: string): void { - const terminalProcess = this._terminalProcessProxies.get(terminalId); - if (terminalProcess) { - terminalProcess.emitTitle(title); - } + this._terminalProcessProxies.get(terminalId)?.emitTitle(title); } public $sendProcessData(terminalId: number, data: string): void { - const terminalProcess = this._terminalProcessProxies.get(terminalId); - if (terminalProcess) { - terminalProcess.emitData(data); - } + this._terminalProcessProxies.get(terminalId)?.emitData(data); } public $sendProcessReady(terminalId: number, pid: number, cwd: string): void { - const terminalProcess = this._terminalProcessProxies.get(terminalId); - if (terminalProcess) { - terminalProcess.emitReady(pid, cwd); - } + this._terminalProcessProxies.get(terminalId)?.emitReady(pid, cwd); } public $sendProcessExit(terminalId: number, exitCode: number | undefined): void { - const terminalProcess = this._terminalProcessProxies.get(terminalId); - if (terminalProcess) { - terminalProcess.emitExit(exitCode); - this._terminalProcessProxies.delete(terminalId); - } + this._terminalProcessProxies.get(terminalId)?.emitExit(exitCode); } public $sendOverrideDimensions(terminalId: number, dimensions: ITerminalDimensions | undefined): void { - const terminalProcess = this._terminalProcessProxies.get(terminalId); - if (terminalProcess) { - terminalProcess.emitOverrideDimensions(dimensions); - } + this._terminalProcessProxies.get(terminalId)?.emitOverrideDimensions(dimensions); } public $sendProcessInitialCwd(terminalId: number, initialCwd: string): void { - const terminalProcess = this._terminalProcessProxies.get(terminalId); - if (terminalProcess) { - terminalProcess.emitInitialCwd(initialCwd); - } + this._terminalProcessProxies.get(terminalId)?.emitInitialCwd(initialCwd); } public $sendProcessCwd(terminalId: number, cwd: string): void { - const terminalProcess = this._terminalProcessProxies.get(terminalId); - if (terminalProcess) { - terminalProcess.emitCwd(cwd); - } + this._terminalProcessProxies.get(terminalId)?.emitCwd(cwd); } public $sendResolvedLaunchConfig(terminalId: number, shellLaunchConfig: IShellLaunchConfig): void { - const instance = this._terminalService.getInstanceFromId(terminalId); - if (instance) { - this._getTerminalProcess(terminalId)?.emitResolvedShellLaunchConfig(shellLaunchConfig); - } + this._getTerminalProcess(terminalId)?.emitResolvedShellLaunchConfig(shellLaunchConfig); } private async _onRequestLatency(terminalId: number): Promise { @@ -350,12 +314,12 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape if (conn) { return this._remoteAuthority === conn.remoteAuthority; } - return true; + return this._extHostKind !== ExtensionHostKind.LocalWebWorker; } private async _onRequestAvailableProfiles(req: IAvailableProfilesRequest): Promise { - if (this._isPrimaryExtHost() && this._extHostKind !== ExtensionHostKind.LocalWebWorker) { - req.callback(await this._proxy.$getAvailableProfiles(req.quickLaunchOnly)); + if (this._isPrimaryExtHost()) { + req.callback(await this._proxy.$getAvailableProfiles(req.configuredProfilesOnly)); } } diff --git a/src/vs/workbench/api/browser/mainThreadTesting.ts b/src/vs/workbench/api/browser/mainThreadTesting.ts index ec01db91..64f6a4a3 100644 --- a/src/vs/workbench/api/browser/mainThreadTesting.ts +++ b/src/vs/workbench/api/browser/mainThreadTesting.ts @@ -3,14 +3,17 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { VSBuffer } from 'vs/base/common/buffer'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { isDefined } from 'vs/base/common/types'; import { URI, UriComponents } from 'vs/base/common/uri'; import { Range } from 'vs/editor/common/core/range'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; -import { getTestSubscriptionKey, ISerializedTestResults, ITestState, RunTestsRequest, TestDiffOpType, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection'; -import { HydratedTestResult, ITestResultService, LiveTestResult } from 'vs/workbench/contrib/testing/common/testResultService'; +import { TestResultState } from 'vs/workbench/api/common/extHostTypes'; +import { ExtensionRunTestsRequest, getTestSubscriptionKey, ITestItem, ITestMessage, ITestRunTask, RunTestsRequest, TestDiffOpType, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection'; +import { LiveTestResult } from 'vs/workbench/contrib/testing/common/testResult'; +import { ITestResultService } from 'vs/workbench/contrib/testing/common/testResultService'; import { ITestRootProvider, ITestService } from 'vs/workbench/contrib/testing/common/testService'; import { ExtHostContext, ExtHostTestingResource, ExtHostTestingShape, IExtHostContext, MainContext, MainThreadTestingShape } from '../common/extHost.protocol'; @@ -44,7 +47,6 @@ export class MainThreadTesting extends Disposable implements MainThreadTestingSh this._register(this.testService.onShouldSubscribe(args => this.proxy.$subscribeToTests(args.resource, args.uri))); this._register(this.testService.onShouldUnsubscribe(args => this.proxy.$unsubscribeFromTests(args.resource, args.uri))); - const prevResults = resultService.results.map(r => r.toJSON()).filter(isDefined); if (prevResults.length) { this.proxy.$publishTestResults(prevResults); @@ -68,31 +70,79 @@ export class MainThreadTesting extends Disposable implements MainThreadTestingSh /** * @inheritdoc */ - public $publishExtensionProvidedResults(results: ISerializedTestResults, persist: boolean): void { - this.resultService.push(new HydratedTestResult(results, persist)); + $addTestsToRun(runId: string, tests: ITestItem[]): void { + for (const test of tests) { + test.uri = URI.revive(test.uri); + if (test.range) { + test.range = Range.lift(test.range); + } + } + + this.withLiveRun(runId, r => r.addTestChainToRun(tests)); } /** * @inheritdoc */ - public $updateTestStateInRun(runId: string, testId: string, state: ITestState): void { + $startedExtensionTestRun(req: ExtensionRunTestsRequest): void { + this.resultService.createLiveResult(req); + } + + /** + * @inheritdoc + */ + $startedTestRunTask(runId: string, task: ITestRunTask): void { + this.withLiveRun(runId, r => r.addTask(task)); + } + + /** + * @inheritdoc + */ + $finishedTestRunTask(runId: string, taskId: string): void { + this.withLiveRun(runId, r => r.markTaskComplete(taskId)); + } + + /** + * @inheritdoc + */ + $finishedExtensionTestRun(runId: string): void { + this.withLiveRun(runId, r => r.markComplete()); + } + + /** + * @inheritdoc + */ + public $updateTestStateInRun(runId: string, taskId: string, testId: string, state: TestResultState, duration?: number): void { + this.withLiveRun(runId, r => r.updateState(testId, taskId, state, duration)); + } + + /** + * @inheritdoc + */ + public $appendOutputToRun(runId: string, _taskId: string, output: VSBuffer): void { + this.withLiveRun(runId, r => r.output.append(output)); + } + + + /** + * @inheritdoc + */ + public $appendTestMessageInRun(runId: string, taskId: string, testId: string, message: ITestMessage): void { const r = this.resultService.getResult(runId); if (r && r instanceof LiveTestResult) { - for (const message of state.messages) { - if (message.location) { - message.location.uri = URI.revive(message.location.uri); - message.location.range = Range.lift(message.location.range); - } + if (message.location) { + message.location.uri = URI.revive(message.location.uri); + message.location.range = Range.lift(message.location.range); } - r.updateState(testId, state); + r.appendMessage(testId, taskId, message); } } /** * @inheritdoc */ - public $registerTestProvider(id: string) { + public $registerTestController(id: string) { const disposable = this.testService.registerTestController(id, { runTests: (req, token) => this.proxy.$runTestsForProvider(req, token), lookupTest: test => this.proxy.$lookupTest(test), @@ -105,7 +155,7 @@ export class MainThreadTesting extends Disposable implements MainThreadTestingSh /** * @inheritdoc */ - public $unregisterTestProvider(id: string) { + public $unregisterTestController(id: string) { this.testProviderRegistrations.get(id)?.dispose(); this.testProviderRegistrations.delete(id); } @@ -142,11 +192,16 @@ export class MainThreadTesting extends Disposable implements MainThreadTestingSh return result.id; } - public dispose() { + public override dispose() { super.dispose(); for (const subscription of this.testSubscriptions.values()) { subscription.dispose(); } this.testSubscriptions.clear(); } + + private withLiveRun(runId: string, fn: (run: LiveTestResult) => T): T | undefined { + const r = this.resultService.getResult(runId); + return r && r instanceof LiveTestResult ? fn(r) : undefined; + } } diff --git a/src/vs/workbench/api/browser/mainThreadTreeViews.ts b/src/vs/workbench/api/browser/mainThreadTreeViews.ts index 40b6794b..b70ff475 100644 --- a/src/vs/workbench/api/browser/mainThreadTreeViews.ts +++ b/src/vs/workbench/api/browser/mainThreadTreeViews.ts @@ -147,7 +147,7 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie return viewDescriptor ? viewDescriptor.treeView : null; } - dispose(): void { + override dispose(): void { this._dataProviders.forEach((dataProvider, treeViewId) => { const treeView = this.getTreeView(treeViewId); if (treeView) { diff --git a/src/vs/workbench/api/browser/mainThreadTunnelService.ts b/src/vs/workbench/api/browser/mainThreadTunnelService.ts index 7c00752e..668e0fae 100644 --- a/src/vs/workbench/api/browser/mainThreadTunnelService.ts +++ b/src/vs/workbench/api/browser/mainThreadTunnelService.ts @@ -12,10 +12,12 @@ import { ITunnelProvider, ITunnelService, TunnelCreationOptions, TunnelProviderF import { Disposable } from 'vs/base/common/lifecycle'; import type { TunnelDescription } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; -import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ILogService } from 'vs/platform/log/common/log'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; @extHostNamedCustomer(MainContext.MainThreadTunnelService) export class MainThreadTunnelService extends Disposable implements MainThreadTunnelServiceShape, PortAttributesProvider { @@ -80,7 +82,8 @@ export class MainThreadTunnelService extends Disposable implements MainThreadTun const portRange = selector.portRange; const portInRange = portRange ? ports.some(port => portRange[0] <= port && port < portRange[1]) : true; const pidMatches = !selector.pid || (selector.pid === pid); - return portInRange || pidMatches; + const commandMatches = !selector.commandMatcher || (commandLine && (commandLine.match(selector.commandMatcher))); + return portInRange && pidMatches && commandMatches; }).map(entry => entry[0]); if (appropriateHandles.length === 0) { @@ -174,19 +177,13 @@ export class MainThreadTunnelService extends Disposable implements MainThreadTun this.remoteAgentService.getEnvironment().then(() => { switch (source) { case CandidatePortSource.None: { - const autoDetectionEnablement = this.configurationService.inspect(PORT_AUTO_FORWARD_SETTING); - if (autoDetectionEnablement.userRemote === undefined) { - // Only update the remote setting if the user hasn't already set it. - this.configurationService.updateValue(PORT_AUTO_FORWARD_SETTING, false, ConfigurationTarget.USER_REMOTE); - } + Registry.as(ConfigurationExtensions.Configuration) + .registerDefaultConfigurations([{ 'remote.autoForwardPorts': false }]); break; } case CandidatePortSource.Output: { - const candidatePortSourceSetting = this.configurationService.inspect(PORT_AUTO_SOURCE_SETTING); - if (candidatePortSourceSetting.userRemote === undefined) { - // Only update the remote setting if the user hasn't already set it. - this.configurationService.updateValue(PORT_AUTO_SOURCE_SETTING, PORT_AUTO_SOURCE_SETTING_OUTPUT, ConfigurationTarget.USER_REMOTE); - } + Registry.as(ConfigurationExtensions.Configuration) + .registerDefaultConfigurations([{ 'remote.autoForwardPortsSource': PORT_AUTO_SOURCE_SETTING_OUTPUT }]); break; } default: // Do nothing, the defaults for these settings should be used. @@ -195,8 +192,4 @@ export class MainThreadTunnelService extends Disposable implements MainThreadTun // The remote failed to get setup. Errors from that area will already be surfaced to the user. }); } - - dispose(): void { - - } } diff --git a/src/vs/workbench/api/browser/mainThreadUriOpeners.ts b/src/vs/workbench/api/browser/mainThreadUriOpeners.ts index 96eb6fd8..daaeb1e5 100644 --- a/src/vs/workbench/api/browser/mainThreadUriOpeners.ts +++ b/src/vs/workbench/api/browser/mainThreadUriOpeners.ts @@ -77,8 +77,8 @@ export class MainThreadUriOpeners extends Disposable implements MainThreadUriOpe await this.proxy.$openUri(id, { resolvedUri: uri, sourceUri: ctx.sourceUri }, token); } catch (e) { if (!isPromiseCanceledError(e)) { - const openDefaultAction = new Action('default', localize('openerFailedUseDefault', "Open using default opener"), undefined, undefined, () => { - return this.openerService.open(uri, { + const openDefaultAction = new Action('default', localize('openerFailedUseDefault', "Open using default opener"), undefined, undefined, async () => { + await this.openerService.open(uri, { allowTunneling: false, allowContributedOpeners: defaultExternalUriOpenerId, }); @@ -128,7 +128,7 @@ export class MainThreadUriOpeners extends Disposable implements MainThreadUriOpe this._contributedExternalUriOpenersStore.delete(id); } - dispose(): void { + override dispose(): void { super.dispose(); this._registeredOpeners.clear(); } diff --git a/src/vs/workbench/api/browser/mainThreadWebviewPanels.ts b/src/vs/workbench/api/browser/mainThreadWebviewPanels.ts index 99809deb..22bfaff3 100644 --- a/src/vs/workbench/api/browser/mainThreadWebviewPanels.ts +++ b/src/vs/workbench/api/browser/mainThreadWebviewPanels.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { onUnexpectedError } from 'vs/base/common/errors'; -import { Disposable, DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, dispose, IDisposable } from 'vs/base/common/lifecycle'; import { URI, UriComponents } from 'vs/base/common/uri'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { MainThreadWebviews, reviveWebviewContentOptions, reviveWebviewExtension } from 'vs/workbench/api/browser/mainThreadWebviews'; @@ -81,7 +81,6 @@ export class MainThreadWebviewPanels extends Disposable implements extHostProtoc private readonly _webviewInputs = new WebviewInputStore(); private readonly _editorProviders = new Map(); - private readonly _webviewFromDiffEditorHandles = new Set(); private readonly _revivers = new Map(); @@ -99,18 +98,17 @@ export class MainThreadWebviewPanels extends Disposable implements extHostProtoc this._proxy = context.getProxy(extHostProtocol.ExtHostContext.ExtHostWebviewPanels); this._register(_editorService.onDidActiveEditorChange(() => { - const activeInput = this._editorService.activeEditor; - if (activeInput instanceof DiffEditorInput && activeInput.primary instanceof WebviewInput && activeInput.secondary instanceof WebviewInput) { - this.registerWebviewFromDiffEditorListeners(activeInput); - } - - this.updateWebviewViewStates(activeInput); + this.updateWebviewViewStates(this._editorService.activeEditor); })); this._register(_editorService.onDidVisibleEditorsChange(() => { this.updateWebviewViewStates(this._editorService.activeEditor); })); + this._register(_webviewWorkbenchService.onDidChangeActiveWebviewEditor(input => { + this.updateWebviewViewStates(input); + })); + // This reviver's only job is to activate extensions. // This should trigger the real reviver to be registered from the extension host side. this._register(_webviewWorkbenchService.registerResolver({ @@ -125,7 +123,7 @@ export class MainThreadWebviewPanels extends Disposable implements extHostProtoc })); } - dispose() { + override dispose() { super.dispose(); dispose(this._editorProviders.values()); @@ -137,9 +135,9 @@ export class MainThreadWebviewPanels extends Disposable implements extHostProtoc public get webviewInputs(): Iterable { return this._webviewInputs; } - public addWebviewInput(handle: extHostProtocol.WebviewHandle, input: WebviewInput): void { + public addWebviewInput(handle: extHostProtocol.WebviewHandle, input: WebviewInput, options: { serializeBuffersForPostMessage: boolean }): void { this._webviewInputs.add(handle, input); - this._mainThreadWebviews.addWebview(handle, input.webview); + this._mainThreadWebviews.addWebview(handle, input.webview, options); input.webview.onDidDispose(() => { this._proxy.$onDidDisposeWebviewPanel(handle).finally(() => { @@ -156,6 +154,7 @@ export class MainThreadWebviewPanels extends Disposable implements extHostProtoc title: string; webviewOptions: extHostProtocol.IWebviewOptions; panelOptions: extHostProtocol.IWebviewPanelOptions; + serializeBuffersForPostMessage: boolean; }, showOptions: { viewColumn?: EditorGroupColumn, preserveFocus?: boolean; }, ): void { @@ -168,7 +167,7 @@ export class MainThreadWebviewPanels extends Disposable implements extHostProtoc const extension = reviveWebviewExtension(extensionData); const webview = this._webviewWorkbenchService.createWebview(handle, this.webviewPanelViewType.fromExternal(viewType), initData.title, mainThreadShowOptions, reviveWebviewOptions(initData.panelOptions), reviveWebviewContentOptions(initData.webviewOptions), extension); - this.addWebviewInput(handle, webview); + this.addWebviewInput(handle, webview, { serializeBuffersForPostMessage: initData.serializeBuffersForPostMessage }); /* __GDPR__ "webviews:createWebviewPanel" : { @@ -205,7 +204,7 @@ export class MainThreadWebviewPanels extends Disposable implements extHostProtoc } } - public $registerSerializer(viewType: string): void { + public $registerSerializer(viewType: string, options: { serializeBuffersForPostMessage: boolean }): void { if (this._revivers.has(viewType)) { throw new Error(`Reviver for ${viewType} already registered`); } @@ -223,7 +222,7 @@ export class MainThreadWebviewPanels extends Disposable implements extHostProtoc const handle = webviewInput.id; - this.addWebviewInput(handle, webviewInput); + this.addWebviewInput(handle, webviewInput, options); let state = undefined; if (webviewInput.webview.state) { @@ -259,27 +258,6 @@ export class MainThreadWebviewPanels extends Disposable implements extHostProtoc this._revivers.delete(viewType); } - private registerWebviewFromDiffEditorListeners(diffEditorInput: DiffEditorInput): void { - const primary = diffEditorInput.primary as WebviewInput; - const secondary = diffEditorInput.secondary as WebviewInput; - - if (this._webviewFromDiffEditorHandles.has(primary.id) || this._webviewFromDiffEditorHandles.has(secondary.id)) { - return; - } - - this._webviewFromDiffEditorHandles.add(primary.id); - this._webviewFromDiffEditorHandles.add(secondary.id); - - const disposables = new DisposableStore(); - disposables.add(primary.webview.onDidFocus(() => this.updateWebviewViewStates(primary))); - disposables.add(secondary.webview.onDidFocus(() => this.updateWebviewViewStates(secondary))); - disposables.add(diffEditorInput.onDispose(() => { - this._webviewFromDiffEditorHandles.delete(primary.id); - this._webviewFromDiffEditorHandles.delete(secondary.id); - dispose(disposables); - })); - } - private updateWebviewViewStates(activeEditorInput: IEditorInput | undefined) { if (!this._webviewInputs.size) { return; diff --git a/src/vs/workbench/api/browser/mainThreadWebviewViews.ts b/src/vs/workbench/api/browser/mainThreadWebviewViews.ts index e369b4f0..eb767e27 100644 --- a/src/vs/workbench/api/browser/mainThreadWebviewViews.ts +++ b/src/vs/workbench/api/browser/mainThreadWebviewViews.ts @@ -28,7 +28,7 @@ export class MainThreadWebviewsViews extends Disposable implements extHostProtoc this._proxy = context.getProxy(extHostProtocol.ExtHostContext.ExtHostWebviewViews); } - dispose() { + override dispose() { super.dispose(); dispose(this._webviewViewProviders.values()); @@ -55,7 +55,7 @@ export class MainThreadWebviewsViews extends Disposable implements extHostProtoc public $registerWebviewViewProvider( extensionData: extHostProtocol.WebviewExtensionDescription, viewType: string, - options?: { retainContextWhenHidden?: boolean } + options: { retainContextWhenHidden?: boolean, serializeBuffersForPostMessage: boolean } ): void { if (this._webviewViewProviders.has(viewType)) { throw new Error(`View provider for ${viewType} already registered`); @@ -68,7 +68,7 @@ export class MainThreadWebviewsViews extends Disposable implements extHostProtoc const handle = webviewView.webview.id; this._webviewViews.set(handle, webviewView); - this.mainThreadWebviews.addWebview(handle, webviewView.webview); + this.mainThreadWebviews.addWebview(handle, webviewView.webview, { serializeBuffersForPostMessage: options.serializeBuffersForPostMessage }); let state = undefined; if (webviewView.webview.state) { diff --git a/src/vs/workbench/api/browser/mainThreadWebviews.ts b/src/vs/workbench/api/browser/mainThreadWebviews.ts index b49181ee..5f5d3ac3 100644 --- a/src/vs/workbench/api/browser/mainThreadWebviews.ts +++ b/src/vs/workbench/api/browser/mainThreadWebviews.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { VSBuffer } from 'vs/base/common/buffer'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import { isWeb } from 'vs/base/common/platform'; @@ -13,6 +14,8 @@ import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IProductService } from 'vs/platform/product/common/productService'; import * as extHostProtocol from 'vs/workbench/api/common/extHost.protocol'; +import { serializeMessage } from 'vs/workbench/api/common/extHostWebview'; +import { deserializeWebviewMessage } from 'vs/workbench/api/common/extHostWebviewMessaging'; import { Webview, WebviewContentOptions, WebviewExtensionDescription, WebviewOverlay } from 'vs/workbench/contrib/webview/browser/webview'; export class MainThreadWebviews extends Disposable implements extHostProtocol.MainThreadWebviewsShape { @@ -39,13 +42,13 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma this._proxy = context.getProxy(extHostProtocol.ExtHostContext.ExtHostWebviews); } - public addWebview(handle: extHostProtocol.WebviewHandle, webview: WebviewOverlay): void { + public addWebview(handle: extHostProtocol.WebviewHandle, webview: WebviewOverlay, options: { serializeBuffersForPostMessage: boolean }): void { if (this._webviews.has(handle)) { throw new Error('Webview already registered'); } this._webviews.set(handle, webview); - this.hookupWebviewEventDelegate(handle, webview); + this.hookupWebviewEventDelegate(handle, webview, options); } public $setHtml(handle: extHostProtocol.WebviewHandle, value: string): void { @@ -58,17 +61,23 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma webview.contentOptions = reviveWebviewContentOptions(options); } - public async $postMessage(handle: extHostProtocol.WebviewHandle, message: any): Promise { + public async $postMessage(handle: extHostProtocol.WebviewHandle, jsonMessage: string, ...buffers: VSBuffer[]): Promise { const webview = this.getWebview(handle); - webview.postMessage(message); + const { message, arrayBuffers } = deserializeWebviewMessage(jsonMessage, buffers); + webview.postMessage(message, arrayBuffers); return true; } - private hookupWebviewEventDelegate(handle: extHostProtocol.WebviewHandle, webview: WebviewOverlay) { + private hookupWebviewEventDelegate(handle: extHostProtocol.WebviewHandle, webview: WebviewOverlay, options: { serializeBuffersForPostMessage: boolean }) { const disposables = new DisposableStore(); disposables.add(webview.onDidClickLink((uri) => this.onDidClickLink(handle, uri))); - disposables.add(webview.onMessage((message: any) => { this._proxy.$onMessage(handle, message); })); + + disposables.add(webview.onMessage((message) => { + const serialized = serializeMessage(message.message, options); + this._proxy.$onMessage(handle, serialized.message, ...serialized.buffers); + })); + disposables.add(webview.onMissingCsp((extension: ExtensionIdentifier) => this._proxy.$onMissingCsp(handle, extension.value))); disposables.add(webview.onDidDispose(() => { @@ -80,7 +89,7 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma private onDidClickLink(handle: extHostProtocol.WebviewHandle, link: string): void { const webview = this.getWebview(handle); if (this.isSupportedLink(webview, URI.parse(link))) { - this._openerService.open(link, { fromUserGesture: true, allowContributedOpeners: true }); + this._openerService.open(link, { fromUserGesture: true, allowContributedOpeners: true, allowCommands: true }); } } diff --git a/src/vs/workbench/api/browser/mainThreadWorkspace.ts b/src/vs/workbench/api/browser/mainThreadWorkspace.ts index fd65baf6..4ded0799 100644 --- a/src/vs/workbench/api/browser/mainThreadWorkspace.ts +++ b/src/vs/workbench/api/browser/mainThreadWorkspace.ts @@ -16,7 +16,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { ILabelService } from 'vs/platform/label/common/label'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IRequestService } from 'vs/platform/request/common/request'; -import { WorkspaceTrustStateChangeEvent, IWorkspaceTrustService, WorkspaceTrustRequestOptions, WorkspaceTrustState } from 'vs/platform/workspace/common/workspaceTrust'; +import { WorkspaceTrustRequestOptions, IWorkspaceTrustManagementService, IWorkspaceTrustRequestService } from 'vs/platform/workspace/common/workspaceTrust'; import { IWorkspace, IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { isUntitledWorkspace } from 'vs/platform/workspaces/common/workspaces'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; @@ -47,20 +47,21 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape { @ILabelService private readonly _labelService: ILabelService, @IEnvironmentService private readonly _environmentService: IEnvironmentService, @IFileService fileService: IFileService, - @IWorkspaceTrustService private readonly _workspaceTrustService: IWorkspaceTrustService + @IWorkspaceTrustManagementService private readonly _workspaceTrustManagementService: IWorkspaceTrustManagementService, + @IWorkspaceTrustRequestService private readonly _workspaceTrustRequestService: IWorkspaceTrustRequestService ) { this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostWorkspace); const workspace = this._contextService.getWorkspace(); // The workspace file is provided be a unknown file system provider. It might come // from the extension host. So initialize now knowing that `rootPath` is undefined. if (workspace.configuration && !isNative && !fileService.canHandleResource(workspace.configuration)) { - this._proxy.$initializeWorkspace(this.getWorkspaceData(workspace), this.getWorkspaceTrustState()); + this._proxy.$initializeWorkspace(this.getWorkspaceData(workspace), this.isWorkspaceTrusted()); } else { - this._contextService.getCompleteWorkspace().then(workspace => this._proxy.$initializeWorkspace(this.getWorkspaceData(workspace), this.getWorkspaceTrustState())); + this._contextService.getCompleteWorkspace().then(workspace => this._proxy.$initializeWorkspace(this.getWorkspaceData(workspace), this.isWorkspaceTrusted())); } this._contextService.onDidChangeWorkspaceFolders(this._onDidChangeWorkspace, this, this._toDispose); this._contextService.onDidChangeWorkbenchState(this._onDidChangeWorkspace, this, this._toDispose); - this._workspaceTrustService.onDidChangeTrustState(this._onDidChangeWorkspaceTrustState, this, this._toDispose); + this._workspaceTrustManagementService.onDidChangeTrust(this._onDidGrantWorkspaceTrust, this, this._toDispose); } dispose(): void { @@ -208,15 +209,15 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape { // --- trust --- - $requireWorkspaceTrust(options?: WorkspaceTrustRequestOptions): Promise { - return this._workspaceTrustService.requireWorkspaceTrust(options); + $requestWorkspaceTrust(options?: WorkspaceTrustRequestOptions): Promise { + return this._workspaceTrustRequestService.requestWorkspaceTrust(options); } - private getWorkspaceTrustState(): WorkspaceTrustState { - return this._workspaceTrustService.getWorkspaceTrustState(); + private isWorkspaceTrusted(): boolean { + return this._workspaceTrustManagementService.isWorkpaceTrusted(); } - private _onDidChangeWorkspaceTrustState(state: WorkspaceTrustStateChangeEvent): void { - this._proxy.$onDidChangeWorkspaceTrustState(state); + private _onDidGrantWorkspaceTrust(): void { + this._proxy.$onDidGrantWorkspaceTrust(); } } diff --git a/src/vs/workbench/api/browser/viewsExtensionPoint.ts b/src/vs/workbench/api/browser/viewsExtensionPoint.ts index 6ff83ca7..56de28a1 100644 --- a/src/vs/workbench/api/browser/viewsExtensionPoint.ts +++ b/src/vs/workbench/api/browser/viewsExtensionPoint.ts @@ -133,7 +133,7 @@ const viewDescriptor: IJSONSchema = { type: 'string' }, contextualTitle: { - description: localize('vscode.extension.contributes.view.contextualTitle', "Human-readable context for when the view is moved out of its original location. By default, the view's container name will be used. Will be shown"), + description: localize('vscode.extension.contributes.view.contextualTitle', "Human-readable context for when the view is moved out of its original location. By default, the view's container name will be used."), type: 'string' }, visibility: { @@ -304,8 +304,8 @@ class ViewsExtensionHandler implements IWorkbenchContribution { const removedExtensions: Set = extensionPoints.reduce((result, e) => { result.add(ExtensionIdentifier.toKey(e.description.identifier)); return result; }, new Set()); for (const viewContainer of viewContainersRegistry.all) { if (viewContainer.extensionId && removedExtensions.has(ExtensionIdentifier.toKey(viewContainer.extensionId))) { - // move only those views that do not belong to the removed extension - const views = this.viewsRegistry.getViews(viewContainer).filter(view => !removedExtensions.has(ExtensionIdentifier.toKey((view as ICustomViewDescriptor).extensionId))); + // move all views in this container into default view container + const views = this.viewsRegistry.getViews(viewContainer); if (views.length) { this.viewsRegistry.moveViews(views, this.getDefaultViewContainer()); } diff --git a/src/vs/workbench/api/common/configurationExtensionPoint.ts b/src/vs/workbench/api/common/configurationExtensionPoint.ts index aab5275f..75476f2f 100644 --- a/src/vs/workbench/api/common/configurationExtensionPoint.ts +++ b/src/vs/workbench/api/common/configurationExtensionPoint.ts @@ -170,7 +170,7 @@ configurationExtPoint.setHandler((extensions, { added, removed }) => { validateProperties(configuration, extension); configuration.id = node.id || extension.description.identifier.value; - configuration.extensionInfo = { id: extension.description.identifier.value }; + configuration.extensionInfo = { id: extension.description.identifier.value, restrictedConfigurations: extension.description.capabilities?.untrustedWorkspaces?.supported === 'limited' ? extension.description.capabilities?.untrustedWorkspaces.restrictedConfigurations : undefined }; configuration.title = configuration.title || extension.description.displayName || extension.description.identifier.value; configurations.push(configuration); return configurations; @@ -181,10 +181,10 @@ configurationExtPoint.setHandler((extensions, { added, removed }) => { for (let extension of added) { const configurations: IConfigurationNode[] = []; const value = extension.value; - if (!Array.isArray(value)) { - configurations.push(...handleConfiguration(value, extension)); - } else { + if (Array.isArray(value)) { value.forEach(v => configurations.push(...handleConfiguration(v, extension))); + } else { + configurations.push(...handleConfiguration(value, extension)); } extensionConfigurations.set(ExtensionIdentifier.toKey(extension.description.identifier), configurations); addedConfigurations.push(...configurations); @@ -213,7 +213,7 @@ function validateProperties(configuration: IConfigurationNode, extension: IExten const propertyConfiguration = properties[key]; if (!isObject(propertyConfiguration)) { delete properties[key]; - extension.collector.error(nls.localize('invalid.property', "'configuration.property' must be an object")); + extension.collector.error(nls.localize('invalid.property', "configuration.properties property '{0}' must be an object", key)); continue; } if (propertyConfiguration.scope) { @@ -320,7 +320,7 @@ jsonRegistry.registerSchema('vscode://schemas/workspaceConfig', { 'remoteAuthority': { type: 'string', doNotSuggest: true, - description: nls.localize('workspaceConfig.remoteAuthority', "The remote server where the workspace is located. Only used by unsaved remote workspaces."), + description: nls.localize('workspaceConfig.remoteAuthority', "The remote server where the workspace is located."), } }, errorMessage: nls.localize('unknownWorkspaceProperty', "Unknown workspace configuration property") diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 6d52bf99..14757bf8 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -47,7 +47,7 @@ import { ExtHostUrls } from 'vs/workbench/api/common/extHostUrls'; import { ExtHostWebviews } from 'vs/workbench/api/common/extHostWebview'; import { IExtHostWindow } from 'vs/workbench/api/common/extHostWindow'; import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; -import { throwProposedApiError, checkProposedApiEnabled, checkRequiresWorkspaceTrust } from 'vs/workbench/services/extensions/common/extensions'; +import { throwProposedApiError, checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; import { ProxyIdentifier } from 'vs/workbench/services/extensions/common/proxyIdentifier'; import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry'; import type * as vscode from 'vscode'; @@ -84,6 +84,9 @@ import { ExtHostUriOpeners } from 'vs/workbench/api/common/extHostUriOpener'; import { IExtHostSecretState } from 'vs/workbench/api/common/exHostSecretState'; import { ExtHostEditorTabs } from 'vs/workbench/api/common/extHostEditorTabs'; import { IExtHostTelemetry } from 'vs/workbench/api/common/extHostTelemetry'; +import { ExtHostNotebookKernels } from 'vs/workbench/api/common/extHostNotebookKernels'; +import { RemoteTrustOption } from 'vs/platform/remote/common/remoteAuthorityResolver'; +import { TextSearchCompleteMessageType } from 'vs/workbench/services/search/common/searchExtTypes'; export interface IExtensionApiFactory { (extension: IExtensionDescription, registry: ExtensionDescriptionRegistry, configProvider: ExtHostConfigProvider): typeof vscode; @@ -140,7 +143,8 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const extHostDocuments = rpcProtocol.set(ExtHostContext.ExtHostDocuments, new ExtHostDocuments(rpcProtocol, extHostDocumentsAndEditors)); const extHostDocumentContentProviders = rpcProtocol.set(ExtHostContext.ExtHostDocumentContentProviders, new ExtHostDocumentContentProvider(rpcProtocol, extHostDocumentsAndEditors, extHostLogService)); const extHostDocumentSaveParticipant = rpcProtocol.set(ExtHostContext.ExtHostDocumentSaveParticipant, new ExtHostDocumentSaveParticipant(extHostLogService, extHostDocuments, rpcProtocol.getProxy(MainContext.MainThreadBulkEdits))); - const extHostNotebook = rpcProtocol.set(ExtHostContext.ExtHostNotebook, new ExtHostNotebookController(rpcProtocol, extHostCommands, extHostDocumentsAndEditors, extHostDocuments, initData.environment, extHostLogService, extensionStoragePaths)); + const extHostNotebook = rpcProtocol.set(ExtHostContext.ExtHostNotebook, new ExtHostNotebookController(rpcProtocol, extHostCommands, extHostDocumentsAndEditors, extHostDocuments, extHostLogService, extensionStoragePaths)); + const extHostNotebookKernels = rpcProtocol.set(ExtHostContext.ExtHostNotebookKernels, new ExtHostNotebookKernels(rpcProtocol, initData, extHostNotebook)); const extHostEditors = rpcProtocol.set(ExtHostContext.ExtHostEditors, new ExtHostEditors(rpcProtocol, extHostDocumentsAndEditors)); const extHostTreeViews = rpcProtocol.set(ExtHostContext.ExtHostTreeViews, new ExtHostTreeViews(rpcProtocol.getProxy(MainContext.MainThreadTreeViews), extHostCommands, extHostLogService)); const extHostEditorInsets = rpcProtocol.set(ExtHostContext.ExtHostEditorInsets, new ExtHostEditorInsets(rpcProtocol.getProxy(MainContext.MainThreadEditorInsets), extHostEditors, initData.environment)); @@ -333,9 +337,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I : extHostTypes.ExtensionKind.UI; const test: typeof vscode.test = { - registerTestProvider(provider) { + registerTestController(provider) { checkProposedApiEnabled(extension); - return extHostTesting.registerTestProvider(provider); + return extHostTesting.registerTestController(extension.identifier.value, provider); }, createDocumentTestObserver(document) { checkProposedApiEnabled(extension); @@ -349,9 +353,12 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I checkProposedApiEnabled(extension); return extHostTesting.runTests(provider); }, - publishTestResult(results, persist = true) { + createTestItem(options: vscode.TestItemOptions, data?: T) { + return new extHostTypes.TestItemImpl(options.id, options.label, options.uri, data); + }, + createTestRun(request, name, persist) { checkProposedApiEnabled(extension); - return extHostTesting.publishExtensionProvidedResults(results, persist); + return extHostTesting.createTestRun(extension.identifier.value, request, name, persist); }, get onDidChangeTestResults() { checkProposedApiEnabled(extension); @@ -363,6 +370,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I }, }; + // todo@connor4312: backwards compatibility for a short period + (test as any).createTestRunTask = test.createTestRun; + // namespace: extensions const extensions: typeof vscode.extensions = { getExtension(extensionId: string): vscode.Extension | undefined { @@ -633,6 +643,12 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I if ('pty' in nameOrOptions) { return extHostTerminalService.createExtensionTerminal(nameOrOptions); } + if (nameOrOptions.message) { + checkProposedApiEnabled(extension); + } + if (nameOrOptions.icon) { + checkProposedApiEnabled(extension); + } return extHostTerminalService.createTerminalFromOptions(nameOrOptions); } return extHostTerminalService.createTerminal(nameOrOptions, shellPath, shellArgs); @@ -900,7 +916,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I checkProposedApiEnabled(extension); return extHostTunnelService.onDidChangeTunnels(listener, thisArg, disposables); }, - registerPortAttributesProvider: (portSelector: { pid?: number, portRange?: [number, number] }, provider: vscode.PortAttributesProvider) => { + registerPortAttributesProvider: (portSelector: { pid?: number, portRange?: [number, number], commandMatcher?: RegExp }, provider: vscode.PortAttributesProvider) => { checkProposedApiEnabled(extension); return extHostTunnelService.registerPortsAttributesProvider(portSelector, provider); }, @@ -908,18 +924,15 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I checkProposedApiEnabled(extension); return extHostTimeline.registerTimelineProvider(scheme, provider, extension.identifier, extHostCommands.converter); }, - get trustState() { - checkProposedApiEnabled(extension); - checkRequiresWorkspaceTrust(extension); - return extHostWorkspace.trustState; + get isTrusted() { + return extHostWorkspace.trusted; }, - requireWorkspaceTrust: (options?: vscode.WorkspaceTrustRequestOptions) => { + requestWorkspaceTrust: (options?: vscode.WorkspaceTrustRequestOptions) => { checkProposedApiEnabled(extension); - checkRequiresWorkspaceTrust(extension); - return extHostWorkspace.requireWorkspaceTrust(options); + return extHostWorkspace.requestWorkspaceTrust(options); }, - onDidChangeWorkspaceTrustState: (listener, thisArgs?, disposables?) => { - return extHostWorkspace.onDidChangeWorkspaceTrustState(listener, thisArgs, disposables); + onDidGrantWorkspaceTrust: (listener, thisArgs?, disposables?) => { + return extHostWorkspace.onDidGrantWorkspaceTrust(listener, thisArgs, disposables); } }; @@ -1045,26 +1058,19 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I }, get notebookDocuments(): vscode.NotebookDocument[] { checkProposedApiEnabled(extension); - return extHostNotebook.notebookDocuments.map(d => d.notebookDocument); - }, - get onDidChangeActiveNotebookKernel() { - checkProposedApiEnabled(extension); - return extHostNotebook.onDidChangeActiveNotebookKernel; + return extHostNotebook.notebookDocuments.map(d => d.apiNotebook); }, registerNotebookSerializer(viewType, serializer, options) { checkProposedApiEnabled(extension); return extHostNotebook.registerNotebookSerializer(extension, viewType, serializer, options); }, - registerNotebookContentProvider: (viewType: string, provider: vscode.NotebookContentProvider, options?: { - transientOutputs: boolean; - transientMetadata: { [K in keyof vscode.NotebookCellMetadata]?: boolean } - }) => { + registerNotebookContentProvider: (viewType, provider, options) => { checkProposedApiEnabled(extension); return extHostNotebook.registerNotebookContentProvider(extension, viewType, provider, options); }, - registerNotebookKernelProvider: (selector: vscode.NotebookDocumentFilter, provider: vscode.NotebookKernelProvider) => { + registerNotebookCellStatusBarItemProvider: (selector: vscode.NotebookSelector, provider: vscode.NotebookCellStatusBarItemProvider) => { checkProposedApiEnabled(extension); - return extHostNotebook.registerNotebookKernelProvider(extension, selector, provider); + return extHostNotebook.registerNotebookCellStatusBarItemProvider(extension, selector, provider); }, createNotebookEditorDecorationType(options: vscode.NotebookDecorationRenderOptions): vscode.NotebookEditorDecorationType { checkProposedApiEnabled(extension); @@ -1094,13 +1100,13 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I checkProposedApiEnabled(extension); return new ExtHostNotebookConcatDocument(extHostNotebook, extHostDocuments, notebook, selector); }, - createCellStatusBarItem(cell: vscode.NotebookCell, alignment?: vscode.NotebookCellStatusBarAlignment, priority?: number): vscode.NotebookCellStatusBarItem { - checkProposedApiEnabled(extension); - return extHostNotebook.createNotebookCellStatusBarItemInternal(cell, alignment, priority); - }, createNotebookCellExecutionTask(uri: vscode.Uri, index: number, kernelId: string): vscode.NotebookCellExecutionTask | undefined { checkProposedApiEnabled(extension); return extHostNotebook.createNotebookCellExecution(uri, index, kernelId); + }, + createNotebookController(id, viewType, label, executeHandler, preloads) { + checkProposedApiEnabled(extension); + return extHostNotebookKernels.createNotebookController(extension, id, viewType, label, executeHandler, preloads); } }; @@ -1233,11 +1239,12 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I InlineHint: extHostTypes.InlineHint, InlineHintKind: extHostTypes.InlineHintKind, RemoteAuthorityResolverError: extHostTypes.RemoteAuthorityResolverError, + RemoteTrustOption: RemoteTrustOption, ResolvedAuthority: extHostTypes.ResolvedAuthority, SourceControlInputBoxValidationType: extHostTypes.SourceControlInputBoxValidationType, ExtensionRuntime: extHostTypes.ExtensionRuntime, TimelineItem: extHostTypes.TimelineItem, - NotebookCellRange: extHostTypes.NotebookCellRange, + NotebookRange: extHostTypes.NotebookRange, NotebookCellKind: extHostTypes.NotebookCellKind, NotebookCellExecutionState: extHostTypes.NotebookCellExecutionState, NotebookDocumentMetadata: extHostTypes.NotebookDocumentMetadata, @@ -1248,11 +1255,13 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I NotebookEditorRevealType: extHostTypes.NotebookEditorRevealType, NotebookCellOutput: extHostTypes.NotebookCellOutput, NotebookCellOutputItem: extHostTypes.NotebookCellOutputItem, + NotebookCellStatusBarItem: extHostTypes.NotebookCellStatusBarItem, + NotebookControllerAffinity: extHostTypes.NotebookControllerAffinity, LinkedEditingRanges: extHostTypes.LinkedEditingRanges, - TestItem: extHostTypes.TestItem, - TestState: extHostTypes.TestState, - TestResult: extHostTypes.TestResult, + TestItemStatus: extHostTypes.TestItemStatus, + TestResultState: extHostTypes.TestResultState, TestMessage: extHostTypes.TestMessage, + TextSearchCompleteMessageType: TextSearchCompleteMessageType, TestMessageSeverity: extHostTypes.TestMessageSeverity, WorkspaceTrustState: extHostTypes.WorkspaceTrustState }; diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 06c7e227..f1a42cd2 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -41,7 +41,7 @@ import * as tasks from 'vs/workbench/api/common/shared/tasks'; import { IRevealOptions, ITreeItem } from 'vs/workbench/common/views'; import { IAdapterDescriptor, IConfig, IDebugSessionReplMode } from 'vs/workbench/contrib/debug/common/debug'; import { ITextQueryBuilderOptions } from 'vs/workbench/contrib/search/common/queryBuilder'; -import { ActivationKind, ExtensionActivationError, ExtensionHostKind } from 'vs/workbench/services/extensions/common/extensions'; +import { ActivationKind, MissingExtensionDependency, ExtensionHostKind } from 'vs/workbench/services/extensions/common/extensions'; import { createExtHostContextProxyIdentifier as createExtId, createMainContextProxyIdentifier as createMainId, IRPCProtocol } from 'vs/workbench/services/extensions/common/proxyIdentifier'; import * as search from 'vs/workbench/services/search/common/search'; import { EditorGroupColumn, SaveReason } from 'vs/workbench/common/editor'; @@ -50,18 +50,21 @@ import { TunnelDto } from 'vs/workbench/api/common/extHostTunnelService'; import { TunnelCreationOptions, TunnelProviderFeatures, TunnelOptions, ProvidedPortAttributes } from 'vs/platform/remote/common/tunnel'; import { Timeline, TimelineChangeEvent, TimelineOptions, TimelineProviderDescriptor, InternalTimelineOptions } from 'vs/workbench/contrib/timeline/common/timeline'; import { revive } from 'vs/base/common/marshalling'; -import { NotebookCellMetadata, NotebookDocumentMetadata, ICellEditOperation, NotebookCellsChangedEventDto, NotebookDataDto, IMainCellDto, INotebookDocumentFilter, TransientMetadata, INotebookCellStatusBarEntry, ICellRange, INotebookDecorationRenderOptions, INotebookExclusiveDocumentFilter, IOutputDto, TransientOptions, IImmediateCellEditOperation } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { NotebookCellMetadata, NotebookDocumentMetadata, ICellEditOperation, NotebookCellsChangedEventDto, NotebookDataDto, IMainCellDto, TransientCellMetadata, INotebookDecorationRenderOptions, INotebookExclusiveDocumentFilter, IOutputDto, TransientOptions, IImmediateCellEditOperation, INotebookCellStatusBarItem, TransientDocumentMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; import { CallHierarchyItem } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy'; import { Dto } from 'vs/base/common/types'; -import { DebugConfigurationProviderTriggerKind, WorkspaceTrustState } from 'vs/workbench/api/common/extHostTypes'; +import { DebugConfigurationProviderTriggerKind, TestResultState } from 'vs/workbench/api/common/extHostTypes'; import { IAccessibilityInformation } from 'vs/platform/accessibility/common/accessibility'; import { IExtensionIdWithVersion } from 'vs/platform/userDataSync/common/extensionsStorageSync'; -import { InternalTestItem, ITestState, RunTestForProviderRequest, RunTestsRequest, TestIdWithSrc, TestsDiff, ISerializedTestResults } from 'vs/workbench/contrib/testing/common/testCollection'; +import { InternalTestItem, RunTestForProviderRequest, RunTestsRequest, TestIdWithSrc, TestsDiff, ISerializedTestResults, ITestMessage, ITestItem, ITestRunTask, ExtensionRunTestsRequest } from 'vs/workbench/contrib/testing/common/testCollection'; import { CandidatePort } from 'vs/workbench/services/remote/common/remoteExplorerService'; -import { WorkspaceTrustRequestOptions, WorkspaceTrustStateChangeEvent } from 'vs/platform/workspace/common/workspaceTrust'; +import { WorkspaceTrustRequestOptions } 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 { IShellLaunchConfig, IShellLaunchConfigDto, ITerminalDimensions, ITerminalEnvironment, ITerminalLaunchError } from 'vs/platform/terminal/common/terminal'; import { ITerminalProfile } from 'vs/workbench/contrib/terminal/common/terminal'; +import { NotebookSelector } from 'vs/workbench/contrib/notebook/common/notebookSelector'; +import { InputValidationType } from 'vs/workbench/contrib/scm/common/scm'; export interface IEnvironment { isExtensionDevelopmentDebug: boolean; @@ -454,7 +457,9 @@ export interface TerminalLaunchConfig { shellPath?: string; shellArgs?: string[] | string; cwd?: string | UriComponents; - env?: { [key: string]: string | null; }; + env?: ITerminalEnvironment; + icon?: string; + initialText?: string; waitOnExit?: boolean; strictEnv?: boolean; hideFromUser?: boolean; @@ -671,10 +676,39 @@ export interface CustomTextEditorCapabilities { readonly supportsMove?: boolean; } +export const enum WebviewMessageArrayBufferViewType { + Int8Array = 1, + Uint8Array = 2, + Uint8ClampedArray = 3, + Int16Array = 4, + Uint16Array = 5, + Int32Array = 6, + Uint32Array = 7, + Float32Array = 8, + Float64Array = 9, + BigInt64Array = 10, + BigUint64Array = 11, +} + +export interface WebviewMessageArrayBufferReference { + readonly $$vscode_array_buffer_reference$$: true, + + readonly index: number; + + /** + * Tracks if the reference is to a view instead of directly to an ArrayBuffer. + */ + readonly view?: { + readonly type: WebviewMessageArrayBufferViewType; + readonly byteLength: number; + readonly byteOffset: number; + }; +} + export interface MainThreadWebviewsShape extends IDisposable { $setHtml(handle: WebviewHandle, value: string): void; $setOptions(handle: WebviewHandle, options: IWebviewOptions): void; - $postMessage(handle: WebviewHandle, value: any): Promise + $postMessage(handle: WebviewHandle, value: any, ...buffers: VSBuffer[]): Promise } export interface MainThreadWebviewPanelsShape extends IDisposable { @@ -686,6 +720,7 @@ export interface MainThreadWebviewPanelsShape extends IDisposable { title: string; webviewOptions: IWebviewOptions; panelOptions: IWebviewPanelOptions; + serializeBuffersForPostMessage: boolean; }, showOptions: WebviewPanelShowOptions, ): void; @@ -694,13 +729,13 @@ export interface MainThreadWebviewPanelsShape extends IDisposable { $setTitle(handle: WebviewHandle, value: string): void; $setIconPath(handle: WebviewHandle, value: { light: UriComponents, dark: UriComponents; } | undefined): void; - $registerSerializer(viewType: string): void; + $registerSerializer(viewType: string, options: { serializeBuffersForPostMessage: boolean }): void; $unregisterSerializer(viewType: string): void; } export interface MainThreadCustomEditorsShape extends IDisposable { - $registerTextEditorProvider(extension: WebviewExtensionDescription, viewType: string, options: IWebviewPanelOptions, capabilities: CustomTextEditorCapabilities): void; - $registerCustomEditorProvider(extension: WebviewExtensionDescription, viewType: string, options: IWebviewPanelOptions, supportsMultipleEditorsPerDocument: boolean): void; + $registerTextEditorProvider(extension: WebviewExtensionDescription, viewType: string, options: IWebviewPanelOptions, capabilities: CustomTextEditorCapabilities, serializeBuffersForPostMessage: boolean): void; + $registerCustomEditorProvider(extension: WebviewExtensionDescription, viewType: string, options: IWebviewPanelOptions, supportsMultipleEditorsPerDocument: boolean, serializeBuffersForPostMessage: boolean): void; $unregisterEditorProvider(viewType: string): void; $onDidEdit(resource: UriComponents, viewType: string, editId: number, label: string | undefined): void; @@ -708,7 +743,7 @@ export interface MainThreadCustomEditorsShape extends IDisposable { } export interface MainThreadWebviewViewsShape extends IDisposable { - $registerWebviewViewProvider(extension: WebviewExtensionDescription, viewType: string, options?: { retainContextWhenHidden?: boolean }): void; + $registerWebviewViewProvider(extension: WebviewExtensionDescription, viewType: string, options: { retainContextWhenHidden?: boolean, serializeBuffersForPostMessage: boolean }): void; $unregisterWebviewViewProvider(viewType: string): void; $setWebviewViewTitle(handle: WebviewHandle, value: string | undefined): void; @@ -726,7 +761,7 @@ export interface WebviewPanelViewStateData { } export interface ExtHostWebviewsShape { - $onMessage(handle: WebviewHandle, message: any): void; + $onMessage(handle: WebviewHandle, jsonSerializedMessage: string, ...buffers: VSBuffer[]): void; $onMissingCsp(handle: WebviewHandle, extensionId: string): void; } @@ -827,37 +862,69 @@ export interface INotebookDocumentShowOptions { position?: EditorGroupColumn; preserveFocus?: boolean; pinned?: boolean; - selection?: ICellRange; + selections?: ICellRange[]; } -export type INotebookCellStatusBarEntryDto = Dto; +export type INotebookCellStatusBarEntryDto = Dto; + +export interface INotebookCellStatusBarListDto { + items: INotebookCellStatusBarEntryDto[]; + cacheId: number; +} export interface MainThreadNotebookShape extends IDisposable { $registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string, options: { transientOutputs: boolean; - transientMetadata: TransientMetadata; + transientCellMetadata: TransientCellMetadata; + transientDocumentMetadata: TransientDocumentMetadata; viewOptions?: { displayName: string; filenamePattern: (string | IRelativePattern | INotebookExclusiveDocumentFilter)[]; exclusive: boolean; }; }): Promise; - $updateNotebookProviderOptions(viewType: string, options?: { transientOutputs: boolean; transientMetadata: TransientMetadata; }): Promise; + $updateNotebookProviderOptions(viewType: string, options?: { transientOutputs: boolean; transientCellMetadata: TransientCellMetadata; transientDocumentMetadata: TransientDocumentMetadata; }): Promise; $unregisterNotebookProvider(viewType: string): Promise; $registerNotebookSerializer(handle: number, extension: NotebookExtensionDescription, viewType: string, options: TransientOptions): void; $unregisterNotebookSerializer(handle: number): void; - $registerNotebookKernelProvider(extension: NotebookExtensionDescription, handle: number, documentFilter: INotebookDocumentFilter): Promise; - $unregisterNotebookKernelProvider(handle: number): Promise; - $onNotebookKernelChange(handle: number, uri: UriComponents | undefined): void; - $trySaveDocument(uri: UriComponents): Promise; - $tryApplyEdits(viewType: string, resource: UriComponents, modelVersionId: number, edits: ICellEditOperation[]): Promise; - $applyEdits(resource: UriComponents, edits: IImmediateCellEditOperation[], computeUndoRedo?: boolean): Promise; - $postMessage(editorId: string, forRendererId: string | undefined, value: any): Promise; - $setStatusBarEntry(id: number, statusBarEntry: INotebookCellStatusBarEntryDto): Promise; - $tryOpenDocument(uriComponents: UriComponents): Promise; + $registerNotebookCellStatusBarItemProvider(handle: number, eventHandle: number | undefined, selector: NotebookSelector): Promise; + $unregisterNotebookCellStatusBarItemProvider(handle: number, eventHandle: number | undefined): Promise; + $emitCellStatusBarEvent(eventHandle: number): void; +} + +export interface MainThreadNotebookEditorsShape extends IDisposable { $tryShowNotebookDocument(uriComponents: UriComponents, viewType: string, options: INotebookDocumentShowOptions): Promise; $tryRevealRange(id: string, range: ICellRange, revealType: NotebookEditorRevealType): Promise; $registerNotebookEditorDecorationType(key: string, options: INotebookDecorationRenderOptions): void; $removeNotebookEditorDecorationType(key: string): void; $trySetDecorations(id: string, range: ICellRange, decorationKey: string): void; + $tryApplyEdits(editorId: string, modelVersionId: number, cellEdits: ICellEditOperation[]): Promise +} + +export interface MainThreadNotebookDocumentsShape extends IDisposable { + $tryOpenDocument(uriComponents: UriComponents): Promise; + $trySaveDocument(uri: UriComponents): Promise; + $applyEdits(resource: UriComponents, edits: IImmediateCellEditOperation[], computeUndoRedo?: boolean): Promise; +} + +export interface INotebookKernelDto2 { + id: string; + viewType: string; + extensionId: ExtensionIdentifier; + extensionLocation: UriComponents; + label: string; + detail?: string; + description?: string; + supportedLanguages?: string[]; + supportsInterrupt?: boolean; + hasExecutionOrder?: boolean; + preloads?: { uri: UriComponents; provides: string[] }[]; +} + +export interface MainThreadNotebookKernelsShape extends IDisposable { + $postMessage(handle: number, editorId: string | undefined, message: any): Promise; + $addKernel(handle: number, data: INotebookKernelDto2): Promise; + $updateKernel(handle: number, data: Partial): void; + $removeKernel(handle: number): void; + $updateNotebookPriority(handle: number, uri: UriComponents, value: number | undefined): void; } export interface MainThreadUrlsShape extends IDisposable { @@ -891,7 +958,7 @@ export interface MainThreadWorkspaceShape extends IDisposable { $saveAll(includeUntitled?: boolean): Promise; $updateWorkspaceFolders(extensionName: string, index: number, deleteCount: number, workspaceFoldersToAdd: { uri: UriComponents, name?: string; }[]): Promise; $resolveProxy(url: string): Promise; - $requireWorkspaceTrust(options?: WorkspaceTrustRequestOptions): Promise; + $requestWorkspaceTrust(options?: WorkspaceTrustRequestOptions): Promise; } export interface IFileChangeDto { @@ -945,7 +1012,7 @@ export interface MainThreadExtensionServiceShape extends IDisposable { $activateExtension(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise; $onWillActivateExtension(extensionId: ExtensionIdentifier): Promise; $onDidActivateExtension(extensionId: ExtensionIdentifier, codeLoadingTime: number, activateCallTime: number, activateResolvedTime: number, activationReason: ExtensionActivationReason): void; - $onExtensionActivationError(extensionId: ExtensionIdentifier, error: ExtensionActivationError): Promise; + $onExtensionActivationError(extensionId: ExtensionIdentifier, error: SerializedError, missingExtensionDependency: MissingExtensionDependency | null): Promise; $onExtensionRuntimeError(extensionId: ExtensionIdentifier, error: SerializedError): void; $setPerformanceMarks(marks: performance.PerformanceMark[]): Promise; } @@ -999,6 +1066,8 @@ export interface MainThreadSCMShape extends IDisposable { $setInputBoxValue(sourceControlHandle: number, value: string): void; $setInputBoxPlaceholder(sourceControlHandle: number, placeholder: string): void; $setInputBoxVisibility(sourceControlHandle: number, visible: boolean): void; + $setInputBoxFocus(sourceControlHandle: number): void; + $showValidationMessage(sourceControlHandle: number, message: string, type: InputValidationType): void; $setValidationProviderIsEnabled(sourceControlHandle: number, enabled: boolean): void; } @@ -1059,6 +1128,7 @@ export enum CandidatePortSource { export interface PortAttributesProviderSelector { pid?: number; portRange?: [number, number]; + commandMatcher?: RegExp; } export interface MainThreadTunnelServiceShape extends IDisposable { @@ -1167,10 +1237,10 @@ export interface ExtHostTreeViewsShape { } export interface ExtHostWorkspaceShape { - $initializeWorkspace(workspace: IWorkspaceData | null, trustState: WorkspaceTrustState): void; + $initializeWorkspace(workspace: IWorkspaceData | null, trusted: boolean): void; $acceptWorkspaceData(workspace: IWorkspaceData | null): void; $handleTextSearchResult(result: search.IRawFileMatch2, requestId: number): void; - $onDidChangeWorkspaceTrustState(state: WorkspaceTrustStateChangeEvent): void; + $onDidGrantWorkspaceTrust(): void; } export interface ExtHostFileSystemInfoShape { @@ -1268,8 +1338,8 @@ export interface IWillRunFileOperationParticipation { export interface ExtHostFileSystemEventServiceShape { $onFileEvent(events: FileSystemEvents): void; - $onWillRunFileOperation(operation: files.FileOperation, files: SourceTargetPair[], timeout: number, token: CancellationToken): Promise; - $onDidRunFileOperation(operation: files.FileOperation, files: SourceTargetPair[]): void; + $onWillRunFileOperation(operation: files.FileOperation, files: readonly SourceTargetPair[], timeout: number, token: CancellationToken): Promise; + $onDidRunFileOperation(operation: files.FileOperation, files: readonly SourceTargetPair[]): void; } export interface ObjectIdentifier { @@ -1609,15 +1679,6 @@ export interface ExtHostTelemetryShape { $onDidChangeTelemetryEnabled(enabled: boolean): void; } -export interface IShellLaunchConfigDto { - name?: string; - executable?: string; - args?: string[] | string; - cwd?: string | UriComponents; - env?: { [key: string]: string | null; }; - hideFromUser?: boolean; -} - export interface IShellAndArgsDto { shell: string; args: string[] | string | undefined; @@ -1656,9 +1717,7 @@ export interface ExtHostTerminalServiceShape { $acceptProcessRequestInitialCwd(id: number): void; $acceptProcessRequestCwd(id: number): void; $acceptProcessRequestLatency(id: number): number; - $acceptWorkspacePermissionsChanged(isAllowed: boolean): void; - // TODO: Change quickLaunchOnly to "includeAutoDetected" or something similar - $getAvailableProfiles(quickLaunchOnly: boolean): Promise; + $getAvailableProfiles(configuredProfilesOnly: boolean): Promise; $getDefaultShellAndArgs(useAutomationShell: boolean): Promise; $provideLinks(id: number, line: string): Promise; $activateLink(id: number, linkId: number): void; @@ -1857,34 +1916,47 @@ export interface INotebookKernelInfoDto2 { description?: string; detail?: string; isPreferred?: boolean; - preloads?: UriComponents[]; + preloads?: { uri: UriComponents; provides: string[] }[]; supportedLanguages?: string[] implementsInterrupt?: boolean; } -export interface ExtHostNotebookShape { - $resolveNotebookEditor(viewType: string, uri: UriComponents, editorId: string): Promise; - $acceptNotebookActiveKernelChange(event: { uri: UriComponents, providerHandle: number | undefined, kernelFriendlyId: string | undefined }): void; - $provideNotebookKernels(handle: number, uri: UriComponents, token: CancellationToken): Promise; - $resolveNotebookKernel(handle: number, editorId: string, uri: UriComponents, kernelId: string, token: CancellationToken): Promise; - $executeNotebookKernelFromProvider(handle: number, uri: UriComponents, kernelId: string, cellRanges: ICellRange[]): Promise; - $cancelNotebookCellExecution(handle: number, uri: UriComponents, kernelId: string, cellRange: ICellRange[]): Promise; - $onDidReceiveMessage(editorId: string, rendererId: string | undefined, message: unknown): void; +export interface ExtHostNotebookShape extends ExtHostNotebookDocumentsAndEditorsShape, ExtHostNotebookDocumentsShape, ExtHostNotebookEditorsShape { + $provideNotebookCellStatusBarItems(handle: number, uri: UriComponents, index: number, token: CancellationToken): Promise; + $releaseNotebookCellStatusBarItems(id: number): void; $openNotebook(viewType: string, uri: UriComponents, backupId: string | undefined, untitledDocumentData: VSBuffer | undefined, token: CancellationToken): Promise; $saveNotebook(viewType: string, uri: UriComponents, token: CancellationToken): Promise; $saveNotebookAs(viewType: string, uri: UriComponents, target: UriComponents, token: CancellationToken): Promise; $backupNotebook(viewType: string, uri: UriComponents, cancellation: CancellationToken): Promise; - $dataToNotebook(handle: number, data: VSBuffer): Promise; - $notebookToData(handle: number, data: NotebookDataDto): Promise; + $dataToNotebook(handle: number, data: VSBuffer, token: CancellationToken): Promise; + $notebookToData(handle: number, data: NotebookDataDto, token: CancellationToken): Promise; +} +export interface ExtHostNotebookDocumentsAndEditorsShape { + $acceptDocumentAndEditorsDelta(delta: INotebookDocumentsAndEditorsDelta): void; +} + +export interface ExtHostNotebookDocumentsShape { $acceptModelChanged(uriComponents: UriComponents, event: NotebookCellsChangedEventDto, isDirty: boolean): void; $acceptDirtyStateChanged(uriComponents: UriComponents, isDirty: boolean): void; $acceptModelSaved(uriComponents: UriComponents): void; $acceptDocumentPropertiesChanged(uriComponents: UriComponents, data: INotebookDocumentPropertiesChangeData): void; - $acceptDocumentAndEditorsDelta(delta: INotebookDocumentsAndEditorsDelta): void; +} + +export type INotebookEditorViewColumnInfo = Record; + +export interface ExtHostNotebookEditorsShape { $acceptEditorPropertiesChanged(id: string, data: INotebookEditorPropertiesChangeData): void; + $acceptEditorViewColumns(data: INotebookEditorViewColumnInfo): void; +} + +export interface ExtHostNotebookKernelsShape { + $acceptSelection(handle: number, uri: UriComponents, value: boolean): void; + $executeCells(handle: number, uri: UriComponents, handles: number[]): Promise; + $cancelCells(handle: number, uri: UriComponents, handles: number[]): Promise; + $acceptRendererMessage(handle: number, editorId: string, message: any): void; } export interface ExtHostStorageShape { @@ -1927,14 +1999,39 @@ export interface ExtHostTestingShape { } export interface MainThreadTestingShape { - $registerTestProvider(id: string): void; - $unregisterTestProvider(id: string): void; + /** Registeres that there's a test controller with the given ID */ + $registerTestController(id: string): void; + /** Diposes of the test controller with the given ID */ + $unregisterTestController(id: string): void; + /** Requests tests from the given resource/uri, from the observer API. */ $subscribeToDiffs(resource: ExtHostTestingResource, uri: UriComponents): void; + /** Stops requesting tests from the given resource/uri, from the observer API. */ $unsubscribeFromDiffs(resource: ExtHostTestingResource, uri: UriComponents): void; + /** Publishes that new tests were available on the given source. */ $publishDiff(resource: ExtHostTestingResource, uri: UriComponents, diff: TestsDiff): void; - $updateTestStateInRun(runId: string, testId: string, state: ITestState): void; + /** Request by an extension to run tests. */ $runTests(req: RunTestsRequest, token: CancellationToken): Promise; - $publishExtensionProvidedResults(results: ISerializedTestResults, persist: boolean): void; + + // --- test run handling: + /** + * Adds tests to the run. The tests are given in descending depth. The first + * item will be a previously-known test, or a test root. + */ + $addTestsToRun(runId: string, tests: ITestItem[]): void; + /** Updates the state of a test run in the given run. */ + $updateTestStateInRun(runId: string, taskId: string, testId: string, state: TestResultState, duration?: number): void; + /** Appends a message to a test in the run. */ + $appendTestMessageInRun(runId: string, taskId: string, testId: string, message: ITestMessage): void; + /** Appends raw output to the test run.. */ + $appendOutputToRun(runId: string, taskId: string, output: VSBuffer): void; + /** Signals a task in a test run started. */ + $startedTestRunTask(runId: string, task: ITestRunTask): void; + /** Signals a task in a test run ended. */ + $finishedTestRunTask(runId: string, taskId: string): void; + /** Start a new extension-provided test run. */ + $startedExtensionTestRun(req: ExtensionRunTestsRequest): void; + /** Signals that an extension-provided test run finished. */ + $finishedExtensionTestRun(runId: string): void; } // --- proxy identifiers @@ -1987,6 +2084,9 @@ export const MainContext = { MainThreadWindow: createMainId('MainThreadWindow'), MainThreadLabelService: createMainId('MainThreadLabelService'), MainThreadNotebook: createMainId('MainThreadNotebook'), + MainThreadNotebookDocuments: createMainId('MainThreadNotebookDocumentsShape'), + MainThreadNotebookEditors: createMainId('MainThreadNotebookEditorsShape'), + MainThreadNotebookKernels: createMainId('MainThreadNotebookKernels'), MainThreadTheming: createMainId('MainThreadTheming'), MainThreadTunnelService: createMainId('MainThreadTunnelService'), MainThreadTimeline: createMainId('MainThreadTimeline'), @@ -2033,6 +2133,7 @@ export const ExtHostContext = { ExtHostOutputService: createMainId('ExtHostOutputService'), ExtHosLabelService: createMainId('ExtHostLabelService'), ExtHostNotebook: createMainId('ExtHostNotebook'), + ExtHostNotebookKernels: createMainId('ExtHostNotebookKernels'), ExtHostTheming: createMainId('ExtHostTheming'), ExtHostTunnelService: createMainId('ExtHostTunnelService'), ExtHostAuthentication: createMainId('ExtHostAuthentication'), diff --git a/src/vs/workbench/api/common/extHostApiCommands.ts b/src/vs/workbench/api/common/extHostApiCommands.ts index c2a22506..f088f4de 100644 --- a/src/vs/workbench/api/common/extHostApiCommands.ts +++ b/src/vs/workbench/api/common/extHostApiCommands.ts @@ -18,7 +18,7 @@ import { ICommandsExecutor, RemoveFromRecentlyOpenedAPICommand, OpenIssueReporte import { isFalsyOrEmpty } from 'vs/base/common/arrays'; import { IRange } from 'vs/editor/common/core/range'; import { IPosition } from 'vs/editor/common/core/position'; -import { TransientMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { TransientCellMetadata, TransientDocumentMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { VSBuffer } from 'vs/base/common/buffer'; import { decodeSemanticTokensDto } from 'vs/editor/common/services/semanticTokensDto'; @@ -30,13 +30,13 @@ const newCommands: ApiCommand[] = [ new ApiCommand( 'vscode.executeDocumentHighlights', '_executeDocumentHighlights', 'Execute document highlight provider.', [ApiCommandArgument.Uri, ApiCommandArgument.Position], - new ApiCommandResult('A promise that resolves to an array of SymbolInformation and DocumentSymbol instances.', tryMapWith(typeConverters.DocumentHighlight.to)) + new ApiCommandResult('A promise that resolves to an array of DocumentHighlight-instances.', tryMapWith(typeConverters.DocumentHighlight.to)) ), // -- document symbols new ApiCommand( 'vscode.executeDocumentSymbolProvider', '_executeDocumentSymbolProvider', 'Execute document symbol provider.', [ApiCommandArgument.Uri], - new ApiCommandResult('A promise that resolves to an array of DocumentHighlight-instances.', (value, apiArgs) => { + new ApiCommandResult('A promise that resolves to an array of SymbolInformation and DocumentSymbol instances.', (value, apiArgs) => { if (isFalsyOrEmpty(value)) { return undefined; @@ -60,7 +60,7 @@ const newCommands: ApiCommand[] = [ range!: vscode.Range; selectionRange!: vscode.Range; children!: vscode.DocumentSymbol[]; - containerName!: string; + override containerName!: string; } return value.map(MergedInfo.to); @@ -343,18 +343,22 @@ const newCommands: ApiCommand[] = [ new ApiCommandResult<{ viewType: string; displayName: string; - options: { transientOutputs: boolean; transientMetadata: TransientMetadata }; + options: { transientOutputs: boolean; transientCellMetadata: TransientCellMetadata; transientDocumentMetadata: TransientDocumentMetadata; }; filenamePattern: (string | types.RelativePattern | { include: string | types.RelativePattern, exclude: string | types.RelativePattern })[] }[], { viewType: string; displayName: string; - filenamePattern: vscode.NotebookFilenamePattern[]; + filenamePattern: (vscode.GlobPattern | { include: vscode.GlobPattern; exclude: vscode.GlobPattern; })[]; options: vscode.NotebookDocumentContentOptions; }[] | undefined>('A promise that resolves to an array of NotebookContentProvider static info objects.', tryMapWith(item => { return { viewType: item.viewType, displayName: item.displayName, - options: { transientOutputs: item.options.transientOutputs, transientMetadata: item.options.transientMetadata }, + options: { + transientOutputs: item.options.transientOutputs, + transientCellMetadata: item.options.transientCellMetadata, + transientDocumentMetadata: item.options.transientDocumentMetadata + }, filenamePattern: item.filenamePattern.map(pattern => typeConverters.NotebookExclusiveDocumentPattern.to(pattern)) }; })) diff --git a/src/vs/workbench/api/common/extHostCustomEditors.ts b/src/vs/workbench/api/common/extHostCustomEditors.ts index ab9e63c1..69557c2a 100644 --- a/src/vs/workbench/api/common/extHostCustomEditors.ts +++ b/src/vs/workbench/api/common/extHostCustomEditors.ts @@ -14,7 +14,7 @@ import { IExtensionDescription } from 'vs/platform/extensions/common/extensions' import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths'; import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters'; -import { ExtHostWebviews, toExtensionData } from 'vs/workbench/api/common/extHostWebview'; +import { ExtHostWebviews, shouldSerializeBuffersForPostMessage, toExtensionData } from 'vs/workbench/api/common/extHostWebview'; import { ExtHostWebviewPanels } from 'vs/workbench/api/common/extHostWebviewPanels'; import { EditorGroupColumn } from 'vs/workbench/common/editor'; import type * as vscode from 'vscode'; @@ -183,7 +183,7 @@ export class ExtHostCustomEditors implements extHostProtocol.ExtHostCustomEditor disposables.add(this._editorProviders.addTextProvider(viewType, extension, provider)); this._proxy.$registerTextEditorProvider(toExtensionData(extension), viewType, options.webviewOptions || {}, { supportsMove: !!provider.moveCustomTextEditor, - }); + }, shouldSerializeBuffersForPostMessage(extension)); } else { disposables.add(this._editorProviders.addCustomProvider(viewType, extension, provider)); @@ -199,7 +199,7 @@ export class ExtHostCustomEditors implements extHostProtocol.ExtHostCustomEditor })); } - this._proxy.$registerCustomEditorProvider(toExtensionData(extension), viewType, options.webviewOptions || {}, !!options.supportsMultipleEditorsPerDocument); + this._proxy.$registerCustomEditorProvider(toExtensionData(extension), viewType, options.webviewOptions || {}, !!options.supportsMultipleEditorsPerDocument, shouldSerializeBuffersForPostMessage(extension)); } return extHostTypes.Disposable.from( diff --git a/src/vs/workbench/api/common/extHostDebugService.ts b/src/vs/workbench/api/common/extHostDebugService.ts index 57fb7472..67477c58 100644 --- a/src/vs/workbench/api/common/extHostDebugService.ts +++ b/src/vs/workbench/api/common/extHostDebugService.ts @@ -384,7 +384,7 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E } }; } - return this._variableResolver.resolveAny(ws, config); + return this._variableResolver.resolveAnyAsync(ws, config); } protected createDebugAdapter(adapter: IAdapterDescriptor, session: ExtHostDebugSession): AbstractDebugAdapter | undefined { @@ -990,7 +990,7 @@ export class ExtHostVariableResolverService extends AbstractVariableResolverServ } return undefined; } - }, undefined, process.env); + }, undefined, Promise.resolve(process.env)); } } diff --git a/src/vs/workbench/api/common/extHostDiagnostics.ts b/src/vs/workbench/api/common/extHostDiagnostics.ts index ef990862..1114e309 100644 --- a/src/vs/workbench/api/common/extHostDiagnostics.ts +++ b/src/vs/workbench/api/common/extHostDiagnostics.ts @@ -288,7 +288,7 @@ export class ExtHostDiagnostics implements ExtHostDiagnosticsShape { super(name!, owner, ExtHostDiagnostics._maxDiagnosticsPerFile, loggingProxy, _onDidChangeDiagnostics); _collections.set(owner, this); } - dispose() { + override dispose() { super.dispose(); _collections.delete(owner); } diff --git a/src/vs/workbench/api/common/extHostDocumentData.ts b/src/vs/workbench/api/common/extHostDocumentData.ts index 0cd5c837..d244a16b 100644 --- a/src/vs/workbench/api/common/extHostDocumentData.ts +++ b/src/vs/workbench/api/common/extHostDocumentData.ts @@ -42,7 +42,7 @@ export class ExtHostDocumentData extends MirrorTextModel { super(uri, lines, eol, versionId); } - dispose(): void { + override dispose(): void { // we don't really dispose documents but let // extensions still read from them. some // operations, live saving, will now error tho diff --git a/src/vs/workbench/api/common/extHostExtensionActivator.ts b/src/vs/workbench/api/common/extHostExtensionActivator.ts index 7e86c340..3bda0335 100644 --- a/src/vs/workbench/api/common/extHostExtensionActivator.ts +++ b/src/vs/workbench/api/common/extHostExtensionActivator.ts @@ -3,12 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; import type * as vscode from 'vscode'; import { IDisposable } from 'vs/base/common/lifecycle'; import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; -import { ExtensionActivationError, MissingDependencyError } from 'vs/workbench/services/extensions/common/extensions'; +import { MissingExtensionDependency } from 'vs/workbench/services/extensions/common/extensions'; import { ILogService } from 'vs/platform/log/common/log'; const NO_OP_VOID_PROMISE = Promise.resolve(undefined); @@ -158,7 +157,7 @@ export class FailedExtension extends ActivatedExtension { } export interface IExtensionsActivatorHost { - onExtensionActivationError(extensionId: ExtensionIdentifier, error: ExtensionActivationError): void; + onExtensionActivationError(extensionId: ExtensionIdentifier, error: Error | null, missingExtensionDependency: MissingExtensionDependency | null): void; actualActivateExtension(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise; } @@ -255,8 +254,12 @@ export class ExtensionsActivator { const currentExtension = this._registry.getExtensionDescription(currentActivation.id); if (!currentExtension) { // Error condition 0: unknown extension - this._host.onExtensionActivationError(currentActivation.id, new MissingDependencyError(currentActivation.id.value)); - const error = new Error(`Unknown dependency '${currentActivation.id.value}'`); + const error = new Error(`Cannot activate unknown extension '${currentActivation.id.value}'`); + this._host.onExtensionActivationError( + currentActivation.id, + error, + new MissingExtensionDependency(currentActivation.id.value) + ); this._activatedExtensions.set(ExtensionIdentifier.toKey(currentActivation.id), new FailedExtension(error)); return; } @@ -280,9 +283,16 @@ export class ExtensionsActivator { if (dep && dep.activationFailed) { // Error condition 2: a dependency has already failed activation - this._host.onExtensionActivationError(currentExtension.identifier, nls.localize('failedDep1', "Cannot activate extension '{0}' because it depends on extension '{1}', which failed to activate.", currentExtension.displayName || currentExtension.identifier.value, depId)); - const error = new Error(`Dependency ${depId} failed to activate`); + const currentExtensionFriendlyName = currentExtension.displayName || currentExtension.identifier.value; + const depDesc = this._registry.getExtensionDescription(depId); + const depFriendlyName = (depDesc ? depDesc.displayName || depId : depId); + const error = new Error(`Cannot activate the '${currentExtensionFriendlyName}' extension because its dependency '${depFriendlyName}' failed to activate`); (error).detail = dep.activationFailedError; + this._host.onExtensionActivationError( + currentExtension.identifier, + error, + null + ); this._activatedExtensions.set(ExtensionIdentifier.toKey(currentExtension.identifier), new FailedExtension(error)); return; } @@ -309,8 +319,13 @@ export class ExtensionsActivator { } // Error condition 1: unknown dependency - this._host.onExtensionActivationError(currentExtension.identifier, new MissingDependencyError(depId)); - const error = new Error(`Unknown dependency '${depId}'`); + const currentExtensionFriendlyName = currentExtension.displayName || currentExtension.identifier.value; + const error = new Error(`Cannot activate the '${currentExtensionFriendlyName}' extension because it depends on unknown extension '${depId}'`); + this._host.onExtensionActivationError( + currentExtension.identifier, + error, + new MissingExtensionDependency(depId) + ); this._activatedExtensions.set(ExtensionIdentifier.toKey(currentExtension.identifier), new FailedExtension(error)); return; } @@ -372,7 +387,25 @@ export class ExtensionsActivator { } const newlyActivatingExtension = this._host.actualActivateExtension(extensionId, reason).then(undefined, (err) => { - this._host.onExtensionActivationError(extensionId, nls.localize('activationError', "Activating extension '{0}' failed: {1}.", extensionId.value, err.message)); + + const error = new Error(); + if (err && err.name) { + error.name = err.name; + } + if (err && err.message) { + error.message = `Activating extension '${extensionId.value}' failed: ${err.message}.`; + } else { + error.message = `Activating extension '${extensionId.value}' failed: ${err}.`; + } + if (err && err.stack) { + error.stack = err.stack; + } + + this._host.onExtensionActivationError( + extensionId, + error, + null + ); this._logService.error(`Activating extension ${extensionId.value} failed due to an error:`); this._logService.error(err); // Treat the extension as being empty diff --git a/src/vs/workbench/api/common/extHostExtensionService.ts b/src/vs/workbench/api/common/extHostExtensionService.ts index 56f193da..d7c4c6a4 100644 --- a/src/vs/workbench/api/common/extHostExtensionService.ts +++ b/src/vs/workbench/api/common/extHostExtensionService.ts @@ -17,12 +17,11 @@ import { ExtHostConfiguration, IExtHostConfiguration } from 'vs/workbench/api/co import { ActivatedExtension, EmptyExtension, ExtensionActivationReason, ExtensionActivationTimes, ExtensionActivationTimesBuilder, ExtensionsActivator, IExtensionAPI, IExtensionModule, HostExtension, ExtensionActivationTimesFragment } from 'vs/workbench/api/common/extHostExtensionActivator'; import { ExtHostStorage, IExtHostStorage } from 'vs/workbench/api/common/extHostStorage'; import { ExtHostWorkspace, IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; -import { ExtensionActivationError, checkProposedApiEnabled, ActivationKind } from 'vs/workbench/services/extensions/common/extensions'; +import { MissingExtensionDependency, checkProposedApiEnabled, ActivationKind } from 'vs/workbench/services/extensions/common/extensions'; import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry'; import * as errors from 'vs/base/common/errors'; import type * as vscode from 'vscode'; import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; -import { Schemas } from 'vs/base/common/network'; import { VSBuffer } from 'vs/base/common/buffer'; import { ExtensionGlobalMemento, ExtensionMemento } from 'vs/workbench/api/common/extHostMemento'; import { RemoteAuthorityResolverError, ExtensionKind, ExtensionMode, ExtensionRuntime } from 'vs/workbench/api/common/extHostTypes'; @@ -161,8 +160,8 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme this._initData.resolvedExtensions, this._initData.hostExtensions, { - onExtensionActivationError: (extensionId: ExtensionIdentifier, error: ExtensionActivationError): void => { - this._mainThreadExtensionsProxy.$onExtensionActivationError(extensionId, error); + onExtensionActivationError: (extensionId: ExtensionIdentifier, error: Error, missingExtensionDependency: MissingExtensionDependency | null): void => { + this._mainThreadExtensionsProxy.$onExtensionActivationError(extensionId, errors.transformErrorForSerialization(error), missingExtensionDependency); }, actualActivateExtension: async (extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise => { @@ -561,17 +560,15 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme private async _doHandleExtensionTests(): Promise { const { extensionDevelopmentLocationURI, extensionTestsLocationURI } = this._initData.environment; - if (!extensionDevelopmentLocationURI || !extensionTestsLocationURI || extensionTestsLocationURI.scheme !== Schemas.file) { + if (!extensionDevelopmentLocationURI || !extensionTestsLocationURI) { throw new Error(nls.localize('extensionTestError1', "Cannot load test runner.")); } - const extensionTestsPath = originalFSPath(extensionTestsLocationURI); - // Require the test runner via node require from the provided path const testRunner: ITestRunner | INewTestRunner | undefined = await this._loadCommonJSModule(null, extensionTestsLocationURI, new ExtensionActivationTimesBuilder(false)); if (!testRunner || typeof testRunner.run !== 'function') { - throw new Error(nls.localize('extensionTestError', "Path {0} does not point to a valid extension test runner.", extensionTestsPath)); + throw new Error(nls.localize('extensionTestError', "Path {0} does not point to a valid extension test runner.", extensionTestsLocationURI.toString())); } // Execute the runner if it follows the old `run` spec @@ -584,6 +581,8 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme } }; + const extensionTestsPath = originalFSPath(extensionTestsLocationURI); // for the old test runner API + const runResult = testRunner.run(extensionTestsPath, oldTestRunnerCallback); // Using the new API `run(): Promise` @@ -655,10 +654,10 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme } try { + this._disposables.add(await this._extHostTunnelService.setTunnelExtensionFunctions(resolver)); performance.mark(`code/extHost/willResolveAuthority/${authorityPrefix}`); const result = await resolver.resolve(remoteAuthority, { resolveAttempt }); performance.mark(`code/extHost/didResolveAuthorityOK/${authorityPrefix}`); - this._disposables.add(await this._extHostTunnelService.setTunnelExtensionFunctions(resolver)); // Split merged API result into separate authority/options const authority: ResolvedAuthority = { @@ -668,7 +667,8 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme connectionToken: result.connectionToken }; const options: ResolvedOptions = { - extensionHostEnv: result.extensionHostEnv + extensionHostEnv: result.extensionHostEnv, + trust: result.trust }; return { diff --git a/src/vs/workbench/api/common/extHostMemento.ts b/src/vs/workbench/api/common/extHostMemento.ts index bac8ab5f..31c1676e 100644 --- a/src/vs/workbench/api/common/extHostMemento.ts +++ b/src/vs/workbench/api/common/extHostMemento.ts @@ -7,6 +7,7 @@ import type * as vscode from 'vscode'; import { IDisposable } from 'vs/base/common/lifecycle'; import { ExtHostStorage } from 'vs/workbench/api/common/extHostStorage'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { DeferredPromise, RunOnceScheduler } from 'vs/base/common/async'; export class ExtensionMemento implements vscode.Memento { @@ -18,6 +19,9 @@ export class ExtensionMemento implements vscode.Memento { private _value?: { [n: string]: any; }; private readonly _storageListener: IDisposable; + private _deferredPromises: Map> = new Map(); + private _scheduler: RunOnceScheduler; + constructor(id: string, global: boolean, storage: ExtHostStorage) { this._id = id; this._shared = global; @@ -33,6 +37,23 @@ export class ExtensionMemento implements vscode.Memento { this._value = e.value; } }); + + this._scheduler = new RunOnceScheduler(() => { + const records = this._deferredPromises; + this._deferredPromises = new Map(); + (async () => { + try { + await this._storage.setValue(this._shared, this._id, this._value!); + for (const value of records.values()) { + value.complete(); + } + } catch (e) { + for (const value of records.values()) { + value.error(e); + } + } + })(); + }, 0); } get whenReady(): Promise { @@ -51,7 +72,20 @@ export class ExtensionMemento implements vscode.Memento { update(key: string, value: any): Promise { this._value![key] = value; - return this._storage.setValue(this._shared, this._id, this._value!); + + let record = this._deferredPromises.get(key); + if (record !== undefined) { + return record.p; + } + + const promise = new DeferredPromise(); + this._deferredPromises.set(key, promise); + + if (!this._scheduler.isScheduled()) { + this._scheduler.schedule(); + } + + return promise.p; } dispose(): void { diff --git a/src/vs/workbench/api/common/extHostNotebook.ts b/src/vs/workbench/api/common/extHostNotebook.ts index 9a0466bb..116b80d1 100644 --- a/src/vs/workbench/api/common/extHostNotebook.ts +++ b/src/vs/workbench/api/common/extHostNotebook.ts @@ -3,176 +3,33 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { timeout } from 'vs/base/common/async'; +import { VSBuffer } from 'vs/base/common/buffer'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; +import { IRelativePattern } from 'vs/base/common/glob'; +import { hash } from 'vs/base/common/hash'; +import { IdGenerator } from 'vs/base/common/idGenerator'; import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { ResourceMap } from 'vs/base/common/map'; +import { isFalsyOrWhitespace } from 'vs/base/common/strings'; +import { assertIsDefined } from 'vs/base/common/types'; import { URI, UriComponents } from 'vs/base/common/uri'; -import * as UUID from 'vs/base/common/uuid'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; -import { ExtHostNotebookShape, ICommandDto, IMainContext, IModelAddedData, INotebookDocumentPropertiesChangeData, INotebookDocumentsAndEditorsDelta, INotebookDocumentShowOptions, INotebookEditorAddData, INotebookEditorPropertiesChangeData, INotebookKernelInfoDto2, MainContext, MainThreadNotebookShape } from 'vs/workbench/api/common/extHost.protocol'; import { ILogService } from 'vs/platform/log/common/log'; +import { Cache } from 'vs/workbench/api/common/cache'; +import { ExtHostNotebookShape, IMainContext, IModelAddedData, INotebookCellStatusBarListDto, INotebookDocumentPropertiesChangeData, INotebookDocumentsAndEditorsDelta, INotebookDocumentShowOptions, INotebookEditorAddData, INotebookEditorPropertiesChangeData, INotebookEditorViewColumnInfo, MainContext, MainThreadNotebookDocumentsShape, MainThreadNotebookEditorsShape, MainThreadNotebookShape } from 'vs/workbench/api/common/extHost.protocol'; import { CommandsConverter, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; +import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths'; import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters'; import * as extHostTypes from 'vs/workbench/api/common/extHostTypes'; -import { asWebviewUri, WebviewInitData } from 'vs/workbench/api/common/shared/webview'; -import { CellEditType, CellStatusbarAlignment, CellUri, ICellRange, INotebookCellStatusBarEntry, INotebookExclusiveDocumentFilter, NotebookCellMetadata, NotebookCellExecutionState, NotebookCellsChangedEventDto, NotebookCellsChangeType, NotebookDataDto, NullablePartialNotebookCellMetadata, IImmediateCellEditOperation } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import * as vscode from 'vscode'; -import { ResourceMap } from 'vs/base/common/map'; +import { CellEditType, IImmediateCellEditOperation, INotebookExclusiveDocumentFilter, NotebookCellsChangedEventDto, NotebookCellsChangeType, NotebookDataDto, NullablePartialNotebookCellMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import type * as vscode from 'vscode'; import { ExtHostCell, ExtHostNotebookDocument } from './extHostNotebookDocument'; import { ExtHostNotebookEditor } from './extHostNotebookEditor'; -import { IdGenerator } from 'vs/base/common/idGenerator'; -import { IRelativePattern } from 'vs/base/common/glob'; -import { assertIsDefined } from 'vs/base/common/types'; -import { VSBuffer } from 'vs/base/common/buffer'; -import { hash } from 'vs/base/common/hash'; -import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; -class ExtHostWebviewCommWrapper extends Disposable { - private readonly _onDidReceiveDocumentMessage = new Emitter(); - private readonly _rendererIdToEmitters = new Map>(); - - constructor( - private _editorId: string, - public uri: URI, - private _proxy: MainThreadNotebookShape, - private _webviewInitData: WebviewInitData, - public document: ExtHostNotebookDocument, - ) { - super(); - } - - public onDidReceiveMessage(forRendererId: string | undefined, message: any) { - this._onDidReceiveDocumentMessage.fire(message); - if (forRendererId !== undefined) { - this._rendererIdToEmitters.get(forRendererId)?.fire(message); - } - } - - public readonly contentProviderComm: vscode.NotebookCommunication = { - editorId: this._editorId, - onDidReceiveMessage: this._onDidReceiveDocumentMessage.event, - postMessage: (message: any) => this._proxy.$postMessage(this._editorId, undefined, message), - asWebviewUri: (uri: vscode.Uri) => this._asWebviewUri(uri), - }; - - public getRendererComm(rendererId: string): vscode.NotebookCommunication { - const emitter = new Emitter(); - this._rendererIdToEmitters.set(rendererId, emitter); - return { - editorId: this._editorId, - onDidReceiveMessage: emitter.event, - postMessage: (message: any) => this._proxy.$postMessage(this._editorId, rendererId, message), - asWebviewUri: (uri: vscode.Uri) => this._asWebviewUri(uri), - }; - } - - - private _asWebviewUri(localResource: vscode.Uri): vscode.Uri { - return asWebviewUri(this._webviewInitData, this._editorId, localResource); - } -} - -export class ExtHostNotebookKernelProviderAdapter extends Disposable { - private _kernelToFriendlyId = new ResourceMap>(); - private _friendlyIdToKernel = new ResourceMap>(); - constructor( - private readonly _proxy: MainThreadNotebookShape, - private readonly _handle: number, - private readonly _extension: IExtensionDescription, - private readonly _provider: vscode.NotebookKernelProvider - ) { - super(); - - if (this._provider.onDidChangeKernels) { - this._register(this._provider.onDidChangeKernels((e: vscode.NotebookDocument | undefined) => { - const uri = e?.uri; - this._proxy.$onNotebookKernelChange(this._handle, uri); - })); - } - } - - async provideKernels(document: ExtHostNotebookDocument, token: vscode.CancellationToken): Promise { - const data = await this._provider.provideKernels(document.notebookDocument, token) || []; - - const newMap = new Map(); - let kernel_unique_pool = 0; - const kernelFriendlyIdCache = new Set(); - - const kernelToFriendlyId = this._kernelToFriendlyId.get(document.uri); - - const transformedData: INotebookKernelInfoDto2[] = data.map(kernel => { - let friendlyId = kernelToFriendlyId?.get(kernel); - if (friendlyId === undefined) { - if (kernel.id && kernelFriendlyIdCache.has(kernel.id)) { - friendlyId = `${this._extension.identifier.value}_${kernel.id}_${kernel_unique_pool++}`; - } else { - friendlyId = `${this._extension.identifier.value}_${kernel.id || UUID.generateUuid()}`; - } - } - - newMap.set(kernel, friendlyId); - - return { - id: kernel.id, - friendlyId: friendlyId, - label: kernel.label, - extension: this._extension.identifier, - extensionLocation: this._extension.extensionLocation, - providerHandle: this._handle, - description: kernel.description, - detail: kernel.detail, - isPreferred: kernel.isPreferred, - preloads: kernel.preloads, - supportedLanguages: kernel.supportedLanguages, - implementsInterrupt: !!kernel.interrupt - }; - }); - - this._kernelToFriendlyId.set(document.uri, newMap); - const friendlyIdToKernel = new Map(); - newMap.forEach((value, key) => { - friendlyIdToKernel.set(value, key); - }); - - this._friendlyIdToKernel.set(document.uri, friendlyIdToKernel); - return transformedData; - } - - getKernelByFriendlyId(uri: URI, kernelId: string) { - return this._friendlyIdToKernel.get(uri)?.get(kernelId); - } - - async resolveNotebook(kernelId: string, document: ExtHostNotebookDocument, webview: vscode.NotebookCommunication, token: CancellationToken) { - const kernel = this._friendlyIdToKernel.get(document.uri)?.get(kernelId); - - if (kernel && this._provider.resolveKernel) { - return this._provider.resolveKernel(kernel, document.notebookDocument, webview, token); - } - } - - async executeNotebook(kernelId: string, document: ExtHostNotebookDocument, cellRange: ICellRange[]): Promise { - const kernel = this._friendlyIdToKernel.get(document.uri)?.get(kernelId); - - if (!kernel) { - return; - } - - const extCellRange = cellRange.map(c => typeConverters.NotebookCellRange.to(c)); - return kernel.executeCellsRequest(document.notebookDocument, extCellRange); - } - - async interruptNotebookExecution(kernelId: string, document: ExtHostNotebookDocument): Promise { - const kernel = this._friendlyIdToKernel.get(document.uri)?.get(kernelId); - - if (!kernel || !kernel.interrupt) { - return; - } - - return kernel.interrupt(document.notebookDocument); - } -} export class NotebookEditorDecorationType { @@ -180,7 +37,7 @@ export class NotebookEditorDecorationType { readonly value: vscode.NotebookEditorDecorationType; - constructor(proxy: MainThreadNotebookShape, options: vscode.NotebookDecorationRenderOptions) { + constructor(proxy: MainThreadNotebookEditorsShape, options: vscode.NotebookDecorationRenderOptions) { const key = NotebookEditorDecorationType._Keys.nextId(); proxy.$registerNotebookEditorDecorationType(key, typeConverters.NotebookDecorationRenderOptions.from(options)); @@ -200,14 +57,16 @@ type NotebookContentProviderData = { }; export class ExtHostNotebookController implements ExtHostNotebookShape { - private static _notebookKernelProviderHandlePool: number = 0; + private static _notebookStatusBarItemProviderHandlePool: number = 0; + + private readonly _notebookProxy: MainThreadNotebookShape; + private readonly _notebookDocumentsProxy: MainThreadNotebookDocumentsShape; + private readonly _notebookEditorsProxy: MainThreadNotebookEditorsShape; - private readonly _proxy: MainThreadNotebookShape; private readonly _notebookContentProviders = new Map(); - private readonly _notebookKernelProviders = new Map(); + private readonly _notebookStatusBarItemProviders = new Map(); private readonly _documents = new ResourceMap(); - private readonly _editors = new Map(); - private readonly _webviewComm = new Map(); + private readonly _editors = new Map(); private readonly _commandsConverter: CommandsConverter; private readonly _onDidChangeNotebookEditorSelection = new Emitter(); readonly onDidChangeNotebookEditorSelection = this._onDidChangeNotebookEditorSelection.event; @@ -228,11 +87,11 @@ export class ExtHostNotebookController implements ExtHostNotebookShape { private _activeNotebookEditor: ExtHostNotebookEditor | undefined; get activeNotebookEditor(): vscode.NotebookEditor | undefined { - return this._activeNotebookEditor?.editor; + return this._activeNotebookEditor?.apiEditor; } private _visibleNotebookEditors: ExtHostNotebookEditor[] = []; get visibleNotebookEditors(): vscode.NotebookEditor[] { - return this._visibleNotebookEditors.map(editor => editor.editor); + return this._visibleNotebookEditors.map(editor => editor.apiEditor); } private _onDidOpenNotebookDocument = new Emitter(); @@ -241,23 +100,24 @@ export class ExtHostNotebookController implements ExtHostNotebookShape { onDidCloseNotebookDocument: Event = this._onDidCloseNotebookDocument.event; private _onDidSaveNotebookDocument = new Emitter(); onDidSaveNotebookDocument: Event = this._onDidSaveNotebookDocument.event; - private _onDidChangeActiveNotebookKernel = new Emitter<{ document: vscode.NotebookDocument, kernel: vscode.NotebookKernel | undefined; }>(); - onDidChangeActiveNotebookKernel = this._onDidChangeActiveNotebookKernel.event; private _onDidChangeVisibleNotebookEditors = new Emitter(); onDidChangeVisibleNotebookEditors = this._onDidChangeVisibleNotebookEditors.event; private _activeExecutions = new ResourceMap(); + private _statusBarCache = new Cache('NotebookCellStatusBarCache'); + constructor( mainContext: IMainContext, commands: ExtHostCommands, private _textDocumentsAndEditors: ExtHostDocumentsAndEditors, private _textDocuments: ExtHostDocuments, - private readonly _webviewInitData: WebviewInitData, private readonly logService: ILogService, private readonly _extensionStoragePaths: IExtensionStoragePaths, ) { - this._proxy = mainContext.getProxy(MainContext.MainThreadNotebook); + this._notebookProxy = mainContext.getProxy(MainContext.MainThreadNotebook); + this._notebookDocumentsProxy = mainContext.getProxy(MainContext.MainThreadNotebookDocuments); + this._notebookEditorsProxy = mainContext.getProxy(MainContext.MainThreadNotebookEditors); this._commandsConverter = commands.converter; commands.registerArgumentProcessor({ @@ -270,7 +130,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape { const data = this._documents.get(notebookUri); const cell = data?.getCell(cellHandle); if (cell) { - return cell.cell; + return cell.apiCell; } } return arg; @@ -278,6 +138,19 @@ export class ExtHostNotebookController implements ExtHostNotebookShape { }); } + getEditorById(editorId: string): ExtHostNotebookEditor | undefined { + return this._editors.get(editorId); + } + + getIdByEditor(editor: vscode.NotebookEditor): string | undefined { + for (const [id, candidate] of this._editors) { + if (candidate.apiEditor === editor) { + return id; + } + } + return undefined; + } + get notebookDocuments() { return [...this._documents.values()]; } @@ -306,9 +179,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape { extension: IExtensionDescription, viewType: string, provider: vscode.NotebookContentProvider, - options?: { - transientOutputs: boolean; - transientMetadata: { [K in keyof NotebookCellMetadata]?: boolean }; + options?: vscode.NotebookDocumentContentOptions & { viewOptions?: { displayName: string; filenamePattern: (vscode.GlobPattern | { include: vscode.GlobPattern; exclude: vscode.GlobPattern })[]; @@ -316,7 +187,9 @@ export class ExtHostNotebookController implements ExtHostNotebookShape { }; } ): vscode.Disposable { - + if (isFalsyOrWhitespace(viewType)) { + throw new Error(`viewType cannot be empty or just whitespace`); + } if (this._notebookContentProviders.has(viewType)) { throw new Error(`Notebook provider for '${viewType}' already registered`); } @@ -328,7 +201,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape { if (provider.onDidChangeNotebookContentOptions) { listener = provider.onDidChangeNotebookContentOptions(() => { const internalOptions = typeConverters.NotebookDocumentContentOptions.from(provider.options); - this._proxy.$updateNotebookProviderOptions(viewType, internalOptions); + this._notebookProxy.$updateNotebookProviderOptions(viewType, internalOptions); }); } @@ -341,64 +214,56 @@ export class ExtHostNotebookController implements ExtHostNotebookShape { } const internalOptions = typeConverters.NotebookDocumentContentOptions.from(options); - this._proxy.$registerNotebookProvider({ id: extension.identifier, location: extension.extensionLocation, description: extension.description }, viewType, { + this._notebookProxy.$registerNotebookProvider({ id: extension.identifier, location: extension.extensionLocation, description: extension.description }, viewType, { transientOutputs: internalOptions.transientOutputs, - transientMetadata: internalOptions.transientMetadata, + transientCellMetadata: internalOptions.transientCellMetadata, + transientDocumentMetadata: internalOptions.transientDocumentMetadata, viewOptions: options?.viewOptions && viewOptionsFilenamePattern ? { displayName: options.viewOptions.displayName, filenamePattern: viewOptionsFilenamePattern, exclusive: options.viewOptions.exclusive || false } : undefined }); return new extHostTypes.Disposable(() => { listener?.dispose(); this._notebookContentProviders.delete(viewType); - this._proxy.$unregisterNotebookProvider(viewType); + this._notebookProxy.$unregisterNotebookProvider(viewType); }); } - registerNotebookKernelProvider(extension: IExtensionDescription, selector: vscode.NotebookDocumentFilter, provider: vscode.NotebookKernelProvider) { - const handle = ExtHostNotebookController._notebookKernelProviderHandlePool++; - const adapter = new ExtHostNotebookKernelProviderAdapter(this._proxy, handle, extension, provider); - this._notebookKernelProviders.set(handle, adapter); - this._proxy.$registerNotebookKernelProvider({ id: extension.identifier, location: extension.extensionLocation, description: extension.description }, handle, { - viewType: selector.viewType, - filenamePattern: selector.filenamePattern ? typeConverters.NotebookExclusiveDocumentPattern.from(selector.filenamePattern) : undefined - }); + registerNotebookCellStatusBarItemProvider(extension: IExtensionDescription, selector: vscode.NotebookSelector, provider: vscode.NotebookCellStatusBarItemProvider) { + + const handle = ExtHostNotebookController._notebookStatusBarItemProviderHandlePool++; + const eventHandle = typeof provider.onDidChangeCellStatusBarItems === 'function' ? ExtHostNotebookController._notebookStatusBarItemProviderHandlePool++ : undefined; + + this._notebookStatusBarItemProviders.set(handle, provider); + this._notebookProxy.$registerNotebookCellStatusBarItemProvider(handle, eventHandle, selector); + + let subscription: vscode.Disposable | undefined; + if (eventHandle !== undefined) { + subscription = provider.onDidChangeCellStatusBarItems!(_ => this._notebookProxy.$emitCellStatusBarEvent(eventHandle)); + } return new extHostTypes.Disposable(() => { - adapter.dispose(); - this._notebookKernelProviders.delete(handle); - this._proxy.$unregisterNotebookKernelProvider(handle); + this._notebookStatusBarItemProviders.delete(handle); + this._notebookProxy.$unregisterNotebookCellStatusBarItemProvider(handle, eventHandle); + if (subscription) { + subscription.dispose(); + } }); } createNotebookEditorDecorationType(options: vscode.NotebookDecorationRenderOptions): vscode.NotebookEditorDecorationType { - return new NotebookEditorDecorationType(this._proxy, options).value; + return new NotebookEditorDecorationType(this._notebookEditorsProxy, options).value; } async openNotebookDocument(uri: URI): Promise { const cached = this._documents.get(uri); if (cached) { - return cached.notebookDocument; + return cached.apiNotebook; } - const canonicalUri = await this._proxy.$tryOpenDocument(uri); + const canonicalUri = await this._notebookDocumentsProxy.$tryOpenDocument(uri); const document = this._documents.get(URI.revive(canonicalUri)); - return assertIsDefined(document?.notebookDocument); + return assertIsDefined(document?.apiNotebook); } - private _withAdapter(handle: number, uri: UriComponents, callback: (adapter: ExtHostNotebookKernelProviderAdapter, document: ExtHostNotebookDocument) => Promise) { - const document = this._documents.get(URI.revive(uri)); - - if (!document) { - return []; - } - - const provider = this._notebookKernelProviders.get(handle); - - if (!provider) { - return []; - } - - return callback(provider, document); - } async showNotebookDocument(notebookOrUri: vscode.NotebookDocument | URI, options?: vscode.NotebookDocumentShowOptions): Promise { @@ -411,7 +276,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape { resolvedOptions = { position: typeConverters.ViewColumn.from(options.viewColumn), preserveFocus: options.preserveFocus, - selection: options.selection && typeConverters.NotebookCellRange.from(options.selection), + selections: options.selections && options.selections.map(typeConverters.NotebookRange.from), pinned: typeof options.preview === 'boolean' ? !options.preview : undefined }; } else { @@ -420,11 +285,11 @@ export class ExtHostNotebookController implements ExtHostNotebookShape { }; } - const editorId = await this._proxy.$tryShowNotebookDocument(notebookOrUri.uri, notebookOrUri.viewType, resolvedOptions); - const editor = editorId && this._editors.get(editorId)?.editor; + const editorId = await this._notebookEditorsProxy.$tryShowNotebookDocument(notebookOrUri.uri, notebookOrUri.viewType, resolvedOptions); + const editor = editorId && this._editors.get(editorId)?.apiEditor; if (editor) { - return editor.editor; + return editor; } if (editorId) { @@ -434,47 +299,35 @@ export class ExtHostNotebookController implements ExtHostNotebookShape { } } - async $provideNotebookKernels(handle: number, uri: UriComponents, token: CancellationToken): Promise { - return this._withAdapter(handle, uri, (adapter, document) => { - return adapter.provideKernels(document, token); - }); - } - - async $resolveNotebookKernel(handle: number, editorId: string, uri: UriComponents, kernelId: string, token: CancellationToken): Promise { - await this._withAdapter(handle, uri, async (adapter, document) => { - const webComm = this._webviewComm.get(editorId); - - if (webComm) { - await adapter.resolveNotebook(kernelId, document, webComm.contentProviderComm, token); - } - }); - } - - async $resolveNotebookEditor(viewType: string, uri: UriComponents, editorId: string): Promise { - const provider = this._notebookContentProviders.get(viewType); + async $provideNotebookCellStatusBarItems(handle: number, uri: UriComponents, index: number, token: CancellationToken): Promise { + const provider = this._notebookStatusBarItemProviders.get(handle); const revivedUri = URI.revive(uri); const document = this._documents.get(revivedUri); if (!document || !provider) { return; } - let webComm = this._webviewComm.get(editorId); - if (!webComm) { - webComm = new ExtHostWebviewCommWrapper(editorId, revivedUri, this._proxy, this._webviewInitData, document); - this._webviewComm.set(editorId, webComm); - } - - if (!provider.provider.resolveNotebook) { + const cell = document.getCellFromIndex(index); + if (!cell) { return; } - await provider.provider.resolveNotebook(document.notebookDocument, webComm.contentProviderComm); + const result = await provider.provideCellStatusBarItems(cell.apiCell, token); + if (!result) { + return undefined; + } + + const disposables = new DisposableStore(); + const cacheId = this._statusBarCache.add([disposables]); + const items = (result && result.map(item => typeConverters.NotebookStatusBarItem.from(item, this._commandsConverter, disposables))) ?? undefined; + return { + cacheId, + items + }; } - async $executeNotebookKernelFromProvider(handle: number, uri: UriComponents, kernelId: string, cellRange: ICellRange[]): Promise { - await this._withAdapter(handle, uri, async (adapter, document) => { - return adapter.executeNotebook(kernelId, document, cellRange); - }); + $releaseNotebookCellStatusBarItems(cacheId: number): void { + this._statusBarCache.delete(cacheId); } // --- serialize/deserialize @@ -483,65 +336,48 @@ export class ExtHostNotebookController implements ExtHostNotebookShape { private readonly _notebookSerializer = new Map(); registerNotebookSerializer(extension: IExtensionDescription, viewType: string, serializer: vscode.NotebookSerializer, options?: vscode.NotebookDocumentContentOptions): vscode.Disposable { + if (isFalsyOrWhitespace(viewType)) { + throw new Error(`viewType cannot be empty or just whitespace`); + } const handle = this._handlePool++; this._notebookSerializer.set(handle, serializer); const internalOptions = typeConverters.NotebookDocumentContentOptions.from(options); - this._proxy.$registerNotebookSerializer( + this._notebookProxy.$registerNotebookSerializer( handle, { id: extension.identifier, location: extension.extensionLocation, description: extension.description }, viewType, internalOptions ); return toDisposable(() => { - this._proxy.$unregisterNotebookSerializer(handle); + this._notebookProxy.$unregisterNotebookSerializer(handle); }); } - async $dataToNotebook(handle: number, bytes: VSBuffer): Promise { + async $dataToNotebook(handle: number, bytes: VSBuffer, token: CancellationToken): Promise { const serializer = this._notebookSerializer.get(handle); if (!serializer) { throw new Error('NO serializer found'); } - const data = await serializer.dataToNotebook(bytes.buffer); + const data = await serializer.deserializeNotebook(bytes.buffer, token); return { metadata: typeConverters.NotebookDocumentMetadata.from(data.metadata), cells: data.cells.map(typeConverters.NotebookCellData.from), }; } - async $notebookToData(handle: number, data: NotebookDataDto): Promise { + async $notebookToData(handle: number, data: NotebookDataDto, token: CancellationToken): Promise { const serializer = this._notebookSerializer.get(handle); if (!serializer) { throw new Error('NO serializer found'); } - const bytes = await serializer.notebookToData({ + const bytes = await serializer.serializeNotebook({ metadata: typeConverters.NotebookDocumentMetadata.to(data.metadata), cells: data.cells.map(typeConverters.NotebookCellData.to) - }); + }, token); return VSBuffer.wrap(bytes); } - async $cancelNotebookCellExecution(handle: number, uri: UriComponents, kernelId: string, cellRange: ICellRange[]): Promise { - await this._withAdapter(handle, uri, async (adapter, document) => { - return adapter.interruptNotebookExecution(kernelId, document); - }); - - const document = this._documents.get(URI.revive(uri)); - if (!document) { - return; - } - - for (let range of cellRange) { - for (let i = range.start; i < range.end; i++) { - const cell = document.getCellFromIndex(i); - if (cell) { - this.cancelOneNotebookCellExecution(cell); - } - } - } - } - - private cancelOneNotebookCellExecution(cell: ExtHostCell): void { + cancelOneNotebookCellExecution(cell: ExtHostCell): void { const execution = this._activeExecutions.get(cell.uri); execution?.cancel(); } @@ -560,14 +396,14 @@ export class ExtHostNotebookController implements ExtHostNotebookShape { async $saveNotebook(viewType: string, uri: UriComponents, token: CancellationToken): Promise { const document = this._getNotebookDocument(URI.revive(uri)); const { provider } = this._getProviderData(viewType); - await provider.saveNotebook(document.notebookDocument, token); + await provider.saveNotebook(document.apiNotebook, token); return true; } async $saveNotebookAs(viewType: string, uri: UriComponents, target: UriComponents, token: CancellationToken): Promise { const document = this._getNotebookDocument(URI.revive(uri)); const { provider } = this._getProviderData(viewType); - await provider.saveNotebookAs(URI.revive(target), document.notebookDocument, token); + await provider.saveNotebookAs(URI.revive(target), document.apiNotebook, token); return true; } @@ -581,29 +417,11 @@ export class ExtHostNotebookController implements ExtHostNotebookShape { const fileName = String(hash([document.uri.toString(), this._backupIdPool++])); const backupUri = URI.joinPath(storagePath, fileName); - const backup = await provider.provider.backupNotebook(document.notebookDocument, { destination: backupUri }, cancellation); + const backup = await provider.provider.backupNotebook(document.apiNotebook, { destination: backupUri }, cancellation); document.updateBackup(backup); return backup.id; } - $acceptNotebookActiveKernelChange(event: { uri: UriComponents, providerHandle: number | undefined, kernelFriendlyId: string | undefined; }) { - if (event.providerHandle !== undefined) { - this._withAdapter(event.providerHandle, event.uri, async (adapter, document) => { - const kernel = event.kernelFriendlyId ? adapter.getKernelByFriendlyId(URI.revive(event.uri), event.kernelFriendlyId) : undefined; - this._editors.forEach(editor => { - if (editor.editor.notebookData === document) { - editor.editor._acceptKernel(kernel); - } - }); - this._onDidChangeActiveNotebookKernel.fire({ document: document.notebookDocument, kernel }); - }); - } - } - - $onDidReceiveMessage(editorId: string, forRendererType: string | undefined, message: any): void { - this._webviewComm.get(editorId)?.onDidReceiveMessage(forRendererType, message); - } - $acceptModelChanged(uri: UriComponents, event: NotebookCellsChangedEventDto, isDirty: boolean): void { const document = this._getNotebookDocument(URI.revive(uri)); document.acceptModelChanged(event, isDirty); @@ -611,12 +429,12 @@ export class ExtHostNotebookController implements ExtHostNotebookShape { $acceptDirtyStateChanged(uri: UriComponents, isDirty: boolean): void { const document = this._getNotebookDocument(URI.revive(uri)); - document.acceptModelChanged({ rawEvents: [], versionId: document.notebookDocument.version }, isDirty); + document.acceptModelChanged({ rawEvents: [], versionId: document.apiNotebook.version }, isDirty); } $acceptModelSaved(uri: UriComponents): void { const document = this._getNotebookDocument(URI.revive(uri)); - this._onDidSaveNotebookDocument.fire(document.notebookDocument); + this._onDidSaveNotebookDocument.fire(document.apiNotebook); } $acceptEditorPropertiesChanged(id: string, data: INotebookEditorPropertiesChangeData): void { @@ -624,67 +442,70 @@ export class ExtHostNotebookController implements ExtHostNotebookShape { const editor = this._editors.get(id); if (!editor) { - throw new Error(`unknown text editor: ${id}`); + throw new Error(`unknown text editor: ${id}. known editors: ${[...this._editors.keys()]} `); } // ONE: make all state updates if (data.visibleRanges) { - editor.editor._acceptVisibleRanges(data.visibleRanges.ranges.map(typeConverters.NotebookCellRange.to)); + editor._acceptVisibleRanges(data.visibleRanges.ranges.map(typeConverters.NotebookRange.to)); } if (data.selections) { - editor.editor._acceptSelections(data.selections.selections.map(typeConverters.NotebookCellRange.to)); + editor._acceptSelections(data.selections.selections.map(typeConverters.NotebookRange.to)); } // TWO: send all events after states have been updated if (data.visibleRanges) { this._onDidChangeNotebookEditorVisibleRanges.fire({ - notebookEditor: editor.editor.editor, - visibleRanges: editor.editor.editor.visibleRanges + notebookEditor: editor.apiEditor, + visibleRanges: editor.apiEditor.visibleRanges }); } if (data.selections) { this._onDidChangeNotebookEditorSelection.fire(Object.freeze({ - notebookEditor: editor.editor.editor, - selections: editor.editor.editor.selections + notebookEditor: editor.apiEditor, + selections: editor.apiEditor.selections })); } } + $acceptEditorViewColumns(data: INotebookEditorViewColumnInfo): void { + for (const id in data) { + const editor = this._editors.get(id); + if (!editor) { + throw new Error(`unknown text editor: ${id}. known editors: ${[...this._editors.keys()]} `); + } + editor._acceptViewColumn(typeConverters.ViewColumn.to(data[id])); + } + } + $acceptDocumentPropertiesChanged(uri: UriComponents, data: INotebookDocumentPropertiesChangeData): void { this.logService.debug('ExtHostNotebook#$acceptDocumentPropertiesChanged', uri.path, data); const document = this._getNotebookDocument(URI.revive(uri)); document.acceptDocumentPropertiesChanged(data); if (data.metadata) { - this._onDidChangeNotebookDocumentMetadata.fire({ document: document.notebookDocument }); + this._onDidChangeNotebookDocumentMetadata.fire({ document: document.apiNotebook }); } } private _createExtHostEditor(document: ExtHostNotebookDocument, editorId: string, data: INotebookEditorAddData) { - const revivedUri = document.uri; - let webComm = this._webviewComm.get(editorId); - if (!webComm) { - webComm = new ExtHostWebviewCommWrapper(editorId, revivedUri, this._proxy, this._webviewInitData, document); - this._webviewComm.set(editorId, webComm); + if (this._editors.has(editorId)) { + throw new Error(`editor with id ALREADY EXSIST: ${editorId}`); } const editor = new ExtHostNotebookEditor( editorId, - document.notebookDocument.viewType, - this._proxy, + this._notebookEditorsProxy, document, - data.visibleRanges.map(typeConverters.NotebookCellRange.to), - data.selections.map(typeConverters.NotebookCellRange.to), + data.visibleRanges.map(typeConverters.NotebookRange.to), + data.selections.map(typeConverters.NotebookRange.to), typeof data.viewColumn === 'number' ? typeConverters.ViewColumn.to(data.viewColumn) : undefined ); - - this._editors.get(editorId)?.editor.dispose(); - this._editors.set(editorId, { editor }); + this._editors.set(editorId, editor); } $acceptDocumentAndEditorsDelta(delta: INotebookDocumentsAndEditorsDelta): void { - let editorChanged = false; if (delta.removedDocuments) { for (const uri of delta.removedDocuments) { @@ -694,15 +515,13 @@ export class ExtHostNotebookController implements ExtHostNotebookShape { if (document) { document.dispose(); this._documents.delete(revivedUri); - this._textDocumentsAndEditors.$acceptDocumentsAndEditorsDelta({ removedDocuments: document.notebookDocument.cells.map(cell => cell.document.uri) }); - this._onDidCloseNotebookDocument.fire(document.notebookDocument); + this._textDocumentsAndEditors.$acceptDocumentsAndEditorsDelta({ removedDocuments: document.apiNotebook.getCells().map(cell => cell.document.uri) }); + this._onDidCloseNotebookDocument.fire(document.apiNotebook); } - for (const e of this._editors.values()) { - if (e.editor.notebookData.uri.toString() === revivedUri.toString()) { - e.editor.dispose(); - this._editors.delete(e.editor.id); - editorChanged = true; + for (const editor of this._editors.values()) { + if (editor.notebookData.uri.toString() === revivedUri.toString()) { + this._editors.delete(editor.id); } } } @@ -717,12 +536,12 @@ export class ExtHostNotebookController implements ExtHostNotebookShape { const viewType = modelData.viewType; if (this._documents.has(uri)) { - throw new Error(`adding EXISTING notebook ${uri}`); + throw new Error(`adding EXISTING notebook ${uri} `); } const that = this; const document = new ExtHostNotebookDocument( - this._proxy, + this._notebookDocumentsProxy, this._textDocumentsAndEditors, this._textDocuments, { @@ -753,13 +572,13 @@ export class ExtHostNotebookController implements ExtHostNotebookShape { }, false); // add cell document as vscode.TextDocument - addedCellDocuments.push(...modelData.cells.map(cell => ExtHostCell.asModelAddData(document.notebookDocument, cell))); + addedCellDocuments.push(...modelData.cells.map(cell => ExtHostCell.asModelAddData(document.apiNotebook, cell))); this._documents.get(uri)?.dispose(); this._documents.set(uri, document); this._textDocumentsAndEditors.$acceptDocumentsAndEditorsDelta({ addedDocuments: addedCellDocuments }); - this._onDidOpenNotebookDocument.fire(document.notebookDocument); + this._onDidOpenNotebookDocument.fire(document.apiNotebook); } } @@ -774,22 +593,20 @@ export class ExtHostNotebookController implements ExtHostNotebookShape { if (document) { this._createExtHostEditor(document, editorModelData.id, editorModelData); - editorChanged = true; } } } - const removedEditors: { editor: ExtHostNotebookEditor; }[] = []; + const removedEditors: ExtHostNotebookEditor[] = []; if (delta.removedEditors) { for (const editorid of delta.removedEditors) { const editor = this._editors.get(editorid); if (editor) { - editorChanged = true; this._editors.delete(editorid); - if (this._activeNotebookEditor?.id === editor.editor.id) { + if (this._activeNotebookEditor?.id === editor.id) { this._activeNotebookEditor = undefined; } @@ -798,23 +615,17 @@ export class ExtHostNotebookController implements ExtHostNotebookShape { } } - if (editorChanged) { - removedEditors.forEach(e => { - e.editor.dispose(); - }); - } - if (delta.visibleEditors) { - this._visibleNotebookEditors = delta.visibleEditors.map(id => this._editors.get(id)!.editor).filter(editor => !!editor) as ExtHostNotebookEditor[]; + this._visibleNotebookEditors = delta.visibleEditors.map(id => this._editors.get(id)!).filter(editor => !!editor) as ExtHostNotebookEditor[]; const visibleEditorsSet = new Set(); this._visibleNotebookEditors.forEach(editor => visibleEditorsSet.add(editor.id)); - for (const e of this._editors.values()) { - const newValue = visibleEditorsSet.has(e.editor.id); - e.editor._acceptVisibility(newValue); + for (const editor of this._editors.values()) { + const newValue = visibleEditorsSet.has(editor.id); + editor._acceptVisibility(newValue); } - this._visibleNotebookEditors = [...this._editors.values()].map(e => e.editor).filter(e => e.visible); + this._visibleNotebookEditors = [...this._editors.values()].map(e => e).filter(e => e.visible); this._onDidChangeVisibleNotebookEditors.fire(this.visibleNotebookEditors); } @@ -822,48 +633,29 @@ export class ExtHostNotebookController implements ExtHostNotebookShape { // clear active notebook as current active editor is non-notebook editor this._activeNotebookEditor = undefined; } else if (delta.newActiveEditor) { - this._activeNotebookEditor = this._editors.get(delta.newActiveEditor)?.editor; + this._activeNotebookEditor = this._editors.get(delta.newActiveEditor); } if (delta.newActiveEditor !== undefined) { - this._onDidChangeActiveNotebookEditor.fire(this._activeNotebookEditor?.editor); + this._onDidChangeActiveNotebookEditor.fire(this._activeNotebookEditor?.apiEditor); } } - - createNotebookCellStatusBarItemInternal(cell: vscode.NotebookCell, alignment: extHostTypes.NotebookCellStatusBarAlignment | undefined, priority: number | undefined) { - const statusBarItem = new NotebookCellStatusBarItemInternal(this._proxy, this._commandsConverter, cell, alignment, priority); - - // Look up the ExtHostCell for this NotebookCell URI, bind to its disposable lifecycle - const parsedUri = CellUri.parse(cell.document.uri); - if (parsedUri) { - const document = this._documents.get(parsedUri.notebook); - if (document) { - const cell = document.getCell(parsedUri.handle); - if (cell) { - Event.once(cell.onDidDispose)(() => statusBarItem.dispose()); - } - } - } - - return statusBarItem; - } - createNotebookCellExecution(docUri: vscode.Uri, index: number, kernelId: string): vscode.NotebookCellExecutionTask | undefined { const document = this.lookupNotebookDocument(docUri); if (!document) { - throw new Error(`Invalid cell uri/index: ${docUri}, ${index}`); + throw new Error(`Invalid uri: ${docUri} `); } const cell = document.getCellFromIndex(index); if (!cell) { - throw new Error(`Invalid cell uri/index: ${docUri}, ${index}`); + throw new Error(`Invalid cell index: ${docUri}, ${index} `); } // TODO@roblou also validate kernelId, once kernel has moved from editor to document if (this._activeExecutions.has(cell.uri)) { - return; + throw new Error(`duplicate execution for ${cell.uri}`); } - const execution = new NotebookCellExecutionTask(docUri, document, cell, this._proxy); + const execution = new NotebookCellExecutionTask(docUri, document, cell, this._notebookDocumentsProxy); this._activeExecutions.set(cell.uri, execution); const listener = execution.onDidChangeState(() => { if (execution.state === NotebookCellExecutionTaskState.Resolved) { @@ -877,181 +669,6 @@ export class ExtHostNotebookController implements ExtHostNotebookShape { } } -export class NotebookCellStatusBarItemInternal extends Disposable { - private static NEXT_ID = 0; - - private readonly _id = NotebookCellStatusBarItemInternal.NEXT_ID++; - private readonly _internalCommandRegistration: DisposableStore; - - private _isDisposed = false; - private _alignment: extHostTypes.NotebookCellStatusBarAlignment; - - constructor( - private readonly _proxy: MainThreadNotebookShape, - private readonly _commands: CommandsConverter, - private readonly _cell: vscode.NotebookCell, - alignment: extHostTypes.NotebookCellStatusBarAlignment | undefined, - private _priority: number | undefined) { - super(); - this._internalCommandRegistration = this._register(new DisposableStore()); - this._alignment = alignment ?? extHostTypes.NotebookCellStatusBarAlignment.Left; - } - - private _apiItem: vscode.NotebookCellStatusBarItem | undefined; - get apiItem(): vscode.NotebookCellStatusBarItem { - if (!this._apiItem) { - this._apiItem = createNotebookCellStatusBarApiItem(this); - } - - return this._apiItem; - } - - get cell(): vscode.NotebookCell { - return this._cell; - } - - get alignment(): extHostTypes.NotebookCellStatusBarAlignment { - return this._alignment; - } - - set alignment(v: extHostTypes.NotebookCellStatusBarAlignment) { - this._alignment = v; - this.update(); - } - - get priority(): number | undefined { - return this._priority; - } - - set priority(v: number | undefined) { - this._priority = v; - this.update(); - } - - private _text: string = ''; - get text(): string { - return this._text; - } - - set text(v: string) { - this._text = v; - this.update(); - } - - private _tooltip: string | undefined; - get tooltip(): string | undefined { - return this._tooltip; - } - - set tooltip(v: string | undefined) { - this._tooltip = v; - this.update(); - } - - private _command?: { - readonly fromApi: string | vscode.Command, - readonly internal: ICommandDto, - }; - get command(): string | vscode.Command | undefined { - return this._command?.fromApi; - } - - set command(command: string | vscode.Command | undefined) { - if (this._command?.fromApi === command) { - return; - } - - this._internalCommandRegistration.clear(); - if (typeof command === 'string') { - this._command = { - fromApi: command, - internal: this._commands.toInternal({ title: '', command }, this._internalCommandRegistration), - }; - } else if (command) { - this._command = { - fromApi: command, - internal: this._commands.toInternal(command, this._internalCommandRegistration), - }; - } else { - this._command = undefined; - } - this.update(); - } - - private _accessibilityInformation: vscode.AccessibilityInformation | undefined; - get accessibilityInformation(): vscode.AccessibilityInformation | undefined { - return this._accessibilityInformation; - } - - set accessibilityInformation(v: vscode.AccessibilityInformation | undefined) { - this._accessibilityInformation = v; - this.update(); - } - - private _visible: boolean = false; - show(): void { - this._visible = true; - this.update(); - } - - hide(): void { - this._visible = false; - this.update(); - } - - dispose(): void { - this.hide(); - this._isDisposed = true; - this._internalCommandRegistration.dispose(); - } - - private update(): void { - if (this._isDisposed) { - return; - } - - const entry: INotebookCellStatusBarEntry = { - alignment: this.alignment === extHostTypes.NotebookCellStatusBarAlignment.Left ? CellStatusbarAlignment.LEFT : CellStatusbarAlignment.RIGHT, - cellResource: this.cell.document.uri, - command: this._command?.internal, - text: this.text, - tooltip: this.tooltip, - accessibilityInformation: this.accessibilityInformation, - priority: this.priority, - visible: this._visible - }; - - this._proxy.$setStatusBarEntry(this._id, entry); - } -} - -function createNotebookCellStatusBarApiItem(internalItem: NotebookCellStatusBarItemInternal): vscode.NotebookCellStatusBarItem { - return Object.freeze({ - cell: internalItem.cell, - get alignment() { return internalItem.alignment; }, - set alignment(v: NotebookCellStatusBarItemInternal['alignment']) { internalItem.alignment = v; }, - - get priority() { return internalItem.priority; }, - set priority(v: NotebookCellStatusBarItemInternal['priority']) { internalItem.priority = v; }, - - get text() { return internalItem.text; }, - set text(v: NotebookCellStatusBarItemInternal['text']) { internalItem.text = v; }, - - get tooltip() { return internalItem.tooltip; }, - set tooltip(v: NotebookCellStatusBarItemInternal['tooltip']) { internalItem.tooltip = v; }, - - get command() { return internalItem.command; }, - set command(v: NotebookCellStatusBarItemInternal['command']) { internalItem.command = v; }, - - get accessibilityInformation() { return internalItem.accessibilityInformation; }, - set accessibilityInformation(v: NotebookCellStatusBarItemInternal['accessibilityInformation']) { internalItem.accessibilityInformation = v; }, - - show() { internalItem.show(); }, - hide() { internalItem.hide(); }, - dispose() { internalItem.dispose(); } - }); -} - enum NotebookCellExecutionTaskState { Init, Started, @@ -1065,7 +682,9 @@ class NotebookCellExecutionTask extends Disposable { private _state = NotebookCellExecutionTaskState.Init; get state(): NotebookCellExecutionTaskState { return this._state; } - private readonly _tokenSource: CancellationTokenSource; + private readonly _tokenSource = this._register(new CancellationTokenSource()); + + private readonly _collector: TimeoutBasedCollector; private _executionOrder: number | undefined; @@ -1073,14 +692,14 @@ class NotebookCellExecutionTask extends Disposable { private readonly _uri: vscode.Uri, private readonly _document: ExtHostNotebookDocument, private readonly _cell: ExtHostCell, - private readonly _proxy: MainThreadNotebookShape) { + private readonly _proxy: MainThreadNotebookDocumentsShape) { super(); - this._tokenSource = this._register(new CancellationTokenSource()); + + this._collector = new TimeoutBasedCollector(10, edits => this.applyEdits(edits)); this._executionOrder = _cell.internalMetadata.executionOrder; this.mixinMetadata({ - runState: NotebookCellExecutionState.Pending, - lastRunDuration: null, + runState: extHostTypes.NotebookCellExecutionState.Pending, executionOrder: null }); } @@ -1089,6 +708,10 @@ class NotebookCellExecutionTask extends Disposable { this._tokenSource.cancel(); } + private async applyEditSoon(edit: IImmediateCellEditOperation): Promise { + await this._collector.addItem(edit); + } + private async applyEdits(edits: IImmediateCellEditOperation[]): Promise { return this._proxy.$applyEdits(this._uri, edits, false); } @@ -1104,10 +727,8 @@ class NotebookCellExecutionTask extends Disposable { } private mixinMetadata(mixinMetadata: NullablePartialNotebookCellMetadata) { - const edits: IImmediateCellEditOperation[] = [ - { editType: CellEditType.PartialMetadata, handle: this._cell.handle, metadata: mixinMetadata } - ]; - this.applyEdits(edits); + const edit: IImmediateCellEditOperation = { editType: CellEditType.PartialMetadata, handle: this._cell.handle, metadata: mixinMetadata }; + this.applyEdits([edit]); } private cellIndexToHandle(cellIndex: number | undefined): number | undefined { @@ -1122,8 +743,8 @@ class NotebookCellExecutionTask extends Disposable { asApiObject(): vscode.NotebookCellExecutionTask { const that = this; return Object.freeze({ - get document() { return that._document.notebookDocument; }, - get cell() { return that._cell.cell; }, + get document() { return that._document.apiNotebook; }, + get cell() { return that._cell.apiCell; }, get executionOrder() { return that._executionOrder; }, set executionOrder(v: number | undefined) { @@ -1142,8 +763,8 @@ class NotebookCellExecutionTask extends Disposable { that._onDidChangeState.fire(); that.mixinMetadata({ - runState: NotebookCellExecutionState.Executing, - runStartTime: context?.startTime + runState: extHostTypes.NotebookCellExecutionState.Executing, + runStartTime: context?.startTime ?? null }); }, @@ -1156,9 +777,9 @@ class NotebookCellExecutionTask extends Disposable { that._onDidChangeState.fire(); that.mixinMetadata({ - runState: NotebookCellExecutionState.Idle, + runState: extHostTypes.NotebookCellExecutionState.Idle, lastRunSuccess: result?.success ?? null, - lastRunDuration: result?.duration ?? null, + runEndTime: result?.endTime ?? null, }); }, @@ -1175,7 +796,7 @@ class NotebookCellExecutionTask extends Disposable { } outputs = Array.isArray(outputs) ? outputs : [outputs]; - return that.applyEdits([{ editType: CellEditType.Output, handle, append: true, outputs: outputs.map(typeConverters.NotebookCellOutput.from) }]); + return that.applyEditSoon({ editType: CellEditType.Output, handle, append: true, outputs: outputs.map(typeConverters.NotebookCellOutput.from) }); }, async replaceOutput(outputs: vscode.NotebookCellOutput | vscode.NotebookCellOutput[], cellIndex?: number): Promise { @@ -1186,22 +807,45 @@ class NotebookCellExecutionTask extends Disposable { } outputs = Array.isArray(outputs) ? outputs : [outputs]; - return that.applyEdits([{ editType: CellEditType.Output, handle, outputs: outputs.map(typeConverters.NotebookCellOutput.from) }]); + return that.applyEditSoon({ editType: CellEditType.Output, handle, outputs: outputs.map(typeConverters.NotebookCellOutput.from) }); }, async appendOutputItems(items: vscode.NotebookCellOutputItem | vscode.NotebookCellOutputItem[], outputId: string): Promise { that.verifyStateForOutput(); items = Array.isArray(items) ? items : [items]; - return that.applyEdits([{ editType: CellEditType.OutputItems, append: true, items: items.map(typeConverters.NotebookCellOutputItem.from), outputId }]); + return that.applyEditSoon({ editType: CellEditType.OutputItems, append: true, items: items.map(typeConverters.NotebookCellOutputItem.from), outputId }); }, async replaceOutputItems(items: vscode.NotebookCellOutputItem | vscode.NotebookCellOutputItem[], outputId: string): Promise { that.verifyStateForOutput(); items = Array.isArray(items) ? items : [items]; - return that.applyEdits([{ editType: CellEditType.OutputItems, items: items.map(typeConverters.NotebookCellOutputItem.from), outputId }]); + return that.applyEditSoon({ editType: CellEditType.OutputItems, items: items.map(typeConverters.NotebookCellOutputItem.from), outputId }); }, token: that._tokenSource.token }); } } + +class TimeoutBasedCollector { + private batch: T[] = []; + private waitPromise: Promise | undefined; + + constructor( + private readonly delay: number, + private readonly callback: (items: T[]) => Promise) { } + + addItem(item: T): Promise { + this.batch.push(item); + if (!this.waitPromise) { + this.waitPromise = timeout(this.delay).then(() => { + this.waitPromise = undefined; + const batch = this.batch; + this.batch = []; + return this.callback(batch); + }); + } + + return this.waitPromise; + } +} diff --git a/src/vs/workbench/api/common/extHostNotebookConcatDocument.ts b/src/vs/workbench/api/common/extHostNotebookConcatDocument.ts index 4eee76c3..215975bd 100644 --- a/src/vs/workbench/api/common/extHostNotebookConcatDocument.ts +++ b/src/vs/workbench/api/common/extHostNotebookConcatDocument.ts @@ -73,7 +73,7 @@ export class ExtHostNotebookConcatDocument implements vscode.NotebookConcatTextD this._cellUris = new ResourceMap(); const cellLengths: number[] = []; const cellLineCounts: number[] = []; - for (const cell of this._notebook.cells) { + for (const cell of this._notebook.getCells()) { if (cell.kind === types.NotebookCellKind.Code && (!this._selector || score(this._selector, cell.document.uri, cell.document.languageId, true))) { this._cellUris.set(cell.document.uri, this._cells.length); this._cells.push(cell); diff --git a/src/vs/workbench/api/common/extHostNotebookDocument.ts b/src/vs/workbench/api/common/extHostNotebookDocument.ts index c9231003..4e46c581 100644 --- a/src/vs/workbench/api/common/extHostNotebookDocument.ts +++ b/src/vs/workbench/api/common/extHostNotebookDocument.ts @@ -3,12 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Emitter, Event } from 'vs/base/common/event'; -import { Disposable, DisposableStore, dispose } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import { deepFreeze, equals } from 'vs/base/common/objects'; import { URI } from 'vs/base/common/uri'; -import { CellKind, INotebookDocumentPropertiesChangeData, MainThreadNotebookShape } from 'vs/workbench/api/common/extHost.protocol'; +import { CellKind, INotebookDocumentPropertiesChangeData, MainThreadNotebookDocumentsShape } from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; import { ExtHostDocumentsAndEditors, IExtHostModelAddedData } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; import * as extHostTypeConverters from 'vs/workbench/api/common/extHostTypeConverters'; @@ -26,7 +24,7 @@ class RawContentChangeEvent { start: event.start, deletedCount: event.deletedCount, deletedItems: event.deletedItems, - items: event.items.map(data => data.cell) + items: event.items.map(data => data.apiCell) }; }); } @@ -46,9 +44,6 @@ export class ExtHostCell { }; } - private _onDidDispose = new Emitter(); - readonly onDidDispose: Event = this._onDidDispose.event; - private _outputs: extHostTypes.NotebookCellOutput[]; private _metadata: extHostTypes.NotebookCellMetadata; private _previousResult: vscode.NotebookCellExecutionSummary | undefined; @@ -74,16 +69,11 @@ export class ExtHostCell { this._previousResult = extHostTypeConverters.NotebookCellPreviousExecutionResult.to(this._internalMetadata); } - dispose() { - this._onDidDispose.fire(); - this._onDidDispose.dispose(); - } - get internalMetadata(): NotebookCellMetadata { return this._internalMetadata; } - get cell(): vscode.NotebookCell { + get apiCell(): vscode.NotebookCell { if (!this._cell) { const that = this; const data = this._extHostDocument.getDocument(this.uri); @@ -92,7 +82,7 @@ export class ExtHostCell { } this._cell = Object.freeze({ get index() { return that._notebook.getCellIndex(that); }, - notebook: that._notebook.notebookDocument, + notebook: that._notebook.apiNotebook, kind: extHostTypeConverters.NotebookCellKind.to(this._cellData.cellKind), document: data.document, get outputs() { return that._outputs.slice(0); }, @@ -133,15 +123,13 @@ export interface INotebookEventEmitter { } -export class ExtHostNotebookDocument extends Disposable { +export class ExtHostNotebookDocument { private static _handlePool: number = 0; readonly handle = ExtHostNotebookDocument._handlePool++; private _cells: ExtHostCell[] = []; - private _cellDisposableMapping = new Map(); - private _notebook: vscode.NotebookDocument | undefined; private _versionId: number = 0; private _isDirty: boolean = false; @@ -149,39 +137,43 @@ export class ExtHostNotebookDocument extends Disposable { private _disposed: boolean = false; constructor( - private readonly _proxy: MainThreadNotebookShape, + private readonly _proxy: MainThreadNotebookDocumentsShape, private readonly _textDocumentsAndEditors: ExtHostDocumentsAndEditors, private readonly _textDocuments: ExtHostDocuments, private readonly _emitter: INotebookEventEmitter, private readonly _viewType: string, private _metadata: extHostTypes.NotebookDocumentMetadata, readonly uri: URI, - ) { - super(); - } + ) { } dispose() { this._disposed = true; - super.dispose(); - dispose(this._cellDisposableMapping.values()); } - - get notebookDocument(): vscode.NotebookDocument { + get apiNotebook(): vscode.NotebookDocument { if (!this._notebook) { const that = this; - this._notebook = Object.freeze({ + this._notebook = { get uri() { return that.uri; }, get version() { return that._versionId; }, - get fileName() { return that.uri.fsPath; }, get viewType() { return that._viewType; }, get isDirty() { return that._isDirty; }, get isUntitled() { return that.uri.scheme === Schemas.untitled; }, - get cells(): ReadonlyArray { return that._cells.map(cell => cell.cell); }, + get isClosed() { return that._disposed; }, get metadata() { return that._metadata; }, - set metadata(_value: Required) { throw new Error('Use WorkspaceEdit to update metadata.'); }, - save() { return that._save(); } - }); + get cellCount() { return that._cells.length; }, + cellAt(index) { + index = that._validateIndex(index); + return that._cells[index].apiCell; + }, + getCells(range) { + const cells = range ? that._getCells(range) : that._cells; + return cells.map(cell => cell.apiCell); + }, + save() { + return that._save(); + } + }; } return this._notebook; } @@ -225,6 +217,35 @@ export class ExtHostNotebookDocument extends Disposable { } } + private _validateIndex(index: number): number { + if (index < 0) { + return 0; + } else if (index >= this._cells.length) { + return this._cells.length - 1; + } else { + return index; + } + } + + private _validateRange(range: vscode.NotebookRange): vscode.NotebookRange { + if (range.start < 0) { + range = range.with({ start: 0 }); + } + if (range.end > this._cells.length) { + range = range.with({ end: this._cells.length }); + } + return range; + } + + private _getCells(range: vscode.NotebookRange): ExtHostCell[] { + range = this._validateRange(range); + const result: ExtHostCell[] = []; + for (let i = range.start; i < range.end; i++) { + result.push(this._cells[i]); + } + return result; + } + private async _save(): Promise { if (this._disposed) { return Promise.reject(new Error('Notebook has been closed')); @@ -246,30 +267,17 @@ export class ExtHostNotebookDocument extends Disposable { const newCells = cellDtos.map(cell => { const extCell = new ExtHostCell(this, this._textDocumentsAndEditors, cell); - if (!initialization) { - addedCellDocuments.push(ExtHostCell.asModelAddData(this.notebookDocument, cell)); + addedCellDocuments.push(ExtHostCell.asModelAddData(this.apiNotebook, cell)); } - - if (!this._cellDisposableMapping.has(extCell.handle)) { - const store = new DisposableStore(); - store.add(extCell); - this._cellDisposableMapping.set(extCell.handle, store); - } - return extCell; }); - for (let j = splice[0]; j < splice[0] + splice[1]; j++) { - this._cellDisposableMapping.get(this._cells[j].handle)?.dispose(); - this._cellDisposableMapping.delete(this._cells[j].handle); - } - const changeEvent = new RawContentChangeEvent(splice[0], splice[1], [], newCells); const deletedItems = this._cells.splice(splice[0], splice[1], ...newCells); for (let cell of deletedItems) { removedCellDocuments.push(cell.uri); - changeEvent.deletedItems.push(cell.cell); + changeEvent.deletedItems.push(cell.apiCell); } contentChangeEvents.push(changeEvent); @@ -282,7 +290,7 @@ export class ExtHostNotebookDocument extends Disposable { if (!initialization) { this._emitter.emitModelChange(deepFreeze({ - document: this.notebookDocument, + document: this.apiNotebook, changes: RawContentChangeEvent.asApiEvents(contentChangeEvents) })); } @@ -292,11 +300,11 @@ export class ExtHostNotebookDocument extends Disposable { const cells = this._cells.splice(index, 1); this._cells.splice(newIdx, 0, ...cells); const changes = [ - new RawContentChangeEvent(index, 1, cells.map(c => c.cell), []), + new RawContentChangeEvent(index, 1, cells.map(c => c.apiCell), []), new RawContentChangeEvent(newIdx, 0, [], cells) ]; this._emitter.emitModelChange(deepFreeze({ - document: this.notebookDocument, + document: this.apiNotebook, changes: RawContentChangeEvent.asApiEvents(changes) })); } @@ -304,18 +312,18 @@ export class ExtHostNotebookDocument extends Disposable { private _setCellOutputs(index: number, outputs: IOutputDto[]): void { const cell = this._cells[index]; cell.setOutputs(outputs); - this._emitter.emitCellOutputsChange(deepFreeze({ document: this.notebookDocument, cells: [cell.cell] })); + this._emitter.emitCellOutputsChange(deepFreeze({ document: this.apiNotebook, cells: [cell.apiCell] })); } private _setCellOutputItems(index: number, outputId: string, append: boolean, outputItems: IOutputItemDto[]): void { const cell = this._cells[index]; cell.setOutputItems(outputId, append, outputItems); - this._emitter.emitCellOutputsChange(deepFreeze({ document: this.notebookDocument, cells: [cell.cell] })); + this._emitter.emitCellOutputsChange(deepFreeze({ document: this.apiNotebook, cells: [cell.apiCell] })); } private _changeCellLanguage(index: number, newModeId: string): void { const cell = this._cells[index]; - if (cell.cell.document.languageId !== newModeId) { + if (cell.apiCell.document.languageId !== newModeId) { this._textDocuments.$acceptModelModeChanged(cell.uri, newModeId); } } @@ -324,17 +332,17 @@ export class ExtHostNotebookDocument extends Disposable { const cell = this._cells[index]; const originalInternalMetadata = cell.internalMetadata; - const originalExtMetadata = cell.cell.metadata; + const originalExtMetadata = cell.apiCell.metadata; cell.setMetadata(newMetadata); - const newExtMetadata = cell.cell.metadata; + const newExtMetadata = cell.apiCell.metadata; if (!equals(originalExtMetadata, newExtMetadata)) { - this._emitter.emitCellMetadataChange(deepFreeze({ document: this.notebookDocument, cell: cell.cell })); + this._emitter.emitCellMetadataChange(deepFreeze({ document: this.apiNotebook, cell: cell.apiCell })); } if (originalInternalMetadata.runState !== newMetadata.runState) { const executionState = newMetadata.runState ?? extHostTypes.NotebookCellExecutionState.Idle; - this._emitter.emitCellExecutionStateChange(deepFreeze({ document: this.notebookDocument, cell: cell.cell, executionState })); + this._emitter.emitCellExecutionStateChange(deepFreeze({ document: this.apiNotebook, cell: cell.apiCell, executionState })); } } diff --git a/src/vs/workbench/api/common/extHostNotebookEditor.ts b/src/vs/workbench/api/common/extHostNotebookEditor.ts index b8b752a8..30f8c6d5 100644 --- a/src/vs/workbench/api/common/extHostNotebookEditor.ts +++ b/src/vs/workbench/api/common/extHostNotebookEditor.ts @@ -3,8 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Emitter, Event } from 'vs/base/common/event'; -import { MainThreadNotebookShape } from 'vs/workbench/api/common/extHost.protocol'; +import { MainThreadNotebookEditorsShape } from 'vs/workbench/api/common/extHost.protocol'; import * as extHostTypes from 'vs/workbench/api/common/extHostTypes'; import * as extHostConverter from 'vs/workbench/api/common/extHostTypeConverters'; import { CellEditType, ICellEditOperation, ICellReplaceEdit } from 'vs/workbench/contrib/notebook/common/notebookCommon'; @@ -58,17 +57,6 @@ class NotebookEditorCellEditBuilder implements vscode.NotebookEditorEdit { }); } - replaceCellOutput(index: number, outputs: vscode.NotebookCellOutput[]): void { - this._throwIfFinalized(); - this._collectedEdits.push({ - editType: CellEditType.Output, - index, - outputs: outputs.map(output => { - return extHostConverter.NotebookCellOutput.from(output); - }) - }); - } - replaceCells(from: number, to: number, cells: vscode.NotebookCellData[]): void { this._throwIfFinalized(); if (from === to && cells.length === 0) { @@ -85,27 +73,21 @@ class NotebookEditorCellEditBuilder implements vscode.NotebookEditorEdit { export class ExtHostNotebookEditor { - private _selections: vscode.NotebookCellRange[] = []; - private _visibleRanges: vscode.NotebookCellRange[] = []; + private _selections: vscode.NotebookRange[] = []; + private _visibleRanges: vscode.NotebookRange[] = []; private _viewColumn?: vscode.ViewColumn; private _visible: boolean = false; - private _kernel?: vscode.NotebookKernel; - private readonly _hasDecorationsForKey = new Set(); - private readonly _onDidDispose = new Emitter(); - readonly onDidDispose: Event = this._onDidDispose.event; - private _editor?: vscode.NotebookEditor; constructor( readonly id: string, - private readonly _viewType: string, - private readonly _proxy: MainThreadNotebookShape, + private readonly _proxy: MainThreadNotebookEditorsShape, readonly notebookData: ExtHostNotebookDocument, - visibleRanges: vscode.NotebookCellRange[], - selections: vscode.NotebookCellRange[], + visibleRanges: vscode.NotebookRange[], + selections: vscode.NotebookRange[], viewColumn: vscode.ViewColumn | undefined ) { this._selections = selections; @@ -113,21 +95,12 @@ export class ExtHostNotebookEditor { this._viewColumn = viewColumn; } - dispose() { - this._onDidDispose.fire(); - this._onDidDispose.dispose(); - } - - get editor(): vscode.NotebookEditor { + get apiEditor(): vscode.NotebookEditor { if (!this._editor) { const that = this; this._editor = { get document() { - return that.notebookData.notebookDocument; - }, - get selection() { - const primarySelection = that._selections[0]; - return primarySelection && that.notebookData.getCellFromIndex(primarySelection.start)?.cell; + return that.notebookData.apiNotebook; }, get selections() { return that._selections; @@ -138,24 +111,18 @@ export class ExtHostNotebookEditor { revealRange(range, revealType) { that._proxy.$tryRevealRange( that.id, - extHostConverter.NotebookCellRange.from(range), + extHostConverter.NotebookRange.from(range), revealType ?? extHostTypes.NotebookEditorRevealType.Default ); }, get viewColumn() { return that._viewColumn; }, - get onDidDispose() { - return that.onDidDispose; - }, edit(callback) { const edit = new NotebookEditorCellEditBuilder(this.document.version); callback(edit); return that._applyEdit(edit.finalize()); }, - get kernel() { - return that._kernel; - }, setDecorations(decorationType, range) { return that.setDecorations(decorationType, range); } @@ -164,10 +131,6 @@ export class ExtHostNotebookEditor { return this._editor; } - _acceptKernel(kernel?: vscode.NotebookKernel) { - this._kernel = kernel; - } - get visible(): boolean { return this._visible; } @@ -176,14 +139,18 @@ export class ExtHostNotebookEditor { this._visible = value; } - _acceptVisibleRanges(value: vscode.NotebookCellRange[]): void { + _acceptVisibleRanges(value: vscode.NotebookRange[]): void { this._visibleRanges = value; } - _acceptSelections(selections: vscode.NotebookCellRange[]): void { + _acceptSelections(selections: vscode.NotebookRange[]): void { this._selections = selections; } + _acceptViewColumn(value: vscode.ViewColumn | undefined) { + this._viewColumn = value; + } + private _applyEdit(editData: INotebookEditData): Promise { // return when there is nothing to do @@ -217,10 +184,10 @@ export class ExtHostNotebookEditor { compressedEditsIndex++; } - return this._proxy.$tryApplyEdits(this._viewType, this.notebookData.uri, editData.documentVersionId, compressedEdits); + return this._proxy.$tryApplyEdits(this.id, editData.documentVersionId, compressedEdits); } - setDecorations(decorationType: vscode.NotebookEditorDecorationType, range: vscode.NotebookCellRange): void { + setDecorations(decorationType: vscode.NotebookEditorDecorationType, range: vscode.NotebookRange): void { if (range.isEmpty && !this._hasDecorationsForKey.has(decorationType.key)) { // avoid no-op call to the renderer return; @@ -233,7 +200,7 @@ export class ExtHostNotebookEditor { return this._proxy.$trySetDecorations( this.id, - extHostConverter.NotebookCellRange.from(range), + extHostConverter.NotebookRange.from(range), decorationType.key ); } diff --git a/src/vs/workbench/api/common/extHostNotebookKernels.ts b/src/vs/workbench/api/common/extHostNotebookKernels.ts new file mode 100644 index 00000000..add0d63d --- /dev/null +++ b/src/vs/workbench/api/common/extHostNotebookKernels.ts @@ -0,0 +1,259 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { DisposableStore } from 'vs/base/common/lifecycle'; +import { ExtHostNotebookKernelsShape, IMainContext, INotebookKernelDto2, MainContext, MainThreadNotebookKernelsShape } from 'vs/workbench/api/common/extHost.protocol'; +import * as vscode from 'vscode'; +import { ExtHostNotebookController } from 'vs/workbench/api/common/extHostNotebook'; +import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { URI, UriComponents } from 'vs/base/common/uri'; +import * as extHostTypeConverters from 'vs/workbench/api/common/extHostTypeConverters'; +import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; +import { asWebviewUri } from 'vs/workbench/api/common/shared/webview'; + +interface IKernelData { + extensionId: ExtensionIdentifier, + controller: vscode.NotebookController; + onDidChangeSelection: Emitter<{ selected: boolean; notebook: vscode.NotebookDocument; }>; + onDidReceiveMessage: Emitter<{ editor: vscode.NotebookEditor, message: any }>; +} + +export class ExtHostNotebookKernels implements ExtHostNotebookKernelsShape { + + private readonly _proxy: MainThreadNotebookKernelsShape; + + private readonly _kernelData = new Map(); + private _handlePool: number = 0; + + constructor( + mainContext: IMainContext, + private readonly _initData: IExtHostInitDataService, + private readonly _extHostNotebook: ExtHostNotebookController + ) { + this._proxy = mainContext.getProxy(MainContext.MainThreadNotebookKernels); + } + + createNotebookController(extension: IExtensionDescription, id: string, viewType: string, label: string, handler?: vscode.NotebookExecuteHandler, preloads?: vscode.NotebookKernelPreload[]): vscode.NotebookController { + + for (let data of this._kernelData.values()) { + if (data.controller.id === id && ExtensionIdentifier.equals(extension.identifier, data.extensionId)) { + throw new Error(`notebook controller with id '${id}' ALREADY exist`); + } + } + + const handle = this._handlePool++; + const that = this; + + const _defaultExecutHandler = () => console.warn(`NO execute handler from notebook controller '${data.id}' of extension: '${extension.identifier}'`); + + let isDisposed = false; + const commandDisposables = new DisposableStore(); + + const onDidChangeSelection = new Emitter<{ selected: boolean, notebook: vscode.NotebookDocument }>(); + const onDidReceiveMessage = new Emitter<{ editor: vscode.NotebookEditor, message: any }>(); + + const data: INotebookKernelDto2 = { + id: `${extension.identifier.value}/${id}`, + viewType, + extensionId: extension.identifier, + extensionLocation: extension.extensionLocation, + label: label || extension.identifier.value, + preloads: preloads ? preloads.map(extHostTypeConverters.NotebookKernelPreload.from) : [] + }; + + // + let _executeHandler: vscode.NotebookExecuteHandler = handler ?? _defaultExecutHandler; + let _interruptHandler: vscode.NotebookInterruptHandler | undefined; + + // todo@jrieken the selector needs to be massaged + this._proxy.$addKernel(handle, data).catch(err => { + // this can happen when a kernel with that ID is already registered + console.log(err); + isDisposed = true; + }); + + // update: all setters write directly into the dto object + // and trigger an update. the actual update will only happen + // once per event loop execution + let tokenPool = 0; + const _update = () => { + if (isDisposed) { + return; + } + const myToken = ++tokenPool; + Promise.resolve().then(() => { + if (myToken === tokenPool) { + this._proxy.$updateKernel(handle, data); + } + }); + }; + + const controller: vscode.NotebookController = { + get id() { return id; }, + get viewType() { return data.viewType; }, + onDidChangeNotebookAssociation: onDidChangeSelection.event, + get label() { + return data.label; + }, + set label(value) { + data.label = value ?? extension.displayName ?? extension.name; + _update(); + }, + get detail() { + return data.detail ?? ''; + }, + set detail(value) { + data.detail = value; + _update(); + }, + get description() { + return data.description ?? ''; + }, + set description(value) { + data.description = value; + _update(); + }, + get supportedLanguages() { + return data.supportedLanguages; + }, + set supportedLanguages(value) { + data.supportedLanguages = value; + _update(); + }, + get hasExecutionOrder() { + return data.hasExecutionOrder ?? false; + }, + set hasExecutionOrder(value) { + data.hasExecutionOrder = value; + _update(); + }, + get preloads() { + return data.preloads ? data.preloads.map(extHostTypeConverters.NotebookKernelPreload.to) : []; + }, + get executeHandler() { + return _executeHandler; + }, + set executeHandler(value) { + _executeHandler = value ?? _defaultExecutHandler; + }, + get interruptHandler() { + return _interruptHandler; + }, + set interruptHandler(value) { + _interruptHandler = value; + data.supportsInterrupt = Boolean(value); + _update(); + }, + createNotebookCellExecutionTask(cell) { + if (isDisposed) { + throw new Error('notebook controller is DISPOSED'); + } + //todo@jrieken + return that._extHostNotebook.createNotebookCellExecution(cell.notebook.uri, cell.index, data.id)!; + }, + dispose: () => { + if (!isDisposed) { + isDisposed = true; + this._kernelData.delete(handle); + commandDisposables.dispose(); + onDidChangeSelection.dispose(); + onDidReceiveMessage.dispose(); + this._proxy.$removeKernel(handle); + } + }, + // --- ipc + onDidReceiveMessage: onDidReceiveMessage.event, + postMessage(message, editor) { + return that._proxy.$postMessage(handle, editor && that._extHostNotebook.getIdByEditor(editor), message); + }, + asWebviewUri(uri: URI) { + return asWebviewUri(that._initData.environment, String(handle), uri); + }, + // --- priority + updateNotebookAffinity(notebook, priority) { + that._proxy.$updateNotebookPriority(handle, notebook.uri, priority); + } + }; + + this._kernelData.set(handle, { extensionId: extension.identifier, controller, onDidChangeSelection, onDidReceiveMessage }); + return controller; + } + + $acceptSelection(handle: number, uri: UriComponents, value: boolean): void { + const obj = this._kernelData.get(handle); + if (obj) { + obj.onDidChangeSelection.fire({ + selected: value, + notebook: this._extHostNotebook.lookupNotebookDocument(URI.revive(uri))!.apiNotebook + }); + } + } + + async $executeCells(handle: number, uri: UriComponents, handles: number[]): Promise { + const obj = this._kernelData.get(handle); + if (!obj) { + // extension can dispose kernels in the meantime + return; + } + const document = this._extHostNotebook.lookupNotebookDocument(URI.revive(uri)); + if (!document) { + throw new Error('MISSING notebook'); + } + + const cells: vscode.NotebookCell[] = []; + for (let cellHandle of handles) { + const cell = document.getCell(cellHandle); + if (cell) { + cells.push(cell.apiCell); + } + } + + try { + await obj.controller.executeHandler.call(obj.controller, cells, document.apiNotebook, obj.controller); + } catch (err) { + // + console.error(err); + } + } + + async $cancelCells(handle: number, uri: UriComponents, handles: number[]): Promise { + const obj = this._kernelData.get(handle); + if (!obj) { + // extension can dispose kernels in the meantime + return; + } + const document = this._extHostNotebook.lookupNotebookDocument(URI.revive(uri)); + if (!document) { + throw new Error('MISSING notebook'); + } + if (obj.controller.interruptHandler) { + await obj.controller.interruptHandler.call(obj.controller, document.apiNotebook); + } + + // we do both? interrupt and cancellation or should we be selective? + for (let cellHandle of handles) { + const cell = document.getCell(cellHandle); + if (cell) { + this._extHostNotebook.cancelOneNotebookCellExecution(cell); + } + } + } + + $acceptRendererMessage(handle: number, editorId: string, message: any): void { + const obj = this._kernelData.get(handle); + if (!obj) { + // extension can dispose kernels in the meantime + return; + } + + const editor = this._extHostNotebook.getEditorById(editorId); + if (!editor) { + throw new Error(`send message for UNKNOWN editor: ${editorId}`); + } + + obj.onDidReceiveMessage.fire(Object.freeze({ editor: editor.apiEditor, message })); + } +} diff --git a/src/vs/workbench/api/common/extHostOutput.ts b/src/vs/workbench/api/common/extHostOutput.ts index a8bc2ddd..1b7a2d4c 100644 --- a/src/vs/workbench/api/common/extHostOutput.ts +++ b/src/vs/workbench/api/common/extHostOutput.ts @@ -73,7 +73,7 @@ export abstract class AbstractExtHostOutputChannel extends Disposable implements } } - dispose(): void { + override dispose(): void { super.dispose(); if (!this._disposed) { @@ -90,7 +90,7 @@ export class ExtHostPushOutputChannel extends AbstractExtHostOutputChannel { super(name, false, undefined, proxy); } - append(value: string): void { + override append(value: string): void { super.append(value); this._id.then(id => this._proxy.$append(id, value)); this._onDidAppend.fire(); @@ -103,7 +103,7 @@ class ExtHostLogFileOutputChannel extends AbstractExtHostOutputChannel { super(name, true, file, proxy); } - append(value: string): void { + override append(value: string): void { throw new Error('Not supported'); } } diff --git a/src/vs/workbench/api/common/extHostSCM.ts b/src/vs/workbench/api/common/extHostSCM.ts index 05806163..41aa464d 100644 --- a/src/vs/workbench/api/common/extHostSCM.ts +++ b/src/vs/workbench/api/common/extHostSCM.ts @@ -259,6 +259,22 @@ export class ExtHostSCMInputBox implements vscode.SourceControlInputBox { // noop } + focus(): void { + checkProposedApiEnabled(this._extension); + + if (!this._visible) { + this.visible = true; + } + + this._proxy.$setInputBoxFocus(this._sourceControlHandle); + } + + showValidationMessage(message: string, type: vscode.SourceControlInputBoxValidationType) { + checkProposedApiEnabled(this._extension); + + this._proxy.$showValidationMessage(this._sourceControlHandle, message, type as any); + } + $onInputBoxValueChange(value: string): void { this.updateValue(value); } diff --git a/src/vs/workbench/api/common/extHostTerminalService.ts b/src/vs/workbench/api/common/extHostTerminalService.ts index 8c34992d..a10d6b97 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, IShellAndArgsDto, ITerminalDimensionsDto, ITerminalLinkDto, TerminalIdentifier } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostTerminalServiceShape, MainContext, MainThreadTerminalServiceShape, 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'; @@ -19,7 +19,7 @@ import { serializeEnvironmentVariableCollection } from 'vs/workbench/contrib/ter import { CancellationTokenSource } from 'vs/base/common/cancellation'; 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 { IShellLaunchConfigDto, ITerminalChildProcess, ITerminalDimensionsOverride, ITerminalEnvironment, 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'; @@ -117,7 +117,9 @@ export class ExtHostTerminal { shellPath?: string, shellArgs?: string[] | string, cwd?: string | URI, - env?: { [key: string]: string | null }, + env?: ITerminalEnvironment, + icon?: string, + initialText?: string, waitOnExit?: boolean, strictEnv?: boolean, hideFromUser?: boolean, @@ -127,7 +129,7 @@ export class ExtHostTerminal { if (typeof this._id !== 'string') { throw new Error('Terminal has already been created'); } - await this._proxy.$createTerminal(this._id, { name: this._name, shellPath, shellArgs, cwd, env, waitOnExit, strictEnv, hideFromUser, isFeatureTerminal, isExtensionOwnedTerminal }); + await this._proxy.$createTerminal(this._id, { name: this._name, shellPath, shellArgs, cwd, env, icon, initialText, waitOnExit, strictEnv, hideFromUser, isFeatureTerminal, isExtensionOwnedTerminal }); } public async createExtensionTerminal(): Promise { @@ -225,6 +227,10 @@ export class ExtHostPseudoterminal implements ITerminalChildProcess { } } + async processBinary(data: string): Promise { + // No-op, processBinary is not supported in extextion owned terminals. + } + acknowledgeDataEvent(charCount: number): void { // No-op, flow control is not supported in extension owned terminals. If this is ever // implemented it will need new pause and resume VS Code APIs. @@ -328,9 +334,8 @@ 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 $getAvailableProfiles(quickLaunchOnly: boolean): Promise; + public abstract $getAvailableProfiles(configuredProfilesOnly: boolean): Promise; public abstract $getDefaultShellAndArgs(useAutomationShell: boolean): Promise; - public abstract $acceptWorkspacePermissionsChanged(isAllowed: boolean): void; public createExtensionTerminal(options: vscode.ExtensionTerminalOptions): vscode.Terminal { const terminal = new ExtHostTerminal(this._proxy, generateUuid(), options, options.name); @@ -771,23 +776,18 @@ export class WorkerExtHostTerminalService extends BaseExtHostTerminalService { } public getDefaultShell(useAutomationShell: boolean, configProvider: ExtHostConfigProvider): string { - // Return the empty string to avoid throwing - return ''; + throw new NotSupportedError(); } public getDefaultShellArgs(useAutomationShell: boolean, configProvider: ExtHostConfigProvider): string[] | string { throw new NotSupportedError(); } - public $getAvailableProfiles(quickLaunchOnly: boolean): Promise { + public $getAvailableProfiles(configuredProfilesOnly: boolean): Promise { throw new NotSupportedError(); } public async $getDefaultShellAndArgs(useAutomationShell: boolean): Promise { throw new NotSupportedError(); } - - public $acceptWorkspacePermissionsChanged(isAllowed: boolean): void { - // No-op for web worker ext host as workspace permissions aren't used - } } diff --git a/src/vs/workbench/api/common/extHostTesting.ts b/src/vs/workbench/api/common/extHostTesting.ts index 5d39e2e6..7afd44df 100644 --- a/src/vs/workbench/api/common/extHostTesting.ts +++ b/src/vs/workbench/api/common/extHostTesting.ts @@ -4,10 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import { mapFind } from 'vs/base/common/arrays'; -import { disposableTimeout } from 'vs/base/common/async'; +import { Barrier, DeferredPromise, disposableTimeout, isThenable } from 'vs/base/common/async'; +import { VSBuffer } from 'vs/base/common/buffer'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { Emitter } from 'vs/base/common/event'; import { once } from 'vs/base/common/functional'; +import { Iterable } from 'vs/base/common/iterator'; import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { deepFreeze } from 'vs/base/common/objects'; import { isDefined } from 'vs/base/common/types'; @@ -17,34 +19,40 @@ import { ExtHostTestingResource, ExtHostTestingShape, MainContext, MainThreadTes import { ExtHostDocumentData } from 'vs/workbench/api/common/extHostDocumentData'; import { IExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; +import { ExtHostTestItemEventType, getPrivateApiFor } from 'vs/workbench/api/common/extHostTestingPrivateApi'; import * as Convert from 'vs/workbench/api/common/extHostTypeConverters'; -import { Disposable, TestItem as TestItemImpl, TestItemHookProperty } from 'vs/workbench/api/common/extHostTypes'; +import { Disposable, TestItemImpl } from 'vs/workbench/api/common/extHostTypes'; import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; import { OwnedTestCollection, SingleUseTestCollection, TestPosition } from 'vs/workbench/contrib/testing/common/ownedTestCollection'; -import { AbstractIncrementalTestCollection, IncrementalChangeCollector, IncrementalTestCollectionItem, InternalTestItem, ISerializedTestResults, RunTestForProviderRequest, TestDiffOpType, TestIdWithSrc, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection'; +import { AbstractIncrementalTestCollection, IncrementalChangeCollector, IncrementalTestCollectionItem, InternalTestItem, ISerializedTestResults, ITestItem, RunTestForProviderRequest, TestDiffOpType, TestIdWithSrc, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection'; import type * as vscode from 'vscode'; const getTestSubscriptionKey = (resource: ExtHostTestingResource, uri: URI) => `${resource}:${uri.toString()}`; export class ExtHostTesting implements ExtHostTestingShape { private readonly resultsChangedEmitter = new Emitter(); - private readonly providers = new Map(); + private readonly controllers = new Map + }>(); private readonly proxy: MainThreadTestingShape; private readonly ownedTests = new OwnedTestCollection(); - private readonly testSubscriptions = new Map void; + subscribeFn: (id: string, provider: vscode.TestController) => void; }>(); private workspaceObservers: WorkspaceFolderTestObserverFactory; private textDocumentObservers: TextDocumentTestObserverFactory; public onResultsChanged = this.resultsChangedEmitter.event; - public results: ReadonlyArray = []; + public results: ReadonlyArray = []; constructor(@IExtHostRpcService rpc: IExtHostRpcService, @IExtHostDocumentsAndEditors private readonly documents: IExtHostDocumentsAndEditors, @IExtHostWorkspace private readonly workspace: IExtHostWorkspace) { this.proxy = rpc.getProxy(MainContext.MainThreadTesting); + this.runQueue = new TestRunQueue(this.proxy); this.workspaceObservers = new WorkspaceFolderTestObserverFactory(this.proxy); this.textDocumentObservers = new TextDocumentTestObserverFactory(this.proxy, documents); } @@ -52,22 +60,22 @@ export class ExtHostTesting implements ExtHostTestingShape { /** * Implements vscode.test.registerTestProvider */ - public registerTestProvider(provider: vscode.TestProvider): vscode.Disposable { - const providerId = generateUuid(); - this.providers.set(providerId, provider); - this.proxy.$registerTestProvider(providerId); + public registerTestController(extensionId: string, controller: vscode.TestController): vscode.Disposable { + const controllerId = generateUuid(); + this.controllers.set(controllerId, { instance: controller, extensionId }); + this.proxy.$registerTestController(controllerId); // give the ext a moment to register things rather than synchronously invoking within activate() - const toSubscribe = [...this.testSubscriptions.keys()]; + const toSubscribe = [...this.testControllers.keys()]; setTimeout(() => { for (const subscription of toSubscribe) { - this.testSubscriptions.get(subscription)?.subscribeFn(providerId, provider); + this.testControllers.get(subscription)?.subscribeFn(controllerId, controller); } }, 0); return new Disposable(() => { - this.providers.delete(providerId); - this.proxy.$unregisterTestProvider(providerId); + this.controllers.delete(controllerId); + this.proxy.$unregisterTestController(controllerId); }); } @@ -88,8 +96,8 @@ export class ExtHostTesting implements ExtHostTestingShape { /** * Implements vscode.test.runTests */ - public async runTests(req: vscode.TestRunOptions, token = CancellationToken.None) { - const testListToProviders = (tests: ReadonlyArray) => + public async runTests(req: vscode.TestRunRequest, token = CancellationToken.None) { + const testListToProviders = (tests: ReadonlyArray>) => tests .map(this.getInternalTestForReference, this) .filter(isDefined) @@ -103,10 +111,10 @@ export class ExtHostTesting implements ExtHostTestingShape { } /** - * Implements vscode.test.publishTestResults + * Implements vscode.test.createTestRun */ - public publishExtensionProvidedResults(results: vscode.TestResults, persist: boolean): void { - this.proxy.$publishExtensionProvidedResults(Convert.TestResults.from(generateUuid(), results), persist); + public createTestRun(extensionId: string, request: vscode.TestRunRequest, name: string | undefined, persist = true): vscode.TestRun { + return this.runQueue.createTestRun(extensionId, request, name, persist); } /** @@ -132,12 +140,12 @@ export class ExtHostTesting implements ExtHostTestingShape { public async $subscribeToTests(resource: ExtHostTestingResource, uriComponents: UriComponents) { const uri = URI.revive(uriComponents); const subscriptionKey = getTestSubscriptionKey(resource, uri); - if (this.testSubscriptions.has(subscriptionKey)) { + if (this.testControllers.has(subscriptionKey)) { return; } const cancellation = new CancellationTokenSource(); - let method: undefined | ((p: vscode.TestProvider) => vscode.ProviderResult); + let method: undefined | ((p: vscode.TestController) => vscode.ProviderResult>); if (resource === ExtHostTestingResource.TextDocument) { let document = this.documents.getDocument(uri); @@ -156,14 +164,14 @@ export class ExtHostTesting implements ExtHostTestingShape { if (document) { const folder = await this.workspace.getWorkspaceFolder2(uri, false); - method = p => p.provideDocumentTestRoot - ? p.provideDocumentTestRoot(document!.document, cancellation.token) + method = p => p.createDocumentTestRoot + ? p.createDocumentTestRoot(document!.document, cancellation.token) : createDefaultDocumentTestRoot(p, document!.document, folder, cancellation.token); } } else { const folder = await this.workspace.getWorkspaceFolder2(uri, false); if (folder) { - method = p => p.provideWorkspaceTestRoot(folder, cancellation.token); + method = p => p.createWorkspaceTestRoot(folder, cancellation.token); } } @@ -171,7 +179,7 @@ export class ExtHostTesting implements ExtHostTestingShape { return; } - const subscribeFn = async (id: string, provider: vscode.TestProvider) => { + const subscribeFn = async (id: string, provider: vscode.TestController) => { try { const root = await method!(provider); if (root) { @@ -186,15 +194,16 @@ export class ExtHostTesting implements ExtHostTestingShape { const collection = disposable.add(this.ownedTests.createForHierarchy( diff => this.proxy.$publishDiff(resource, uriComponents, diff))); disposable.add(toDisposable(() => cancellation.dispose(true))); - for (const [id, provider] of this.providers) { - subscribeFn(id, provider); + const subscribes: Promise[] = []; + for (const [id, controller] of this.controllers) { + subscribes.push(subscribeFn(id, controller.instance)); } - // note: we don't increment the root count initially -- this is done by the + // note: we don't increment the count initially -- this is done by the // main thread, incrementing once per extension host. We just push the // diff to signal that roots have been discovered. - collection.pushDiff([TestDiffOpType.DeltaRootsComplete, -1]); - this.testSubscriptions.set(subscriptionKey, { store: disposable, collection, subscribeFn }); + Promise.all(subscribes).then(() => collection.pushDiff([TestDiffOpType.IncrementPendingExtHosts, -1])); + this.testControllers.set(subscriptionKey, { store: disposable, collection, subscribeFn }); } /** @@ -203,7 +212,7 @@ export class ExtHostTesting implements ExtHostTestingShape { * @override */ public async $expandTest(test: TestIdWithSrc, levels: number) { - const sub = mapFind(this.testSubscriptions.values(), s => s.collection.treeId === test.src.tree ? s : undefined); + const sub = mapFind(this.testControllers.values(), s => s.collection.treeId === test.src.tree ? s : undefined); await sub?.collection.expand(test.testId, levels < 0 ? Infinity : levels); this.flushCollectionDiffs(); } @@ -215,8 +224,8 @@ export class ExtHostTesting implements ExtHostTestingShape { public $unsubscribeFromTests(resource: ExtHostTestingResource, uriComponents: UriComponents) { const uri = URI.revive(uriComponents); const subscriptionKey = getTestSubscriptionKey(resource, uri); - this.testSubscriptions.get(subscriptionKey)?.store.dispose(); - this.testSubscriptions.delete(subscriptionKey); + this.testControllers.get(subscriptionKey)?.store.dispose(); + this.testControllers.delete(subscriptionKey); } /** @@ -237,14 +246,14 @@ export class ExtHostTesting implements ExtHostTestingShape { * providers to be run. * @override */ - public async $runTestsForProvider(req: RunTestForProviderRequest, cancellation: CancellationToken): Promise { - const provider = this.providers.get(req.tests[0].src.provider); - if (!provider) { + public async $runTestsForProvider(req: RunTestForProviderRequest, token: CancellationToken): Promise { + const controller = this.controllers.get(req.tests[0].src.controller); + if (!controller) { return; } const includeTests = req.tests - .map(({ testId, src }) => this.ownedTests.getTestById(testId, src.tree)) + .map(({ testId, src }) => this.ownedTests.getTestById(testId, src?.tree)) .filter(isDefined) .map(([_tree, test]) => test); @@ -259,35 +268,19 @@ export class ExtHostTesting implements ExtHostTestingShape { return; } - try { - await provider.runTests({ - setState: (test, state) => { - // for test providers that don't support excluding natively, - // make sure not to report excluded result otherwise summaries will be off. - for (const [tree, exclude] of excludeTests) { - const e = tree.comparePositions(exclude, test.id); - if (e === TestPosition.IsChild || e === TestPosition.IsSame) { - return; - } - } + const publicReq: vscode.TestRunRequest = { + tests: includeTests.map(t => TestItemFilteredWrapper.unwrap(t.actual)), + exclude: excludeTests.map(([, t]) => TestItemFilteredWrapper.unwrap(t.actual)), + debug: req.debug, + }; - this.flushCollectionDiffs(); - this.proxy.$updateTestStateInRun(req.runId, test.id, Convert.TestState.from(state)); - }, - tests: includeTests.map(t => TestItemFilteredWrapper.unwrap(t.actual)), - exclude: excludeTests.map(([, t]) => TestItemFilteredWrapper.unwrap(t.actual)), - debug: req.debug, - }, cancellation); - - for (const { collection } of this.testSubscriptions.values()) { - collection.flushDiff(); // ensure all states are updated - } - - return; - } catch (e) { - console.error(e); // so it appears to attached debuggers - throw e; - } + await this.runQueue.enqueueRun({ + dto: TestRunDto.fromInternal(req), + token, + extensionId: controller.extensionId, + req: publicReq, + doRun: () => controller!.instance.runTests(publicReq, token) + }); } public $lookupTest(req: TestIdWithSrc): Promise { @@ -305,7 +298,7 @@ export class ExtHostTesting implements ExtHostTestingShape { * main thread is updated. */ private flushCollectionDiffs() { - for (const { collection } of this.testSubscriptions.values()) { + for (const { collection } of this.testControllers.values()) { collection.flushDiff(); } } @@ -313,18 +306,236 @@ export class ExtHostTesting implements ExtHostTestingShape { /** * Gets the internal test item associated with the reference from the extension. */ - private getInternalTestForReference(test: vscode.TestItem) { + private getInternalTestForReference(test: vscode.TestItem) { // Find workspace items first, then owned tests, then document tests. // If a test instance exists in both the workspace and document, prefer // the workspace because it's less ephemeral. return this.workspaceObservers.getMirroredTestDataByReference(test) - ?? mapFind(this.testSubscriptions.values(), c => c.collection.getTestByReference(test)) + ?? mapFind(this.testControllers.values(), c => c.collection.getTestByReference(test)) ?? this.textDocumentObservers.getMirroredTestDataByReference(test); } } -export const createDefaultDocumentTestRoot = async ( - provider: vscode.TestProvider, +/** + * Queues runs for a single extension and provides the currently-executing + * run so that `createTestRun` can be properly correlated. + */ +class TestRunQueue { + private readonly state = new Map, + factory: (name: string | undefined) => TestRunTask, + }, + queue: (() => (Promise | void))[]; + }>(); + + constructor(private readonly proxy: MainThreadTestingShape) { } + + /** + * Registers and enqueues a test run. `doRun` will be called when an + * invokation to {@link TestController.runTests} should be called. + */ + public enqueueRun(opts: { + extensionId: string, + req: vscode.TestRunRequest, + dto: TestRunDto, + token: CancellationToken, + doRun: () => Thenable | void, + }, + ) { + let record = this.state.get(opts.extensionId); + if (!record) { + record = { queue: [], current: undefined as any }; + this.state.set(opts.extensionId, record); + } + + const deferred = new DeferredPromise(); + const runner = () => { + const tasks: TestRunTask[] = []; + const shared = new Set(); + record!.current = { + publicReq: opts.req, + factory: name => { + const task = new TestRunTask(name, opts.dto, shared, this.proxy); + tasks.push(task); + opts.token.onCancellationRequested(() => task.end()); + return task; + }, + }; + + this.invokeRunner(opts.extensionId, opts.dto.id, opts.doRun, tasks).finally(() => deferred.complete()); + }; + + record.queue.push(runner); + if (record.queue.length === 1) { + runner(); + } + + return deferred.p; + } + + /** + * Implements the public `createTestRun` API. + */ + public createTestRun(extensionId: string, request: vscode.TestRunRequest, name: string | undefined, persist: boolean): vscode.TestRun { + const state = this.state.get(extensionId); + // If the request is for the currently-executing `runTests`, then correlate + // it to that existing run. Otherwise return a new, detached run. + if (state?.current.publicReq === request) { + return state.current.factory(name); + } + + const dto = TestRunDto.fromPublic(request); + const task = new TestRunTask(name, dto, new Set(), this.proxy); + this.proxy.$startedExtensionTestRun({ + debug: request.debug, + exclude: request.exclude?.map(t => t.id) ?? [], + id: dto.id, + tests: request.tests.map(t => t.id), + persist: persist + }); + task.onEnd.wait().then(() => this.proxy.$finishedExtensionTestRun(dto.id)); + return task; + } + + private invokeRunner(extensionId: string, runId: string, fn: () => Thenable | void, tasks: TestRunTask[]): Promise { + try { + const res = fn(); + if (isThenable(res)) { + return res + .then(() => this.handleInvokeResult(extensionId, runId, tasks, undefined)) + .catch(err => this.handleInvokeResult(extensionId, runId, tasks, err)); + } else { + return this.handleInvokeResult(extensionId, runId, tasks, undefined); + } + } catch (e) { + return this.handleInvokeResult(extensionId, runId, tasks, e); + } + } + + private async handleInvokeResult(extensionId: string, runId: string, tasks: TestRunTask[], error?: Error) { + const record = this.state.get(extensionId); + if (!record) { + return; + } + + record.queue.shift(); + if (record.queue.length > 0) { + record.queue[0](); + } else { + this.state.delete(extensionId); + } + + await Promise.all(tasks.map(t => t.onEnd.wait())); + } +} + +class TestRunDto { + public static fromPublic(request: vscode.TestRunRequest) { + return new TestRunDto( + generateUuid(), + new Set(request.tests.map(t => t.id)), + new Set(request.exclude?.map(t => t.id) ?? Iterable.empty()), + ); + } + + public static fromInternal(request: RunTestForProviderRequest) { + return new TestRunDto( + request.runId, + new Set(request.tests.map(t => t.testId)), + new Set(request.excludeExtIds), + ); + } + + constructor( + public readonly id: string, + private readonly include: ReadonlySet, + private readonly exclude: ReadonlySet, + ) { } + + public isIncluded(test: vscode.TestItem) { + for (let t: vscode.TestItem | undefined = test; t; t = t.parent) { + if (this.include.has(t.id)) { + return true; + } else if (this.exclude.has(t.id)) { + return false; + } + } + + return true; + } +} + +class TestRunTask implements vscode.TestRun { + readonly #proxy: MainThreadTestingShape; + readonly #req: TestRunDto; + readonly #taskId = generateUuid(); + readonly #sharedIds: Set; + public readonly onEnd = new Barrier(); + + constructor( + public readonly name: string | undefined, + dto: TestRunDto, + sharedTestIds: Set, + proxy: MainThreadTestingShape, + ) { + this.#proxy = proxy; + this.#req = dto; + this.#sharedIds = sharedTestIds; + proxy.$startedTestRunTask(dto.id, { id: this.#taskId, name, running: true }); + } + + setState(test: vscode.TestItem, state: vscode.TestResultState, duration?: number): void { + if (this.#req.isIncluded(test)) { + this.ensureTestIsKnown(test); + this.#proxy.$updateTestStateInRun(this.#req.id, this.#taskId, test.id, state, duration); + } + } + + appendMessage(test: vscode.TestItem, message: vscode.TestMessage): void { + if (this.#req.isIncluded(test)) { + this.ensureTestIsKnown(test); + this.#proxy.$appendTestMessageInRun(this.#req.id, this.#taskId, test.id, Convert.TestMessage.from(message)); + } + } + + appendOutput(output: string): void { + this.#proxy.$appendOutputToRun(this.#req.id, this.#taskId, VSBuffer.fromString(output)); + } + + end(): void { + this.#proxy.$finishedTestRunTask(this.#req.id, this.#taskId); + this.onEnd.open(); + } + + private ensureTestIsKnown(test: vscode.TestItem) { + const sent = this.#sharedIds; + if (sent.has(test.id)) { + return; + } + + const chain: ITestItem[] = []; + while (true) { + chain.unshift(Convert.TestItem.from(test)); + + if (sent.has(test.id)) { + break; + } + + sent.add(test.id); + if (!test.parent) { + break; + } + + test = test.parent; + } + + this.#proxy.$addTestsToRun(this.#req.id, chain); + } +} + +export const createDefaultDocumentTestRoot = async ( + provider: vscode.TestController, document: vscode.TextDocument, folder: vscode.WorkspaceFolder | undefined, token: CancellationToken, @@ -333,7 +544,7 @@ export const createDefaultDocumentTestRoot = async ( return; } - const root = await provider.provideWorkspaceTestRoot(folder, token); + const root = await provider.createWorkspaceTestRoot(folder, token); if (!root) { return; } @@ -342,32 +553,35 @@ export const createDefaultDocumentTestRoot = async ( TestItemFilteredWrapper.removeFilter(document); }); - return TestItemFilteredWrapper.getWrapperForTestItem(root, document); + const wrapper = TestItemFilteredWrapper.getWrapperForTestItem(root, document); + wrapper.refreshMatch(); + return wrapper; }; /* * A class which wraps a vscode.TestItem that provides the ability to filter a TestItem's children * to only the children that are located in a certain vscode.Uri. */ -export class TestItemFilteredWrapper extends TestItemImpl { - private static wrapperMap = new WeakMap>(); +export class TestItemFilteredWrapper extends TestItemImpl { + private static wrapperMap = new WeakMap, TestItemFilteredWrapper>>(); + public static removeFilter(document: vscode.TextDocument): void { this.wrapperMap.delete(document); } // Wraps the TestItem specified in a TestItemFilteredWrapper and pulls from a cache if it already exists. - public static getWrapperForTestItem( - item: T, + public static getWrapperForTestItem( + item: vscode.TestItem, filterDocument: vscode.TextDocument, - parent?: TestItemFilteredWrapper, - ): TestItemFilteredWrapper { + parent?: TestItemFilteredWrapper, + ): TestItemFilteredWrapper { let innerMap = this.wrapperMap.get(filterDocument); if (innerMap?.has(item)) { - return innerMap.get(item) as TestItemFilteredWrapper; + return innerMap.get(item) as TestItemFilteredWrapper; } if (!innerMap) { - innerMap = new WeakMap(); + innerMap = new WeakMap(); this.wrapperMap.set(filterDocument, innerMap); } @@ -380,8 +594,8 @@ export class TestItemFilteredWrapper(item: vscode.TestItem | TestItemFilteredWrapper) { + return item instanceof TestItemFilteredWrapper ? item.actual as vscode.TestItem : item; } private _cachedMatchesFilter: boolean | undefined; @@ -398,21 +612,39 @@ export class TestItemFilteredWrapper, private filterDocument: vscode.TextDocument, - public readonly parent?: TestItemFilteredWrapper, + public readonly actualParent?: TestItemFilteredWrapper, ) { - super(actual.id, actual.label, actual.uri, actual.expandable); + super(actual.id, actual.label, actual.uri, undefined); if (!(actual instanceof TestItemImpl)) { throw new Error(`TestItems provided to the VS Code API must extend \`vscode.TestItem\`, but ${actual.id} did not`); } - (actual as TestItemImpl)[TestItemHookProperty] = { - setProp: (key, value) => (this as Record)[key] = value, - created: child => TestItemFilteredWrapper.getWrapperForTestItem(child, this.filterDocument, this).refreshMatch(), - invalidate: () => this.invalidate(), - delete: child => this.children.delete(child), - }; + this.debuggable = actual.debuggable; + this.runnable = actual.runnable; + this.description = actual.description; + this.error = actual.error; + this.status = actual.status; + this.range = actual.range; + this.resolveHandler = actual.resolveHandler; + + const wrapperApi = getPrivateApiFor(this); + const actualApi = getPrivateApiFor(actual); + actualApi.bus.event(evt => { + switch (evt[0]) { + case ExtHostTestItemEventType.SetProp: + (this as Record)[evt[1]] = evt[2]; + break; + case ExtHostTestItemEventType.NewChild: + const wrapper = TestItemFilteredWrapper.getWrapperForTestItem(evt[1], this.filterDocument, this); + getPrivateApiFor(wrapper).parent = actual; + wrapper.refreshMatch(); + break; + default: + wrapperApi.bus.fire(evt); + } + }); } /** @@ -420,17 +652,17 @@ export class TestItemFilteredWrapper; depth: number; } @@ -464,21 +704,21 @@ class MirroredChangeCollector extends IncrementalChangeCollector) { + constructor(private readonly emitter: Emitter) { super(); } /** * @override */ - public add(node: MirroredCollectionTestItem): void { + public override add(node: MirroredCollectionTestItem): void { this.added.add(node); } /** * @override */ - public update(node: MirroredCollectionTestItem): void { + public override update(node: MirroredCollectionTestItem): void { Object.assign(node.revived, Convert.TestItem.toPlain(node.item)); if (!this.added.has(node)) { this.updated.add(node); @@ -488,7 +728,7 @@ class MirroredChangeCollector extends IncrementalChangeCollector n.revived); }, @@ -516,7 +756,7 @@ class MirroredChangeCollector extends IncrementalChangeCollector { - private changeEmitter = new Emitter(); + private changeEmitter = new Emitter(); /** * Change emitter that fires with the same sematics as `TestObserver.onDidChangeTests`. @@ -546,7 +786,7 @@ export class MirroredTestCollection extends AbstractIncrementalTestCollection) { - let output: vscode.TestItem[] = []; + let output: vscode.TestItem[] = []; for (const itemId of itemIds) { const item = this.items.get(itemId); if (item) { @@ -568,7 +808,7 @@ export class MirroredTestCollection extends AbstractIncrementalTestCollection) { return this.items.get(item.id); } @@ -579,7 +819,7 @@ export class MirroredTestCollection extends AbstractIncrementalTestCollection, depth: parent ? parent.depth + 1 : 0, children: new Set(), }; @@ -588,7 +828,7 @@ export class MirroredTestCollection extends AbstractIncrementalTestCollection) { for (const { tests } of this.resources.values()) { const v = tests.getMirroredTestDataByReference(ref); if (v) { diff --git a/src/vs/workbench/api/common/extHostTestingPrivateApi.ts b/src/vs/workbench/api/common/extHostTestingPrivateApi.ts new file mode 100644 index 00000000..66394266 --- /dev/null +++ b/src/vs/workbench/api/common/extHostTestingPrivateApi.ts @@ -0,0 +1,44 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { TestItemImpl } from 'vs/workbench/api/common/extHostTypes'; +import * as vscode from 'vscode'; + +export const enum ExtHostTestItemEventType { + NewChild, + Disposed, + Invalidated, + SetProp, +} + +export type ExtHostTestItemEvent = + | [evt: ExtHostTestItemEventType.NewChild, item: TestItemImpl] + | [evt: ExtHostTestItemEventType.Disposed] + | [evt: ExtHostTestItemEventType.Invalidated] + | [evt: ExtHostTestItemEventType.SetProp, key: keyof vscode.TestItem, value: any]; + +export interface IExtHostTestItemApi { + children: Map; + parent?: TestItemImpl; + bus: Emitter; +} + +const eventPrivateApis = new WeakMap(); + +/** + * Gets the private API for a test item implementation. This implementation + * is a managed object, but we keep a weakmap to avoid exposing any of the + * internals to extensions. + */ +export const getPrivateApiFor = (impl: TestItemImpl) => { + let api = eventPrivateApis.get(impl); + if (!api) { + api = { children: new Map(), bus: new Emitter() }; + eventPrivateApis.set(impl, api); + } + + return api; +}; diff --git a/src/vs/workbench/api/common/extHostTreeViews.ts b/src/vs/workbench/api/common/extHostTreeViews.ts index df51908e..bf280170 100644 --- a/src/vs/workbench/api/common/extHostTreeViews.ts +++ b/src/vs/workbench/api/common/extHostTreeViews.ts @@ -497,12 +497,12 @@ class ExtHostTreeView extends Disposable { private getHandlesToRefresh(elements: T[]): TreeItemHandle[] { const elementsToUpdate = new Set(); - for (const element of elements) { - const elementNode = this.nodes.get(element); + const elementNodes = elements.map(element => this.nodes.get(element)); + for (const elementNode of elementNodes) { if (elementNode && !elementsToUpdate.has(elementNode.item.handle)) { - // check if an ancestor of extElement is already in the elements to update list + // check if an ancestor of extElement is already in the elements list let currentNode: TreeNode | undefined = elementNode; - while (currentNode && currentNode.parent && !elementsToUpdate.has(currentNode.parent.item.handle)) { + while (currentNode && currentNode.parent && elementNodes.findIndex(node => currentNode && currentNode.parent && node && node.item.handle === currentNode.parent.item.handle) === -1) { const parentElement: T | undefined = this.elements.get(currentNode.parent.item.handle); currentNode = parentElement ? this.nodes.get(parentElement) : undefined; } @@ -749,7 +749,7 @@ class ExtHostTreeView extends Disposable { this.nodes.clear(); } - dispose() { + override dispose() { this._refreshCancellationSource.dispose(); this.clearAll(); diff --git a/src/vs/workbench/api/common/extHostTunnelService.ts b/src/vs/workbench/api/common/extHostTunnelService.ts index ae7bad01..f9e02e6d 100644 --- a/src/vs/workbench/api/common/extHostTunnelService.ts +++ b/src/vs/workbench/api/common/extHostTunnelService.ts @@ -46,7 +46,7 @@ export interface IExtHostTunnelService extends ExtHostTunnelServiceShape { getTunnels(): Promise; onDidChangeTunnels: vscode.Event; setTunnelExtensionFunctions(provider: vscode.RemoteAuthorityResolver | undefined): Promise; - registerPortsAttributesProvider(portSelector: { pid?: number, portRange?: [number, number] }, provider: vscode.PortAttributesProvider): IDisposable; + registerPortsAttributesProvider(portSelector: { pid?: number, portRange?: [number, number], commandMatcher?: RegExp }, provider: vscode.PortAttributesProvider): IDisposable; } export const IExtHostTunnelService = createDecorator('IExtHostTunnelService'); diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 280066bd..df146128 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -5,6 +5,7 @@ import { coalesce, isNonEmptyArray } from 'vs/base/common/arrays'; import * as htmlContent from 'vs/base/common/htmlContent'; +import { DisposableStore } from 'vs/base/common/lifecycle'; import * as marked from 'vs/base/common/marked/marked'; import { parse } from 'vs/base/common/marshalling'; import { cloneAndChange } from 'vs/base/common/objects'; @@ -27,8 +28,9 @@ import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocum import { ExtHostNotebookController } from 'vs/workbench/api/common/extHostNotebook'; import { EditorGroupColumn, SaveReason } from 'vs/workbench/common/editor'; import * as notebooks from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; import * as search from 'vs/workbench/contrib/search/common/search'; -import { ISerializedTestResults, ITestItem, ITestMessage, ITestState, SerializedTestResultItem, TestItemExpandState } from 'vs/workbench/contrib/testing/common/testCollection'; +import { ISerializedTestResults, ITestItem, ITestMessage, SerializedTestResultItem } from 'vs/workbench/contrib/testing/common/testCollection'; import { ACTIVE_GROUP, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import type * as vscode from 'vscode'; import * as types from './extHostTypes'; @@ -543,7 +545,7 @@ export namespace WorkspaceEdit { resource: entry.uri, edit: entry.edit, notebookMetadata: entry.notebookMetadata, - notebookVersionId: extHostNotebooks?.lookupNotebookDocument(entry.uri)?.notebookDocument.version + notebookVersionId: extHostNotebooks?.lookupNotebookDocument(entry.uri)?.apiNotebook.version }); } else if (entry._type === types.FileEditType.CellOutput) { @@ -578,7 +580,7 @@ export namespace WorkspaceEdit { _type: extHostProtocol.WorkspaceEditType.Cell, metadata: entry.metadata, resource: entry.uri, - notebookVersionId: extHostNotebooks?.lookupNotebookDocument(entry.uri)?.notebookDocument.version, + notebookVersionId: extHostNotebooks?.lookupNotebookDocument(entry.uri)?.apiNotebook.version, edit: { editType: notebooks.CellEditType.Replace, index: entry.index, @@ -1404,21 +1406,31 @@ export namespace LanguageSelector { } } -export namespace NotebookCellRange { +export namespace NotebookRange { - export function from(range: vscode.NotebookCellRange): notebooks.ICellRange { + export function from(range: vscode.NotebookRange): ICellRange { return { start: range.start, end: range.end }; } - export function to(range: notebooks.ICellRange): types.NotebookCellRange { - return new types.NotebookCellRange(range.start, range.end); + export function to(range: ICellRange): types.NotebookRange { + return new types.NotebookRange(range.start, range.end); } } export namespace NotebookCellMetadata { export function to(data: notebooks.NotebookCellMetadata): types.NotebookCellMetadata { - return new types.NotebookCellMetadata(data.editable, data.breakpointMargin, data.hasExecutionOrder, data.statusMessage, data.inputCollapsed, data.outputCollapsed, data.custom); + return new types.NotebookCellMetadata().with({ + ...data, + ...{ + executionOrder: null, + lastRunSuccess: null, + runState: null, + runStartTime: null, + runStartTimeAdjustment: null, + runEndTime: null + } + }); } } @@ -1429,14 +1441,15 @@ export namespace NotebookDocumentMetadata { } export function to(data: notebooks.NotebookDocumentMetadata): types.NotebookDocumentMetadata { - return new types.NotebookDocumentMetadata(data.editable, data.cellEditable, data.cellHasExecutionOrder, data.custom, data.trusted); + return new types.NotebookDocumentMetadata().with(data); } } export namespace NotebookCellPreviousExecutionResult { export function to(data: notebooks.NotebookCellMetadata): vscode.NotebookCellExecutionSummary { return { - duration: data.lastRunDuration, + startTime: data.runStartTime, + endTime: data.runEndTime, executionOrder: data.executionOrder, success: data.lastRunSuccess }; @@ -1445,7 +1458,8 @@ export namespace NotebookCellPreviousExecutionResult { export function from(data: vscode.NotebookCellExecutionSummary): Partial { return { lastRunSuccess: data.success, - lastRunDuration: data.duration, + runStartTime: data.startTime, + runEndTime: data.endTime, executionOrder: data.executionOrder }; } @@ -1612,38 +1626,52 @@ export namespace NotebookDecorationRenderOptions { } } -export namespace NotebookDocumentContentOptions { - export function from(options: vscode.NotebookDocumentContentOptions | undefined): notebooks.TransientOptions { +export namespace NotebookStatusBarItem { + export function from(item: vscode.NotebookCellStatusBarItem, commandsConverter: CommandsConverter, disposables: DisposableStore): notebooks.INotebookCellStatusBarItem { + const command = typeof item.command === 'string' ? { title: '', command: item.command } : item.command; return { - transientOutputs: options ? options.transientOutputs : false, - transientMetadata: { - ...(options?.transientMetadata ?? {}), - ...{ - executionOrder: true, - lastRunDuration: true, - runState: true, - runStartTime: true, - lastRunSuccess: true - } - } + alignment: item.alignment === types.NotebookCellStatusBarAlignment.Left ? notebooks.CellStatusbarAlignment.Left : notebooks.CellStatusbarAlignment.Right, + command: commandsConverter.toInternal(command, disposables), // TODO@roblou + text: item.text, + tooltip: item.tooltip, + accessibilityInformation: item.accessibilityInformation, + priority: item.priority }; } } -export namespace TestState { - export function from(item: vscode.TestState): ITestState { +export namespace NotebookDocumentContentOptions { + export function from(options: vscode.NotebookDocumentContentOptions | undefined): notebooks.TransientOptions { return { - state: item.state, - duration: item.duration, - messages: item.messages.map(TestMessage.from), + transientOutputs: options?.transientOutputs ?? false, + transientCellMetadata: { + ...options?.transientCellMetadata, + executionOrder: true, + runState: true, + runStartTime: true, + runStartTimeAdjustment: true, + runEndTime: true, + lastRunSuccess: true + }, + transientDocumentMetadata: options?.transientDocumentMetadata ?? {} }; } +} - export function to(item: ITestState): vscode.TestState { - const ts = new types.TestState(item.state); - ts.duration = item.duration; - ts.messages = item.messages.map(TestMessage.to); - return ts; +export namespace NotebookKernelPreload { + export function from(preload: vscode.NotebookKernelPreload): { uri: UriComponents; provides: string[] } { + return { + uri: preload.uri, + provides: typeof preload.provides === 'string' + ? [preload.provides] + : preload.provides ?? [] + }; + } + export function to(preload: { uri: UriComponents; provides: string[] }): vscode.NotebookKernelPreload { + return { + uri: URI.revive(preload.uri), + provides: preload.provides + }; } } @@ -1668,18 +1696,18 @@ export namespace TestMessage { } export namespace TestItem { - export type Raw = vscode.TestItem; + export type Raw = vscode.TestItem; - export function from(item: vscode.TestItem): ITestItem { + export function from(item: vscode.TestItem): ITestItem { return { extId: item.id, label: item.label, uri: item.uri, - range: Range.from(item.range), + range: Range.from(item.range) || null, debuggable: item.debuggable ?? false, - description: item.description, + description: item.description || null, runnable: item.runnable ?? true, - expandable: item.expandable, + error: item.error ? (MarkdownString.fromStrict(item.error) || null) : null, }; } @@ -1688,84 +1716,55 @@ export namespace TestItem { extId: item.id, label: item.label, uri: item.uri, - range: Range.from(item.range), + range: Range.from(item.range) || null, debuggable: false, - description: item.description, + description: item.description || null, + error: null, runnable: true, - expandable: true, }; } - export function toPlain(item: ITestItem): Omit { + export function toPlain(item: ITestItem): Omit, 'children' | 'invalidate' | 'discoverChildren'> { return { id: item.extId, label: item.label, uri: URI.revive(item.uri), - range: Range.to(item.range), - expandable: item.expandable, + range: Range.to(item.range || undefined), + addChild: () => undefined, + dispose: () => undefined, + status: types.TestItemStatus.Pending, + data: undefined as never, debuggable: item.debuggable, - description: item.description, + description: item.description || undefined, runnable: item.runnable, }; } - export function to(item: ITestItem): types.TestItem { - const testItem = new types.TestItem(item.extId, item.label, URI.revive(item.uri), item.expandable); - testItem.range = Range.to(item.range); + export function to(item: ITestItem): types.TestItemImpl { + const testItem = new types.TestItemImpl(item.extId, item.label, URI.revive(item.uri), undefined); + testItem.range = Range.to(item.range || undefined); testItem.debuggable = item.debuggable; - testItem.description = item.description; + testItem.description = item.description || undefined; testItem.runnable = item.runnable; return testItem; } } export namespace TestResults { - export function from(id: string, results: vscode.TestResults): ISerializedTestResults { - const serialized: ISerializedTestResults = { - completedAt: results.completedAt, - id, - items: [], - }; - - const queue: [parent: SerializedTestResultItem | null, children: Iterable][] = [ - [null, results.results], - ]; - - while (queue.length) { - const [parent, children] = queue.pop()!; - for (const item of children) { - const serializedItem: SerializedTestResultItem = { - children: item.children?.map(c => c.id) ?? [], - computedState: item.result.state, - item: TestItem.fromResultSnapshot(item), - state: TestState.from(item.result), - retired: undefined, - expand: TestItemExpandState.Expanded, - parent: parent?.item.extId ?? null, - src: { provider: '', tree: -1 }, - direct: !parent, - }; - - serialized.items.push(serializedItem); - if (item.children) { - queue.push([serializedItem, item.children]); - } - } - } - - return serialized; - } - const convertTestResultItem = (item: SerializedTestResultItem, byInternalId: Map): vscode.TestResultSnapshot => ({ ...TestItem.toPlain(item.item), - result: TestState.to(item.state), + taskStates: item.tasks.map(t => ({ + state: t.state, + duration: t.duration, + messages: t.messages.map(TestMessage.to), + })), children: item.children .map(c => byInternalId.get(c)) .filter(isDefined) .map(c => convertTestResultItem(c, byInternalId)), }); - export function to(serialized: ISerializedTestResults): vscode.TestResults { + export function to(serialized: ISerializedTestResults): vscode.TestRunResult { const roots: SerializedTestResultItem[] = []; const byInternalId = new Map(); for (const item of serialized.items) { diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 655b3798..85e8d64c 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -7,12 +7,14 @@ import { coalesceInPlace, equals } from 'vs/base/common/arrays'; import { illegalArgument } from 'vs/base/common/errors'; import { IRelativePattern } from 'vs/base/common/glob'; import { isMarkdownString, MarkdownString as BaseMarkdownString } from 'vs/base/common/htmlContent'; -import { ResourceMap } from 'vs/base/common/map'; +import { ReadonlyMapView, ResourceMap } from 'vs/base/common/map'; +import { isFalsyOrWhitespace } from 'vs/base/common/strings'; import { isStringArray } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import { FileSystemProviderErrorCode, markAsFileSystemProviderError } from 'vs/platform/files/common/files'; import { RemoteAuthorityResolverErrorCode } from 'vs/platform/remote/common/remoteAuthorityResolver'; +import { getPrivateApiFor, ExtHostTestItemEventType, IExtHostTestItemApi } from 'vs/workbench/api/common/extHostTestingPrivateApi'; import { CellEditType, ICellEditOperation } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import type * as vscode from 'vscode'; @@ -419,7 +421,7 @@ export class Selection extends Range { return this._anchor === this._end; } - toJSON() { + override toJSON() { return { start: this.start, end: this.end, @@ -676,34 +678,33 @@ export class WorkspaceEdit implements vscode.WorkspaceEdit { this._edits.push({ _type: FileEditType.Cell, metadata, uri, edit: { editType: CellEditType.DocumentMetadata, metadata: value }, notebookMetadata: value }); } - replaceNotebookCells(uri: URI, start: number, end: number, cells: vscode.NotebookCellData[], metadata?: vscode.WorkspaceEditEntryMetadata): void { - if (start !== end || cells.length > 0) { - this._edits.push({ _type: FileEditType.CellReplace, uri, index: start, count: end - start, cells, metadata }); + replaceNotebookCells(uri: URI, range: vscode.NotebookRange, cells: vscode.NotebookCellData[], metadata?: vscode.WorkspaceEditEntryMetadata): void; + replaceNotebookCells(uri: URI, start: number, end: number, cells: vscode.NotebookCellData[], metadata?: vscode.WorkspaceEditEntryMetadata): void; + replaceNotebookCells(uri: URI, startOrRange: number | vscode.NotebookRange, endOrCells: number | vscode.NotebookCellData[], cellsOrMetadata?: vscode.NotebookCellData[] | vscode.WorkspaceEditEntryMetadata, metadata?: vscode.WorkspaceEditEntryMetadata): void { + let start: number | undefined; + let end: number | undefined; + let cellData: vscode.NotebookCellData[] = []; + let workspaceEditMetadata: vscode.WorkspaceEditEntryMetadata | undefined; + + if (NotebookRange.isNotebookRange(startOrRange) && NotebookCellData.isNotebookCellDataArray(endOrCells) && !NotebookCellData.isNotebookCellDataArray(cellsOrMetadata)) { + start = startOrRange.start; + end = startOrRange.end; + cellData = endOrCells; + workspaceEditMetadata = cellsOrMetadata; + } else if (typeof startOrRange === 'number' && typeof endOrCells === 'number' && NotebookCellData.isNotebookCellDataArray(cellsOrMetadata)) { + start = startOrRange; + end = endOrCells; + cellData = cellsOrMetadata; + workspaceEditMetadata = metadata; } - } - replaceNotebookCellOutput(uri: URI, index: number, outputs: vscode.NotebookCellOutput[], metadata?: vscode.WorkspaceEditEntryMetadata): void { - this._editNotebookCellOutput(uri, index, false, outputs, metadata); - } + if (start === undefined || end === undefined) { + throw new Error('Invalid arguments'); + } - appendNotebookCellOutput(uri: URI, index: number, outputs: vscode.NotebookCellOutput[], metadata?: vscode.WorkspaceEditEntryMetadata): void { - this._editNotebookCellOutput(uri, index, true, outputs, metadata); - } - - replaceNotebookCellOutputItems(uri: URI, index: number, outputId: string, items: NotebookCellOutputItem[], metadata?: vscode.WorkspaceEditEntryMetadata): void { - this._editNotebookCellOutputItems(uri, index, outputId, false, items, metadata); - } - - appendNotebookCellOutputItems(uri: URI, index: number, outputId: string, items: NotebookCellOutputItem[], metadata?: vscode.WorkspaceEditEntryMetadata): void { - this._editNotebookCellOutputItems(uri, index, outputId, true, items, metadata); - } - - private _editNotebookCellOutputItems(uri: URI, index: number, id: string, append: boolean, items: vscode.NotebookCellOutputItem[], metadata: vscode.WorkspaceEditEntryMetadata | undefined): void { - this._edits.push({ _type: FileEditType.CellOutputItem, metadata, uri, index, outputId: id, append, newOutputItems: items }); - } - - private _editNotebookCellOutput(uri: URI, index: number, append: boolean, outputs: vscode.NotebookCellOutput[], metadata: vscode.WorkspaceEditEntryMetadata | undefined): void { - this._edits.push({ _type: FileEditType.CellOutput, metadata, uri, index, append, newOutputs: outputs, newMetadata: undefined }); + if (start !== end || cellData.length > 0) { + this._edits.push({ _type: FileEditType.CellReplace, uri, index: start, count: end - start, cells: cellData, metadata: workspaceEditMetadata }); + } } replaceNotebookCellMetadata(uri: URI, index: number, cellMetadata: vscode.NotebookCellMetadata, metadata?: vscode.WorkspaceEditEntryMetadata): void { @@ -2895,7 +2896,17 @@ export enum ColorThemeKind { //#region Notebook -export class NotebookCellRange { +export class NotebookRange { + static isNotebookRange(thing: any): thing is vscode.NotebookRange { + if (thing instanceof NotebookRange) { + return true; + } + if (!thing) { + return false; + } + return typeof (thing).start === 'number' + && typeof (thing).end === 'number'; + } private _start: number; private _end: number; @@ -2916,58 +2927,59 @@ export class NotebookCellRange { if (start < 0) { throw illegalArgument('start must be positive'); } - if (end < start) { - throw illegalArgument('end cannot be smaller than start'); + if (end < 0) { + throw illegalArgument('end must be positive'); } - this._start = start; - this._end = end; + if (start <= end) { + this._start = start; + this._end = end; + } else { + this._start = end; + this._end = start; + } + } + + with(change: { start?: number, end?: number }): NotebookRange { + let start = this._start; + let end = this._end; + + if (change.start !== undefined) { + start = change.start; + } + if (change.end !== undefined) { + end = change.end; + } + if (start === this._start && end === this._end) { + return this; + } + return new NotebookRange(start, end); } } export class NotebookCellMetadata { + readonly inputCollapsed?: boolean; + readonly outputCollapsed?: boolean; + readonly [key: string]: any; - constructor( - readonly editable?: boolean, - readonly breakpointMargin?: boolean, - readonly hasExecutionOrder?: boolean, - readonly statusMessage?: string, - readonly inputCollapsed?: boolean, - readonly outputCollapsed?: boolean, - readonly custom?: Record, - ) { } + constructor(inputCollapsed?: boolean, outputCollapsed?: boolean); + constructor(data: Record); + constructor(inputCollapsedOrData: (boolean | undefined) | Record, outputCollapsed?: boolean) { + if (typeof inputCollapsedOrData === 'object') { + Object.assign(this, inputCollapsedOrData); + } else { + this.inputCollapsed = inputCollapsedOrData; + this.outputCollapsed = outputCollapsed; + } + } with(change: { - editable?: boolean | null, - breakpointMargin?: boolean | null, - hasExecutionOrder?: boolean | null, - statusMessage?: string | null, inputCollapsed?: boolean | null, outputCollapsed?: boolean | null, - custom?: Record | null, + [key: string]: any }): NotebookCellMetadata { - let { editable, breakpointMargin, hasExecutionOrder, statusMessage, inputCollapsed, outputCollapsed, custom } = change; + let { inputCollapsed, outputCollapsed, ...remaining } = change; - if (editable === undefined) { - editable = this.editable; - } else if (editable === null) { - editable = undefined; - } - if (breakpointMargin === undefined) { - breakpointMargin = this.breakpointMargin; - } else if (breakpointMargin === null) { - breakpointMargin = undefined; - } - if (hasExecutionOrder === undefined) { - hasExecutionOrder = this.hasExecutionOrder; - } else if (hasExecutionOrder === null) { - hasExecutionOrder = undefined; - } - if (statusMessage === undefined) { - statusMessage = this.statusMessage; - } else if (statusMessage === null) { - statusMessage = undefined; - } if (inputCollapsed === undefined) { inputCollapsed = this.inputCollapsed; } else if (inputCollapsed === null) { @@ -2978,103 +2990,78 @@ export class NotebookCellMetadata { } else if (outputCollapsed === null) { outputCollapsed = undefined; } - if (custom === undefined) { - custom = this.custom; - } else if (custom === null) { - custom = undefined; - } - if (editable === this.editable && - breakpointMargin === this.breakpointMargin && - hasExecutionOrder === this.hasExecutionOrder && - statusMessage === this.statusMessage && - inputCollapsed === this.inputCollapsed && + if (inputCollapsed === this.inputCollapsed && outputCollapsed === this.outputCollapsed && - custom === this.custom + Object.keys(remaining).length === 0 ) { return this; } return new NotebookCellMetadata( - editable, - breakpointMargin, - hasExecutionOrder, - statusMessage, - inputCollapsed, - outputCollapsed, - custom, + { + inputCollapsed, + outputCollapsed, + ...remaining + } ); } } export class NotebookDocumentMetadata { + readonly trusted: boolean; + readonly [key: string]: any; - constructor( - readonly editable: boolean = true, - readonly cellEditable: boolean = true, - readonly cellHasExecutionOrder: boolean = true, - readonly custom: { [key: string]: any; } = {}, - readonly trusted: boolean = true, - ) { } + constructor(trusted?: boolean); + constructor(data: Record); + constructor(trustedOrData: boolean | Record = true) { + if (typeof trustedOrData === 'object') { + Object.assign(this, trustedOrData); + this.trusted = trustedOrData.trusted ?? true; + } else { + this.trusted = trustedOrData; + } + } with(change: { - editable?: boolean | null, - cellEditable?: boolean | null, - cellHasExecutionOrder?: boolean | null, - custom?: { [key: string]: any; } | null, trusted?: boolean | null, + [key: string]: any }): NotebookDocumentMetadata { - let { editable, cellEditable, cellHasExecutionOrder, custom, trusted } = change; + let { trusted, ...remaining } = change; - if (editable === undefined) { - editable = this.editable; - } else if (editable === null) { - editable = undefined; - } - if (cellEditable === undefined) { - cellEditable = this.cellEditable; - } else if (cellEditable === null) { - cellEditable = undefined; - } - if (cellHasExecutionOrder === undefined) { - cellHasExecutionOrder = this.cellHasExecutionOrder; - } else if (cellHasExecutionOrder === null) { - cellHasExecutionOrder = undefined; - } - if (custom === undefined) { - custom = this.custom; - } else if (custom === null) { - custom = undefined; - } if (trusted === undefined) { trusted = this.trusted; } else if (trusted === null) { trusted = undefined; } - if (editable === this.editable && - cellEditable === this.cellEditable && - cellHasExecutionOrder === this.cellHasExecutionOrder && - custom === this.custom && - trusted === this.trusted + if (trusted === this.trusted && + Object.keys(remaining).length === 0 ) { return this; } - return new NotebookDocumentMetadata( - editable, - cellEditable, - cellHasExecutionOrder, - custom, - trusted + { + trusted, + ...remaining + } ); } } export class NotebookCellData { + static isNotebookCellDataArray(value: unknown): value is vscode.NotebookCellData[] { + return Array.isArray(value) && (value).every(elem => NotebookCellData.isNotebookCellData(elem)); + } + + static isNotebookCellData(value: unknown): value is vscode.NotebookCellData { + // return value instanceof NotebookCellData; + return true; + } + kind: NotebookCellKind; source: string; language: string; @@ -3111,17 +3098,21 @@ export class NotebookCellOutputItem { } constructor( - readonly mime: string, - readonly value: unknown, // JSON'able - readonly metadata?: Record - ) { } + public mime: string, + public value: unknown, // JSON'able + public metadata?: Record + ) { + if (isFalsyOrWhitespace(this.mime)) { + throw new Error('INVALID mime type, must not be empty or falsy'); + } + } } export class NotebookCellOutput { - readonly outputs: NotebookCellOutputItem[]; - readonly id: string; - readonly metadata?: Record; + id: string; + outputs: NotebookCellOutputItem[]; + metadata?: Record; constructor( outputs: NotebookCellOutputItem[], @@ -3162,6 +3153,21 @@ export enum NotebookEditorRevealType { AtTop = 3 } +export class NotebookCellStatusBarItem { + constructor( + public text: string, + public alignment: NotebookCellStatusBarAlignment, + public command?: string | vscode.Command, + public tooltip?: string, + public priority?: number, + public accessibilityInformation?: vscode.AccessibilityInformation) { } +} + + +export enum NotebookControllerAffinity { + Default = 1, + Preferred = 2 +} //#endregion @@ -3223,7 +3229,7 @@ export class LinkedEditingRanges { } //#region Testing -export enum TestResult { +export enum TestResultState { Unset = 0, Queued = 1, Running = 2, @@ -3240,16 +3246,17 @@ export enum TestMessageSeverity { Hint = 3 } -export const TestItemHookProperty = Symbol('TestItemHookProperty'); - -export interface ITestItemHook { - created(item: vscode.TestItem): void; - setProp(key: K, value: vscode.TestItem[K]): void; - invalidate(id: string): void; - delete(id: string): void; +export enum TestItemStatus { + Pending = 0, + Resolved = 1, } -const testItemPropAccessor = (item: TestItem, key: K, defaultValue: vscode.TestItem[K]) => { +const testItemPropAccessor = >( + api: IExtHostTestItemApi, + key: K, + defaultValue: vscode.TestItem[K], + equals: (a: vscode.TestItem[K], b: vscode.TestItem[K]) => boolean +) => { let value = defaultValue; return { enumerable: true, @@ -3257,80 +3264,41 @@ const testItemPropAccessor = (item: TestItem, k get() { return value; }, - set(newValue: vscode.TestItem[K]) { - item[TestItemHookProperty]?.setProp(key, newValue); - value = newValue; + set(newValue: vscode.TestItem[K]) { + if (!equals(value, newValue)) { + value = newValue; + api.bus.fire([ExtHostTestItemEventType.SetProp, key, newValue]); + } }, }; }; -export class TestChildrenCollection implements vscode.TestChildrenCollection { - #map = new Map(); - #hookRef: () => ITestItemHook | undefined; +const strictEqualComparator = (a: T, b: T) => a === b; +const rangeComparator = (a: vscode.Range | undefined, b: vscode.Range | undefined) => { + if (a === b) { return true; } + if (!a || !b) { return false; } + return a.isEqual(b); +}; - public get size() { - return this.#map.size; - } +export class TestItemImpl implements vscode.TestItem { + public readonly id!: string; + public readonly uri!: vscode.Uri; + public readonly children!: ReadonlyMap; + public readonly parent!: TestItemImpl | undefined; - constructor(hookRef: () => ITestItemHook | undefined) { - this.#hookRef = hookRef; - } - - public add(child: vscode.TestItem) { - const map = this.#map; - const hook = this.#hookRef(); - - const existing = map.get(child.id); - if (existing === child) { - return; - } - - if (existing) { - hook?.delete(child.id); - } - - map.set(child.id, child); - hook?.created(child); - } - - public get(id: string) { - return this.#map.get(id); - } - - public clear() { - for (const key of this.#map.keys()) { - this.delete(key); - } - } - - public delete(childOrId: vscode.TestItem | string) { - const id = typeof childOrId === 'string' ? childOrId : childOrId.id; - if (this.#map.has(id)) { - this.#map.delete(id); - this.#hookRef()?.delete(id); - } - } - - public toJSON() { - return [...this.#map.values()]; - } - - public [Symbol.iterator]() { - return this.#map.values(); - } -} - -export class TestItem implements vscode.TestItem { - public id!: string; public range!: vscode.Range | undefined; public description!: string | undefined; public runnable!: boolean; public debuggable!: boolean; - public children!: TestChildrenCollection; - public uri!: vscode.Uri; - public [TestItemHookProperty]!: ITestItemHook | undefined; + public error!: string | vscode.MarkdownString; + public status!: vscode.TestItemStatus; + + /** Extension-owned resolve handler */ + public resolveHandler?: (token: vscode.CancellationToken) => void; + + constructor(id: string, public label: string, uri: vscode.Uri, public data: unknown) { + const api = getPrivateApiFor(this); - constructor(id: string, public label: string, uri: vscode.Uri, public expandable: boolean) { Object.defineProperties(this, { id: { value: id, @@ -3342,38 +3310,52 @@ export class TestItem implements vscode.TestItem { enumerable: true, writable: false, }, + parent: { + enumerable: false, + get: () => api.parent, + }, children: { - value: new TestChildrenCollection(() => this[TestItemHookProperty]), + value: new ReadonlyMapView(api.children), enumerable: true, writable: false, }, - [TestItemHookProperty]: { - enumerable: false, - writable: true, - configurable: false, - }, - range: testItemPropAccessor(this, 'range', undefined), - description: testItemPropAccessor(this, 'description', undefined), - runnable: testItemPropAccessor(this, 'runnable', true), - debuggable: testItemPropAccessor(this, 'debuggable', true), + range: testItemPropAccessor(api, 'range', undefined, rangeComparator), + description: testItemPropAccessor(api, 'description', undefined, strictEqualComparator), + runnable: testItemPropAccessor(api, 'runnable', true, strictEqualComparator), + debuggable: testItemPropAccessor(api, 'debuggable', true, strictEqualComparator), + status: testItemPropAccessor(api, 'status', TestItemStatus.Resolved, strictEqualComparator), + error: testItemPropAccessor(api, 'error', undefined, strictEqualComparator), }); } public invalidate() { - this[TestItemHookProperty]?.invalidate(this.id); + getPrivateApiFor(this).bus.fire([ExtHostTestItemEventType.Invalidated]); } - public discoverChildren(progress: vscode.Progress<{ busy: boolean }>, _token: vscode.CancellationToken) { - progress.report({ busy: false }); + public dispose() { + const api = getPrivateApiFor(this); + if (api.parent) { + getPrivateApiFor(api.parent).children.delete(this.id); + } + + api.bus.fire([ExtHostTestItemEventType.Disposed]); + } + + public addChild(child: vscode.TestItem) { + if (!(child instanceof TestItemImpl)) { + throw new Error('Test child must be created through vscode.test.createTestItem()'); + } + + const api = getPrivateApiFor(this); + if (api.children.has(child.id)) { + throw new Error(`Attempted to insert a duplicate test item ID ${child.id}`); + } + + api.children.set(child.id, child); + api.bus.fire([ExtHostTestItemEventType.NewChild, child]); } } -export class TestState implements vscode.TestState { - public messages: TestMessage[] = []; - public duration?: number; - - constructor(public state: TestResult) { } -} export class TestMessage implements vscode.TestMessage { public severity = TestMessageSeverity.Error; @@ -3402,7 +3384,7 @@ export enum ExternalUriOpenerPriority { export enum WorkspaceTrustState { Untrusted = 0, Trusted = 1, - Unknown = 2 + Unspecified = 2 } export enum PortAutoForwardAction { diff --git a/src/vs/workbench/api/common/extHostWebview.ts b/src/vs/workbench/api/common/extHostWebview.ts index d6b9376d..b4a280b1 100644 --- a/src/vs/workbench/api/common/extHostWebview.ts +++ b/src/vs/workbench/api/common/extHostWebview.ts @@ -3,11 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { VSBuffer } from 'vs/base/common/buffer'; import { Emitter, Event } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { normalizeVersion, parseVersion } from 'vs/platform/extensions/common/extensionValidator'; import { ILogService } from 'vs/platform/log/common/log'; import { IExtHostApiDeprecationService } from 'vs/workbench/api/common/extHostApiDeprecationService'; +import { deserializeWebviewMessage } from 'vs/workbench/api/common/extHostWebviewMessaging'; import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; import { asWebviewUri, WebviewInitData } from 'vs/workbench/api/common/shared/webview'; import type * as vscode from 'vscode'; @@ -28,6 +31,8 @@ export class ExtHostWebview implements vscode.Webview { #isDisposed: boolean = false; #hasCalledAsWebviewUri = false; + #serializeBuffersForPostMessage = false; + constructor( handle: extHostProtocol.WebviewHandle, proxy: extHostProtocol.MainThreadWebviewsShape, @@ -43,6 +48,7 @@ export class ExtHostWebview implements vscode.Webview { this.#initData = initData; this.#workspace = workspace; this.#extension = extension; + this.#serializeBuffersForPostMessage = shouldSerializeBuffersForPostMessage(extension); this.#deprecationService = deprecationService; } @@ -104,7 +110,8 @@ export class ExtHostWebview implements vscode.Webview { if (this.#isDisposed) { return false; } - return this.#proxy.$postMessage(this.#handle, message); + const serialized = serializeMessage(message, { serializeBuffersForPostMessage: this.#serializeBuffersForPostMessage }); + return this.#proxy.$postMessage(this.#handle, serialized.message, ...serialized.buffers); } private assertNotDisposed() { @@ -114,6 +121,49 @@ export class ExtHostWebview implements vscode.Webview { } } +export function shouldSerializeBuffersForPostMessage(extension: IExtensionDescription): boolean { + if (!extension.enableProposedApi) { + return false; + } + + try { + const version = normalizeVersion(parseVersion(extension.engines.vscode)); + return !!version && version.majorBase >= 1 && version.minorBase >= 56; + } catch { + return false; + } +} + +export function serializeMessage(message: any, options: { serializeBuffersForPostMessage?: boolean }): { message: string, buffers: VSBuffer[] } { + if (options.serializeBuffersForPostMessage) { + // Extract all ArrayBuffers from the message and replace them with references. + const vsBuffers: Array<{ original: ArrayBuffer, vsBuffer: VSBuffer }> = []; + + const replacer = (_key: string, value: any) => { + if (value && value instanceof ArrayBuffer) { + let index = vsBuffers.findIndex(x => x.original === value); + if (index === -1) { + const bytes = new Uint8Array(value); + const vsBuffer = VSBuffer.wrap(bytes); + index = vsBuffers.length; + vsBuffers.push({ original: value, vsBuffer }); + } + + return { + $$vscode_array_buffer_reference$$: true, + index, + }; + } + return value; + }; + + const serializedMessage = JSON.stringify(message, replacer); + return { message: serializedMessage, buffers: vsBuffers.map(x => x.vsBuffer) }; + } else { + return { message: JSON.stringify(message), buffers: [] }; + } +} + export class ExtHostWebviews implements extHostProtocol.ExtHostWebviewsShape { private readonly _webviewProxy: extHostProtocol.MainThreadWebviewsShape; @@ -132,10 +182,12 @@ export class ExtHostWebviews implements extHostProtocol.ExtHostWebviewsShape { public $onMessage( handle: extHostProtocol.WebviewHandle, - message: any + jsonMessage: string, + ...buffers: VSBuffer[] ): void { const webview = this.getWebview(handle); if (webview) { + const { message } = deserializeWebviewMessage(jsonMessage, buffers); webview._onMessageEmitter.fire(message); } } diff --git a/src/vs/workbench/api/common/extHostWebviewMessaging.ts b/src/vs/workbench/api/common/extHostWebviewMessaging.ts new file mode 100644 index 00000000..06e76ab5 --- /dev/null +++ b/src/vs/workbench/api/common/extHostWebviewMessaging.ts @@ -0,0 +1,122 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { VSBuffer } from 'vs/base/common/buffer'; +import * as extHostProtocol from './extHost.protocol'; + +class ArrayBufferSet { + public readonly buffers: ArrayBuffer[] = []; + + public add(buffer: ArrayBuffer): number { + let index = this.buffers.indexOf(buffer); + if (index < 0) { + index = this.buffers.length; + this.buffers.push(buffer); + } + return index; + } +} + +export function serializeWebviewMessage( + message: any, + transfer?: readonly ArrayBuffer[] +): { message: string, buffers: VSBuffer[] } { + if (transfer) { + // Extract all ArrayBuffers from the message and replace them with references. + const arrayBuffers = new ArrayBufferSet(); + + const replacer = (_key: string, value: any) => { + if (value instanceof ArrayBuffer) { + const index = arrayBuffers.add(value); + return { + $$vscode_array_buffer_reference$$: true, + index, + }; + } else if (ArrayBuffer.isView(value)) { + const type = getTypedArrayType(value); + if (type) { + const index = arrayBuffers.add(value.buffer); + return { + $$vscode_array_buffer_reference$$: true, + index, + view: { + type: type, + byteLength: value.byteLength, + byteOffset: value.byteOffset, + } + }; + } + } + + return value; + }; + + const serializedMessage = JSON.stringify(message, replacer); + + const buffers = arrayBuffers.buffers.map(arrayBuffer => { + const bytes = new Uint8Array(arrayBuffer); + return VSBuffer.wrap(bytes); + }); + + return { message: serializedMessage, buffers }; + } else { + return { message: JSON.stringify(message), buffers: [] }; + } +} + +function getTypedArrayType(value: ArrayBufferView): extHostProtocol.WebviewMessageArrayBufferViewType | undefined { + switch (value.constructor.name) { + case 'Int8Array': return extHostProtocol.WebviewMessageArrayBufferViewType.Int8Array; + case 'Uint8Array': return extHostProtocol.WebviewMessageArrayBufferViewType.Uint8Array; + case 'Uint8ClampedArray': return extHostProtocol.WebviewMessageArrayBufferViewType.Uint8ClampedArray; + case 'Int16Array': return extHostProtocol.WebviewMessageArrayBufferViewType.Int16Array; + case 'Uint16Array': return extHostProtocol.WebviewMessageArrayBufferViewType.Uint16Array; + case 'Int32Array': return extHostProtocol.WebviewMessageArrayBufferViewType.Int32Array; + case 'Uint32Array': return extHostProtocol.WebviewMessageArrayBufferViewType.Uint32Array; + case 'Float32Array': return extHostProtocol.WebviewMessageArrayBufferViewType.Float32Array; + case 'Float64Array': return extHostProtocol.WebviewMessageArrayBufferViewType.Float64Array; + case 'BigInt64Array': return extHostProtocol.WebviewMessageArrayBufferViewType.BigInt64Array; + case 'BigUint64Array': return extHostProtocol.WebviewMessageArrayBufferViewType.BigUint64Array; + } + return undefined; +} + +export function deserializeWebviewMessage(jsonMessage: string, buffers: VSBuffer[]): { message: any, arrayBuffers: ArrayBuffer[] } { + const arrayBuffers: ArrayBuffer[] = buffers.map(buffer => { + const arrayBuffer = new ArrayBuffer(buffer.byteLength); + const uint8Array = new Uint8Array(arrayBuffer); + uint8Array.set(buffer.buffer); + return arrayBuffer; + }); + + const reviver = !buffers.length ? undefined : (_key: string, value: any) => { + if (typeof value === 'object' && (value as extHostProtocol.WebviewMessageArrayBufferReference).$$vscode_array_buffer_reference$$) { + const ref = value as extHostProtocol.WebviewMessageArrayBufferReference; + const { index } = ref; + const arrayBuffer = arrayBuffers[index]; + if (ref.view) { + switch (ref.view.type) { + case extHostProtocol.WebviewMessageArrayBufferViewType.Int8Array: return new Int8Array(arrayBuffer, ref.view.byteOffset, ref.view.byteLength / Int8Array.BYTES_PER_ELEMENT); + case extHostProtocol.WebviewMessageArrayBufferViewType.Uint8Array: return new Uint8Array(arrayBuffer, ref.view.byteOffset, ref.view.byteLength / Uint8Array.BYTES_PER_ELEMENT); + case extHostProtocol.WebviewMessageArrayBufferViewType.Uint8ClampedArray: return new Uint8ClampedArray(arrayBuffer, ref.view.byteOffset, ref.view.byteLength / Uint8ClampedArray.BYTES_PER_ELEMENT); + case extHostProtocol.WebviewMessageArrayBufferViewType.Int16Array: return new Int16Array(arrayBuffer, ref.view.byteOffset, ref.view.byteLength / Int16Array.BYTES_PER_ELEMENT); + case extHostProtocol.WebviewMessageArrayBufferViewType.Uint16Array: return new Uint16Array(arrayBuffer, ref.view.byteOffset, ref.view.byteLength / Uint16Array.BYTES_PER_ELEMENT); + case extHostProtocol.WebviewMessageArrayBufferViewType.Int32Array: return new Int32Array(arrayBuffer, ref.view.byteOffset, ref.view.byteLength / Int32Array.BYTES_PER_ELEMENT); + case extHostProtocol.WebviewMessageArrayBufferViewType.Uint32Array: return new Uint32Array(arrayBuffer, ref.view.byteOffset, ref.view.byteLength / Uint32Array.BYTES_PER_ELEMENT); + case extHostProtocol.WebviewMessageArrayBufferViewType.Float32Array: return new Float32Array(arrayBuffer, ref.view.byteOffset, ref.view.byteLength / Float32Array.BYTES_PER_ELEMENT); + case extHostProtocol.WebviewMessageArrayBufferViewType.Float64Array: return new Float64Array(arrayBuffer, ref.view.byteOffset, ref.view.byteLength / Float64Array.BYTES_PER_ELEMENT); + case extHostProtocol.WebviewMessageArrayBufferViewType.BigInt64Array: return new BigInt64Array(arrayBuffer, ref.view.byteOffset, ref.view.byteLength / BigInt64Array.BYTES_PER_ELEMENT); + case extHostProtocol.WebviewMessageArrayBufferViewType.BigUint64Array: return new BigUint64Array(arrayBuffer, ref.view.byteOffset, ref.view.byteLength / BigUint64Array.BYTES_PER_ELEMENT); + default: throw new Error('Unknown array buffer view type'); + } + } + return arrayBuffer; + } + return value; + }; + + const message = JSON.parse(jsonMessage, reviver); + return { message, arrayBuffers }; +} diff --git a/src/vs/workbench/api/common/extHostWebviewPanels.ts b/src/vs/workbench/api/common/extHostWebviewPanels.ts index 3e30cffc..9622f879 100644 --- a/src/vs/workbench/api/common/extHostWebviewPanels.ts +++ b/src/vs/workbench/api/common/extHostWebviewPanels.ts @@ -9,7 +9,7 @@ import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters'; -import { serializeWebviewOptions, ExtHostWebview, ExtHostWebviews, toExtensionData } from 'vs/workbench/api/common/extHostWebview'; +import { serializeWebviewOptions, ExtHostWebview, ExtHostWebviews, toExtensionData, shouldSerializeBuffersForPostMessage } from 'vs/workbench/api/common/extHostWebview'; import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; import { EditorGroupColumn } from 'vs/workbench/common/editor'; import type * as vscode from 'vscode'; @@ -60,7 +60,7 @@ class ExtHostWebviewPanel extends Disposable implements vscode.WebviewPanel { this.#webview = webview; } - public dispose() { + public override dispose() { if (this.#isDisposed) { return; } @@ -199,11 +199,13 @@ export class ExtHostWebviewPanels implements extHostProtocol.ExtHostWebviewPanel preserveFocus: typeof showOptions === 'object' && !!showOptions.preserveFocus }; + const serializeBuffersForPostMessage = shouldSerializeBuffersForPostMessage(extension); const handle = ExtHostWebviewPanels.newHandle(); this._proxy.$createWebviewPanel(toExtensionData(extension), handle, viewType, { title, panelOptions: serializeWebviewPanelOptions(options), webviewOptions: serializeWebviewOptions(extension, this.workspace, options), + serializeBuffersForPostMessage, }, webviewShowOptions); const webview = this.webviews.createNewWebview(handle, options, extension); @@ -263,7 +265,9 @@ export class ExtHostWebviewPanels implements extHostProtocol.ExtHostWebviewPanel } this._serializers.set(viewType, { serializer, extension }); - this._proxy.$registerSerializer(viewType); + this._proxy.$registerSerializer(viewType, { + serializeBuffersForPostMessage: shouldSerializeBuffersForPostMessage(extension) + }); return new extHostTypes.Disposable(() => { this._serializers.delete(viewType); diff --git a/src/vs/workbench/api/common/extHostWebviewView.ts b/src/vs/workbench/api/common/extHostWebviewView.ts index 205ad24c..d91a5d30 100644 --- a/src/vs/workbench/api/common/extHostWebviewView.ts +++ b/src/vs/workbench/api/common/extHostWebviewView.ts @@ -43,7 +43,7 @@ class ExtHostWebviewView extends Disposable implements vscode.WebviewView { this.#isVisible = isVisible; } - public dispose() { + public override dispose() { if (this.#isDisposed) { return; } @@ -146,7 +146,10 @@ export class ExtHostWebviewViews implements extHostProtocol.ExtHostWebviewViewsS } this._viewProviders.set(viewType, { provider, extension }); - this._proxy.$registerWebviewViewProvider(toExtensionData(extension), viewType, webviewOptions); + this._proxy.$registerWebviewViewProvider(toExtensionData(extension), viewType, { + retainContextWhenHidden: webviewOptions?.retainContextWhenHidden, + serializeBuffersForPostMessage: false, + }); return new extHostTypes.Disposable(() => { this._viewProviders.delete(viewType); diff --git a/src/vs/workbench/api/common/extHostWorkspace.ts b/src/vs/workbench/api/common/extHostWorkspace.ts index 07e1a00e..4e80bce8 100644 --- a/src/vs/workbench/api/common/extHostWorkspace.ts +++ b/src/vs/workbench/api/common/extHostWorkspace.ts @@ -20,12 +20,11 @@ import { FileSystemProviderCapabilities } from 'vs/platform/files/common/files'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; import { Severity } from 'vs/platform/notification/common/notification'; -import { WorkspaceTrustStateChangeEvent } from 'vs/platform/workspace/common/workspaceTrust'; import { Workspace, WorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { IExtHostFileSystemInfo } from 'vs/workbench/api/common/extHostFileSystemInfo'; import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; -import { Range, RelativePattern, WorkspaceTrustState } from 'vs/workbench/api/common/extHostTypes'; +import { Range, RelativePattern } from 'vs/workbench/api/common/extHostTypes'; import { ITextQueryBuilderOptions } from 'vs/workbench/contrib/search/common/queryBuilder'; import { IRawFileMatch2, resultIsMatch } from 'vs/workbench/services/search/common/search'; import * as vscode from 'vscode'; @@ -169,8 +168,8 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac private readonly _onDidChangeWorkspace = new Emitter(); readonly onDidChangeWorkspace: Event = this._onDidChangeWorkspace.event; - private readonly _onDidChangeWorkspaceTrustState = new Emitter(); - readonly onDidChangeWorkspaceTrustState: Event = this._onDidChangeWorkspaceTrustState.event; + private readonly _onDidGrantWorkspaceTrust = new Emitter(); + readonly onDidGrantWorkspaceTrust: Event = this._onDidGrantWorkspaceTrust.event; private readonly _logService: ILogService; private readonly _requestIdProvider: Counter; @@ -185,7 +184,7 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac private readonly _activeSearchCallbacks: ((match: IRawFileMatch2) => any)[] = []; - private _workspaceTrustState: WorkspaceTrustState = WorkspaceTrustState.Unknown; + private _trusted: boolean = false; constructor( @IExtHostRpcService extHostRpc: IExtHostRpcService, @@ -204,8 +203,8 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac this._confirmedWorkspace = data ? new ExtHostWorkspaceImpl(data.id, data.name, [], data.configuration ? URI.revive(data.configuration) : null, !!data.isUntitled, uri => ignorePathCasing(uri, extHostFileSystemInfo)) : undefined; } - $initializeWorkspace(data: IWorkspaceData | null, trustState: WorkspaceTrustState): void { - this._workspaceTrustState = trustState; + $initializeWorkspace(data: IWorkspaceData | null, trusted: boolean): void { + this._trusted = trusted; this.$acceptWorkspaceData(data); this._barrier.open(); } @@ -559,17 +558,20 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac // --- trust --- - get trustState(): WorkspaceTrustState { - return this._workspaceTrustState; + get trusted(): boolean { + return this._trusted; } - requireWorkspaceTrust(options?: vscode.WorkspaceTrustRequestOptions): Promise { - return this._proxy.$requireWorkspaceTrust(options); + requestWorkspaceTrust(options?: vscode.WorkspaceTrustRequestOptions): Promise { + const promise = this._proxy.$requestWorkspaceTrust(options); + return options?.modal ? promise : Promise.resolve(this._trusted); } - $onDidChangeWorkspaceTrustState(state: WorkspaceTrustStateChangeEvent): void { - this._workspaceTrustState = state.currentTrustState; - this._onDidChangeWorkspaceTrustState.fire(Object.freeze(state)); + $onDidGrantWorkspaceTrust(): void { + if (!this._trusted) { + this._trusted = true; + this._onDidGrantWorkspaceTrust.fire(); + } } } diff --git a/src/vs/workbench/api/common/menusExtensionPoint.ts b/src/vs/workbench/api/common/menusExtensionPoint.ts index e3d61f42..3046d981 100644 --- a/src/vs/workbench/api/common/menusExtensionPoint.ts +++ b/src/vs/workbench/api/common/menusExtensionPoint.ts @@ -138,6 +138,12 @@ const apiMenus: IAPIMenu[] = [ proposed: true, supportsSubmenus: false }, + { + key: 'statusBar/remoteIndicator', + id: MenuId.StatusBarRemoteIndicatorMenu, + description: localize('menus.statusBarRemoteIndicator', "The remote indicator menu in the status bar"), + supportsSubmenus: false + }, { key: 'view/title', id: MenuId.ViewTitle, @@ -212,6 +218,11 @@ const apiMenus: IAPIMenu[] = [ key: 'ports/item/origin/inline', id: MenuId.TunnelOriginInline, description: localize('view.tunnelOriginInline', "The Ports view item origin inline menu") + }, + { + key: 'ports/item/port/inline', + id: MenuId.TunnelPortInline, + description: localize('view.tunnelPortInline', "The Ports view item port inline menu") } ]; diff --git a/src/vs/workbench/api/node/extHostCLIServer.ts b/src/vs/workbench/api/node/extHostCLIServer.ts index 21f053df..6177f03c 100644 --- a/src/vs/workbench/api/node/extHostCLIServer.ts +++ b/src/vs/workbench/api/node/extHostCLIServer.ts @@ -33,12 +33,6 @@ export interface StatusPipeArgs { type: 'status'; } -export interface RunCommandPipeArgs { - type: 'command'; - command: string; - args: any[]; -} - export interface ExtensionManagementPipeArgs { type: 'extensionManagement'; list?: { showVersions?: boolean, category?: string; }; @@ -47,7 +41,7 @@ export interface ExtensionManagementPipeArgs { force?: boolean; } -export type PipeCommand = OpenCommandPipeArgs | StatusPipeArgs | RunCommandPipeArgs | OpenExternalCommandPipeArgs | ExtensionManagementPipeArgs; +export type PipeCommand = OpenCommandPipeArgs | StatusPipeArgs | OpenExternalCommandPipeArgs | ExtensionManagementPipeArgs; export interface ICommandsExecuter { executeCommand(id: string, ...args: any[]): Promise; @@ -99,10 +93,6 @@ export class CLIServerBase { case 'status': this.getStatus(data, res); break; - case 'command': - this.runCommand(data, res) - .catch(this.logService.error); - break; case 'extensionManagement': this.manageExtensions(data, res) .catch(this.logService.error); @@ -149,7 +139,7 @@ export class CLIServerBase { const waitMarkerFileURI = waitMarkerFilePath ? URI.file(waitMarkerFilePath) : undefined; const preferNewWindow = !forceReuseWindow && !waitMarkerFileURI && !addMode; const windowOpenArgs: IOpenWindowOptions = { forceNewWindow, diffMode, addMode, gotoLineMode, forceReuseWindow, preferNewWindow, waitMarkerFileURI }; - this._commands.executeCommand('_files.windowOpen', urisToOpen, windowOpenArgs); + this._commands.executeCommand('_remoteCLI.windowOpen', urisToOpen, windowOpenArgs); } res.writeHead(200); res.end(); @@ -190,7 +180,7 @@ export class CLIServerBase { private async getStatus(data: StatusPipeArgs, res: http.ServerResponse) { try { - const status = await this._commands.executeCommand('_issues.getSystemStatus'); + const status = await this._commands.executeCommand('_remoteCLI.getSystemStatus'); res.writeHead(200); res.write(status); res.end(); @@ -205,28 +195,6 @@ export class CLIServerBase { } } - private async runCommand(data: RunCommandPipeArgs, res: http.ServerResponse) { - try { - const { command, args } = data; - const result = await this._commands.executeCommand(command, ...args); - res.writeHead(200); - res.write(JSON.stringify(result), err => { - if (err) { - this.logService.error(err); - } - }); - res.end(); - } catch (err) { - res.writeHead(500); - res.write(String(err), err => { - if (err) { - this.logService.error(err); - } - }); - res.end(); - } - } - dispose(): void { this._server.close(); diff --git a/src/vs/workbench/api/node/extHostDebugService.ts b/src/vs/workbench/api/node/extHostDebugService.ts index c8c7f4cd..9ba96a89 100644 --- a/src/vs/workbench/api/node/extHostDebugService.ts +++ b/src/vs/workbench/api/node/extHostDebugService.ts @@ -27,7 +27,7 @@ import { createCancelablePromise, firstParallel } from 'vs/base/common/async'; export class ExtHostDebugService extends ExtHostDebugServiceBase { - readonly _serviceBrand: undefined; + override readonly _serviceBrand: undefined; private _integratedTerminalInstances = new DebugTerminalCollection(); private _terminalDisposedListener: IDisposable | undefined; @@ -43,7 +43,7 @@ export class ExtHostDebugService extends ExtHostDebugServiceBase { super(extHostRpcService, workspaceService, extensionService, editorsService, configurationService); } - protected createDebugAdapter(adapter: IAdapterDescriptor, session: ExtHostDebugSession): AbstractDebugAdapter | undefined { + protected override createDebugAdapter(adapter: IAdapterDescriptor, session: ExtHostDebugSession): AbstractDebugAdapter | undefined { switch (adapter.type) { case 'server': return new SocketDebugAdapter(adapter); @@ -55,7 +55,7 @@ export class ExtHostDebugService extends ExtHostDebugServiceBase { return super.createDebugAdapter(adapter, session); } - protected daExecutableFromPackage(session: ExtHostDebugSession, extensionRegistry: ExtensionDescriptionRegistry): DebugAdapterExecutable | undefined { + protected override daExecutableFromPackage(session: ExtHostDebugSession, extensionRegistry: ExtensionDescriptionRegistry): DebugAdapterExecutable | undefined { const dae = ExecutableDebugAdapter.platformAdapterExecutable(extensionRegistry.getAllExtensionDescriptions(), session.type); if (dae) { return new DebugAdapterExecutable(dae.command, dae.args, dae.options); @@ -63,11 +63,11 @@ export class ExtHostDebugService extends ExtHostDebugServiceBase { return undefined; } - protected createSignService(): ISignService | undefined { + protected override createSignService(): ISignService | undefined { return new SignService(); } - public async $runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments, sessionId: string): Promise { + public override async $runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments, sessionId: string): Promise { if (args.kind === 'integrated') { diff --git a/src/vs/workbench/api/node/extHostOutputService.ts b/src/vs/workbench/api/node/extHostOutputService.ts index 51b92b3d..518c90b4 100644 --- a/src/vs/workbench/api/node/extHostOutputService.ts +++ b/src/vs/workbench/api/node/extHostOutputService.ts @@ -47,23 +47,23 @@ export class ExtHostOutputChannelBackedByFile extends AbstractExtHostOutputChann this._appender = appender; } - append(value: string): void { + override append(value: string): void { super.append(value); this._appender.append(value); this._onDidAppend.fire(); } - update(): void { + override update(): void { this._appender.flush(); super.update(); } - show(columnOrPreserveFocus?: vscode.ViewColumn | boolean, preserveFocus?: boolean): void { + override show(columnOrPreserveFocus?: vscode.ViewColumn | boolean, preserveFocus?: boolean): void { this._appender.flush(); super.show(columnOrPreserveFocus, preserveFocus); } - clear(): void { + override clear(): void { this._appender.flush(); super.clear(); } @@ -85,7 +85,7 @@ export class ExtHostOutputService2 extends ExtHostOutputService { this._logsLocation = initData.logsLocation; } - $setVisibleChannel(channelId: string): void { + override $setVisibleChannel(channelId: string): void { if (channelId) { const channel = this._channels.get(channelId); if (channel) { @@ -94,7 +94,7 @@ export class ExtHostOutputService2 extends ExtHostOutputService { } } - createOutputChannel(name: string): vscode.OutputChannel { + override createOutputChannel(name: string): vscode.OutputChannel { name = name.trim(); if (!name) { throw new Error('illegal argument `name`. must not be falsy'); diff --git a/src/vs/workbench/api/node/extHostSearch.ts b/src/vs/workbench/api/node/extHostSearch.ts index 0dba1956..be31f4ad 100644 --- a/src/vs/workbench/api/node/extHostSearch.ts +++ b/src/vs/workbench/api/node/extHostSearch.ts @@ -59,7 +59,7 @@ export class NativeExtHostSearch extends ExtHostSearch { }); } - $provideFileSearchResults(handle: number, session: number, rawQuery: IRawFileQuery, token: vscode.CancellationToken): Promise { + override $provideFileSearchResults(handle: number, session: number, rawQuery: IRawFileQuery, token: vscode.CancellationToken): Promise { const query = reviveQuery(rawQuery); if (handle === this._internalFileSearchHandle) { return this.doInternalFileSearch(handle, session, query, token); @@ -91,7 +91,7 @@ export class NativeExtHostSearch extends ExtHostSearch { return >this._internalFileSearchProvider.doFileSearch(rawQuery, onResult, token); } - $clearCache(cacheKey: string): Promise { + override $clearCache(cacheKey: string): Promise { if (this._internalFileSearchProvider) { this._internalFileSearchProvider.clearCache(cacheKey); } @@ -99,7 +99,7 @@ export class NativeExtHostSearch extends ExtHostSearch { return super.$clearCache(cacheKey); } - protected createTextSearchManager(query: ITextQuery, provider: vscode.TextSearchProvider): TextSearchManager { + protected override createTextSearchManager(query: ITextQuery, provider: vscode.TextSearchProvider): TextSearchManager { return new NativeTextSearchManager(query, provider); } } diff --git a/src/vs/workbench/api/node/extHostTask.ts b/src/vs/workbench/api/node/extHostTask.ts index 54b50a8b..dd250c83 100644 --- a/src/vs/workbench/api/node/extHostTask.ts +++ b/src/vs/workbench/api/node/extHostTask.ts @@ -44,6 +44,12 @@ export class ExtHostTask extends ExtHostTaskBase { authority: initData.remote.authority, platform: process.platform }); + } else { + this.registerTaskSystem(Schemas.file, { + scheme: Schemas.file, + authority: '', + platform: process.platform + }); } this._proxy.$registerSupportedExecutions(true, true, true); } @@ -147,19 +153,19 @@ export class ExtHostTask extends ExtHostTaskBase { } }; for (let variable of toResolve.variables) { - result.variables[variable] = resolver.resolve(ws, variable); + result.variables[variable] = await resolver.resolveAsync(ws, variable); } if (toResolve.process !== undefined) { let paths: string[] | undefined = undefined; if (toResolve.process.path !== undefined) { paths = toResolve.process.path.split(path.delimiter); for (let i = 0; i < paths.length; i++) { - paths[i] = resolver.resolve(ws, paths[i]); + paths[i] = await resolver.resolveAsync(ws, paths[i]); } } result.process = await win32.findExecutable( - resolver.resolve(ws, toResolve.process.name), - toResolve.process.cwd !== undefined ? resolver.resolve(ws, toResolve.process.cwd) : undefined, + await resolver.resolveAsync(ws, toResolve.process.name), + toResolve.process.cwd !== undefined ? await resolver.resolveAsync(ws, toResolve.process.cwd) : undefined, paths ); } diff --git a/src/vs/workbench/api/node/extHostTerminalService.ts b/src/vs/workbench/api/node/extHostTerminalService.ts index 6bade2ed..87218189 100644 --- a/src/vs/workbench/api/node/extHostTerminalService.ts +++ b/src/vs/workbench/api/node/extHostTerminalService.ts @@ -8,6 +8,7 @@ import { withNullAsUndefined } from 'vs/base/common/types'; import { generateUuid } from 'vs/base/common/uuid'; import { getSystemShell, getSystemShellSync } from 'vs/base/node/shell'; import { ILogService } from 'vs/platform/log/common/log'; +import { SafeConfigProvider } from 'vs/platform/terminal/common/terminal'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { IShellAndArgsDto } from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostConfigProvider, ExtHostConfiguration, IExtHostConfiguration } from 'vs/workbench/api/common/extHostConfiguration'; @@ -16,7 +17,7 @@ import { ExtHostDocumentsAndEditors, IExtHostDocumentsAndEditors } from 'vs/work 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 { ITerminalProfile } from 'vs/workbench/contrib/terminal/common/terminal'; import * as terminalEnvironment from 'vs/workbench/contrib/terminal/common/terminalEnvironment'; import { detectAvailableProfiles } from 'vs/workbench/contrib/terminal/node/terminalProfiles'; import type * as vscode from 'vscode'; @@ -27,8 +28,6 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService { private _variableResolverPromise: Promise; private _lastActiveWorkspace: IWorkspaceFolder | undefined; - // TODO: Pull this from main side - private _isWorkspaceShellAllowed: boolean = false; private _defaultShell: string | undefined; constructor( @@ -43,7 +42,7 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService { // Getting the SystemShell is an async operation, however, the ExtHost terminal service is mostly synchronous // and the API `vscode.env.shell` is also synchronous. The default shell _should_ be set when extensions are // starting up but if not, we run getSystemShellSync below which gets a sane default. - getSystemShell(platform.platform, process.env as platform.IProcessEnvironment).then(s => this._defaultShell = s); + getSystemShell(platform.OS, process.env as platform.IProcessEnvironment).then(s => this._defaultShell = s); this._updateLastActiveWorkspace(); this._variableResolverPromise = this._updateVariableResolver(); @@ -65,6 +64,8 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService { withNullAsUndefined(options.shellArgs), withNullAsUndefined(options.cwd), withNullAsUndefined(options.env), + withNullAsUndefined(options.icon), + withNullAsUndefined(options.message), /*options.waitOnExit*/ undefined, withNullAsUndefined(options.strictEnv), withNullAsUndefined(options.hideFromUser), @@ -75,44 +76,24 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService { } public getDefaultShell(useAutomationShell: boolean, configProvider: ExtHostConfigProvider): string { - const fetchSetting = (key: string): { userValue: string | string[] | undefined, value: string | string[] | undefined, defaultValue: string | string[] | undefined } => { - const setting = configProvider - .getConfiguration(key.substr(0, key.lastIndexOf('.'))) - .inspect(key.substr(key.lastIndexOf('.') + 1)); - return this._apiInspectConfigToPlain(setting); - }; - return terminalEnvironment.getDefaultShell( - fetchSetting, - this._isWorkspaceShellAllowed, - this._defaultShell ?? getSystemShellSync(platform.platform, process.env as platform.IProcessEnvironment), + this._buildSafeConfigProvider(configProvider), + this._defaultShell ?? getSystemShellSync(platform.OS, process.env as platform.IProcessEnvironment), process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432'), process.env.windir, - terminalEnvironment.createVariableResolver(this._lastActiveWorkspace, this._variableResolver), + terminalEnvironment.createVariableResolver(this._lastActiveWorkspace, process.env, this._variableResolver), this._logService, useAutomationShell ); } public getDefaultShellArgs(useAutomationShell: boolean, configProvider: ExtHostConfigProvider): string[] | string { - const fetchSetting = (key: string): { userValue: string | string[] | undefined, value: string | string[] | undefined, defaultValue: string | string[] | undefined } => { - const setting = configProvider - .getConfiguration(key.substr(0, key.lastIndexOf('.'))) - .inspect(key.substr(key.lastIndexOf('.') + 1)); - return this._apiInspectConfigToPlain(setting); - }; - - return terminalEnvironment.getDefaultShellArgs(fetchSetting, this._isWorkspaceShellAllowed, useAutomationShell, terminalEnvironment.createVariableResolver(this._lastActiveWorkspace, this._variableResolver), this._logService); - } - - private _apiInspectConfigToPlain( - config: { key: string; defaultValue?: T; globalValue?: T; workspaceValue?: T, workspaceFolderValue?: T } | undefined - ): { userValue: T | undefined, value: T | undefined, defaultValue: T | undefined } { - return { - userValue: config ? config.globalValue : undefined, - value: config ? config.workspaceValue : undefined, - defaultValue: config ? config.defaultValue : undefined, - }; + return terminalEnvironment.getDefaultShellArgs( + this._buildSafeConfigProvider(configProvider), + useAutomationShell, + terminalEnvironment.createVariableResolver(this._lastActiveWorkspace, process.env, this._variableResolver), + this._logService + ); } private _registerListeners(): void { @@ -136,9 +117,9 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService { return this._variableResolver; } - 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, await this._variableResolverPromise, this._lastActiveWorkspace); + public async $getAvailableProfiles(configuredProfilesOnly: boolean): Promise { + const safeConfigProvider = this._buildSafeConfigProvider(await this._extHostConfiguration.getConfigProvider()); + return detectAvailableProfiles(configuredProfilesOnly, safeConfigProvider, undefined, this._logService, await this._variableResolverPromise, this._lastActiveWorkspace); } public async $getDefaultShellAndArgs(useAutomationShell: boolean): Promise { @@ -149,7 +130,16 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService { }; } - public $acceptWorkspacePermissionsChanged(isAllowed: boolean): void { - this._isWorkspaceShellAllowed = isAllowed; + // TODO: Remove when workspace trust is enabled + private _buildSafeConfigProvider(configProvider: ExtHostConfigProvider): SafeConfigProvider { + const config = configProvider.getConfiguration(); + return (key: string) => { + const isWorkspaceConfigAllowed = config.get('terminal.integrated.allowWorkspaceConfiguration'); + if (isWorkspaceConfigAllowed) { + return config.get(key) as any; + } + const inspected = config.inspect(key); + return inspected?.globalValue || inspected?.defaultValue; + }; } } diff --git a/src/vs/workbench/api/node/extHostTunnelService.ts b/src/vs/workbench/api/node/extHostTunnelService.ts index eb6df17a..a33f7851 100644 --- a/src/vs/workbench/api/node/extHostTunnelService.ts +++ b/src/vs/workbench/api/node/extHostTunnelService.ts @@ -22,7 +22,6 @@ import { IExtensionDescription } from 'vs/platform/extensions/common/extensions' import { MovingAverage } from 'vs/base/common/numbers'; import { CandidatePort } from 'vs/workbench/services/remote/common/remoteExplorerService'; import { ILogService } from 'vs/platform/log/common/log'; -import { flatten } from 'vs/base/common/arrays'; class ExtensionTunnel implements vscode.Tunnel { private _onDispose: Emitter = new Emitter(); @@ -203,7 +202,7 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe } async openTunnel(extension: IExtensionDescription, forward: TunnelOptions): Promise { - this.logService.trace(`ForwardedPorts: (ExtHostTunnelService) ${extension.identifier} called openTunnel API for ${forward.remoteAddress.port}.`); + this.logService.trace(`ForwardedPorts: (ExtHostTunnelService) ${extension.identifier.value} called openTunnel API for ${forward.remoteAddress.host}:${forward.remoteAddress.port}.`); const tunnel = await this._proxy.$openTunnel(forward, extension.displayName); if (tunnel) { const disposableTunnel: vscode.Tunnel = new ExtensionTunnel(tunnel.remoteAddress, tunnel.localAddress, () => { @@ -240,17 +239,20 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe } async $providePortAttributes(handles: number[], ports: number[], pid: number | undefined, commandline: string | undefined, cancellationToken: vscode.CancellationToken): Promise { - const providedAttributes = await Promise.all(handles.map(handle => { + const providedAttributes: vscode.ProviderResult[] = []; + for (const handle of handles) { const provider = this._portAttributesProviders.get(handle); if (!provider) { return []; } - return provider.provider.providePortAttributes(ports, pid, commandline, cancellationToken); - })); + providedAttributes.push(...(await Promise.all(ports.map(async (port) => { + return provider.provider.providePortAttributes(port, pid, commandline, cancellationToken); + })))); + } - const allAttributes = providedAttributes.filter(attribute => !!attribute && attribute.length > 0); + const allAttributes = providedAttributes.filter(attribute => !!attribute); - return (allAttributes.length > 0) ? flatten(allAttributes).map(attributes => { + return (allAttributes.length > 0) ? allAttributes.map(attributes => { return { autoForwardAction: attributes.autoForwardAction, port: attributes.port @@ -282,17 +284,19 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe } async setTunnelExtensionFunctions(provider: vscode.RemoteAuthorityResolver | undefined): Promise { + // Do not wait for any of the proxy promises here. + // It will delay startup and there is nothing that needs to be waited for. if (provider) { if (provider.candidatePortSource !== undefined) { - await this._proxy.$setCandidatePortSource(provider.candidatePortSource); + this._proxy.$setCandidatePortSource(provider.candidatePortSource); } if (provider.showCandidatePort) { this._showCandidatePort = provider.showCandidatePort; - await this._proxy.$setCandidateFilter(); + this._proxy.$setCandidateFilter(); } if (provider.tunnelFactory) { this._forwardPortProvider = provider.tunnelFactory; - await this._proxy.$setTunnelProvider(provider.tunnelFeatures ?? { + this._proxy.$setTunnelProvider(provider.tunnelFeatures ?? { elevation: false, public: false }); diff --git a/src/vs/workbench/api/worker/extHostExtensionService.ts b/src/vs/workbench/api/worker/extHostExtensionService.ts index fad39115..6b113e86 100644 --- a/src/vs/workbench/api/worker/extHostExtensionService.ts +++ b/src/vs/workbench/api/worker/extHostExtensionService.ts @@ -70,7 +70,7 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService { protected async _beforeAlmostReadyToRunExtensions(): Promise { const mainThreadConsole = this._extHostContext.getProxy(MainContext.MainThreadConsole); - wrapConsoleMethods(mainThreadConsole); + wrapConsoleMethods(mainThreadConsole, this._initData.environment.isExtensionDevelopmentDebug); // initialize API and register actors const apiFactory = this._instaService.invokeFunction(createApiFactoryAndRegisterActors); @@ -173,15 +173,19 @@ function ensureSuffix(path: string, suffix: string): string { } // copied from bootstrap-fork.js -function wrapConsoleMethods(service: MainThreadConsoleShape) { +function wrapConsoleMethods(service: MainThreadConsoleShape, callToNative: boolean) { wrap('info', 'log'); wrap('log', 'log'); wrap('warn', 'warn'); wrap('error', 'error'); function wrap(method: 'error' | 'warn' | 'info' | 'log', severity: 'error' | 'warn' | 'log') { + const original = console[method]; console[method] = function () { service.$logExtensionHostMessage({ type: '__$console', severity, arguments: safeToArray(arguments) }); + if (callToNative) { + original.apply(console, arguments as any); + } }; } diff --git a/src/vs/workbench/browser/actions/developerActions.ts b/src/vs/workbench/browser/actions/developerActions.ts index 94160516..82c377cd 100644 --- a/src/vs/workbench/browser/actions/developerActions.ts +++ b/src/vs/workbench/browser/actions/developerActions.ts @@ -28,6 +28,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { CATEGORIES } from 'vs/workbench/common/actions'; +import { IWorkingCopyBackupService } from 'vs/workbench/services/workingCopy/common/workingCopyBackup'; class InspectContextKeysAction extends Action2 { @@ -263,15 +264,24 @@ class LogWorkingCopiesAction extends Action2 { }); } - run(accessor: ServicesAccessor): void { + async run(accessor: ServicesAccessor): Promise { const workingCopyService = accessor.get(IWorkingCopyService); + const workingCopyBackupService = accessor.get(IWorkingCopyBackupService); const logService = accessor.get(ILogService); + + const backups = await workingCopyBackupService.getBackups(); + const msg = [ - `Dirty Working Copies:`, - ...workingCopyService.dirtyWorkingCopies.map(workingCopy => workingCopy.resource.toString(true)), ``, - `All Working Copies:`, - ...workingCopyService.workingCopies.map(workingCopy => workingCopy.resource.toString(true)), + `[Working Copies]`, + ...(workingCopyService.workingCopies.length > 0) ? + workingCopyService.workingCopies.map(workingCopy => `${workingCopy.isDirty() ? '● ' : ''}${workingCopy.resource.toString(true)} (typeId: ${workingCopy.typeId || ''})`) : + [''], + ``, + `[Backups]`, + ...(backups.length > 0) ? + backups.map(backup => `${backup.resource.toString(true)} (typeId: ${backup.typeId || ''})`) : + [''], ]; logService.info(msg.join('\n')); diff --git a/src/vs/workbench/browser/actions/layoutActions.ts b/src/vs/workbench/browser/actions/layoutActions.ts index 75fb709e..1cd44e6d 100644 --- a/src/vs/workbench/browser/actions/layoutActions.ts +++ b/src/vs/workbench/browser/actions/layoutActions.ts @@ -139,7 +139,7 @@ export class ToggleSidebarPositionAction extends Action { super(id, label); } - run(): Promise { + override run(): Promise { const position = this.layoutService.getSideBarPosition(); const newPositionValue = (position === Position.LEFT) ? 'right' : 'left'; @@ -244,7 +244,7 @@ export class ToggleEditorVisibilityAction extends Action { super(id, label); } - async run(): Promise { + override async run(): Promise { this.layoutService.toggleMaximizedPanel(); } } @@ -339,7 +339,7 @@ export class ToggleStatusbarVisibilityAction extends Action { super(id, label); } - run(): Promise { + override run(): Promise { const visibility = this.layoutService.isVisible(Parts.STATUSBAR_PART); const newVisibilityValue = !visibility; @@ -376,7 +376,7 @@ class ToggleTabsVisibilityAction extends Action { super(id, label); } - run(): Promise { + override run(): Promise { const visibility = this.configurationService.getValue(ToggleTabsVisibilityAction.tabsVisibleKey); const newVisibilityValue = !visibility; @@ -405,7 +405,7 @@ class ToggleZenMode extends Action { super(id, label); } - async run(): Promise { + override async run(): Promise { this.layoutService.toggleZenMode(); } } @@ -448,7 +448,7 @@ export class ToggleMenuBarAction extends Action { super(id, label); } - async run(): Promise { + override async run(): Promise { this.layoutService.toggleMenuBar(); } } @@ -482,7 +482,7 @@ export class ResetViewLocationsAction extends Action { super(id, label); } - async run(): Promise { + override async run(): Promise { this.viewDescriptorService.reset(); } } @@ -585,7 +585,7 @@ export class MoveViewAction extends Action { }); } - async run(): Promise { + override async run(): Promise { const focusedViewId = FocusedViewContext.getValue(this.contextKeyService); let viewId: string; @@ -624,7 +624,7 @@ export class MoveFocusedViewAction extends Action { super(id, label); } - async run(viewId: string): Promise { + override async run(viewId: string): Promise { const focusedViewId = viewId || FocusedViewContext.getValue(this.contextKeyService); if (focusedViewId === undefined || focusedViewId.trim() === '') { @@ -744,7 +744,7 @@ export class ResetFocusedViewLocationAction extends Action { super(id, label); } - async run(): Promise { + override async run(): Promise { const focusedViewId = FocusedViewContext.getValue(this.contextKeyService); let viewDescriptor: IViewDescriptor | null = null; diff --git a/src/vs/workbench/browser/actions/navigationActions.ts b/src/vs/workbench/browser/actions/navigationActions.ts index 6122d65d..f5014b8b 100644 --- a/src/vs/workbench/browser/actions/navigationActions.ts +++ b/src/vs/workbench/browser/actions/navigationActions.ts @@ -32,7 +32,7 @@ abstract class BaseNavigationAction extends Action { super(id, label); } - async run(): Promise { + override async run(): Promise { const isEditorFocus = this.layoutService.hasFocus(Parts.EDITOR_PART); const isPanelFocus = this.layoutService.hasFocus(Parts.PANEL_PART); const isSidebarFocus = this.layoutService.hasFocus(Parts.SIDEBAR_PART); @@ -41,7 +41,7 @@ abstract class BaseNavigationAction extends Action { if (isEditorFocus) { const didNavigate = this.navigateAcrossEditorGroup(this.toGroupDirection(this.direction)); if (didNavigate) { - return true; + return; } neighborPart = this.layoutService.getVisibleNeighborPart(Parts.EDITOR_PART, this.direction); @@ -56,18 +56,12 @@ abstract class BaseNavigationAction extends Action { } if (neighborPart === Parts.EDITOR_PART) { - return this.navigateToEditorGroup(this.direction === Direction.Right ? GroupLocation.FIRST : GroupLocation.LAST); + this.navigateToEditorGroup(this.direction === Direction.Right ? GroupLocation.FIRST : GroupLocation.LAST); + } else if (neighborPart === Parts.SIDEBAR_PART) { + this.navigateToSidebar(); + } else if (neighborPart === Parts.PANEL_PART) { + this.navigateToPanel(); } - - if (neighborPart === Parts.SIDEBAR_PART) { - return this.navigateToSidebar(); - } - - if (neighborPart === Parts.PANEL_PART) { - return this.navigateToPanel(); - } - - return false; } private async navigateToPanel(): Promise { @@ -240,7 +234,7 @@ export class FocusNextPart extends Action { super(id, label); } - async run(): Promise { + override async run(): Promise { focusNextOrPreviousPart(this.layoutService, this.editorService, true); } } @@ -258,7 +252,7 @@ export class FocusPreviousPart extends Action { super(id, label); } - async run(): Promise { + override async run(): Promise { focusNextOrPreviousPart(this.layoutService, this.editorService, false); } } diff --git a/src/vs/workbench/browser/actions/textInputActions.ts b/src/vs/workbench/browser/actions/textInputActions.ts index 606099d2..60dad795 100644 --- a/src/vs/workbench/browser/actions/textInputActions.ts +++ b/src/vs/workbench/browser/actions/textInputActions.ts @@ -42,7 +42,7 @@ export class TextInputActionsProvider extends Disposable implements IWorkbenchCo // Cut / Copy / Paste new Action('editor.action.clipboardCutAction', localize('cut', "Cut"), undefined, true, async () => document.execCommand('cut')), new Action('editor.action.clipboardCopyAction', localize('copy', "Copy"), undefined, true, async () => document.execCommand('copy')), - new Action('editor.action.clipboardPasteAction', localize('paste', "Paste"), undefined, true, async (element: HTMLInputElement | HTMLTextAreaElement) => { + new Action('editor.action.clipboardPasteAction', localize('paste', "Paste"), undefined, true, async element => { // Native: paste is supported if (isNative) { diff --git a/src/vs/workbench/browser/actions/windowActions.ts b/src/vs/workbench/browser/actions/windowActions.ts index de5ccf08..358af680 100644 --- a/src/vs/workbench/browser/actions/windowActions.ts +++ b/src/vs/workbench/browser/actions/windowActions.ts @@ -78,7 +78,7 @@ abstract class BaseOpenRecentAction extends Action { protected abstract isQuickNavigate(): boolean; - async run(): Promise { + override async run(): Promise { const recentlyOpened = await this.workspacesService.getRecentlyOpened(); const dirtyWorkspacesAndFolders = await this.workspacesService.getDirtyWorkspaces(); @@ -286,7 +286,7 @@ class ToggleFullScreenAction extends Action { super(id, label); } - run(): Promise { + override run(): Promise { return this.hostService.toggleFullScreen(); } } @@ -304,10 +304,8 @@ export class ReloadWindowAction extends Action { super(id, label); } - async run(): Promise { + override async run(): Promise { await this.hostService.reload(); - - return true; } } @@ -324,7 +322,7 @@ class ShowAboutDialogAction extends Action { super(id, label); } - run(): Promise { + override run(): Promise { return this.dialogService.about(); } } @@ -342,7 +340,7 @@ export class NewWindowAction extends Action { super(id, label); } - run(): Promise { + override run(): Promise { return this.hostService.openWindow({ remoteAuthority: null }); } } diff --git a/src/vs/workbench/browser/actions/workspaceActions.ts b/src/vs/workbench/browser/actions/workspaceActions.ts index 03628a38..58837b5a 100644 --- a/src/vs/workbench/browser/actions/workspaceActions.ts +++ b/src/vs/workbench/browser/actions/workspaceActions.ts @@ -24,6 +24,7 @@ import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IWorkspacesService, hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces'; import { WORKSPACE_TRUST_ENABLED } from 'vs/workbench/services/workspaces/common/workspaceTrust'; +import { IsWebContext } from 'vs/platform/contextkey/common/contextkeys'; export class OpenFileAction extends Action { @@ -38,7 +39,7 @@ export class OpenFileAction extends Action { super(id, label); } - run(event?: unknown, data?: ITelemetryData): Promise { + override run(event?: unknown, data?: ITelemetryData): Promise { return this.dialogService.pickFileAndOpen({ forceNewWindow: false, telemetryExtraData: data }); } } @@ -56,7 +57,7 @@ export class OpenFolderAction extends Action { super(id, label); } - run(event?: unknown, data?: ITelemetryData): Promise { + override run(event?: unknown, data?: ITelemetryData): Promise { return this.dialogService.pickFolderAndOpen({ forceNewWindow: false, telemetryExtraData: data }); } } @@ -74,7 +75,7 @@ export class OpenFileFolderAction extends Action { super(id, label); } - run(event?: unknown, data?: ITelemetryData): Promise { + override run(event?: unknown, data?: ITelemetryData): Promise { return this.dialogService.pickFileFolderAndOpen({ forceNewWindow: false, telemetryExtraData: data }); } } @@ -92,7 +93,7 @@ export class OpenWorkspaceAction extends Action { super(id, label); } - run(event?: unknown, data?: ITelemetryData): Promise { + override run(event?: unknown, data?: ITelemetryData): Promise { return this.dialogService.pickWorkspaceAndOpen({ telemetryExtraData: data }); } } @@ -113,7 +114,7 @@ export class CloseWorkspaceAction extends Action { super(id, label); } - async run(): Promise { + override async run(): Promise { if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) { this.notificationService.info(localize('noWorkspaceOrFolderOpened', "There is currently no workspace or folder opened in this instance to close.")); return; @@ -139,7 +140,7 @@ export class OpenWorkspaceConfigFileAction extends Action { this.enabled = !!this.workspaceContextService.getWorkspace().configuration; } - async run(): Promise { + override async run(): Promise { const configuration = this.workspaceContextService.getWorkspace().configuration; if (configuration) { await this.editorService.openEditor({ resource: configuration, options: { pinned: true } }); @@ -160,7 +161,7 @@ export class AddRootFolderAction extends Action { super(id, label); } - run(): Promise { + override run(): Promise { return this.commandService.executeCommand(ADD_ROOT_FOLDER_COMMAND_ID); } } @@ -180,7 +181,7 @@ export class GlobalRemoveRootFolderAction extends Action { super(id, label); } - async run(): Promise { + override async run(): Promise { const state = this.contextService.getWorkbenchState(); // Workspace / Folder @@ -208,7 +209,7 @@ export class SaveWorkspaceAsAction extends Action { super(id, label); } - async run(): Promise { + override async run(): Promise { const configPathUri = await this.workspaceEditingService.pickNewWorkspacePath(); if (configPathUri && hasWorkspaceFileExtension(configPathUri)) { switch (this.contextService.getWorkbenchState()) { @@ -240,7 +241,7 @@ export class DuplicateWorkspaceInNewWindowAction extends Action { super(id, label); } - async run(): Promise { + override async run(): Promise { const folders = this.workspaceContextService.getWorkspace().folders; const remoteAuthority = this.environmentService.remoteAuthority; @@ -256,7 +257,7 @@ class WorkspaceTrustManageAction extends Action2 { super({ id: 'workbench.action.manageTrust', title: { value: localize('manageTrustAction', "Manage Workspace Trust"), original: 'Manage Workspace Trust' }, - precondition: ContextKeyExpr.equals(`config.${WORKSPACE_TRUST_ENABLED}`, true), + precondition: ContextKeyExpr.and(IsWebContext.negate(), ContextKeyExpr.equals(`config.${WORKSPACE_TRUST_ENABLED}`, true)), category: localize('workspacesCategory', "Workspaces"), f1: true }); diff --git a/src/vs/workbench/browser/actions/workspaceCommands.ts b/src/vs/workbench/browser/actions/workspaceCommands.ts index 204c6f05..c9e7bdde 100644 --- a/src/vs/workbench/browser/actions/workspaceCommands.ts +++ b/src/vs/workbench/browser/actions/workspaceCommands.ts @@ -17,11 +17,12 @@ import { IQuickInputService, IPickOptions, IQuickPickItem } from 'vs/platform/qu import { getIconClasses } from 'vs/editor/common/services/getIconClasses'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IModeService } from 'vs/editor/common/services/modeService'; -import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IFileDialogService, IPickAndOpenOptions } from 'vs/platform/dialogs/common/dialogs'; import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; import { IOpenWindowOptions, IWindowOpenable } from 'vs/platform/windows/common/windows'; import { hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces'; +import { IPathService } from 'vs/workbench/services/path/common/pathService'; export const ADD_ROOT_FOLDER_COMMAND_ID = 'addRootFolder'; export const ADD_ROOT_FOLDER_LABEL = localize('addFolderToWorkspace', "Add Folder to Workspace..."); @@ -60,12 +61,14 @@ CommandsRegistry.registerCommand({ handler: async (accessor) => { const workspaceEditingService = accessor.get(IWorkspaceEditingService); const dialogsService = accessor.get(IFileDialogService); + const pathService = accessor.get(IPathService); const folders = await dialogsService.showOpenDialog({ openLabel: mnemonicButtonLabel(localize({ key: 'add', comment: ['&& denotes a mnemonic'] }, "&&Add")), title: localize('addFolderToWorkspaceTitle', "Add Folder to Workspace"), canSelectFolders: true, canSelectMany: true, - defaultUri: await dialogsService.defaultFolderPath() + defaultUri: await dialogsService.defaultFolderPath(), + availableFileSystems: [pathService.defaultUriScheme] }); if (!folders || !folders.length) { @@ -127,21 +130,28 @@ interface IOpenFolderAPICommandOptions { forceNewWindow?: boolean; forceReuseWindow?: boolean; noRecentEntry?: boolean; + forceLocalWindow?: boolean; } CommandsRegistry.registerCommand({ id: 'vscode.openFolder', handler: (accessor: ServicesAccessor, uri?: URI, arg?: boolean | IOpenFolderAPICommandOptions) => { const commandService = accessor.get(ICommandService); - // Be compatible to previous args by converting to options if (typeof arg === 'boolean') { arg = { forceNewWindow: arg }; } - // Without URI, ask to pick a folder or workpsace to open + // Without URI, ask to pick a folder or workspace to open if (!uri) { - return commandService.executeCommand('_files.pickFolderAndOpen', { forceNewWindow: arg?.forceNewWindow }); + const options: IPickAndOpenOptions = { + forceNewWindow: arg?.forceNewWindow + }; + if (arg?.forceLocalWindow) { + options.remoteAuthority = null; + options.availableFileSystems = ['file']; + } + return commandService.executeCommand('_files.pickFolderAndOpen', options); } uri = URI.revive(uri); @@ -149,17 +159,28 @@ CommandsRegistry.registerCommand({ const options: IOpenWindowOptions = { forceNewWindow: arg?.forceNewWindow, forceReuseWindow: arg?.forceReuseWindow, - noRecentEntry: arg?.noRecentEntry + noRecentEntry: arg?.noRecentEntry, + remoteAuthority: arg?.forceLocalWindow ? null : undefined }; - const uriToOpen: IWindowOpenable = (hasWorkspaceFileExtension(uri) || uri.scheme === Schemas.untitled) ? { workspaceUri: uri } : { folderUri: uri }; return commandService.executeCommand('_files.windowOpen', [uriToOpen], options); }, description: { description: 'Open a folder or workspace in the current window or new window depending on the newWindow argument. Note that opening in the same window will shutdown the current extension host process and start a new one on the given folder/workspace unless the newWindow parameter is set to true.', args: [ - { name: 'uri', description: '(optional) Uri of the folder or workspace file to open. If not provided, a native dialog will ask the user for the folder', constraint: (value: any) => value === undefined || value instanceof URI }, - { name: 'options', description: '(optional) Options. Object with the following properties: `forceNewWindow `: Whether to open the folder/workspace in a new window or the same. Defaults to opening in the same window. `noRecentEntry`: Whether the opened URI will appear in the \'Open Recent\' list. Defaults to true. Note, for backward compatibility, options can also be of type boolean, representing the `forceNewWindow` setting.', constraint: (value: any) => value === undefined || typeof value === 'object' || typeof value === 'boolean' } + { + name: 'uri', description: '(optional) Uri of the folder or workspace file to open. If not provided, a native dialog will ask the user for the folder', + constraint: (value: any) => value === undefined || value === null || value instanceof URI + }, + { + name: 'options', + description: '(optional) Options. Object with the following properties: ' + + '`forceNewWindow`: Whether to open the folder/workspace in a new window or the same. Defaults to opening in the same window. ' + + '`forceReuseWindow`: Whether to force opening the folder/workspace in the same window. Defaults to false. ' + + '`noRecentEntry`: Whether the opened URI will appear in the \'Open Recent\' list. Defaults to false. ' + + 'Note, for backward compatibility, options can also be of type boolean, representing the `forceNewWindow` setting.', + constraint: (value: any) => value === undefined || typeof value === 'object' || typeof value === 'boolean' + } ] } }); diff --git a/src/vs/workbench/browser/codeeditor.ts b/src/vs/workbench/browser/codeeditor.ts index f7d116f6..c25acef3 100644 --- a/src/vs/workbench/browser/codeeditor.ts +++ b/src/vs/workbench/browser/codeeditor.ts @@ -76,10 +76,9 @@ export class RangeHighlightDecorations extends Disposable { } private getEditor(resourceRange: IRangeHighlightDecoration): ICodeEditor | undefined { - const activeEditor = this.editorService.activeEditor; - const resource = activeEditor?.resource; - if (resource && isEqual(resource, resourceRange.resource)) { - return this.editorService.activeTextEditorControl as ICodeEditor; + const resource = this.editorService.activeEditor?.resource; + if (resource && isEqual(resource, resourceRange.resource) && isCodeEditor(this.editorService.activeTextEditorControl)) { + return this.editorService.activeTextEditorControl; } return undefined; @@ -122,7 +121,7 @@ export class RangeHighlightDecorations extends Disposable { return (isWholeLine ? RangeHighlightDecorations._WHOLE_LINE_RANGE_HIGHLIGHT : RangeHighlightDecorations._RANGE_HIGHLIGHT); } - dispose() { + override dispose() { super.dispose(); if (this.editor?.getModel()) { @@ -201,7 +200,7 @@ export class FloatingClickWidget extends Widget implements IOverlayWidget { this.editor.addOverlayWidget(this); } - dispose(): void { + override dispose(): void { this.editor.removeOverlayWidget(this); super.dispose(); @@ -292,7 +291,7 @@ export class OpenWorkspaceButtonContribution extends Disposable implements IEdit this.openWorkspaceButton = undefined; } - dispose(): void { + override dispose(): void { this.disposeOpenWorkspaceWidgetRenderer(); super.dispose(); diff --git a/src/vs/workbench/browser/composite.ts b/src/vs/workbench/browser/composite.ts index a6c58ee4..59cec851 100644 --- a/src/vs/workbench/browser/composite.ts +++ b/src/vs/workbench/browser/composite.ts @@ -118,7 +118,7 @@ export abstract class Composite extends Component implements IComposite { this.parent = parent; } - updateStyles(): void { + override updateStyles(): void { super.updateStyles(); } @@ -161,7 +161,7 @@ export abstract class Composite extends Component implements IComposite { /** * Returns an array of actions to show in the action bar of the composite. */ - getActions(): ReadonlyArray { + getActions(): readonly IAction[] { return []; } @@ -169,14 +169,14 @@ export abstract class Composite extends Component implements IComposite { * Returns an array of actions to show in the action bar of the composite * in a less prominent way then action from getActions. */ - getSecondaryActions(): ReadonlyArray { + getSecondaryActions(): readonly IAction[] { return []; } /** * Returns an array of actions to show in the context menu of the composite */ - getContextMenuActions(): ReadonlyArray { + getContextMenuActions(): readonly IAction[] { return []; } diff --git a/src/vs/workbench/browser/contextkeys.ts b/src/vs/workbench/browser/contextkeys.ts index bc128afe..ebf069f2 100644 --- a/src/vs/workbench/browser/contextkeys.ts +++ b/src/vs/workbench/browser/contextkeys.ts @@ -19,7 +19,7 @@ import { SideBarVisibleContext } from 'vs/workbench/common/viewlet'; import { IWorkbenchLayoutService, Parts, positionToString } from 'vs/workbench/services/layout/browser/layoutService'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { PanelMaximizedContext, PanelPositionContext, PanelVisibleContext } from 'vs/workbench/common/panel'; -import { getRemoteName } from 'vs/platform/remote/common/remoteHosts'; +import { getRemoteName, getVirtualWorkspaceScheme } from 'vs/platform/remote/common/remoteHosts'; import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { isNative } from 'vs/base/common/platform'; @@ -27,9 +27,10 @@ export const WorkbenchStateContext = new RawContextKey('workbenchState', export const WorkspaceFolderCountContext = new RawContextKey('workspaceFolderCount', 0, localize('workspaceFolderCount', "The number of root folders in the workspace")); export const EmptyWorkspaceSupportContext = new RawContextKey('emptyWorkspaceSupport', true, true); -export const DirtyWorkingCopiesContext = new RawContextKey('dirtyWorkingCopies', false, localize('dirtyWorkingCopies', "Wether there are any dirty working copies")); +export const DirtyWorkingCopiesContext = new RawContextKey('dirtyWorkingCopies', false, localize('dirtyWorkingCopies', "Whether there are any dirty working copies")); export const RemoteNameContext = new RawContextKey('remoteName', '', localize('remoteName', "The name of the remote the window is connected to or an empty string if not connected to any remote")); +export const VirtualWorkspaceContext = new RawContextKey('virtualWorkspace', '', localize('virtualWorkspace', "The scheme of the current workspace if is from a virtual file system or an empty string.")); export const IsFullscreenContext = new RawContextKey('isFullscreen', false, localize('isFullscreen', "Whether the window is in fullscreen mode")); @@ -68,6 +69,8 @@ export class WorkbenchContextKeysHandler extends Disposable { private panelVisibleContext: IContextKey; private panelMaximizedContext: IContextKey; + private vitualWorkspaceContext: IContextKey; + constructor( @IContextKeyService private readonly contextKeyService: IContextKeyService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @@ -91,6 +94,9 @@ export class WorkbenchContextKeysHandler extends Disposable { RemoteNameContext.bindTo(this.contextKeyService).set(getRemoteName(this.environmentService.remoteAuthority) || ''); + this.vitualWorkspaceContext = VirtualWorkspaceContext.bindTo(this.contextKeyService); + this.updateVirtualWorkspaceContextKey(); + // Capabilities HasWebFileSystemAccess.bindTo(this.contextKeyService).set(WebFileSystemAccess.supported(window)); @@ -161,7 +167,7 @@ export class WorkbenchContextKeysHandler extends Disposable { } private registerListeners(): void { - this.editorGroupService.whenRestored.then(() => this.updateEditorContextKeys()); + this.editorGroupService.whenReady.then(() => this.updateEditorContextKeys()); this._register(this.editorService.onDidActiveEditorChange(() => this.updateEditorContextKeys())); this._register(this.editorService.onDidVisibleEditorsChange(() => this.updateEditorContextKeys())); @@ -173,7 +179,10 @@ export class WorkbenchContextKeysHandler extends Disposable { this._register(addDisposableListener(window, EventType.FOCUS_IN, () => this.updateInputContextKeys(), true)); this._register(this.contextService.onDidChangeWorkbenchState(() => this.updateWorkbenchStateContextKey())); - this._register(this.contextService.onDidChangeWorkspaceFolders(() => this.updateWorkspaceFolderCountContextKey())); + this._register(this.contextService.onDidChangeWorkspaceFolders(() => { + this.updateWorkspaceFolderCountContextKey(); + this.updateVirtualWorkspaceContextKey(); + })); this._register(this.configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration('workbench.editor.openSideBySideDirection')) { @@ -285,4 +294,8 @@ export class WorkbenchContextKeysHandler extends Disposable { private updateSideBarContextKeys(): void { this.sideBarVisibleContext.set(this.layoutService.isVisible(Parts.SIDEBAR_PART)); } + + private updateVirtualWorkspaceContextKey(): void { + this.vitualWorkspaceContext.set(getVirtualWorkspaceScheme(this.contextService.getWorkspace()) || ''); + } } diff --git a/src/vs/workbench/browser/dnd.ts b/src/vs/workbench/browser/dnd.ts index bfb5933b..8a188db9 100644 --- a/src/vs/workbench/browser/dnd.ts +++ b/src/vs/workbench/browser/dnd.ts @@ -3,13 +3,15 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { Registry } from 'vs/platform/registry/common/platform'; import { hasWorkspaceFileExtension, IWorkspaceFolderCreationData, IRecentFile, IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; import { normalize } from 'vs/base/common/path'; import { basename, isEqual } from 'vs/base/common/resources'; import { IFileService } from 'vs/platform/files/common/files'; import { IWindowOpenable } from 'vs/platform/windows/common/windows'; import { URI } from 'vs/base/common/uri'; -import { ITextFileService, stringToSnapshot } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { bufferToReadable, VSBuffer } from 'vs/base/common/buffer'; import { FileAccess, Schemas } from 'vs/base/common/network'; import { ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { DataTransfers, IDragAndDropData } from 'vs/base/browser/dnd'; @@ -19,7 +21,7 @@ import { MIME_BINARY } from 'vs/base/common/mime'; import { isWindows } from 'vs/base/common/platform'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; -import { IEditorIdentifier, GroupIdentifier } from 'vs/workbench/common/editor'; +import { IEditorIdentifier, GroupIdentifier, IEditorInputFactoryRegistry, EditorExtensions } from 'vs/workbench/common/editor'; import { IEditorService, IResourceEditorInputType } from 'vs/workbench/services/editor/common/editorService'; import { Disposable, IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { addDisposableListener, EventType } from 'vs/base/browser/dom'; @@ -27,8 +29,9 @@ import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsSe import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing'; import { withNullAsUndefined } from 'vs/base/common/types'; import { IHostService } from 'vs/workbench/services/host/browser/host'; -import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; +import { IWorkingCopyBackupService } from 'vs/workbench/services/workingCopy/common/workingCopyBackup'; import { Emitter } from 'vs/base/common/event'; +import { NO_TYPE_ID } from 'vs/workbench/services/workingCopy/common/workingCopy'; export interface IDraggedResource { resource: URI; @@ -110,7 +113,7 @@ export function extractResources(e: DragEvent, externalOnly?: boolean): Array r.resource.fsPath === file.path) /* prevent duplicates */) { + if (file?.path /* Electron only */ && !resources.some(resource => resource.resource.fsPath === file.path) /* prevent duplicates */) { try { resources.push({ resource: URI.file(file.path), isExternal: true }); } catch (error) { @@ -126,7 +129,7 @@ export function extractResources(e: DragEvent, externalOnly?: boolean): Array { - if (!resources.some(r => r.resource.fsPath === codeFile) /* prevent duplicates */) { + if (!resources.some(resource => resource.resource.fsPath === codeFile) /* prevent duplicates */) { resources.push({ resource: URI.file(codeFile), isExternal: true }); } }); @@ -159,7 +162,7 @@ export class ResourcesDropHandler { @IFileService private readonly fileService: IFileService, @IWorkspacesService private readonly workspacesService: IWorkspacesService, @ITextFileService private readonly textFileService: ITextFileService, - @IBackupFileService private readonly backupFileService: IBackupFileService, + @IWorkingCopyBackupService private readonly workingCopyBackupService: IWorkingCopyBackupService, @IEditorService private readonly editorService: IEditorService, @IWorkspaceEditingService private readonly workspaceEditingService: IWorkspaceEditingService, @IHostService private readonly hostService: IHostService @@ -167,7 +170,7 @@ export class ResourcesDropHandler { } async handleDrop(event: DragEvent, resolveTargetGroup: () => IEditorGroup | undefined, afterDrop: (targetGroup: IEditorGroup | undefined) => void, targetIndex?: number): Promise { - const untitledOrFileResources = extractResources(event).filter(r => this.fileService.canHandleResource(r.resource) || r.resource.scheme === Schemas.untitled); + const untitledOrFileResources = extractResources(event).filter(resource => this.fileService.canHandleResource(resource.resource) || resource.resource.scheme === Schemas.untitled); if (!untitledOrFileResources.length) { return; } @@ -227,6 +230,7 @@ export class ResourcesDropHandler { } private async handleDirtyEditorDrop(droppedDirtyEditor: IDraggedEditor): Promise { + const fileEditorFactory = Registry.as(EditorExtensions.EditorInputFactories).getFileEditorInputFactory(); // Untitled: always ensure that we open a new untitled editor for each file we drop if (droppedDirtyEditor.resource.scheme === Schemas.untitled) { @@ -237,7 +241,7 @@ export class ResourcesDropHandler { } // File: ensure the file is not dirty or opened already - else if (this.textFileService.isDirty(droppedDirtyEditor.resource) || this.editorService.isOpen({ resource: droppedDirtyEditor.resource })) { + else if (this.textFileService.isDirty(droppedDirtyEditor.resource) || this.editorService.isOpened({ resource: droppedDirtyEditor.resource, typeId: fileEditorFactory.typeId })) { return false; } @@ -245,7 +249,7 @@ export class ResourcesDropHandler { // content and turn it into a backup so that it loads the contents if (typeof droppedDirtyEditor.dirtyContent === 'string') { try { - await this.backupFileService.backup(droppedDirtyEditor.resource, stringToSnapshot(droppedDirtyEditor.dirtyContent)); + await this.workingCopyBackupService.backup({ resource: droppedDirtyEditor.resource, typeId: NO_TYPE_ID }, bufferToReadable(VSBuffer.fromString(droppedDirtyEditor.dirtyContent))); } catch (e) { // Ignore error } diff --git a/src/vs/workbench/browser/editor.ts b/src/vs/workbench/browser/editor.ts index f129f7ef..adce1b32 100644 --- a/src/vs/workbench/browser/editor.ts +++ b/src/vs/workbench/browser/editor.ts @@ -4,22 +4,19 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; -import { Event } from 'vs/base/common/event'; -import { EditorInput } from 'vs/workbench/common/editor'; +import { EditorInput, EditorResourceAccessor, IEditorInput, EditorExtensions, SideBySideEditor } from 'vs/workbench/common/editor'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { Registry } from 'vs/platform/registry/common/platform'; import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; -import { IConstructorSignature0, IInstantiationService, BrandedService } from 'vs/platform/instantiation/common/instantiation'; +import { IConstructorSignature0, IInstantiationService, BrandedService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { insert } from 'vs/base/common/arrays'; import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { IJSONSchema } from 'vs/base/common/jsonSchema'; -import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration'; -import { Extensions as ConfigurationExtensions, IConfigurationNode, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; - -export const Extensions = { - Editors: 'workbench.contributions.editors', - Associations: 'workbench.editors.associations' -}; +import { Promises } from 'vs/base/common/async'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; +import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { URI } from 'vs/workbench/workbench.web.api'; +import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; //#region Editors Registry @@ -161,7 +158,7 @@ class EditorRegistry implements IEditorRegistry { if (descriptors.length > 0) { // Ask the input for its preferred Editor - const preferredEditorId = input.getPreferredEditorId(descriptors.map(d => d.getId())); + const preferredEditorId = input.getPreferredEditorId(descriptors.map(descriptor => descriptor.getId())); if (preferredEditorId) { return this.getEditorById(preferredEditorId); } @@ -194,137 +191,91 @@ class EditorRegistry implements IEditorRegistry { } } -Registry.add(Extensions.Editors, new EditorRegistry()); +Registry.add(EditorExtensions.Editors, new EditorRegistry()); //#endregion +//#region Editor Close Tracker -//#region Editor Associations +export function whenEditorClosed(accessor: ServicesAccessor, resources: URI[]): Promise { + const editorService = accessor.get(IEditorService); + const uriIdentityService = accessor.get(IUriIdentityService); + const workingCopyService = accessor.get(IWorkingCopyService); -export const editorsAssociationsSettingId = 'workbench.editorAssociations'; + return new Promise(resolve => { + let remainingResources = [...resources]; -export const DEFAULT_EDITOR_ASSOCIATION: IEditorType = { - id: 'default', - displayName: localize('promptOpenWith.defaultEditor.displayName', "Text Editor"), - providerDisplayName: localize('builtinProviderDisplayName', "Built-in") -}; + // Observe any editor closing from this moment on + const listener = editorService.onDidCloseEditor(async event => { + const primaryResource = EditorResourceAccessor.getOriginalUri(event.editor, { supportSideBySide: SideBySideEditor.PRIMARY }); + const secondaryResource = EditorResourceAccessor.getOriginalUri(event.editor, { supportSideBySide: SideBySideEditor.SECONDARY }); -export type EditorAssociation = { - readonly viewType: string; - readonly filenamePattern?: string; -}; - -export type EditorsAssociations = readonly EditorAssociation[]; - -const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); - -const editorTypeSchemaAddition: IJSONSchema = { - type: 'string', - enum: [] -}; - -const editorAssociationsConfigurationNode: IConfigurationNode = { - ...workbenchConfigurationNodeBase, - properties: { - 'workbench.editorAssociations': { - type: 'array', - markdownDescription: localize('editor.editorAssociations', "Configure which editor to use for specific file types."), - items: { - type: 'object', - defaultSnippets: [{ - body: { - 'viewType': '$1', - 'filenamePattern': '$2' - } - }], - properties: { - 'viewType': { - anyOf: [ - { - type: 'string', - description: localize('editor.editorAssociations.viewType', "The unique id of the editor to use."), - }, - editorTypeSchemaAddition - ] - }, - 'filenamePattern': { - type: 'string', - description: localize('editor.editorAssociations.filenamePattern', "Glob pattern specifying which files the editor should be used for."), - } + // Remove from resources to wait for being closed based on the + // resources from editors that got closed + remainingResources = remainingResources.filter(resource => { + if (uriIdentityService.extUri.isEqual(resource, primaryResource) || uriIdentityService.extUri.isEqual(resource, secondaryResource)) { + return false; // remove - the closing editor matches this resource } + + return true; // keep - not yet closed + }); + + // All resources to wait for being closed are closed + if (remainingResources.length === 0) { + + // If auto save is configured with the default delay (1s) it is possible + // to close the editor while the save still continues in the background. As such + // we have to also check if the editors to track for are dirty and if so wait + // for them to get saved. + const dirtyResources = resources.filter(resource => workingCopyService.isDirty(resource)); + if (dirtyResources.length > 0) { + await Promises.settled(dirtyResources.map(async resource => await new Promise(resolve => { + if (!workingCopyService.isDirty(resource)) { + return resolve(); // return early if resource is not dirty + } + + // Otherwise resolve promise when resource is saved + const listener = workingCopyService.onDidChangeDirty(workingCopy => { + if (!workingCopy.isDirty() && uriIdentityService.extUri.isEqual(resource, workingCopy.resource)) { + listener.dispose(); + + return resolve(); + } + }); + }))); + } + + listener.dispose(); + + return resolve(); } - } - } -}; - -export interface IEditorType { - readonly id: string; - readonly displayName: string; - readonly providerDisplayName: string; -} - -export interface IEditorTypesHandler { - readonly onDidChangeEditorTypes: Event; - - getEditorTypes(): IEditorType[]; -} - -export interface IEditorAssociationsRegistry { - - /** - * Register handlers for editor types - */ - registerEditorTypesHandler(id: string, handler: IEditorTypesHandler): IDisposable; -} - -class EditorAssociationsRegistry implements IEditorAssociationsRegistry { - - private readonly editorTypesHandlers = new Map(); - - registerEditorTypesHandler(id: string, handler: IEditorTypesHandler): IDisposable { - if (this.editorTypesHandlers.has(id)) { - throw new Error(`An editor type handler with ${id} was already registered.`); - } - - this.editorTypesHandlers.set(id, handler); - this.updateEditorAssociationsSchema(); - - const editorTypeChangeEvent = handler.onDidChangeEditorTypes(() => { - this.updateEditorAssociationsSchema(); }); - - return { - dispose: () => { - editorTypeChangeEvent.dispose(); - this.editorTypesHandlers.delete(id); - this.updateEditorAssociationsSchema(); - } - }; - } - - private updateEditorAssociationsSchema() { - const enumValues: string[] = []; - const enumDescriptions: string[] = []; - - const editorTypes: IEditorType[] = [DEFAULT_EDITOR_ASSOCIATION]; - - for (const [, handler] of this.editorTypesHandlers) { - editorTypes.push(...handler.getEditorTypes()); - } - - for (const { id, providerDisplayName } of editorTypes) { - enumValues.push(id); - enumDescriptions.push(localize('editorAssociations.viewType.sourceDescription', "Source: {0}", providerDisplayName)); - } - - editorTypeSchemaAddition.enum = enumValues; - editorTypeSchemaAddition.enumDescriptions = enumDescriptions; - - configurationRegistry.notifyConfigurationSchemaUpdated(editorAssociationsConfigurationNode); - } + }); +} + +//#endregion + + +//#region ARIA + +export function computeEditorAriaLabel(input: IEditorInput, index: number | undefined, group: IEditorGroup | undefined, groupCount: number): string { + let ariaLabel = input.getAriaLabel(); + if (group && !group.isPinned(input)) { + ariaLabel = localize('preview', "{0}, preview", ariaLabel); + } + + if (group?.isSticky(index ?? input)) { + ariaLabel = localize('pinned', "{0}, pinned", ariaLabel); + } + + // Apply group information to help identify in + // which group we are (only if more than one group + // is actually opened) + if (group && groupCount > 1) { + ariaLabel = `${ariaLabel}, ${group.ariaLabel}`; + } + + return ariaLabel; } -Registry.add(Extensions.Associations, new EditorAssociationsRegistry()); -configurationRegistry.registerConfiguration(editorAssociationsConfigurationNode); - //#endregion diff --git a/src/vs/workbench/browser/labels.ts b/src/vs/workbench/browser/labels.ts index 5bafb730..489dde0d 100644 --- a/src/vs/workbench/browser/labels.ts +++ b/src/vs/workbench/browser/labels.ts @@ -227,7 +227,7 @@ export class ResourceLabels extends Disposable { this.labels = []; } - dispose(): void { + override dispose(): void { super.dispose(); this.clear(); @@ -569,6 +569,7 @@ class ResourceLabelWidget extends IconLabel { if (this.options.fileDecorations.badges) { iconLabelOptions.extraClasses.push(deco.badgeClassName); + iconLabelOptions.extraClasses.push(deco.iconClassName); } } } @@ -580,7 +581,7 @@ class ResourceLabelWidget extends IconLabel { return true; } - dispose(): void { + override dispose(): void { super.dispose(); this.label = undefined; diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 464059aa..02ff8b3f 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -7,7 +7,7 @@ import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Event, Emitter } from 'vs/base/common/event'; import { EventType, addDisposableListener, getClientArea, Dimension, position, size, IDimension, isAncestorUsingFlowTo } from 'vs/base/browser/dom'; import { onDidChangeFullscreen, isFullscreen } from 'vs/base/browser/browser'; -import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; +import { IWorkingCopyBackupService } from 'vs/workbench/services/workingCopy/common/workingCopyBackup'; import { Registry } from 'vs/platform/registry/common/platform'; import { isWindows, isLinux, isMacintosh, isWeb, isNative } from 'vs/base/common/platform'; import { pathsToEditors, SideBySideEditorInput } from 'vs/workbench/common/editor'; @@ -22,7 +22,7 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { ITitleService } from 'vs/workbench/services/title/common/titleService'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { LifecyclePhase, StartupKind, ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { StartupKind, ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { MenuBarVisibility, getTitleBarStyle, getMenuBarVisibility, IPath } from 'vs/platform/windows/common/windows'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IEditor } from 'vs/editor/common/editorCommon'; @@ -159,7 +159,6 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi private environmentService!: IWorkbenchEnvironmentService; private extensionService!: IExtensionService; private configurationService!: IConfigurationService; - private lifecycleService!: ILifecycleService; private storageService!: IStorageService; private hostService!: IHostService; private editorService!: IEditorService; @@ -169,7 +168,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi private viewletService!: IViewletService; private viewDescriptorService!: IViewDescriptorService; private contextService!: IWorkspaceContextService; - private backupFileService!: IBackupFileService; + private workingCopyBackupService!: IWorkingCopyBackupService; private notificationService!: INotificationService; private themeService!: IThemeService; private activityBarService!: IActivityBarService; @@ -232,7 +231,6 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi wasPanelVisible: false, transitionDisposables: new DisposableStore(), setNotificationsFilter: false, - editorWidgetSet: new Set() } }; @@ -247,11 +245,10 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Services this.environmentService = accessor.get(IWorkbenchEnvironmentService); this.configurationService = accessor.get(IConfigurationService); - this.lifecycleService = accessor.get(ILifecycleService); this.hostService = accessor.get(IHostService); this.contextService = accessor.get(IWorkspaceContextService); this.storageService = accessor.get(IStorageService); - this.backupFileService = accessor.get(IBackupFileService); + this.workingCopyBackupService = accessor.get(IWorkingCopyBackupService); this.themeService = accessor.get(IThemeService); this.extensionService = accessor.get(IExtensionService); this.logService = accessor.get(ILogService); @@ -314,7 +311,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } // Theme changes - this._register(this.themeService.onDidColorThemeChange(theme => this.updateStyles())); + this._register(this.themeService.onDidColorThemeChange(() => this.updateStyles())); // Window focus changes this._register(this.hostService.onDidChangeFocus(e => this.onWindowFocusChanged(e))); @@ -582,7 +579,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi const { views } = defaultLayout; if (views?.length) { - this.state.views.defaults = views.map(v => v.id); + this.state.views.defaults = views.map(view => view.id); } } @@ -599,7 +596,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Files to diff is exclusive return pathsToEditors(initialFilesToOpen.filesToDiff, fileService).then(filesToDiff => { - if (filesToDiff?.length === 2) { + if (filesToDiff.length === 2) { return [{ leftResource: filesToDiff[0].resource, rightResource: filesToDiff[1].resource, @@ -613,13 +610,13 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi }); } - // Empty workbench + // Empty workbench configured to open untitled file if empty else if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY && this.configurationService.getValue('workbench.startupEditor') === 'newUntitledFile') { - if (this.editorGroupService.willRestoreEditors) { - return []; // do not open any empty untitled file if we restored editors from previous session + if (this.editorGroupService.hasRestorableState) { + return []; // do not open any empty untitled file if we restored groups/editors from previous session } - return this.backupFileService.hasBackups().then(hasBackups => { + return this.workingCopyBackupService.hasBackups().then(hasBackups => { if (hasBackups) { return []; // do not open any empty untitled file if we have backups to restore } @@ -635,6 +632,8 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi get openedDefaultEditors() { return this._openedDefaultEditors; } private getInitialFilesToOpen(): { filesToOpenOrCreate?: IPath[], filesToDiff?: IPath[]; } | undefined { + + // Check for editors from `defaultLayout` options first const defaultLayout = this.environmentService.options?.defaultLayout; if (defaultLayout?.editors?.length && (defaultLayout.force || this.storageService.isNew(StorageScope.WORKSPACE))) { this._openedDefaultEditors = true; @@ -646,6 +645,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi }; } + // Then check for files to open, create or diff from main side const { filesToOpenOrCreate, filesToDiff } = this.environmentService.configuration; if (filesToOpenOrCreate || filesToDiff) { return { filesToOpenOrCreate, filesToDiff }; @@ -654,17 +654,40 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi return undefined; } - protected async restoreWorkbenchLayout(): Promise { - const restorePromises: Promise[] = []; + private whenReadyResolve: (() => void) | undefined; + protected readonly whenReady = new Promise(resolve => (this.whenReadyResolve = resolve)); + + private whenRestoredResolve: (() => void) | undefined; + readonly whenRestored = new Promise(resolve => (this.whenRestoredResolve = resolve)); + private restored = false; + + isRestored(): boolean { + return this.restored; + } + + protected restoreParts(): void { + + // distinguish long running restore operations that + // are required for the layout to be ready from those + // that are needed to signal restoring is done + const layoutReadyPromises: Promise[] = []; + const layoutRestoredPromises: Promise[] = []; // Restore editors - restorePromises.push((async () => { + layoutReadyPromises.push((async () => { mark('code/willRestoreEditors'); - // first ensure the editor part is restored - await this.editorGroupService.whenRestored; + // first ensure the editor part is ready + await this.editorGroupService.whenReady; // then see for editors to open as instructed + // it is important that we trigger this from + // the overall restore flow to reduce possible + // flicker on startup: we want any editor to + // open to get a chance to open first before + // signaling that layout is restored, but we do + // not need to await the editors from having + // fully loaded. let editors: IResourceEditorInputType[]; if (Array.isArray(this.state.editor.editorsToOpen)) { editors = this.state.editor.editorsToOpen; @@ -672,19 +695,32 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi editors = await this.state.editor.editorsToOpen; } + let openEditorsPromise: Promise | undefined = undefined; if (editors.length) { - await this.editorService.openEditors(editors); + openEditorsPromise = this.editorService.openEditors(editors); } - mark('code/didRestoreEditors'); + // do not block the overall layout ready flow from potentially + // slow editors to resolve on startup + layoutRestoredPromises.push( + Promise.all([ + openEditorsPromise, + this.editorGroupService.whenRestored + ]).finally(() => { + // the `code/didRestoreEditors` perf mark is specifically + // for when visible editors have resolved, so we only mark + // if when editor group service has restored. + mark('code/didRestoreEditors'); + }) + ); })()); - // Restore default views + // Restore default views (only when `IDefaultLayout` is provided) const restoreDefaultViewsPromise = (async () => { if (this.state.views.defaults?.length) { mark('code/willOpenDefaultViews'); - let locationsRestored: { id: string; order: number; }[] = []; + const locationsRestored: { id: string; order: number; }[] = []; const tryOpenView = (view: { id: string; order: number; }): boolean => { const location = this.viewDescriptorService.getViewLocationById(view.id); @@ -742,10 +778,10 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi mark('code/didOpenDefaultViews'); } })(); - restorePromises.push(restoreDefaultViewsPromise); + layoutReadyPromises.push(restoreDefaultViewsPromise); // Restore Sidebar - restorePromises.push((async () => { + layoutReadyPromises.push((async () => { // Restoring views could mean that sidebar already // restored, as such we need to test again @@ -765,7 +801,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi })()); // Restore Panel - restorePromises.push((async () => { + layoutReadyPromises.push((async () => { // Restoring views could mean that panel already // restored, as such we need to test again @@ -776,7 +812,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi mark('code/willRestorePanel'); - const panel = await this.panelService.openPanel(this.state.panel.panelToRestore!); + const panel = await this.panelService.openPanel(this.state.panel.panelToRestore); if (!panel) { await this.panelService.openPanel(Registry.as(PanelExtensions.Panels).getDefaultPanelId()); // fallback to default panel as needed } @@ -794,8 +830,16 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.centerEditorLayout(true, true); } - // Await restore to be done - await Promises.settled(restorePromises); + // Await for promises that we recorded to update + // our ready and restored states properly. + Promises.settled(layoutReadyPromises).finally(() => { + this.whenReadyResolve?.(); + + Promises.settled(layoutRestoredPromises).finally(() => { + this.restored = true; + this.whenRestoredResolve?.(); + }); + }); } private updatePanelPosition() { @@ -822,10 +866,6 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this._register(delegate.onDidChangeNotificationsVisibility(visible => this._onDidChangeNotificationsVisibility.fire(visible))); } - isRestored(): boolean { - return this.lifecycleService.phase >= LifecyclePhase.Restored; - } - hasFocus(part: Parts): boolean { const activeElement = document.activeElement; if (!activeElement) { @@ -976,22 +1016,13 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi editor.updateOptions({ lineNumbers }); }; - const editorControlSet = this.state.zenMode.editorWidgetSet; if (!lineNumbers) { // Reset line numbers on all editors visible and non-visible - for (const editor of editorControlSet) { - setEditorLineNumbers(editor); + for (const editorControl of this.editorService.visibleTextEditorControls) { + setEditorLineNumbers(editorControl); } - editorControlSet.clear(); } else { for (const editorControl of this.editorService.visibleTextEditorControls) { - if (!editorControlSet.has(editorControl)) { - editorControlSet.add(editorControl); - this.state.zenMode.transitionDisposables.add(editorControl.onDidDispose(() => { - editorControlSet.delete(editorControl); - })); - } - setEditorLineNumbers(editorControl); } } @@ -1044,9 +1075,9 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi if (config.silentNotifications) { this.notificationService.setFilter(NotificationsFilter.ERROR); } - this.state.zenMode.transitionDisposables.add(this.configurationService.onDidChangeConfiguration(c => { + this.state.zenMode.transitionDisposables.add(this.configurationService.onDidChangeConfiguration(e => { const silentNotificationsKey = 'zenMode.silentNotifications'; - if (c.affectsConfiguration(silentNotificationsKey)) { + if (e.affectsConfiguration(silentNotificationsKey)) { const filter = this.configurationService.getValue(silentNotificationsKey) ? NotificationsFilter.ERROR : NotificationsFilter.OFF; this.notificationService.setFilter(filter); } @@ -1780,7 +1811,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi return result; } - dispose(): void { + override dispose(): void { super.dispose(); this.disposed = true; diff --git a/src/vs/workbench/browser/media/part.css b/src/vs/workbench/browser/media/part.css index caff1977..921f3a26 100644 --- a/src/vs/workbench/browser/media/part.css +++ b/src/vs/workbench/browser/media/part.css @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ .monaco-workbench .part { - box-sizing: border-box; + box-sizing: border-box; overflow: hidden; } @@ -29,7 +29,7 @@ .monaco-workbench .part > .title { height: 35px; display: flex; - box-sizing: border-box; + box-sizing: border-box; overflow: hidden; } @@ -73,9 +73,6 @@ .monaco-workbench .part > .title > .title-actions .action-label { display: block; - height: 35px; - line-height: 35px; - min-width: 28px; background-size: 16px; background-position: center center; background-repeat: no-repeat; @@ -103,6 +100,10 @@ } .monaco-workbench .part > .content > .monaco-progress-container .progress-bit, -.monaco-workbench .part.editor > .content .monaco-progress-container .progress-bit { +.monaco-workbench + .part.editor + > .content + .monaco-progress-container + .progress-bit { height: 2px; } diff --git a/src/vs/workbench/browser/panecomposite.ts b/src/vs/workbench/browser/panecomposite.ts index 38e33e2b..60ae591c 100644 --- a/src/vs/workbench/browser/panecomposite.ts +++ b/src/vs/workbench/browser/panecomposite.ts @@ -36,13 +36,13 @@ export abstract class PaneComposite extends Composite implements IPaneComposite super(id, telemetryService, themeService, storageService); } - create(parent: HTMLElement): void { + override create(parent: HTMLElement): void { this.viewPaneContainer = this._register(this.createViewPaneContainer(parent)); this._register(this.viewPaneContainer.onTitleAreaUpdate(() => this.updateTitleArea())); this.viewPaneContainer.create(parent); } - setVisible(visible: boolean): void { + override setVisible(visible: boolean): void { super.setVisible(visible); this.viewPaneContainer?.setVisible(visible); } @@ -63,15 +63,15 @@ export abstract class PaneComposite extends Composite implements IPaneComposite return this.viewPaneContainer; } - getActionsContext(): unknown { + override getActionsContext(): unknown { return this.getViewPaneContainer()?.getActionsContext(); } - getContextMenuActions(): ReadonlyArray { + override getContextMenuActions(): readonly IAction[] { return this.viewPaneContainer?.menuActions?.getContextMenuActions() ?? []; } - getActions(): ReadonlyArray { + override getActions(): readonly IAction[] { const result = []; if (this.viewPaneContainer?.menuActions) { result.push(...this.viewPaneContainer.menuActions.getPrimaryActions()); @@ -82,7 +82,7 @@ export abstract class PaneComposite extends Composite implements IPaneComposite return result; } - getSecondaryActions(): ReadonlyArray { + override getSecondaryActions(): readonly IAction[] { if (!this.viewPaneContainer?.menuActions) { return []; } @@ -116,19 +116,19 @@ export abstract class PaneComposite extends Composite implements IPaneComposite return menuActions.length ? menuActions : viewPaneActions; } - getActionViewItem(action: IAction): IActionViewItem | undefined { + override getActionViewItem(action: IAction): IActionViewItem | undefined { return this.viewPaneContainer?.getActionViewItem(action); } - getTitle(): string { + override getTitle(): string { return this.viewPaneContainer?.getTitle() ?? ''; } - saveState(): void { + override saveState(): void { super.saveState(); } - focus(): void { + override focus(): void { this.viewPaneContainer?.focus(); } diff --git a/src/vs/workbench/browser/panel.ts b/src/vs/workbench/browser/panel.ts index ab678781..1364f49a 100644 --- a/src/vs/workbench/browser/panel.ts +++ b/src/vs/workbench/browser/panel.ts @@ -37,19 +37,19 @@ export abstract class Panel extends PaneComposite implements IPanel { this._register(this.panelActions.onDidChange(() => this.updateTitleArea())); } - getActions(): ReadonlyArray { + override getActions(): readonly IAction[] { return [...super.getActions(), ...this.panelActions.getPrimaryActions()]; } - getSecondaryActions(): ReadonlyArray { + override getSecondaryActions(): readonly IAction[] { return this.mergeSecondaryActions(super.getSecondaryActions(), this.panelActions.getSecondaryActions()); } - getContextMenuActions(): ReadonlyArray { + override getContextMenuActions(): readonly IAction[] { return this.mergeSecondaryActions(super.getContextMenuActions(), this.panelActions.getContextMenuActions()); } - private mergeSecondaryActions(actions: ReadonlyArray, panelActions: IAction[]): ReadonlyArray { + private mergeSecondaryActions(actions: readonly IAction[], panelActions: IAction[]): readonly IAction[] { if (panelActions.length && actions.length) { return [ ...actions, diff --git a/src/vs/workbench/browser/part.ts b/src/vs/workbench/browser/part.ts index 79e16356..fd89c569 100644 --- a/src/vs/workbench/browser/part.ts +++ b/src/vs/workbench/browser/part.ts @@ -52,7 +52,7 @@ export abstract class Part extends Component implements ISerializableView { layoutService.registerPart(this); } - protected onThemeChange(theme: IColorTheme): void { + protected override onThemeChange(theme: IColorTheme): void { // only call if our create() method has been called if (this.parent) { @@ -60,7 +60,7 @@ export abstract class Part extends Component implements ISerializableView { } } - updateStyles(): void { + override updateStyles(): void { super.updateStyles(); } diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts b/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts index ceaf2150..103c6957 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts @@ -16,7 +16,7 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { activeContrastBorder, focusBorder } from 'vs/platform/theme/common/colorRegistry'; import { IColorTheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; -import { ActivityAction, ActivityActionViewItem, ICompositeBar, ICompositeBarColors, ToggleCompositePinnedAction } from 'vs/workbench/browser/parts/compositeBarActions'; +import { ActivityAction, ActivityActionViewItem, IActivityHoverOptions, ICompositeBar, ICompositeBarColors, ToggleCompositePinnedAction } from 'vs/workbench/browser/parts/compositeBarActions'; import { CATEGORIES } from 'vs/workbench/common/actions'; import { IActivity } from 'vs/workbench/common/activity'; import { ACTIVITY_BAR_FOREGROUND, ACTIVITY_BAR_ACTIVE_BORDER, ACTIVITY_BAR_ACTIVE_FOCUS_BORDER, ACTIVITY_BAR_ACTIVE_BACKGROUND } from 'vs/workbench/common/theme'; @@ -35,6 +35,8 @@ import { AnchorAlignment, AnchorAxisAlignment } from 'vs/base/browser/ui/context import { getTitleBarStyle } from 'vs/platform/windows/common/windows'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; +import { IHoverService } from 'vs/workbench/services/hover/browser/hover'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; export class ViewContainerActivityAction extends ActivityAction { @@ -56,7 +58,7 @@ export class ViewContainerActivityAction extends ActivityAction { this.activity = activity; } - async run(event: unknown): Promise { + override async run(event: unknown): Promise { if (event instanceof MouseEvent && event.button === 2) { return; // do not run on right click } @@ -111,17 +113,20 @@ class MenuActivityActionViewItem extends ActivityActionViewItem { action: ActivityAction, private contextMenuActionsProvider: () => IAction[], colors: (theme: IColorTheme) => ICompositeBarColors, + hoverOptions: IActivityHoverOptions, @IThemeService themeService: IThemeService, + @IHoverService hoverService: IHoverService, @IMenuService protected readonly menuService: IMenuService, @IContextMenuService protected readonly contextMenuService: IContextMenuService, @IContextKeyService protected readonly contextKeyService: IContextKeyService, - @IConfigurationService protected readonly configurationService: IConfigurationService, - @IWorkbenchEnvironmentService protected readonly environmentService: IWorkbenchEnvironmentService + @IConfigurationService configurationService: IConfigurationService, + @IWorkbenchEnvironmentService protected readonly environmentService: IWorkbenchEnvironmentService, + @IKeybindingService keybindingService: IKeybindingService, ) { - super(action, { draggable: false, colors, icon: true, hasPopup: true }, themeService); + super(action, { draggable: false, colors, icon: true, hasPopup: true, hoverOptions }, themeService, hoverService, configurationService, keybindingService); } - render(container: HTMLElement): void { + override render(container: HTMLElement): void { super.render(container); // Context menus are triggered on mouse down so that an item can be picked @@ -190,7 +195,9 @@ export class AccountsActivityActionViewItem extends MenuActivityActionViewItem { action: ActivityAction, contextMenuActionsProvider: () => IAction[], colors: (theme: IColorTheme) => ICompositeBarColors, + activityHoverOptions: IActivityHoverOptions, @IThemeService themeService: IThemeService, + @IHoverService hoverService: IHoverService, @IContextMenuService contextMenuService: IContextMenuService, @IMenuService menuService: IMenuService, @IContextKeyService contextKeyService: IContextKeyService, @@ -198,12 +205,13 @@ export class AccountsActivityActionViewItem extends MenuActivityActionViewItem { @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, @IProductService private readonly productService: IProductService, @IConfigurationService configurationService: IConfigurationService, - @IStorageService private readonly storageService: IStorageService + @IStorageService private readonly storageService: IStorageService, + @IKeybindingService keybindingService: IKeybindingService, ) { - super(MenuId.AccountsContext, action, contextMenuActionsProvider, colors, themeService, menuService, contextMenuService, contextKeyService, configurationService, environmentService); + super(MenuId.AccountsContext, action, contextMenuActionsProvider, colors, activityHoverOptions, themeService, hoverService, menuService, contextMenuService, contextKeyService, configurationService, environmentService, keybindingService); } - protected async resolveMainMenuActions(accountsMenu: IMenu, disposables: DisposableStore): Promise { + protected override async resolveMainMenuActions(accountsMenu: IMenu, disposables: DisposableStore): Promise { await super.resolveMainMenuActions(accountsMenu, disposables); const otherCommands = accountsMenu.getActions(); @@ -279,7 +287,7 @@ export class AccountsActivityActionViewItem extends MenuActivityActionViewItem { return menus; } - protected async resolveContextMenuActions(disposables: DisposableStore): Promise { + protected override async resolveContextMenuActions(disposables: DisposableStore): Promise { const actions = await super.resolveContextMenuActions(disposables); actions.unshift(...[ @@ -297,14 +305,17 @@ export class GlobalActivityActionViewItem extends MenuActivityActionViewItem { action: ActivityAction, contextMenuActionsProvider: () => IAction[], colors: (theme: IColorTheme) => ICompositeBarColors, + activityHoverOptions: IActivityHoverOptions, @IThemeService themeService: IThemeService, + @IHoverService hoverService: IHoverService, @IMenuService menuService: IMenuService, @IContextMenuService contextMenuService: IContextMenuService, @IContextKeyService contextKeyService: IContextKeyService, @IConfigurationService configurationService: IConfigurationService, - @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService + @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, + @IKeybindingService keybindingService: IKeybindingService, ) { - super(MenuId.GlobalActivity, action, contextMenuActionsProvider, colors, themeService, menuService, contextMenuService, contextKeyService, configurationService, environmentService); + super(MenuId.GlobalActivity, action, contextMenuActionsProvider, colors, activityHoverOptions, themeService, hoverService, menuService, contextMenuService, contextKeyService, configurationService, environmentService, keybindingService); } } diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index f9ee130d..21c6e8d0 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -10,7 +10,7 @@ import { GLOBAL_ACTIVITY_ID, IActivity, ACCOUNTS_ACTIVITY_ID } from 'vs/workbenc import { Part } from 'vs/workbench/browser/part'; import { GlobalActivityActionViewItem, ViewContainerActivityAction, PlaceHolderToggleCompositePinnedAction, PlaceHolderViewContainerActivityAction, AccountsActivityActionViewItem } from 'vs/workbench/browser/parts/activitybar/activitybarActions'; import { IBadge, NumberBadge } from 'vs/workbench/services/activity/common/activity'; -import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; +import { IWorkbenchLayoutService, Parts, Position } from 'vs/workbench/services/layout/browser/layoutService'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IDisposable, toDisposable, DisposableStore, Disposable } from 'vs/base/common/lifecycle'; import { ToggleActivityBarVisibilityAction, ToggleSidebarPositionAction } from 'vs/workbench/browser/actions/layoutActions'; @@ -22,7 +22,7 @@ import { Dimension, createCSSRule, asCSSUrl, addDisposableListener, EventType } import { IStorageService, StorageScope, IStorageValueChangeEvent, StorageTarget } from 'vs/platform/storage/common/storage'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { URI, UriComponents } from 'vs/base/common/uri'; -import { ToggleCompositePinnedAction, ICompositeBarColors, ActivityAction, ICompositeActivity } from 'vs/workbench/browser/parts/compositeBarActions'; +import { ToggleCompositePinnedAction, ICompositeBarColors, ActivityAction, ICompositeActivity, IActivityHoverOptions } from 'vs/workbench/browser/parts/compositeBarActions'; import { IViewDescriptorService, ViewContainer, IViewContainerModel, ViewContainerLocation, IViewsService, getEnabledViewContainerContextKey } from 'vs/workbench/common/views'; import { IContextKeyService, ContextKeyExpr, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { assertIsDefined, isString } from 'vs/base/common/types'; @@ -43,6 +43,7 @@ import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; import { CATEGORIES } from 'vs/workbench/common/actions'; import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; import { StringSHA1 } from 'vs/base/common/hash'; +import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget'; interface IPlaceholderViewContainer { readonly id: string; @@ -156,6 +157,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { return this._register(this.instantiationService.createInstance(CompositeBar, cachedItems, { icon: true, orientation: ActionsOrientation.VERTICAL, + activityHoverOptions: this.getActivityHoverOptions(), preventLoopNavigation: true, openComposite: compositeId => this.viewsService.openViewContainer(compositeId, true), getActivityAction: compositeId => this.getCompositeActions(compositeId).activityAction, @@ -218,6 +220,13 @@ export class ActivitybarPart extends Part implements IActivityBarService { })); } + private getActivityHoverOptions(): IActivityHoverOptions { + return { + position: () => this.layoutService.getSideBarPosition() === Position.LEFT ? HoverPosition.RIGHT : HoverPosition.LEFT, + delay: () => 0 + }; + } + private getContextMenuActionsForComposite(compositeId: string): IAction[] { const actions: IAction[] = []; @@ -269,7 +278,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { })); } - private onDidChangeViewContainers(added: ReadonlyArray<{ container: ViewContainer, location: ViewContainerLocation; }>, removed: ReadonlyArray<{ container: ViewContainer, location: ViewContainerLocation; }>) { + private onDidChangeViewContainers(added: readonly { container: ViewContainer, location: ViewContainerLocation; }[], removed: readonly { container: ViewContainer, location: ViewContainerLocation; }[]) { removed.filter(({ location }) => location === ViewContainerLocation.Sidebar).forEach(({ container }) => this.onDidDeregisterViewContainer(container)); this.onDidRegisterViewContainers(added.filter(({ location }) => location === ViewContainerLocation.Sidebar).map(({ container }) => container)); } @@ -447,7 +456,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { this.registerKeyboardNavigationListeners(); } - createContentArea(parent: HTMLElement): HTMLElement { + override createContentArea(parent: HTMLElement): HTMLElement { this.element = parent; this.content = document.createElement('div'); @@ -522,11 +531,11 @@ export class ActivitybarPart extends Part implements IActivityBarService { this.globalActivityActionBar = this._register(new ActionBar(container, { actionViewItemProvider: action => { if (action.id === 'workbench.actions.manage') { - return this.instantiationService.createInstance(GlobalActivityActionViewItem, action as ActivityAction, () => this.compositeBar.getContextMenuActions(), (theme: IColorTheme) => this.getActivitybarItemColors(theme)); + return this.instantiationService.createInstance(GlobalActivityActionViewItem, action as ActivityAction, () => this.compositeBar.getContextMenuActions(), (theme: IColorTheme) => this.getActivitybarItemColors(theme), this.getActivityHoverOptions()); } if (action.id === 'workbench.actions.accounts') { - return this.instantiationService.createInstance(AccountsActivityActionViewItem, action as ActivityAction, () => this.compositeBar.getContextMenuActions(), (theme: IColorTheme) => this.getActivitybarItemColors(theme)); + return this.instantiationService.createInstance(AccountsActivityActionViewItem, action as ActivityAction, () => this.compositeBar.getContextMenuActions(), (theme: IColorTheme) => this.getActivitybarItemColors(theme), this.getActivityHoverOptions()); } throw new Error(`No view item for action '${action.id}'`); @@ -598,7 +607,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { return compositeActions; } - private onDidRegisterViewContainers(viewContainers: ReadonlyArray): void { + private onDidRegisterViewContainers(viewContainers: readonly ViewContainer[]): void { for (const viewContainer of viewContainers) { this.addComposite(viewContainer); @@ -765,7 +774,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { this.compositeBar.focus(); } - updateStyles(): void { + override updateStyles(): void { super.updateStyles(); const container = assertIsDefined(this.getContainer()); @@ -790,7 +799,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { }; } - layout(width: number, height: number): void { + override layout(width: number, height: number): void { if (!this.layoutService.isVisible(Parts.ACTIVITYBAR_PART)) { return; } @@ -814,7 +823,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { return viewContainer && this.viewDescriptorService.getViewContainerLocation(viewContainer) === this.location ? viewContainer : undefined; } - private getViewContainers(): ReadonlyArray { + private getViewContainers(): readonly ViewContainer[] { return this.viewDescriptorService.getViewContainersByLocation(this.location); } diff --git a/src/vs/workbench/browser/parts/activitybar/media/activityaction.css b/src/vs/workbench/browser/parts/activitybar/media/activityaction.css index c633e22a..f3bf5589 100644 --- a/src/vs/workbench/browser/parts/activitybar/media/activityaction.css +++ b/src/vs/workbench/browser/parts/activitybar/media/activityaction.css @@ -57,6 +57,7 @@ z-index: 1; display: flex; overflow: hidden; + width: 48px; height: 48px; margin-right: 0; box-sizing: border-box; @@ -154,7 +155,8 @@ .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .badge .codicon.badge-content { font-size: 12px; font-weight: unset; - padding: 0 2px; + padding: 0; + justify-content: center; } .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .badge .codicon.badge-content::before { diff --git a/src/vs/workbench/browser/parts/compositeBar.ts b/src/vs/workbench/browser/parts/compositeBar.ts index 78d88f18..bb7970f5 100644 --- a/src/vs/workbench/browser/parts/compositeBar.ts +++ b/src/vs/workbench/browser/parts/compositeBar.ts @@ -10,7 +10,7 @@ import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { IBadge } from 'vs/workbench/services/activity/common/activity'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ActionBar, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; -import { CompositeActionViewItem, CompositeOverflowActivityAction, ICompositeActivity, CompositeOverflowActivityActionViewItem, ActivityAction, ICompositeBar, ICompositeBarColors } from 'vs/workbench/browser/parts/compositeBarActions'; +import { CompositeActionViewItem, CompositeOverflowActivityAction, ICompositeActivity, CompositeOverflowActivityActionViewItem, ActivityAction, ICompositeBar, ICompositeBarColors, IActivityHoverOptions } from 'vs/workbench/browser/parts/compositeBarActions'; import { Dimension, $, addDisposableListener, EventType, EventHelper, isAncestor } from 'vs/base/browser/dom'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; @@ -145,6 +145,7 @@ export interface ICompositeBarOptions { readonly compositeSize: number; readonly overflowActionSize: number; readonly dndHandler: ICompositeDragAndDrop; + readonly activityHoverOptions: IActivityHoverOptions; readonly preventLoopNavigation?: boolean; getActivityAction: (compositeId: string) => ActivityAction; @@ -213,11 +214,12 @@ export class CompositeBar extends Widget implements ICompositeBar { } const item = this.model.findItem(action.id); return item && this.instantiationService.createInstance( - CompositeActionViewItem, action as ActivityAction, item.pinnedAction, + CompositeActionViewItem, + { draggable: true, colors: this.options.colors, icon: this.options.icon, hoverOptions: this.options.activityHoverOptions }, + action as ActivityAction, + item.pinnedAction, compositeId => this.options.getContextMenuActionsForComposite(compositeId), () => this.getContextMenuActions(), - this.options.colors, - this.options.icon, this.options.dndHandler, this ); @@ -595,7 +597,8 @@ export class CompositeBar extends Widget implements ICompositeBar { return item?.activity[0]?.badge; }, this.options.getOnCompositeClickAction, - this.options.colors + this.options.colors, + this.options.activityHoverOptions ); compositeSwitcherBar.push(this.compositeOverflowAction, { label: false, icon: true }); diff --git a/src/vs/workbench/browser/parts/compositeBarActions.ts b/src/vs/workbench/browser/parts/compositeBarActions.ts index 9920e317..485670a0 100644 --- a/src/vs/workbench/browser/parts/compositeBarActions.ts +++ b/src/vs/workbench/browser/parts/compositeBarActions.ts @@ -7,7 +7,7 @@ import { localize } from 'vs/nls'; import { Action, IAction, Separator } from 'vs/base/common/actions'; import { $, addDisposableListener, append, clearNode, EventHelper, EventType, getDomNodePagePosition, hide, show } from 'vs/base/browser/dom'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { dispose, toDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; +import { dispose, toDisposable, MutableDisposable, IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IThemeService, IColorTheme, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { TextBadge, NumberBadge, IBadge, IconBadge, ProgressBadge } from 'vs/workbench/services/activity/common/activity'; @@ -21,6 +21,11 @@ import { CompositeDragAndDropObserver, ICompositeDragAndDrop, Before2D, toggleDr import { Color } from 'vs/base/common/color'; import { IBaseActionViewItemOptions, BaseActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { Codicon } from 'vs/base/common/codicons'; +import { IHoverService } from 'vs/workbench/services/hover/browser/hover'; +import { domEvent } from 'vs/base/browser/event'; +import { RunOnceScheduler } from 'vs/base/common/async'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget'; export interface ICompositeActivity { badge: IBadge; @@ -101,7 +106,7 @@ export class ActivityAction extends Action { this._onDidChangeBadge.fire(this); } - dispose(): void { + override dispose(): void { this._onDidChangeActivity.dispose(); this._onDidChangeBadge.dispose(); @@ -122,9 +127,15 @@ export interface ICompositeBarColors { dragAndDropBorder?: Color; } +export interface IActivityHoverOptions { + position: () => HoverPosition; + delay: () => number; +} + export interface IActivityActionViewItemOptions extends IBaseActionViewItemOptions { icon?: boolean; colors: (theme: IColorTheme) => ICompositeBarColors; + hoverOptions: IActivityHoverOptions; hasPopup?: boolean; } @@ -132,22 +143,35 @@ export class ActivityActionViewItem extends BaseActionViewItem { protected container!: HTMLElement; protected label!: HTMLElement; protected badge!: HTMLElement; - protected options!: IActivityActionViewItemOptions; + protected override readonly options: IActivityActionViewItemOptions; private badgeContent: HTMLElement | undefined; private readonly badgeDisposable = this._register(new MutableDisposable()); private mouseUpTimeout: any; + private keybindingLabel: string | undefined | null; + + private readonly hoverDisposables = this._register(new DisposableStore()); + private readonly hover = this._register(new MutableDisposable()); + private readonly showHoverScheduler = new RunOnceScheduler(() => this.showHover(), 0); constructor( action: ActivityAction, options: IActivityActionViewItemOptions, - @IThemeService protected readonly themeService: IThemeService + @IThemeService protected readonly themeService: IThemeService, + @IHoverService private readonly hoverService: IHoverService, + @IConfigurationService protected readonly configurationService: IConfigurationService, + @IKeybindingService protected readonly keybindingService: IKeybindingService, ) { super(null, action, options); + this.options = options; + this._register(this.themeService.onDidColorThemeChange(this.onThemeChange, this)); this._register(action.onDidChangeActivity(this.updateActivity, this)); + this._register(Event.filter(keybindingService.onDidUpdateKeybindings, () => this.keybindingLabel !== this.computeKeybindingLabel())(() => this.updateTitle())); + this._register(Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('workbench.experimental.useCustomHover'))(() => this.updateHover())); this._register(action.onDidChangeBadge(this.updateBadge, this)); + this._register(toDisposable(() => this.showHoverScheduler.cancel())); } protected get activity(): IActivity { @@ -195,10 +219,13 @@ export class ActivityActionViewItem extends BaseActionViewItem { } } - render(container: HTMLElement): void { + override render(container: HTMLElement): void { super.render(container); this.container = container; + if (this.options.icon) { + this.container.classList.add('icon'); + } if (this.options.hasPopup) { this.container.setAttribute('role', 'button'); @@ -239,6 +266,7 @@ export class ActivityActionViewItem extends BaseActionViewItem { this.updateActivity(); this.updateStyles(); + this.updateHover(); } private onThemeChange(theme: IColorTheme): void { @@ -247,7 +275,7 @@ export class ActivityActionViewItem extends BaseActionViewItem { protected updateActivity(): void { this.updateLabel(); - this.updateTitle(this.activity.name); + this.updateTitle(); this.updateBadge(); this.updateStyles(); } @@ -311,22 +339,10 @@ export class ActivityActionViewItem extends BaseActionViewItem { } } - // Title - let title: string; - if (badge?.getDescription()) { - if (this.activity.name) { - title = localize('badgeTitle', "{0} - {1}", this.activity.name, badge.getDescription()); - } else { - title = badge.getDescription(); - } - } else { - title = this.activity.name; - } - - this.updateTitle(title); + this.updateTitle(); } - protected updateLabel(): void { + protected override updateLabel(): void { this.label.className = 'action-label'; if (this.activity.cssClass) { @@ -343,16 +359,77 @@ export class ActivityActionViewItem extends BaseActionViewItem { } } - private updateTitle(title: string): void { + private updateTitle(): void { + // Title + const title = this.computeTitle(); [this.label, this.badge, this.container].forEach(element => { if (element) { element.setAttribute('aria-label', title); - element.title = title; + if (this.useCustomHover) { + element.setAttribute('title', ''); + element.removeAttribute('title'); + } else { + element.setAttribute('title', title); + } } }); } - dispose(): void { + private computeTitle(): string { + this.keybindingLabel = this.computeKeybindingLabel(); + let title = this.keybindingLabel ? localize('titleKeybinding', "{0} ({1})", this.activity.name, this.keybindingLabel) : this.activity.name; + const badge = (this.getAction() as ActivityAction).getBadge(); + if (badge?.getDescription()) { + title = localize('badgeTitle', "{0} - {1}", title, badge.getDescription()); + } + return title; + } + + private computeKeybindingLabel(): string | undefined | null { + const keybinding = this.activity.keybindingId ? this.keybindingService.lookupKeybinding(this.activity.keybindingId) : null; + return keybinding?.getLabel(); + } + + private updateHover(): void { + this.hoverDisposables.clear(); + + this.updateTitle(); + if (this.useCustomHover) { + this.hoverDisposables.add(domEvent(this.container, EventType.MOUSE_OVER, true)(() => { + if (!this.showHoverScheduler.isScheduled()) { + this.showHoverScheduler.schedule(this.options.hoverOptions!.delay() || 150); + } + })); + this.hoverDisposables.add(domEvent(this.container, EventType.MOUSE_LEAVE, true)(() => { + this.hover.value = undefined; + this.showHoverScheduler.cancel(); + })); + this.hoverDisposables.add(toDisposable(() => { + this.hover.value = undefined; + this.showHoverScheduler.cancel(); + })); + } + } + + private showHover(): void { + if (this.hover.value) { + return; + } + const hoverPosition = this.options.hoverOptions!.position(); + this.hover.value = this.hoverService.showHover({ + target: this.container, + hoverPosition, + text: this.computeTitle(), + showPointer: true, + compact: true + }); + } + + private get useCustomHover(): boolean { + return !!this.configurationService.getValue('workbench.experimental.useCustomHover'); + } + + override dispose(): void { super.dispose(); if (this.mouseUpTimeout) { @@ -375,7 +452,7 @@ export class CompositeOverflowActivityAction extends ActivityAction { }); } - async run(): Promise { + override async run(): Promise { this.showMenu(); } } @@ -390,10 +467,14 @@ export class CompositeOverflowActivityActionViewItem extends ActivityActionViewI private getBadge: (compositeId: string) => IBadge, private getCompositeOpenAction: (compositeId: string) => IAction, colors: (theme: IColorTheme) => ICompositeBarColors, + hoverOptions: IActivityHoverOptions, @IContextMenuService private readonly contextMenuService: IContextMenuService, - @IThemeService themeService: IThemeService + @IThemeService themeService: IThemeService, + @IHoverService hoverService: IHoverService, + @IConfigurationService configurationService: IConfigurationService, + @IKeybindingService keybindingService: IKeybindingService, ) { - super(action, { icon: true, colors, hasPopup: true }, themeService); + super(action, { icon: true, colors, hasPopup: true, hoverOptions }, themeService, hoverService, configurationService, keybindingService); } showMenu(): void { @@ -434,7 +515,7 @@ export class CompositeOverflowActivityActionViewItem extends ActivityActionViewI }); } - dispose(): void { + override dispose(): void { super.dispose(); if (this.actions) { @@ -451,7 +532,7 @@ class ManageExtensionAction extends Action { super('activitybar.manage.extension', localize('manageExtension', "Manage Extension")); } - run(id: string): Promise { + override run(id: string): Promise { return this.commandService.executeCommand('_extensions.manage', id); } } @@ -460,64 +541,29 @@ export class CompositeActionViewItem extends ActivityActionViewItem { private static manageExtensionAction: ManageExtensionAction; - private compositeActivity: IActivity | undefined; - constructor( - private compositeActivityAction: ActivityAction, - private toggleCompositePinnedAction: IAction, - private compositeContextMenuActionsProvider: (compositeId: string) => IAction[], - private contextMenuActionsProvider: () => IAction[], - colors: (theme: IColorTheme) => ICompositeBarColors, - icon: boolean, - private dndHandler: ICompositeDragAndDrop, - private compositeBar: ICompositeBar, + options: IActivityActionViewItemOptions, + private readonly compositeActivityAction: ActivityAction, + private readonly toggleCompositePinnedAction: IAction, + private readonly compositeContextMenuActionsProvider: (compositeId: string) => IAction[], + private readonly contextMenuActionsProvider: () => IAction[], + private readonly dndHandler: ICompositeDragAndDrop, + private readonly compositeBar: ICompositeBar, @IContextMenuService private readonly contextMenuService: IContextMenuService, - @IKeybindingService private readonly keybindingService: IKeybindingService, + @IKeybindingService keybindingService: IKeybindingService, @IInstantiationService instantiationService: IInstantiationService, - @IThemeService themeService: IThemeService + @IThemeService themeService: IThemeService, + @IHoverService hoverService: IHoverService, + @IConfigurationService configurationService: IConfigurationService, ) { - super(compositeActivityAction, { draggable: true, colors, icon }, themeService); + super(compositeActivityAction, options, themeService, hoverService, configurationService, keybindingService); if (!CompositeActionViewItem.manageExtensionAction) { CompositeActionViewItem.manageExtensionAction = instantiationService.createInstance(ManageExtensionAction); } - - this._register(compositeActivityAction.onDidChangeActivity(() => { - this.compositeActivity = undefined; - this.updateActivity(); - }, this)); - this._register(Event.any( - compositeActivityAction.onDidChangeActivity, - Event.filter(keybindingService.onDidUpdateKeybindings, () => this.compositeActivity!.name !== this.getActivtyName()) - )(() => { - if (this.compositeActivity && this.compositeActivity.name !== this.getActivtyName()) { - this.compositeActivity = undefined; - this.updateActivity(); - } - })); } - protected get activity(): IActivity { - if (!this.compositeActivity) { - this.compositeActivity = { - ...this.compositeActivityAction.activity, - ... { name: this.getActivtyName() } - }; - } - return this.compositeActivity; - } - - private getActivtyName(skipKeybinding = false): string { - let name = this.compositeActivityAction.activity.name; - if (skipKeybinding) { - return name; - } - - const keybinding = this.compositeActivityAction.activity.keybindingId ? this.keybindingService.lookupKeybinding(this.compositeActivityAction.activity.keybindingId) : null; - return keybinding ? localize('titleKeybinding', "{0} ({1})", name, keybinding.getLabel()) : name; - } - - render(container: HTMLElement): void { + override render(container: HTMLElement): void { super.render(container); this.updateChecked(); @@ -627,10 +673,10 @@ export class CompositeActionViewItem extends ActivityActionViewItem { const isPinned = this.compositeBar.isPinned(this.activity.id); if (isPinned) { - this.toggleCompositePinnedAction.label = localize('hide', "Hide '{0}'", this.getActivtyName(true)); + this.toggleCompositePinnedAction.label = localize('hide', "Hide '{0}'", this.activity.name); this.toggleCompositePinnedAction.checked = false; } else { - this.toggleCompositePinnedAction.label = localize('keep', "Keep '{0}'", this.getActivtyName(true)); + this.toggleCompositePinnedAction.label = localize('keep', "Keep '{0}'", this.activity.name); } const otherActions = this.contextMenuActionsProvider(); @@ -652,7 +698,7 @@ export class CompositeActionViewItem extends ActivityActionViewItem { }); } - protected updateChecked(): void { + protected override updateChecked(): void { if (this.getAction().checked) { this.container.classList.add('checked'); this.container.setAttribute('aria-label', this.container.title); @@ -667,7 +713,7 @@ export class CompositeActionViewItem extends ActivityActionViewItem { this.updateStyles(); } - protected updateEnabled(): void { + protected override updateEnabled(): void { if (!this.element) { return; } @@ -679,7 +725,7 @@ export class CompositeActionViewItem extends ActivityActionViewItem { } } - dispose(): void { + override dispose(): void { super.dispose(); this.label.remove(); } @@ -696,7 +742,7 @@ export class ToggleCompositePinnedAction extends Action { this.checked = !!this.activity && this.compositeBar.isPinned(this.activity.id); } - async run(context: string): Promise { + override async run(context: string): Promise { const id = this.activity ? this.activity.id : context; if (this.compositeBar.isPinned(id)) { diff --git a/src/vs/workbench/browser/parts/compositePart.ts b/src/vs/workbench/browser/parts/compositePart.ts index 1ead62e5..5906a9ea 100644 --- a/src/vs/workbench/browser/parts/compositePart.ts +++ b/src/vs/workbench/browser/parts/compositePart.ts @@ -75,7 +75,7 @@ export abstract class CompositePart extends Part { protected readonly storageService: IStorageService, private readonly telemetryService: ITelemetryService, protected readonly contextMenuService: IContextMenuService, - protected readonly layoutService: IWorkbenchLayoutService, + layoutService: IWorkbenchLayoutService, protected readonly keybindingService: IKeybindingService, protected readonly instantiationService: IInstantiationService, themeService: IThemeService, @@ -263,9 +263,7 @@ export abstract class CompositePart extends Part { } // Log in telemetry - if (this.telemetryService) { - this.telemetryService.publicLog2('workbenchActionExecuted', { id: e.action.id, from: this.nameForTelemetry }); - } + this.telemetryService.publicLog2('workbenchActionExecuted', { id: e.action.id, from: this.nameForTelemetry }); }); // Indicate to composite that it is now visible @@ -376,7 +374,7 @@ export abstract class CompositePart extends Part { return composite; } - createTitleArea(parent: HTMLElement): HTMLElement { + override createTitleArea(parent: HTMLElement): HTMLElement { // Title Area Container const titleArea = append(parent, $('.composite')); @@ -423,7 +421,7 @@ export abstract class CompositePart extends Part { }; } - updateStyles(): void { + override updateStyles(): void { super.updateStyles(); // Forward to title label @@ -451,7 +449,7 @@ export abstract class CompositePart extends Part { return null; } - createContentArea(parent: HTMLElement): HTMLElement { + override createContentArea(parent: HTMLElement): HTMLElement { const contentContainer = append(parent, $('.content')); this.progressBar = this._register(new ProgressBar(contentContainer)); @@ -471,7 +469,7 @@ export abstract class CompositePart extends Part { return AnchorAlignment.RIGHT; } - layout(width: number, height: number): void { + override layout(width: number, height: number): void { super.layout(width, height); // Layout contents @@ -500,7 +498,7 @@ export abstract class CompositePart extends Part { return true; } - dispose(): void { + override dispose(): void { this.mapCompositeToCompositeContainer.clear(); this.mapActionsBindingToComposite.clear(); diff --git a/src/vs/workbench/browser/parts/dialogs/dialog.web.contribution.ts b/src/vs/workbench/browser/parts/dialogs/dialog.web.contribution.ts index 836303db..719871a4 100644 --- a/src/vs/workbench/browser/parts/dialogs/dialog.web.contribution.ts +++ b/src/vs/workbench/browser/parts/dialogs/dialog.web.contribution.ts @@ -17,6 +17,7 @@ import { BrowserDialogHandler } from 'vs/workbench/browser/parts/dialogs/dialogH import { DialogService } from 'vs/workbench/services/dialogs/common/dialogService'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { Disposable } from 'vs/base/common/lifecycle'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; export class DialogHandlerContribution extends Disposable implements IWorkbenchContribution { private readonly model: IDialogsModel; @@ -30,12 +31,13 @@ export class DialogHandlerContribution extends Disposable implements IWorkbenchC @ILayoutService layoutService: ILayoutService, @IThemeService themeService: IThemeService, @IKeybindingService keybindingService: IKeybindingService, + @IInstantiationService instantiationService: IInstantiationService, @IProductService productService: IProductService, @IClipboardService clipboardService: IClipboardService ) { super(); - this.impl = new BrowserDialogHandler(logService, layoutService, themeService, keybindingService, productService, clipboardService); + this.impl = new BrowserDialogHandler(logService, layoutService, themeService, keybindingService, instantiationService, productService, clipboardService); this.model = (this.dialogService as DialogService).model; diff --git a/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts b/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts index babf68f0..84c51a94 100644 --- a/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts +++ b/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; -import { IDialogOptions, IConfirmation, IConfirmationResult, DialogType, IShowResult, IInputResult, ICheckbox, IInput, IDialogHandler } from 'vs/platform/dialogs/common/dialogs'; +import { IDialogOptions, IConfirmation, IConfirmationResult, DialogType, IShowResult, IInputResult, ICheckbox, IInput, IDialogHandler, ICustomDialogOptions } from 'vs/platform/dialogs/common/dialogs'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { ILogService } from 'vs/platform/log/common/log'; import Severity from 'vs/base/common/severity'; @@ -18,6 +18,8 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IProductService } from 'vs/platform/product/common/productService'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { fromNow } from 'vs/base/common/date'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { MarkdownRenderer } from 'vs/editor/browser/core/markdownRenderer'; export class BrowserDialogHandler implements IDialogHandler { @@ -30,14 +32,19 @@ export class BrowserDialogHandler implements IDialogHandler { 'editor.action.clipboardPasteAction' ]; + private readonly markdownRenderer: MarkdownRenderer; + constructor( @ILogService private readonly logService: ILogService, @ILayoutService private readonly layoutService: ILayoutService, @IThemeService private readonly themeService: IThemeService, @IKeybindingService private readonly keybindingService: IKeybindingService, + @IInstantiationService private readonly instantiationService: IInstantiationService, @IProductService private readonly productService: IProductService, @IClipboardService private readonly clipboardService: IClipboardService - ) { } + ) { + this.markdownRenderer = this.instantiationService.createInstance(MarkdownRenderer, {}); + } async confirm(confirmation: IConfirmation): Promise { this.logService.trace('DialogService#confirm', confirmation.message); @@ -67,7 +74,7 @@ export class BrowserDialogHandler implements IDialogHandler { async show(severity: Severity, message: string, buttons: string[], options?: IDialogOptions): Promise { this.logService.trace('DialogService#show', message); - const result = await this.doShow(this.getDialogType(severity), message, buttons, options?.detail, options?.cancelId, options?.checkbox); + const result = await this.doShow(this.getDialogType(severity), message, buttons, options?.detail, options?.cancelId, options?.checkbox, undefined, typeof options?.custom === 'object' ? options.custom : undefined); return { choice: result.button, @@ -75,8 +82,19 @@ export class BrowserDialogHandler implements IDialogHandler { }; } - private async doShow(type: 'none' | 'info' | 'error' | 'question' | 'warning' | 'pending' | undefined, message: string, buttons: string[], detail?: string, cancelId?: number, checkbox?: ICheckbox, inputs?: IInput[]): Promise { + private async doShow(type: 'none' | 'info' | 'error' | 'question' | 'warning' | 'pending' | undefined, message: string, buttons: string[], detail?: string, cancelId?: number, checkbox?: ICheckbox, inputs?: IInput[], customOptions?: ICustomDialogOptions): Promise { const dialogDisposables = new DisposableStore(); + + const renderBody = customOptions ? (parent: HTMLElement) => { + parent.classList.add(...(customOptions.classes || [])); + (customOptions.markdownDetails || []).forEach(markdownDetail => { + const result = this.markdownRenderer.render(markdownDetail.markdown); + parent.appendChild(result.element); + result.element.classList.add(...(markdownDetail.classes || [])); + dialogDisposables.add(result); + }); + } : undefined; + const dialog = new Dialog( this.layoutService.container, message, @@ -93,6 +111,10 @@ export class BrowserDialogHandler implements IDialogHandler { } } }, + renderBody, + icon: customOptions?.icon, + disableCloseAction: customOptions?.disableCloseAction, + buttonDetails: customOptions?.buttonDetails, checkboxLabel: checkbox?.label, checkboxChecked: checkbox?.checked, inputs diff --git a/src/vs/workbench/browser/parts/editor/binaryDiffEditor.ts b/src/vs/workbench/browser/parts/editor/binaryDiffEditor.ts index 1e3ebf38..c80075e6 100644 --- a/src/vs/workbench/browser/parts/editor/binaryDiffEditor.ts +++ b/src/vs/workbench/browser/parts/editor/binaryDiffEditor.ts @@ -17,7 +17,7 @@ import { IStorageService } from 'vs/platform/storage/common/storage'; */ export class BinaryResourceDiffEditor extends SideBySideEditor { - static readonly ID = BINARY_DIFF_EDITOR_ID; + static override readonly ID = BINARY_DIFF_EDITOR_ID; constructor( @ITelemetryService telemetryService: ITelemetryService, diff --git a/src/vs/workbench/browser/parts/editor/binaryEditor.ts b/src/vs/workbench/browser/parts/editor/binaryEditor.ts index 53a93906..edfc6118 100644 --- a/src/vs/workbench/browser/parts/editor/binaryEditor.ts +++ b/src/vs/workbench/browser/parts/editor/binaryEditor.ts @@ -13,18 +13,15 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { ScrollbarVisibility } from 'vs/base/common/scrollable'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { URI } from 'vs/base/common/uri'; import { Dimension, size, clearNode, append, addDisposableListener, EventType, $ } from 'vs/base/browser/dom'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { dispose, IDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { DisposableStore, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { IStorageService } from 'vs/platform/storage/common/storage'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { assertIsDefined, assertAllDefined } from 'vs/base/common/types'; import { ByteSize } from 'vs/platform/files/common/files'; export interface IOpenCallbacks { openInternal: (input: EditorInput, options: EditorOptions | undefined) => Promise; - openExternal: (uri: URI) => void; } /* @@ -42,22 +39,21 @@ export abstract class BaseBinaryResourceEditor extends EditorPane { private metadata: string | undefined; private binaryContainer: HTMLElement | undefined; private scrollbar: DomScrollableElement | undefined; - private resourceViewerContext: ResourceViewerContext | undefined; + private inputDisposable = this._register(new MutableDisposable()); constructor( id: string, callbacks: IOpenCallbacks, telemetryService: ITelemetryService, themeService: IThemeService, - @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, - @IStorageService storageService: IStorageService, + @IStorageService storageService: IStorageService ) { super(id, telemetryService, themeService, storageService); this.callbacks = callbacks; } - getTitle(): string { + override getTitle(): string { return this.input ? this.input.getName() : localize('binaryEditor', "Binary Viewer"); } @@ -65,7 +61,7 @@ export abstract class BaseBinaryResourceEditor extends EditorPane { // Container for Binary this.binaryContainer = document.createElement('div'); - this.binaryContainer.className = 'binary-container'; + this.binaryContainer.className = 'monaco-binary-resource-editor'; this.binaryContainer.style.outline = 'none'; this.binaryContainer.tabIndex = 0; // enable focus support from the editor part (do not remove) @@ -74,7 +70,7 @@ export abstract class BaseBinaryResourceEditor extends EditorPane { parent.appendChild(this.scrollbar.getDomNode()); } - async setInput(input: EditorInput, options: EditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise { + override async setInput(input: EditorInput, options: EditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise { await super.setInput(input, options, context, token); const model = await input.resolve(); @@ -89,23 +85,38 @@ export abstract class BaseBinaryResourceEditor extends EditorPane { } // Render Input - if (this.resourceViewerContext) { - this.resourceViewerContext.dispose(); - } - - const [binaryContainer, scrollbar] = assertAllDefined(this.binaryContainer, this.scrollbar); - this.resourceViewerContext = ResourceViewer.show({ name: model.getName(), resource: model.resource, size: model.getSize(), etag: model.getETag(), mime: model.getMime() }, binaryContainer, scrollbar, { - openInternalClb: () => this.handleOpenInternalCallback(input, options), - openExternalClb: this.environmentService.remoteAuthority ? undefined : resource => this.callbacks.openExternal(resource), - metadataClb: meta => this.handleMetadataChanged(meta) - }); + this.inputDisposable.value = this.renderInput(input, options, model); } - private async handleOpenInternalCallback(input: EditorInput, options: EditorOptions | undefined): Promise { - await this.callbacks.openInternal(input, options); + private renderInput(input: EditorInput, options: EditorOptions | undefined, model: BinaryEditorModel): IDisposable { + const [binaryContainer, scrollbar] = assertAllDefined(this.binaryContainer, this.scrollbar); - // Signal to listeners that the binary editor has been opened in-place - this._onDidOpenInPlace.fire(); + clearNode(binaryContainer); + + const disposables = new DisposableStore(); + + const label = document.createElement('p'); + label.textContent = localize('nativeBinaryError', "The file is not displayed in the editor because it is either binary or uses an unsupported text encoding."); + binaryContainer.appendChild(label); + + const link = append(label, $('a.embedded-link')); + link.setAttribute('role', 'button'); + link.textContent = localize('openAsText', "Do you want to open it anyway?"); + + disposables.add(addDisposableListener(link, EventType.CLICK, async () => { + await this.callbacks.openInternal(input, options); + + // Signal to listeners that the binary editor has been opened in-place + this._onDidOpenInPlace.fire(); + })); + + scrollbar.scanDomNode(); + + // Update metadata + const size = model.getSize(); + this.handleMetadataChanged(typeof size === 'number' ? ByteSize.formatSize(size) : ''); + + return disposables; } private handleMetadataChanged(meta: string | undefined): void { @@ -118,7 +129,7 @@ export abstract class BaseBinaryResourceEditor extends EditorPane { return this.metadata; } - clearInput(): void { + override clearInput(): void { // Clear Meta this.handleMetadataChanged(undefined); @@ -127,8 +138,7 @@ export abstract class BaseBinaryResourceEditor extends EditorPane { if (this.binaryContainer) { clearNode(this.binaryContainer); } - dispose(this.resourceViewerContext); - this.resourceViewerContext = undefined; + this.inputDisposable.clear(); super.clearInput(); } @@ -139,118 +149,17 @@ export abstract class BaseBinaryResourceEditor extends EditorPane { const [binaryContainer, scrollbar] = assertAllDefined(this.binaryContainer, this.scrollbar); size(binaryContainer, dimension.width, dimension.height); scrollbar.scanDomNode(); - if (typeof this.resourceViewerContext?.layout === 'function') { - this.resourceViewerContext.layout(dimension); - } } - focus(): void { + override focus(): void { const binaryContainer = assertIsDefined(this.binaryContainer); binaryContainer.focus(); } - dispose(): void { - if (this.binaryContainer) { - this.binaryContainer.remove(); - } - - dispose(this.resourceViewerContext); - this.resourceViewerContext = undefined; + override dispose(): void { + this.binaryContainer?.remove(); super.dispose(); } } - -export interface IResourceDescriptor { - readonly resource: URI; - readonly name: string; - readonly size?: number; - readonly etag?: string; - readonly mime: string; -} - -interface ResourceViewerContext extends IDisposable { - layout?(dimension: Dimension): void; -} - -interface ResourceViewerDelegate { - openInternalClb(uri: URI): void; - openExternalClb?(uri: URI): void; - metadataClb(meta: string): void; -} - -class ResourceViewer { - - private static readonly MAX_OPEN_INTERNAL_SIZE = ByteSize.MB * 200; // max size until we offer an action to open internally - - static show( - descriptor: IResourceDescriptor, - container: HTMLElement, - scrollbar: DomScrollableElement, - delegate: ResourceViewerDelegate, - ): ResourceViewerContext { - - // Ensure CSS class - container.className = 'monaco-binary-resource-editor'; - - // Large Files - if (typeof descriptor.size === 'number' && descriptor.size > ResourceViewer.MAX_OPEN_INTERNAL_SIZE) { - return FileTooLargeFileView.create(container, descriptor.size, scrollbar, delegate); - } - - // Seemingly Binary Files - return FileSeemsBinaryFileView.create(container, descriptor, scrollbar, delegate); - } -} - -class FileTooLargeFileView { - static create( - container: HTMLElement, - descriptorSize: number, - scrollbar: DomScrollableElement, - delegate: ResourceViewerDelegate - ) { - const size = ByteSize.formatSize(descriptorSize); - delegate.metadataClb(size); - - clearNode(container); - - const label = document.createElement('span'); - label.textContent = localize('nativeFileTooLargeError', "The file is not displayed in the editor because it is too large ({0}).", size); - container.appendChild(label); - - scrollbar.scanDomNode(); - - return Disposable.None; - } -} - -class FileSeemsBinaryFileView { - static create( - container: HTMLElement, - descriptor: IResourceDescriptor, - scrollbar: DomScrollableElement, - delegate: ResourceViewerDelegate - ) { - delegate.metadataClb(typeof descriptor.size === 'number' ? ByteSize.formatSize(descriptor.size) : ''); - - clearNode(container); - - const disposables = new DisposableStore(); - - const label = document.createElement('p'); - label.textContent = localize('nativeBinaryError', "The file is not displayed in the editor because it is either binary or uses an unsupported text encoding."); - container.appendChild(label); - - const link = append(label, $('a.embedded-link')); - link.setAttribute('role', 'button'); - link.textContent = localize('openAsText', "Do you want to open it anyway?"); - - disposables.add(addDisposableListener(link, EventType.CLICK, () => delegate.openInternalClb(descriptor.resource))); - - scrollbar.scanDomNode(); - - return disposables; - } -} diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts index 3f7b8895..f0957c16 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts @@ -54,7 +54,7 @@ class OutlineItem extends BreadcrumbsItem { super(); } - dispose(): void { + override dispose(): void { this._disposables.dispose(); } @@ -114,7 +114,7 @@ class FileItem extends BreadcrumbsItem { super(); } - dispose(): void { + override dispose(): void { this._disposables.dispose(); } @@ -147,6 +147,7 @@ export interface IBreadcrumbsControlOptions { showSymbolIcons: boolean; showDecorationColors: boolean; breadcrumbsBackground: ColorIdentifier | ColorFunction; + showPlaceholder: boolean; } export class BreadcrumbsControl { @@ -288,8 +289,21 @@ export class BreadcrumbsControl { showSymbolIcons: this._options.showSymbolIcons && showIcons }; const items = model.getElements().map(element => element instanceof FileElement ? new FileItem(model, element, options, this._instantiationService) : new OutlineItem(model, element, options)); - this._widget.setItems(items); - this._widget.reveal(items[items.length - 1]); + if (items.length === 0) { + this._widget.setEnabled(false); + this._widget.setItems([new class extends BreadcrumbsItem { + render(container: HTMLElement): void { + container.innerText = localize('empty', "no elements"); + } + equals(other: BreadcrumbsItem): boolean { + return other === this; + } + }]); + } else { + this._widget.setEnabled(true); + this._widget.setItems(items); + this._widget.reveal(items[items.length - 1]); + } }; const listener = model.onDidUpdate(updateBreadcrumbs); const configListener = this._cfShowIcons.onDidChange(updateBreadcrumbs); diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts index 3b23f21c..040fb843 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts @@ -348,7 +348,7 @@ export class BreadcrumbsFilePicker extends BreadcrumbsPicker { constructor( parent: HTMLElement, - protected resource: URI, + resource: URI, @IInstantiationService instantiationService: IInstantiationService, @IThemeService themeService: IThemeService, @IConfigurationService configService: IConfigurationService, diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index ddfd40d9..54d76867 100644 --- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -6,8 +6,8 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { localize } from 'vs/nls'; import { URI, UriComponents } from 'vs/base/common/uri'; -import { IEditorRegistry, EditorDescriptor, Extensions as EditorExtensions } from 'vs/workbench/browser/editor'; -import { EditorInput, IEditorInputFactory, SideBySideEditorInput, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions, TextCompareEditorActiveContext, ActiveEditorPinnedContext, EditorGroupEditorsCountContext, ActiveEditorStickyContext, ActiveEditorAvailableEditorIdsContext, MultipleEditorGroupsContext, ActiveEditorDirtyContext } from 'vs/workbench/common/editor'; +import { IEditorRegistry, EditorDescriptor } from 'vs/workbench/browser/editor'; +import { EditorInput, IEditorInputSerializer, SideBySideEditorInput, IEditorInputFactoryRegistry, TextCompareEditorActiveContext, ActiveEditorPinnedContext, EditorGroupEditorsCountContext, ActiveEditorStickyContext, ActiveEditorAvailableEditorIdsContext, MultipleEditorGroupsContext, ActiveEditorDirtyContext, EditorExtensions } from 'vs/workbench/common/editor'; import { TextResourceEditor } from 'vs/workbench/browser/parts/editor/textResourceEditor'; import { SideBySideEditor } from 'vs/workbench/browser/parts/editor/sideBySideEditor'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; @@ -118,8 +118,8 @@ interface ISerializedUntitledTextEditorInput { encoding: string | undefined; } -// Register Editor Input Factory -class UntitledTextEditorInputFactory implements IEditorInputFactory { +// Register Editor Input Serializer +class UntitledTextEditorInputSerializer implements IEditorInputSerializer { constructor( @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService, @@ -136,7 +136,7 @@ class UntitledTextEditorInputFactory implements IEditorInputFactory { return undefined; } - const untitledTextEditorInput = editorInput; + const untitledTextEditorInput = editorInput as UntitledTextEditorInput; let resource = untitledTextEditorInput.resource; if (untitledTextEditorInput.model.hasAssociatedFilePath) { @@ -165,7 +165,7 @@ class UntitledTextEditorInputFactory implements IEditorInputFactory { } deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): UntitledTextEditorInput { - return instantiationService.invokeFunction(accessor => { + return instantiationService.invokeFunction(accessor => { const deserialized: ISerializedUntitledTextEditorInput = JSON.parse(serializedEditorInput); const resource = URI.revive(deserialized.resourceJSON); const mode = deserialized.modeId; @@ -176,9 +176,9 @@ class UntitledTextEditorInputFactory implements IEditorInputFactory { } } -Registry.as(EditorInputExtensions.EditorInputFactories).registerEditorInputFactory(UntitledTextEditorInput.ID, UntitledTextEditorInputFactory); +Registry.as(EditorExtensions.EditorInputFactories).registerEditorInputSerializer(UntitledTextEditorInput.ID, UntitledTextEditorInputSerializer); -// Register SideBySide/DiffEditor Input Factory +// Register SideBySide/DiffEditor Input Serializer interface ISerializedSideBySideEditorInput { name: string; description: string | undefined; @@ -190,21 +190,21 @@ interface ISerializedSideBySideEditorInput { secondaryTypeId: string; } -export abstract class AbstractSideBySideEditorInputFactory implements IEditorInputFactory { +export abstract class AbstractSideBySideEditorInputSerializer implements IEditorInputSerializer { - private getInputFactories(secondaryId: string, primaryId: string): [IEditorInputFactory | undefined, IEditorInputFactory | undefined] { - const registry = Registry.as(EditorInputExtensions.EditorInputFactories); + private getInputSerializers(secondaryEditorInputTypeId: string, primaryEditorInputTypeId: string): [IEditorInputSerializer | undefined, IEditorInputSerializer | undefined] { + const registry = Registry.as(EditorExtensions.EditorInputFactories); - return [registry.getEditorInputFactory(secondaryId), registry.getEditorInputFactory(primaryId)]; + return [registry.getEditorInputSerializer(secondaryEditorInputTypeId), registry.getEditorInputSerializer(primaryEditorInputTypeId)]; } canSerialize(editorInput: EditorInput): boolean { const input = editorInput as SideBySideEditorInput | DiffEditorInput; if (input.primary && input.secondary) { - const [secondaryInputFactory, primaryInputFactory] = this.getInputFactories(input.secondary.getTypeId(), input.primary.getTypeId()); + const [secondaryInputSerializer, primaryInputSerializer] = this.getInputSerializers(input.secondary.typeId, input.primary.typeId); - return !!(secondaryInputFactory?.canSerialize(input.secondary) && primaryInputFactory?.canSerialize(input.primary)); + return !!(secondaryInputSerializer?.canSerialize(input.secondary) && primaryInputSerializer?.canSerialize(input.primary)); } return false; @@ -214,10 +214,10 @@ export abstract class AbstractSideBySideEditorInputFactory implements IEditorInp const input = editorInput as SideBySideEditorInput | DiffEditorInput; if (input.primary && input.secondary) { - const [secondaryInputFactory, primaryInputFactory] = this.getInputFactories(input.secondary.getTypeId(), input.primary.getTypeId()); - if (primaryInputFactory && secondaryInputFactory) { - const primarySerialized = primaryInputFactory.serialize(input.primary); - const secondarySerialized = secondaryInputFactory.serialize(input.secondary); + const [secondaryInputSerializer, primaryInputSerializer] = this.getInputSerializers(input.secondary.typeId, input.primary.typeId); + if (primaryInputSerializer && secondaryInputSerializer) { + const primarySerialized = primaryInputSerializer.serialize(input.primary); + const secondarySerialized = secondaryInputSerializer.serialize(input.secondary); if (primarySerialized && secondarySerialized) { const serializedEditorInput: ISerializedSideBySideEditorInput = { @@ -225,8 +225,8 @@ export abstract class AbstractSideBySideEditorInputFactory implements IEditorInp description: input.getDescription(), primarySerialized: primarySerialized, secondarySerialized: secondarySerialized, - primaryTypeId: input.primary.getTypeId(), - secondaryTypeId: input.secondary.getTypeId() + primaryTypeId: input.primary.typeId, + secondaryTypeId: input.secondary.typeId }; return JSON.stringify(serializedEditorInput); @@ -240,10 +240,10 @@ export abstract class AbstractSideBySideEditorInputFactory implements IEditorInp deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): EditorInput | undefined { const deserialized: ISerializedSideBySideEditorInput = JSON.parse(serializedEditorInput); - const [secondaryInputFactory, primaryInputFactory] = this.getInputFactories(deserialized.secondaryTypeId, deserialized.primaryTypeId); - if (primaryInputFactory && secondaryInputFactory) { - const primaryInput = primaryInputFactory.deserialize(instantiationService, deserialized.primarySerialized); - const secondaryInput = secondaryInputFactory.deserialize(instantiationService, deserialized.secondarySerialized); + const [secondaryInputSerializer, primaryInputSerializer] = this.getInputSerializers(deserialized.secondaryTypeId, deserialized.primaryTypeId); + if (primaryInputSerializer && secondaryInputSerializer) { + const primaryInput = primaryInputSerializer.deserialize(instantiationService, deserialized.primarySerialized); + const secondaryInput = secondaryInputSerializer.deserialize(instantiationService, deserialized.secondarySerialized); if (primaryInput && secondaryInput) { return this.createEditorInput(instantiationService, deserialized.name, deserialized.description, secondaryInput, primaryInput); @@ -256,22 +256,22 @@ export abstract class AbstractSideBySideEditorInputFactory implements IEditorInp protected abstract createEditorInput(instantiationService: IInstantiationService, name: string, description: string | undefined, secondaryInput: EditorInput, primaryInput: EditorInput): EditorInput; } -class SideBySideEditorInputFactory extends AbstractSideBySideEditorInputFactory { +class SideBySideEditorInputSerializer extends AbstractSideBySideEditorInputSerializer { protected createEditorInput(instantiationService: IInstantiationService, name: string, description: string | undefined, secondaryInput: EditorInput, primaryInput: EditorInput): EditorInput { return new SideBySideEditorInput(name, description, secondaryInput, primaryInput); } } -class DiffEditorInputFactory extends AbstractSideBySideEditorInputFactory { +class DiffEditorInputSerializer extends AbstractSideBySideEditorInputSerializer { protected createEditorInput(instantiationService: IInstantiationService, name: string, description: string | undefined, secondaryInput: EditorInput, primaryInput: EditorInput): EditorInput { return instantiationService.createInstance(DiffEditorInput, name, description, secondaryInput, primaryInput, undefined); } } -Registry.as(EditorInputExtensions.EditorInputFactories).registerEditorInputFactory(SideBySideEditorInput.ID, SideBySideEditorInputFactory); -Registry.as(EditorInputExtensions.EditorInputFactories).registerEditorInputFactory(DiffEditorInput.ID, DiffEditorInputFactory); +Registry.as(EditorExtensions.EditorInputFactories).registerEditorInputSerializer(SideBySideEditorInput.ID, SideBySideEditorInputSerializer); +Registry.as(EditorExtensions.EditorInputFactories).registerEditorInputSerializer(DiffEditorInput.ID, DiffEditorInputSerializer); // Register Editor Contributions registerEditorContribution(OpenWorkspaceButtonContribution.ID, OpenWorkspaceButtonContribution); diff --git a/src/vs/workbench/browser/parts/editor/editor.ts b/src/vs/workbench/browser/parts/editor/editor.ts index 68146c8b..6984779b 100644 --- a/src/vs/workbench/browser/parts/editor/editor.ts +++ b/src/vs/workbench/browser/parts/editor/editor.ts @@ -3,9 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { GroupIdentifier, IWorkbenchEditorConfiguration, EditorOptions, TextEditorOptions, IEditorInput, IEditorIdentifier, IEditorCloseEvent, IEditorPane, IEditorPartOptions, IEditorPartOptionsChangeEvent, EditorInput } from 'vs/workbench/common/editor'; -import { EditorGroup } from 'vs/workbench/common/editor/editorGroup'; -import { IEditorGroup, GroupDirection, IAddGroupOptions, IMergeGroupOptions, GroupsOrder, GroupsArrangement, OpenEditorContext } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { GroupIdentifier, IWorkbenchEditorConfiguration, EditorOptions, TextEditorOptions, IEditorInput, IEditorIdentifier, IEditorCloseEvent, IEditorPartOptions, IEditorPartOptionsChangeEvent, EditorInput } from 'vs/workbench/common/editor'; +import { IEditorGroup, GroupDirection, IAddGroupOptions, IMergeGroupOptions, GroupsOrder, GroupsArrangement } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IDisposable } from 'vs/base/common/lifecycle'; import { Dimension } from 'vs/base/browser/dom'; import { Event } from 'vs/base/common/event'; @@ -13,7 +12,6 @@ import { IConfigurationChangeEvent, IConfigurationService } from 'vs/platform/co import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ISerializableView } from 'vs/base/browser/ui/grid/grid'; import { getIEditor } from 'vs/editor/browser/editorBrowser'; -import { IEditorOptions } from 'vs/platform/editor/common/editor'; import { IEditorService, IResourceEditorInputType } from 'vs/workbench/services/editor/common/editorService'; export interface IEditorPartCreationOptions { @@ -60,28 +58,6 @@ export function getEditorPartOptions(configurationService: IConfigurationService return options; } -export interface IEditorOpeningEvent extends IEditorIdentifier { - - /** - * The options used when opening the editor. - */ - readonly options?: IEditorOptions; - - /** - * Context indicates how the editor open event is initialized. - */ - readonly context?: OpenEditorContext; - - /** - * Allows to prevent the opening of an editor by providing a callback - * that will be executed instead. By returning another editor promise - * it is possible to override the opening with another editor. It is ok - * to return a promise that resolves to `undefined` to prevent the opening - * alltogether. - */ - prevent(callback: () => Promise | undefined): void; -} - export interface IEditorGroupsAccessor { readonly groups: IEditorGroupView[]; @@ -128,18 +104,20 @@ export interface IEditorGroupTitleHeight { export interface IEditorGroupView extends IDisposable, ISerializableView, IEditorGroup { readonly onDidFocus: Event; - readonly onWillDispose: Event; - readonly onWillOpenEditor: Event; + readonly onDidOpenEditorFail: Event; - readonly onWillCloseEditor: Event; readonly onDidCloseEditor: Event; - readonly group: EditorGroup; + /** + * A promise that resolves when the group has been restored. + * + * For a group with active editor, the promise will resolve + * when the active editor has finished to resolve. + */ readonly whenRestored: Promise; readonly titleHeight: IEditorGroupTitleHeight; - readonly isEmpty: boolean; readonly isMinimized: boolean; readonly disposed: boolean; diff --git a/src/vs/workbench/browser/parts/editor/editorActions.ts b/src/vs/workbench/browser/parts/editor/editorActions.ts index d035505c..0cb09967 100644 --- a/src/vs/workbench/browser/parts/editor/editorActions.ts +++ b/src/vs/workbench/browser/parts/editor/editorActions.ts @@ -11,7 +11,7 @@ import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { CLOSE_EDITOR_COMMAND_ID, MOVE_ACTIVE_EDITOR_COMMAND_ID, ActiveEditorMoveArguments, SPLIT_EDITOR_LEFT, SPLIT_EDITOR_RIGHT, SPLIT_EDITOR_UP, SPLIT_EDITOR_DOWN, splitEditor, LAYOUT_EDITOR_GROUPS_COMMAND_ID, UNPIN_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; -import { IEditorGroupsService, IEditorGroup, GroupsArrangement, GroupLocation, GroupDirection, preferredSideBySideGroupDirection, IFindGroupScope, GroupOrientation, EditorGroupLayout, GroupsOrder, OpenEditorContext } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IEditorGroupsService, IEditorGroup, GroupsArrangement, GroupLocation, GroupDirection, preferredSideBySideGroupDirection, IFindGroupScope, GroupOrientation, EditorGroupLayout, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { DisposableStore } from 'vs/base/common/lifecycle'; @@ -23,6 +23,7 @@ import { AllEditorsByMostRecentlyUsedQuickAccess, ActiveGroupEditorsByMostRecent import { Codicon } from 'vs/base/common/codicons'; import { IFilesConfigurationService, AutoSaveMode } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { EditorOverride } from 'vs/platform/editor/common/editor'; +import { Schemas } from 'vs/base/common/network'; export class ExecuteCommandAction extends Action { @@ -36,7 +37,7 @@ export class ExecuteCommandAction extends Action { super(id, label); } - run(): Promise { + override run(): Promise { return this.commandService.executeCommand(this.commandId, this.commandArgs); } } @@ -70,7 +71,7 @@ export class BaseSplitEditorAction extends Action { })); } - async run(context?: IEditorIdentifier): Promise { + override async run(context?: IEditorIdentifier): Promise { splitEditor(this.editorGroupService, this.direction, context); } } @@ -104,7 +105,7 @@ export class SplitEditorOrthogonalAction extends BaseSplitEditorAction { super(id, label, editorGroupService, configurationService); } - protected getDirection(): GroupDirection { + protected override getDirection(): GroupDirection { const direction = preferredSideBySideGroupDirection(this.configurationService); return direction === GroupDirection.RIGHT ? GroupDirection.DOWN : GroupDirection.RIGHT; @@ -180,7 +181,7 @@ export class JoinTwoGroupsAction extends Action { super(id, label); } - async run(context?: IEditorIdentifier): Promise { + override async run(context?: IEditorIdentifier): Promise { let sourceGroup: IEditorGroup | undefined; if (context && typeof context.groupId === 'number') { sourceGroup = this.editorGroupService.getGroup(context.groupId); @@ -215,7 +216,7 @@ export class JoinAllGroupsAction extends Action { super(id, label); } - async run(): Promise { + override async run(): Promise { this.editorGroupService.mergeAllGroups(); } } @@ -233,7 +234,7 @@ export class NavigateBetweenGroupsAction extends Action { super(id, label); } - async run(): Promise { + override async run(): Promise { const nextGroup = this.editorGroupService.findGroup({ location: GroupLocation.NEXT }, this.editorGroupService.activeGroup, true); nextGroup.focus(); } @@ -252,7 +253,7 @@ export class FocusActiveGroupAction extends Action { super(id, label); } - async run(): Promise { + override async run(): Promise { this.editorGroupService.activeGroup.focus(); } } @@ -268,7 +269,7 @@ export abstract class BaseFocusGroupAction extends Action { super(id, label); } - async run(): Promise { + override async run(): Promise { const group = this.editorGroupService.findGroup(this.scope, this.editorGroupService.activeGroup, true); if (group) { group.focus(); @@ -401,7 +402,7 @@ export class CloseEditorAction extends Action { super(id, label, Codicon.close.classNames); } - run(context?: IEditorCommandsContext): Promise { + override run(context?: IEditorCommandsContext): Promise { return this.commandService.executeCommand(CLOSE_EDITOR_COMMAND_ID, undefined, context); } } @@ -419,7 +420,7 @@ export class UnpinEditorAction extends Action { super(id, label, Codicon.pinned.classNames); } - run(context?: IEditorCommandsContext): Promise { + override run(context?: IEditorCommandsContext): Promise { return this.commandService.executeCommand(UNPIN_EDITOR_COMMAND_ID, undefined, context); } } @@ -437,7 +438,7 @@ export class CloseOneEditorAction extends Action { super(id, label, Codicon.close.classNames); } - async run(context?: IEditorCommandsContext): Promise { + override async run(context?: IEditorCommandsContext): Promise { let group: IEditorGroup | undefined; let editorIndex: number | undefined; if (context) { @@ -480,7 +481,7 @@ export class RevertAndCloseEditorAction extends Action { super(id, label); } - async run(): Promise { + override async run(): Promise { const activeEditorPane = this.editorService.activeEditorPane; if (activeEditorPane) { const editor = activeEditorPane.input; @@ -515,7 +516,7 @@ export class CloseLeftEditorsInGroupAction extends Action { super(id, label); } - async run(context?: IEditorIdentifier): Promise { + override async run(context?: IEditorIdentifier): Promise { const { group, editor } = this.getTarget(context); if (group && editor) { return group.closeEditors({ direction: CloseDirection.LEFT, except: editor, excludeSticky: true }); @@ -561,7 +562,7 @@ abstract class BaseCloseAllAction extends Action { return groupsToClose; } - async run(): Promise { + override async run(): Promise { // Just close all if there are no dirty editors if (!this.workingCopyService.hasDirty) { @@ -714,7 +715,7 @@ export class CloseEditorsInOtherGroupsAction extends Action { super(id, label); } - async run(context?: IEditorIdentifier): Promise { + override async run(context?: IEditorIdentifier): Promise { const groupToSkip = context ? this.editorGroupService.getGroup(context.groupId) : this.editorGroupService.activeGroup; await Promise.all(this.editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE).map(async group => { if (groupToSkip && group.id === groupToSkip.id) { @@ -740,7 +741,7 @@ export class CloseEditorInAllGroupsAction extends Action { super(id, label); } - async run(): Promise { + override async run(): Promise { const activeEditor = this.editorService.activeEditor; if (activeEditor) { await Promise.all(this.editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE).map(group => group.closeEditor(activeEditor))); @@ -760,7 +761,7 @@ class BaseMoveCopyGroupAction extends Action { super(id, label); } - async run(context?: IEditorIdentifier): Promise { + override async run(context?: IEditorIdentifier): Promise { let sourceGroup: IEditorGroup | undefined; if (context && typeof context.groupId === 'number') { sourceGroup = this.editorGroupService.getGroup(context.groupId); @@ -958,7 +959,7 @@ export class MinimizeOtherGroupsAction extends Action { super(id, label); } - async run(): Promise { + override async run(): Promise { this.editorGroupService.arrangeGroups(GroupsArrangement.MINIMIZE_OTHERS); } } @@ -972,7 +973,7 @@ export class ResetGroupSizesAction extends Action { super(id, label); } - async run(): Promise { + override async run(): Promise { this.editorGroupService.arrangeGroups(GroupsArrangement.EVEN); } } @@ -986,7 +987,7 @@ export class ToggleGroupSizesAction extends Action { super(id, label); } - async run(): Promise { + override async run(): Promise { this.editorGroupService.arrangeGroups(GroupsArrangement.TOGGLE); } } @@ -1006,7 +1007,7 @@ export class MaximizeGroupAction extends Action { super(id, label); } - async run(): Promise { + override async run(): Promise { if (this.editorService.activeEditor) { this.editorGroupService.arrangeGroups(GroupsArrangement.MINIMIZE_OTHERS); this.layoutService.setSideBarHidden(true); @@ -1025,7 +1026,7 @@ export abstract class BaseNavigateEditorAction extends Action { super(id, label); } - async run(): Promise { + override async run(): Promise { const result = this.navigate(); if (!result) { return; @@ -1214,7 +1215,7 @@ export class NavigateForwardAction extends Action { super(id, label); } - async run(): Promise { + override async run(): Promise { this.historyService.forward(); } } @@ -1228,7 +1229,7 @@ export class NavigateBackwardsAction extends Action { super(id, label); } - async run(): Promise { + override async run(): Promise { this.historyService.back(); } } @@ -1242,7 +1243,7 @@ export class NavigateToLastEditLocationAction extends Action { super(id, label); } - async run(): Promise { + override async run(): Promise { this.historyService.openLastEditLocation(); } } @@ -1256,7 +1257,7 @@ export class NavigateLastAction extends Action { super(id, label); } - async run(): Promise { + override async run(): Promise { this.historyService.last(); } } @@ -1274,7 +1275,7 @@ export class ReopenClosedEditorAction extends Action { super(id, label); } - async run(): Promise { + override async run(): Promise { this.historyService.reopenLastClosedEditor(); } } @@ -1293,7 +1294,7 @@ export class ClearRecentFilesAction extends Action { super(id, label); } - async run(): Promise { + override async run(): Promise { // Clear global recently opened this.workspacesService.clearRecentlyOpened(); @@ -1316,7 +1317,7 @@ export class ShowEditorsInActiveGroupByMostRecentlyUsedAction extends Action { super(id, label); } - async run(): Promise { + override async run(): Promise { this.quickInputService.quickAccess.show(ActiveGroupEditorsByMostRecentlyUsedQuickAccess.PREFIX); } } @@ -1334,7 +1335,7 @@ export class ShowAllEditorsByAppearanceAction extends Action { super(id, label); } - async run(): Promise { + override async run(): Promise { this.quickInputService.quickAccess.show(AllEditorsByAppearanceQuickAccess.PREFIX); } } @@ -1352,7 +1353,7 @@ export class ShowAllEditorsByMostRecentlyUsedAction extends Action { super(id, label); } - async run(): Promise { + override async run(): Promise { this.quickInputService.quickAccess.show(AllEditorsByMostRecentlyUsedQuickAccess.PREFIX); } } @@ -1370,7 +1371,7 @@ export class BaseQuickAccessEditorAction extends Action { super(id, label); } - async run(): Promise { + override async run(): Promise { const keybindings = this.keybindingService.lookupKeybindings(this.id); this.quickInputService.quickAccess.show(this.prefix, { @@ -1455,7 +1456,7 @@ export class QuickAccessPreviousEditorFromHistoryAction extends Action { super(id, label); } - async run(): Promise { + override async run(): Promise { const keybindings = this.keybindingService.lookupKeybindings(this.id); // Enforce to activate the first item in quick access if @@ -1482,7 +1483,7 @@ export class OpenNextRecentlyUsedEditorAction extends Action { super(id, label); } - async run(): Promise { + override async run(): Promise { this.historyService.openNextRecentlyUsedEditor(); } } @@ -1500,7 +1501,7 @@ export class OpenPreviousRecentlyUsedEditorAction extends Action { super(id, label); } - async run(): Promise { + override async run(): Promise { this.historyService.openPreviouslyUsedEditor(); } } @@ -1519,7 +1520,7 @@ export class OpenNextRecentlyUsedEditorInGroupAction extends Action { super(id, label); } - async run(): Promise { + override async run(): Promise { this.historyService.openNextRecentlyUsedEditor(this.editorGroupsService.activeGroup.id); } } @@ -1538,7 +1539,7 @@ export class OpenPreviousRecentlyUsedEditorInGroupAction extends Action { super(id, label); } - async run(): Promise { + override async run(): Promise { this.historyService.openPreviouslyUsedEditor(this.editorGroupsService.activeGroup.id); } } @@ -1556,7 +1557,7 @@ export class ClearEditorHistoryAction extends Action { super(id, label); } - async run(): Promise { + override async run(): Promise { // Editor history this.historyService.clear(); @@ -1826,7 +1827,7 @@ export class BaseCreateEditorGroupAction extends Action { super(id, label); } - async run(): Promise { + override async run(): Promise { this.editorGroupService.addGroup(this.editorGroupService.activeGroup, this.direction, { activate: true }); } } @@ -1900,7 +1901,7 @@ export class ReopenResourcesAction extends Action { super(id, label); } - async run(): Promise { + override async run(): Promise { const activeInput = this.editorService.activeEditor; if (!activeInput) { return; @@ -1913,7 +1914,14 @@ export class ReopenResourcesAction extends Action { const options = activeEditorPane.options; const group = activeEditorPane.group; - await this.editorService.openEditor(activeInput, { ...options, override: EditorOverride.PICK }, group); + await this.editorService.replaceEditors([ + { + editor: activeInput, + replacement: activeInput, + forceReplaceDirty: activeInput.resource?.scheme === Schemas.untitled, + options: { ...options, override: EditorOverride.PICK } + } + ], group); } } @@ -1930,7 +1938,7 @@ export class ToggleEditorTypeAction extends Action { super(id, label); } - async run(): Promise { + override async run(): Promise { const activeEditorPane = this.editorService.activeEditorPane; if (!activeEditorPane) { return; @@ -1950,6 +1958,6 @@ export class ToggleEditorTypeAction extends Action { return; } - await firstNonActiveOverride[0].open(activeEditorPane.input, { ...options, override: firstNonActiveOverride[1].id }, group, OpenEditorContext.NEW_EDITOR)?.override; + await firstNonActiveOverride[0].open(activeEditorPane.input, { ...options, override: firstNonActiveOverride[1].id }, group)?.override; } } diff --git a/src/vs/workbench/browser/parts/editor/editorAutoSave.ts b/src/vs/workbench/browser/parts/editor/editorAutoSave.ts index b61b8d45..8bddd352 100644 --- a/src/vs/workbench/browser/parts/editor/editorAutoSave.ts +++ b/src/vs/workbench/browser/parts/editor/editorAutoSave.ts @@ -11,7 +11,8 @@ import { SaveReason, IEditorIdentifier, IEditorInput, GroupIdentifier, ISaveOpti import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { withNullAsUndefined } from 'vs/base/common/types'; -import { IWorkingCopyService, IWorkingCopy, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { IWorkingCopy, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopy'; import { ILogService } from 'vs/platform/log/common/log'; export class EditorAutoSave extends Disposable implements IWorkbenchContribution { @@ -184,7 +185,7 @@ export class EditorAutoSave extends Disposable implements IWorkbenchContribution // Clear any running auto save operation this.discardAutoSave(workingCopy); - this.logService.trace(`[editor auto save] scheduling auto save after ${this.autoSaveAfterDelay}ms`, workingCopy.resource.toString()); + this.logService.trace(`[editor auto save] scheduling auto save after ${this.autoSaveAfterDelay}ms`, workingCopy.resource.toString(true), workingCopy.typeId); // Schedule new auto save const handle = setTimeout(() => { @@ -194,7 +195,7 @@ export class EditorAutoSave extends Disposable implements IWorkbenchContribution // Save if dirty if (workingCopy.isDirty()) { - this.logService.trace(`[editor auto save] running auto save`, workingCopy.resource.toString()); + this.logService.trace(`[editor auto save] running auto save`, workingCopy.resource.toString(true), workingCopy.typeId); workingCopy.save({ reason: SaveReason.AUTO }); } @@ -202,7 +203,7 @@ export class EditorAutoSave extends Disposable implements IWorkbenchContribution // Keep in map for disposal as needed this.pendingAutoSavesAfterDelay.set(workingCopy, toDisposable(() => { - this.logService.trace(`[editor auto save] clearing pending auto save`, workingCopy.resource.toString()); + this.logService.trace(`[editor auto save] clearing pending auto save`, workingCopy.resource.toString(true), workingCopy.typeId); clearTimeout(handle); })); diff --git a/src/vs/workbench/browser/parts/editor/editorCommands.ts b/src/vs/workbench/browser/parts/editor/editorCommands.ts index 3362b223..62574298 100644 --- a/src/vs/workbench/browser/parts/editor/editorCommands.ts +++ b/src/vs/workbench/browser/parts/editor/editorCommands.ts @@ -487,8 +487,7 @@ function registerOpenEditorAPICommands(): void { group = editorGroupsService.getGroup(viewColumnToEditorGroup(editorGroupsService, columnArg)) ?? editorGroupsService.activeGroup; } - const input = editorService.createEditorInput({ resource: URI.revive(resource) }); - return editorService.openEditor(input, { ...optionsArg, override: id }, group); + return editorService.openEditor({ resource: URI.revive(resource), options: { ...optionsArg, override: id } }, group); }); } @@ -632,12 +631,13 @@ export function splitEditor(editorGroupService: IEditorGroupsService, direction: editorToCopy = withNullAsUndefined(sourceGroup.activeEditor); } - if (editorToCopy && (editorToCopy as EditorInput).supportsSplitEditor()) { + // Copy the editor to the new group, else move the editor to the new group + if (editorToCopy && (editorToCopy as EditorInput).canSplit()) { sourceGroup.copyEditor(editorToCopy, newGroup); + // Focus + newGroup.focus(); } - // Focus - newGroup.focus(); } function registerSplitEditorCommands() { diff --git a/src/vs/workbench/browser/parts/editor/editorControl.ts b/src/vs/workbench/browser/parts/editor/editorControl.ts index 353d5def..10819e7d 100644 --- a/src/vs/workbench/browser/parts/editor/editorControl.ts +++ b/src/vs/workbench/browser/parts/editor/editorControl.ts @@ -4,10 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { EditorInput, EditorOptions, IEditorOpenContext, IVisibleEditorPane } from 'vs/workbench/common/editor'; +import { EditorExtensions, EditorInput, EditorOptions, IEditorOpenContext, IVisibleEditorPane } from 'vs/workbench/common/editor'; import { Dimension, show, hide } from 'vs/base/browser/dom'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IEditorRegistry, Extensions as EditorExtensions, IEditorDescriptor } from 'vs/workbench/browser/editor'; +import { IEditorRegistry, IEditorDescriptor } from 'vs/workbench/browser/editor'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -58,7 +58,7 @@ export class EditorControl extends Disposable { // Editor pane const descriptor = Registry.as(EditorExtensions.Editors).getEditor(editor); if (!descriptor) { - throw new Error(`No editor descriptor found for input id ${editor.getTypeId()}`); + throw new Error(`No editor descriptor found for input id ${editor.typeId}`); } const editorPane = this.doShowEditorPane(descriptor); diff --git a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts index d2531b9d..2d813d3e 100644 --- a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts +++ b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts @@ -12,7 +12,7 @@ import { IThemeService, Themable } from 'vs/platform/theme/common/themeService'; import { activeContrastBorder } from 'vs/platform/theme/common/colorRegistry'; import { IEditorIdentifier, EditorInput, EditorOptions } from 'vs/workbench/common/editor'; import { isMacintosh, isWeb } from 'vs/base/common/platform'; -import { GroupDirection, IEditorGroupsService, MergeGroupMode, OpenEditorContext } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { GroupDirection, IEditorGroupsService, MergeGroupMode } from 'vs/workbench/services/editor/common/editorGroupsService'; import { toDisposable } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { RunOnceScheduler } from 'vs/base/common/async'; @@ -26,7 +26,6 @@ import { assertIsDefined, assertAllDefined } from 'vs/base/common/types'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { localize } from 'vs/nls'; import { ByteSize } from 'vs/platform/files/common/files'; -import { EditorOverride } from 'vs/platform/editor/common/editor'; interface IDropOperation { splitDirection?: GroupDirection; @@ -98,7 +97,7 @@ class DropOverlay extends Themable { this.updateStyles(); } - protected updateStyles(): void { + protected override updateStyles(): void { const overlay = assertIsDefined(this.overlay); // Overlay drop background @@ -285,18 +284,17 @@ class DropOverlay extends Themable { const options = getActiveTextEditorOptions(sourceGroup, draggedEditor.editor, EditorOptions.create({ pinned: true, // always pin dropped editor sticky: sourceGroup.isSticky(draggedEditor.editor), // preserve sticky state - override: EditorOverride.DISABLED // preserve editor type })); + const copyEditor = this.isCopyOperation(event, draggedEditor); - targetGroup.openEditor(draggedEditor.editor, options, copyEditor ? OpenEditorContext.COPY_EDITOR : OpenEditorContext.MOVE_EDITOR); + if (!copyEditor) { + sourceGroup.moveEditor(draggedEditor.editor, targetGroup, options); + } else { + sourceGroup.copyEditor(draggedEditor.editor, targetGroup, options); + } // Ensure target has focus targetGroup.focus(); - - // Close in source group unless we copy - if (!copyEditor) { - sourceGroup.closeEditor(draggedEditor.editor); - } } this.editorTransfer.clearData(DraggedEditorIdentifier.prototype); @@ -365,7 +363,7 @@ class DropOverlay extends Themable { } private isCopyOperation(e: DragEvent, draggedEditor?: IEditorIdentifier): boolean { - if (draggedEditor?.editor instanceof EditorInput && !draggedEditor.editor.supportsSplitEditor()) { + if (draggedEditor?.editor instanceof EditorInput && !draggedEditor.editor.canSplit()) { return false; } @@ -545,7 +543,7 @@ class DropOverlay extends Themable { return element === this.container || element === this.overlay; } - dispose(): void { + override dispose(): void { super.dispose(); this._disposed = true; @@ -654,7 +652,7 @@ export class EditorDropTarget extends Themable { this.container.classList.toggle('dragged-over', isDraggedOver); } - dispose(): void { + override dispose(): void { super.dispose(); this.disposeOverlay(); diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index 7f08383a..b30b4a70 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/editorgroupview'; -import { EditorGroup, IEditorOpenOptions, EditorCloseEvent, ISerializedEditorGroup, isSerializedEditorGroup } from 'vs/workbench/common/editor/editorGroup'; -import { EditorInput, EditorOptions, GroupIdentifier, SideBySideEditorInput, CloseDirection, IEditorCloseEvent, ActiveEditorDirtyContext, IEditorPane, EditorGroupEditorsCountContext, SaveReason, IEditorPartOptionsChangeEvent, EditorsOrder, IVisibleEditorPane, ActiveEditorStickyContext, ActiveEditorPinnedContext, EditorResourceAccessor } from 'vs/workbench/common/editor'; +import { EditorGroupModel, IEditorOpenOptions, EditorCloseEvent, ISerializedEditorGroupModel, isSerializedEditorGroupModel } from 'vs/workbench/common/editor/editorGroupModel'; +import { EditorInput, EditorOptions, GroupIdentifier, SideBySideEditorInput, CloseDirection, IEditorCloseEvent, ActiveEditorDirtyContext, IEditorPane, EditorGroupEditorsCountContext, SaveReason, IEditorPartOptionsChangeEvent, EditorsOrder, IVisibleEditorPane, ActiveEditorStickyContext, ActiveEditorPinnedContext, EditorResourceAccessor, IEditorMoveEvent } from 'vs/workbench/common/editor'; import { Event, Emitter, Relay } from 'vs/base/common/event'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { Dimension, trackFocus, addDisposableListener, EventType, EventHelper, findParentWithClass, clearNode, isAncestor, asCSSUrl } from 'vs/base/browser/dom'; @@ -16,7 +16,7 @@ import { attachProgressBarStyler } from 'vs/platform/theme/common/styler'; import { IThemeService, registerThemingParticipant, Themable } from 'vs/platform/theme/common/themeService'; import { editorBackground, contrastBorder } from 'vs/platform/theme/common/colorRegistry'; import { EDITOR_GROUP_HEADER_TABS_BACKGROUND, EDITOR_GROUP_HEADER_NO_TABS_BACKGROUND, EDITOR_GROUP_EMPTY_BACKGROUND, EDITOR_GROUP_FOCUSED_EMPTY_BORDER, EDITOR_GROUP_HEADER_BORDER } from 'vs/workbench/common/theme'; -import { IMoveEditorOptions, ICopyEditorOptions, ICloseEditorsFilter, IGroupChangeEvent, GroupChangeKind, GroupsOrder, ICloseEditorOptions, ICloseAllEditorsOptions, OpenEditorContext } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { ICloseEditorsFilter, IGroupChangeEvent, GroupChangeKind, GroupsOrder, ICloseEditorOptions, ICloseAllEditorsOptions, IEditorReplacement } from 'vs/workbench/services/editor/common/editorGroupsService'; import { TabsTitleControl } from 'vs/workbench/browser/parts/editor/tabsTitleControl'; import { EditorControl } from 'vs/workbench/browser/parts/editor/editorControl'; import { IEditorProgressService } from 'vs/platform/progress/common/progress'; @@ -30,7 +30,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { Promises, RunOnceWorker } from 'vs/base/common/async'; import { EventType as TouchEventType, GestureEvent } from 'vs/base/browser/touch'; import { TitleControl } from 'vs/workbench/browser/parts/editor/titleControl'; -import { IEditorGroupsAccessor, IEditorGroupView, getActiveTextEditorOptions, IEditorOpeningEvent, EditorServiceImpl, IEditorGroupTitleHeight } from 'vs/workbench/browser/parts/editor/editor'; +import { IEditorGroupsAccessor, IEditorGroupView, getActiveTextEditorOptions, EditorServiceImpl, IEditorGroupTitleHeight } from 'vs/workbench/browser/parts/editor/editor'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ActionRunner, IAction, Action } from 'vs/base/common/actions'; @@ -41,16 +41,18 @@ import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { withNullAsUndefined, withUndefinedAsNull } from 'vs/base/common/types'; import { hash } from 'vs/base/common/hash'; import { guessMimeTypes } from 'vs/base/common/mime'; -import { extname } from 'vs/base/common/resources'; +import { extname, isEqual } from 'vs/base/common/resources'; import { FileAccess, Schemas } from 'vs/base/common/network'; import { EditorActivation, EditorOpenContext } from 'vs/platform/editor/common/editor'; import { IDialogService, IFileDialogService, ConfirmResult } from 'vs/platform/dialogs/common/dialogs'; import { ILogService } from 'vs/platform/log/common/log'; import { Codicon } from 'vs/base/common/codicons'; import { IFilesConfigurationService, AutoSaveMode } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; +import { withNullAsUndefined } from 'vs/base/common/types'; +import { URI } from 'vs/base/common/uri'; +import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; export class EditorGroupView extends Themable implements IEditorGroupView { @@ -60,7 +62,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { return instantiationService.createInstance(EditorGroupView, accessor, null, index); } - static createFromSerialized(serialized: ISerializedEditorGroup, accessor: IEditorGroupsAccessor, index: number, instantiationService: IInstantiationService): IEditorGroupView { + static createFromSerialized(serialized: ISerializedEditorGroupModel, accessor: IEditorGroupsAccessor, index: number, instantiationService: IInstantiationService): IEditorGroupView { return instantiationService.createInstance(EditorGroupView, accessor, serialized, index); } @@ -86,9 +88,6 @@ export class EditorGroupView extends Themable implements IEditorGroupView { private readonly _onDidGroupChange = this._register(new Emitter()); readonly onDidGroupChange = this._onDidGroupChange.event; - private readonly _onWillOpenEditor = this._register(new Emitter()); - readonly onWillOpenEditor = this._onWillOpenEditor.event; - private readonly _onDidOpenEditorFail = this._register(new Emitter()); readonly onDidOpenEditorFail = this._onDidOpenEditorFail.event; @@ -98,16 +97,16 @@ export class EditorGroupView extends Themable implements IEditorGroupView { private readonly _onDidCloseEditor = this._register(new Emitter()); readonly onDidCloseEditor = this._onDidCloseEditor.event; + private readonly _onWillMoveEditor = this._register(new Emitter()); + readonly onWillMoveEditor = this._onWillMoveEditor.event; + //#endregion - private readonly _group: EditorGroup; + private readonly model: EditorGroupModel; private active: boolean | undefined; private dimension: Dimension | undefined; - private readonly _whenRestored: Promise; - private isRestored = false; - private readonly scopedInstantiationService: IInstantiationService; private readonly titleContainer: HTMLElement; @@ -122,9 +121,13 @@ export class EditorGroupView extends Themable implements IEditorGroupView { private readonly mapEditorToPendingConfirmation = new Map>(); + private whenRestoredResolve: (() => void) | undefined; + readonly whenRestored = new Promise(resolve => (this.whenRestoredResolve = resolve)); + private isRestored = false; + constructor( private accessor: IEditorGroupsAccessor, - from: IEditorGroupView | ISerializedEditorGroup | null, + from: IEditorGroupView | ISerializedEditorGroupModel | null, private _index: number, @IInstantiationService private readonly instantiationService: IInstantiationService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @@ -138,16 +141,17 @@ export class EditorGroupView extends Themable implements IEditorGroupView { @IFileDialogService private readonly fileDialogService: IFileDialogService, @ILogService private readonly logService: ILogService, @IEditorService private readonly editorService: EditorServiceImpl, - @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService + @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService, + @IUriIdentityService private readonly uriIdentityService: IUriIdentityService ) { super(themeService); if (from instanceof EditorGroupView) { - this._group = this._register(from.group.clone()); - } else if (isSerializedEditorGroup(from)) { - this._group = this._register(instantiationService.createInstance(EditorGroup, from)); + this.model = this._register(from.model.clone()); + } else if (isSerializedEditorGroupModel(from)) { + this.model = this._register(instantiationService.createInstance(EditorGroupModel, from)); } else { - this._group = this._register(instantiationService.createInstance(EditorGroup, undefined)); + this.model = this._register(instantiationService.createInstance(EditorGroupModel, undefined)); } //#region create() @@ -213,9 +217,16 @@ export class EditorGroupView extends Themable implements IEditorGroupView { } //#endregion - this._whenRestored = this.restoreEditors(from); - this._whenRestored.then(() => this.isRestored = true); + // Restore editors if provided + const restoreEditorsPromise = this.restoreEditors(from) ?? Promise.resolve(); + // Signal restored once editors have restored + restoreEditorsPromise.finally(() => { + this.isRestored = true; + this.whenRestoredResolve?.(); + }); + + // Register Listeners this.registerListeners(); } @@ -230,7 +241,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { const observeActiveEditor = () => { activeEditorListener.clear(); - const activeEditor = this._group.activeEditor; + const activeEditor = this.model.activeEditor; if (activeEditor) { groupActiveEditorDirtyContext.set(activeEditor.isDirty() && !activeEditor.isSaving()); activeEditorListener.value = activeEditor.onDidChangeDirty(() => { @@ -250,13 +261,13 @@ export class EditorGroupView extends Themable implements IEditorGroupView { observeActiveEditor(); break; case GroupChangeKind.EDITOR_PIN: - if (e.editor && e.editor === this._group.activeEditor) { - groupActiveEditorPinnedContext.set(this._group.isPinned(this._group.activeEditor)); + if (e.editor && e.editor === this.model.activeEditor) { + groupActiveEditorPinnedContext.set(this.model.isPinned(this.model.activeEditor)); } break; case GroupChangeKind.EDITOR_STICKY: - if (e.editor && e.editor === this._group.activeEditor) { - groupActiveEditorStickyContext.set(this._group.isSticky(this._group.activeEditor)); + if (e.editor && e.editor === this.model.activeEditor) { + groupActiveEditorStickyContext.set(this.model.isSticky(this.model.activeEditor)); } break; } @@ -297,11 +308,11 @@ export class EditorGroupView extends Themable implements IEditorGroupView { this.element.appendChild(toolbarContainer); // Toolbar - const groupId = this._group.id; + const groupId = this.model.id; const containerToolbar = this._register(new ActionBar(toolbarContainer, { ariaLabel: localize('ariaLabelGroupActions', "Editor group actions"), actionRunner: this._register(new class extends ActionRunner { - run(action: IAction) { - return action.run(groupId); + override async run(action: IAction) { + await action.run(groupId); } }) })); @@ -439,8 +450,8 @@ export class EditorGroupView extends Themable implements IEditorGroupView { return this.titleAreaControl; } - private async restoreEditors(from: IEditorGroupView | ISerializedEditorGroup | null): Promise { - if (this._group.count === 0) { + private restoreEditors(from: IEditorGroupView | ISerializedEditorGroupModel | null): Promise | undefined { + if (this.model.count === 0) { return; // nothing to show } @@ -452,27 +463,29 @@ export class EditorGroupView extends Themable implements IEditorGroupView { options = new EditorOptions(); } - const activeEditor = this._group.activeEditor; + const activeEditor = this.model.activeEditor; if (!activeEditor) { return; } - options.pinned = this._group.isPinned(activeEditor); // preserve pinned state - options.sticky = this._group.isSticky(activeEditor); // preserve sticky state + options.pinned = this.model.isPinned(activeEditor); // preserve pinned state + options.sticky = this.model.isSticky(activeEditor); // preserve sticky state options.preserveFocus = true; // handle focus after editor is opened const activeElement = document.activeElement; - // Show active editor - await this.doShowEditor(activeEditor, { active: true, isNew: false /* restored */ }, options); + // Show active editor (intentionally not using async to keep + // `restoreEditors` from executing in same stack) + return this.doShowEditor(activeEditor, { active: true, isNew: false /* restored */ }, options).then(() => { - // Set focused now if this is the active group and focus has - // not changed meanwhile. This prevents focus from being - // stolen accidentally on startup when the user already - // clicked somewhere. - if (this.accessor.activeGroup === this && activeElement === document.activeElement) { - this.focus(); - } + // Set focused now if this is the active group and focus has + // not changed meanwhile. This prevents focus from being + // stolen accidentally on startup when the user already + // clicked somewhere. + if (this.accessor.activeGroup === this && activeElement === document.activeElement) { + this.focus(); + } + }); } //#region event handling @@ -480,13 +493,13 @@ export class EditorGroupView extends Themable implements IEditorGroupView { private registerListeners(): void { // Model Events - this._register(this._group.onDidChangeEditorPinned(editor => this.onDidChangeEditorPinned(editor))); - this._register(this._group.onDidChangeEditorSticky(editor => this.onDidChangeEditorSticky(editor))); - this._register(this._group.onDidOpenEditor(editor => this.onDidOpenEditor(editor))); - this._register(this._group.onDidCloseEditor(editor => this.handleOnDidCloseEditor(editor))); - this._register(this._group.onDidDisposeEditor(editor => this.onDidDisposeEditor(editor))); - this._register(this._group.onDidChangeEditorDirty(editor => this.onDidChangeEditorDirty(editor))); - this._register(this._group.onDidEditorLabelChange(editor => this.onDidEditorLabelChange(editor))); + this._register(this.model.onDidChangeEditorPinned(editor => this.onDidChangeEditorPinned(editor))); + this._register(this.model.onDidChangeEditorSticky(editor => this.onDidChangeEditorSticky(editor))); + this._register(this.model.onDidOpenEditor(editor => this.onDidOpenEditor(editor))); + this._register(this.model.onDidCloseEditor(editor => this.handleOnDidCloseEditor(editor))); + this._register(this.model.onWillDisposeEditor(editor => this.onWillDisposeEditor(editor))); + this._register(this.model.onDidChangeEditorDirty(editor => this.onDidChangeEditorDirty(editor))); + this._register(this.model.onDidEditorLabelChange(editor => this.onDidEditorLabelChange(editor))); // Option Changes this._register(this.accessor.onDidChangeEditorPartOptions(e => this.onDidChangeEditorPartOptions(e))); @@ -540,10 +553,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // (including being visible in side by side / diff editors) and as such we // only dispose when they are not opened elsewhere. for (const editor of editorsToClose) { - if (!this.accessor.groups.some(groupView => groupView.group.contains(editor, { - strictEquals: true, // only if this input is not shared across editor groups - supportSideBySide: true // include side by side editor primary & secondary - }))) { + if (this.canDispose(editor)) { editor.dispose(); } } @@ -565,6 +575,19 @@ export class EditorGroupView extends Themable implements IEditorGroupView { this._onDidGroupChange.fire({ kind: GroupChangeKind.EDITOR_CLOSE, editor, editorIndex: event.index }); } + private canDispose(editor: EditorInput): boolean { + for (const groupView of this.accessor.groups) { + if (groupView instanceof EditorGroupView && groupView.model.contains(editor, { + strictEquals: true, // only if this input is not shared across editor groups + supportSideBySide: true // include side by side editor primary & secondary + })) { + return false; + } + } + + return true; + } + private toEditorTelemetryDescriptor(editor: EditorInput): object { const descriptor = editor.getTelemetryDescriptor(); @@ -584,7 +607,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { return descriptor; } - private onDidDisposeEditor(editor: EditorInput): void { + private onWillDisposeEditor(editor: EditorInput): void { // To prevent race conditions, we handle disposed editors in our worker with a timeout // because it can happen that an input is being disposed with the intent to replace @@ -598,9 +621,9 @@ export class EditorGroupView extends Themable implements IEditorGroupView { let activeEditor: EditorInput | undefined; const inactiveEditors: EditorInput[] = []; for (const editor of editors) { - if (this._group.isActive(editor)) { + if (this.model.isActive(editor)) { activeEditor = editor; - } else if (this._group.contains(editor)) { + } else if (this.model.contains(editor)) { inactiveEditors.push(editor); } } @@ -631,8 +654,8 @@ export class EditorGroupView extends Themable implements IEditorGroupView { this.relayout(); // Ensure to show active editor if any - if (this._group.activeEditor) { - this.titleAreaControl.openEditor(this._group.activeEditor); + if (this.model.activeEditor) { + this.titleAreaControl.openEditor(this.model.activeEditor); } } @@ -646,8 +669,8 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Pin preview editor once user disables preview if (event.oldPartOptions.enablePreview && !event.newPartOptions.enablePreview) { - if (this._group.previewEditor) { - this.pinEditor(this._group.previewEditor); + if (this.model.previewEditor) { + this.pinEditor(this.model.previewEditor); } } } @@ -681,11 +704,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { //#endregion - //region IEditorGroupView - - get group(): EditorGroup { - return this._group; - } + //#region IEditorGroupView get index(): number { return this._index; @@ -704,12 +723,8 @@ export class EditorGroupView extends Themable implements IEditorGroupView { return this._disposed; } - get whenRestored(): Promise { - return this._whenRestored; - } - get isEmpty(): boolean { - return this._group.count === 0; + return this.model.count === 0; } get titleHeight(): IEditorGroupTitleHeight { @@ -755,19 +770,19 @@ export class EditorGroupView extends Themable implements IEditorGroupView { //#region basics() get id(): GroupIdentifier { - return this._group.id; + return this.model.id; } get editors(): EditorInput[] { - return this._group.getEditors(EditorsOrder.SEQUENTIAL); + return this.model.getEditors(EditorsOrder.SEQUENTIAL); } get count(): number { - return this._group.count; + return this.model.count; } get stickyCount(): number { - return this._group.stickyCount; + return this.model.stickyCount; } get activeEditorPane(): IVisibleEditorPane | undefined { @@ -775,39 +790,46 @@ export class EditorGroupView extends Themable implements IEditorGroupView { } get activeEditor(): EditorInput | null { - return this._group.activeEditor; + return this.model.activeEditor; } get previewEditor(): EditorInput | null { - return this._group.previewEditor; + return this.model.previewEditor; } isPinned(editor: EditorInput): boolean { - return this._group.isPinned(editor); + return this.model.isPinned(editor); } isSticky(editorOrIndex: EditorInput | number): boolean { - return this._group.isSticky(editorOrIndex); + return this.model.isSticky(editorOrIndex); } isActive(editor: EditorInput): boolean { - return this._group.isActive(editor); + return this.model.isActive(editor); + } + + contains(candidate: EditorInput): boolean { + return this.model.contains(candidate); } getEditors(order: EditorsOrder, options?: { excludeSticky?: boolean }): EditorInput[] { - return this._group.getEditors(order, options); + return this.model.getEditors(order, options); + } + + findEditors(resource: URI): EditorInput[] { + const canonicalResource = this.uriIdentityService.asCanonicalUri(resource); + return this.getEditors(EditorsOrder.SEQUENTIAL).filter(editor => { + return editor.resource && isEqual(editor.resource, canonicalResource); + }); } getEditorByIndex(index: number): EditorInput | undefined { - return this._group.getEditorByIndex(index); + return this.model.getEditorByIndex(index); } getIndexOfEditor(editor: EditorInput): number { - return this._group.indexOf(editor); - } - - isOpened(editor: EditorInput): boolean { - return this._group.contains(editor); + return this.model.indexOf(editor); } focus(): void { @@ -824,10 +846,10 @@ export class EditorGroupView extends Themable implements IEditorGroupView { } pinEditor(candidate: EditorInput | undefined = this.activeEditor || undefined): void { - if (candidate && !this._group.isPinned(candidate)) { + if (candidate && !this.model.isPinned(candidate)) { // Update model - const editor = this._group.pin(candidate); + const editor = this.model.pin(candidate); // Forward to title control if (editor) { @@ -845,11 +867,11 @@ export class EditorGroupView extends Themable implements IEditorGroupView { } private doStickEditor(candidate: EditorInput | undefined, sticky: boolean): void { - if (candidate && this._group.isSticky(candidate) !== sticky) { + if (candidate && this.model.isSticky(candidate) !== sticky) { const oldIndexOfEditor = this.getIndexOfEditor(candidate); // Update model - const editor = sticky ? this._group.stick(candidate) : this._group.unstick(candidate); + const editor = sticky ? this.model.stick(candidate) : this.model.unstick(candidate); if (!editor) { return; } @@ -877,23 +899,15 @@ export class EditorGroupView extends Themable implements IEditorGroupView { //#region openEditor() - async openEditor(editor: EditorInput, options?: EditorOptions, context?: OpenEditorContext): Promise { + async openEditor(editor: EditorInput, options?: EditorOptions): Promise { // Guard against invalid inputs if (!editor) { - return null; - } - - // Editor opening event allows for prevention - const event = new EditorOpeningEvent(this._group.id, editor, options, context); - this._onWillOpenEditor.fire(event); - const prevented = event.isPrevented(); - if (prevented) { - return withUndefinedAsNull(await prevented()); + return undefined; } // Proceed with opening - return withUndefinedAsNull(await this.doOpenEditor(editor, options)); + return this.doOpenEditor(editor, options); } private async doOpenEditor(editor: EditorInput, options?: EditorOptions): Promise { @@ -908,19 +922,19 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Determine options const openEditorOptions: IEditorOpenOptions = { index: options ? options.index : undefined, - pinned: options?.sticky || !this.accessor.partOptions.enablePreview || editor.isDirty() || (options?.pinned ?? typeof options?.index === 'number' /* unless specified, prefer to pin when opening with index */) || (typeof options?.index === 'number' && this._group.isSticky(options.index)), - sticky: options?.sticky || (typeof options?.index === 'number' && this._group.isSticky(options.index)), - active: this._group.count === 0 || !options || !options.inactive + pinned: options?.sticky || !this.accessor.partOptions.enablePreview || editor.isDirty() || (options?.pinned ?? typeof options?.index === 'number' /* unless specified, prefer to pin when opening with index */) || (typeof options?.index === 'number' && this.model.isSticky(options.index)), + sticky: options?.sticky || (typeof options?.index === 'number' && this.model.isSticky(options.index)), + active: this.model.count === 0 || !options || !options.inactive }; - if (options?.sticky && typeof options?.index === 'number' && !this._group.isSticky(options.index)) { + if (options?.sticky && typeof options?.index === 'number' && !this.model.isSticky(options.index)) { // Special case: we are to open an editor sticky but at an index that is not sticky // In that case we prefer to open the editor at the index but not sticky. This enables // to drag a sticky editor to an index that is not sticky to unstick it. openEditorOptions.sticky = false; } - if (!openEditorOptions.active && !openEditorOptions.pinned && this._group.activeEditor && !this._group.isPinned(this._group.activeEditor)) { + if (!openEditorOptions.active && !openEditorOptions.pinned && this.model.activeEditor && !this.model.isPinned(this.model.activeEditor)) { // Special case: we are to open an editor inactive and not pinned, but the current active // editor is also not pinned, which means it will get replaced with this one. As such, // the editor can only be active. @@ -953,7 +967,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // out that the editor is already opened at a different index. This // ensures the right set of events are fired to the outside. if (typeof openEditorOptions.index === 'number') { - const indexOfEditor = this._group.indexOf(editor); + const indexOfEditor = this.model.indexOf(editor); if (indexOfEditor !== -1 && indexOfEditor !== openEditorOptions.index) { this.doMoveEditorInsideGroup(editor, openEditorOptions); } @@ -962,7 +976,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Update model and make sure to continue to use the editor we get from // the model. It is possible that the editor was already opened and we // want to ensure that we use the existing instance in that case. - const { editor: openedEditor, isNew } = this._group.openEditor(editor, openEditorOptions); + const { editor: openedEditor, isNew } = this.model.openEditor(editor, openEditorOptions); // Show editor const showEditorResult = this.doShowEditor(openedEditor, { active: !!openEditorOptions.active, isNew }, options); @@ -977,10 +991,10 @@ export class EditorGroupView extends Themable implements IEditorGroupView { return showEditorResult; } - private async doShowEditor(editor: EditorInput, context: { active: boolean, isNew: boolean }, options?: EditorOptions): Promise { + private doShowEditor(editor: EditorInput, context: { active: boolean, isNew: boolean }, options?: EditorOptions): Promise { // Show in editor control if the active editor changed - let openEditorPromise: Promise | undefined; + let openEditorPromise: Promise; if (context.active) { openEditorPromise = (async () => { try { @@ -1001,7 +1015,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { } })(); } else { - openEditorPromise = undefined; // inactive: return undefined as result to signal this + openEditorPromise = Promise.resolve(undefined); // inactive: return undefined as result to signal this } // Show in title control after editor control because some actions depend on it @@ -1021,7 +1035,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { if (this.isRestored) { // Extract possible error actions from the error - let errorActions: ReadonlyArray | undefined = undefined; + let errorActions: readonly IAction[] | undefined = undefined; if (isErrorWithActions(error)) { errorActions = error.actions; } @@ -1070,6 +1084,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { } const handle = this.notificationService.notify({ + id: `${hash(editor.resource?.toString())}`, // unique per editor severity: Severity.Error, message: localize('editorOpenError', "Unable to open '{0}': {1}.", editor.getName(), toErrorMessage(error)), actions @@ -1132,7 +1147,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { //#region moveEditor() - moveEditor(editor: EditorInput, target: IEditorGroupView, options?: IMoveEditorOptions): void { + moveEditor(editor: EditorInput, target: IEditorGroupView, options?: EditorOptions): void { // Move within same group if (this === target) { @@ -1145,13 +1160,13 @@ export class EditorGroupView extends Themable implements IEditorGroupView { } } - private doMoveEditorInsideGroup(candidate: EditorInput, moveOptions?: IMoveEditorOptions): void { - const moveToIndex = moveOptions ? moveOptions.index : undefined; + private doMoveEditorInsideGroup(candidate: EditorInput, options?: IEditorOpenOptions): void { + const moveToIndex = options ? options.index : undefined; if (typeof moveToIndex !== 'number') { return; // do nothing if we move into same group without index } - const currentIndex = this._group.indexOf(candidate); + const currentIndex = this.model.indexOf(candidate); if (currentIndex === -1 || currentIndex === moveToIndex) { return; // do nothing if editor unknown in model or is already at the given index } @@ -1159,14 +1174,14 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Update model and make sure to continue to use the editor we get from // the model. It is possible that the editor was already opened and we // want to ensure that we use the existing instance in that case. - const editor = this._group.getEditorByIndex(currentIndex); + const editor = this.model.getEditorByIndex(currentIndex); if (!editor) { return; } // Update model - this._group.moveEditor(editor, moveToIndex); - this._group.pin(editor); + this.model.moveEditor(editor, moveToIndex); + this.model.pin(editor); // Forward to title area this.titleAreaControl.moveEditor(editor, currentIndex, moveToIndex); @@ -1176,19 +1191,28 @@ export class EditorGroupView extends Themable implements IEditorGroupView { this._onDidGroupChange.fire({ kind: GroupChangeKind.EDITOR_MOVE, editor }); } - private doMoveOrCopyEditorAcrossGroups(editor: EditorInput, target: IEditorGroupView, moveOptions: IMoveEditorOptions = Object.create(null), keepCopy?: boolean): void { + private doMoveOrCopyEditorAcrossGroups(editor: EditorInput, target: IEditorGroupView, openOptions?: IEditorOpenOptions, keepCopy?: boolean): void { // When moving/copying an editor, try to preserve as much view state as possible // by checking for the editor to be a text editor and creating the options accordingly // if so const options = getActiveTextEditorOptions(this, editor, EditorOptions.create({ - ...moveOptions, + ...openOptions, pinned: true, // always pin moved editor - sticky: !keepCopy && this._group.isSticky(editor) // preserve sticky state only if editor is moved (https://github.com/microsoft/vscode/issues/99035) + sticky: !keepCopy && this.model.isSticky(editor) // preserve sticky state only if editor is moved (https://github.com/microsoft/vscode/issues/99035) })); + // Indicate will move event + if (!keepCopy) { + this._onWillMoveEditor.fire({ + groupId: this.id, + editor, + target: target.id, + }); + } + // A move to another group is an open first... - target.openEditor(editor, options, keepCopy ? OpenEditorContext.COPY_EDITOR : OpenEditorContext.MOVE_EDITOR); + target.openEditor(keepCopy ? editor.copy() : editor, options); // ...and a close afterwards (unless we copy) if (!keepCopy) { @@ -1200,7 +1224,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { //#region copyEditor() - copyEditor(editor: EditorInput, target: IEditorGroupView, options?: ICopyEditorOptions): void { + copyEditor(editor: EditorInput, target: IEditorGroupView, options?: EditorOptions): void { // Move within same group because we do not support to show the same editor // multiple times in the same group @@ -1242,7 +1266,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { private doCloseEditor(editor: EditorInput, focusNext = (this.accessor.activeGroup === this), fromError?: boolean): void { // Closing the active editor of the group is a bit more work - if (this._group.isActive(editor)) { + if (this.model.isActive(editor)) { this.doCloseActiveEditor(focusNext, fromError); } @@ -1267,7 +1291,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // event because it became empty, only to then trigger another one when the next // group gets active. const closeEmptyGroup = this.accessor.partOptions.closeEmptyGroups; - if (closeEmptyGroup && this.active && this._group.count === 1) { + if (closeEmptyGroup && this.active && this.model.count === 1) { const mostRecentlyActiveGroups = this.accessor.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE); const nextActiveGroup = mostRecentlyActiveGroups[1]; // [0] will be the current one, so take [1] if (nextActiveGroup) { @@ -1281,11 +1305,11 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Update model if (editorToClose) { - this._group.closeEditor(editorToClose); + this.model.closeEditor(editorToClose); } // Open next active if there are more to show - const nextActiveEditor = this._group.activeEditor; + const nextActiveEditor = this.model.activeEditor; if (nextActiveEditor) { const preserveFocus = !focusNext; @@ -1349,7 +1373,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { private doCloseInactiveEditor(editor: EditorInput) { // Update model - this._group.closeEditor(editor); + this.model.closeEditor(editor); } private async handleDirtyClosing(editors: EditorInput[]): Promise { @@ -1388,7 +1412,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { return false; // editor must be dirty and not saving } - if (editor instanceof SideBySideEditorInput && this._group.contains(editor.primary)) { + if (editor instanceof SideBySideEditorInput && this.model.contains(editor.primary)) { return false; // primary-side of editor is still opened somewhere else } @@ -1402,7 +1426,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { return false; // skip this group to avoid false assumptions about the editor being opened still } - const otherGroup = groupView.group; + const otherGroup = groupView; if (otherGroup.contains(editor)) { return true; // exact editor still opened } @@ -1517,7 +1541,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { const filter = args; const hasDirection = typeof filter.direction === 'number'; - let editorsToClose = this._group.getEditors(hasDirection ? EditorsOrder.SEQUENTIAL : EditorsOrder.MOST_RECENTLY_ACTIVE, filter); // in MRU order only if direction is not specified + let editorsToClose = this.model.getEditors(hasDirection ? EditorsOrder.SEQUENTIAL : EditorsOrder.MOST_RECENTLY_ACTIVE, filter); // in MRU order only if direction is not specified // Filter: saved or saving only if (filter.savedOnly) { @@ -1527,8 +1551,8 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Filter: direction (left / right) else if (hasDirection && filter.except) { editorsToClose = (filter.direction === CloseDirection.LEFT) ? - editorsToClose.slice(0, this._group.indexOf(filter.except, editorsToClose)) : - editorsToClose.slice(this._group.indexOf(filter.except, editorsToClose) + 1); + editorsToClose.slice(0, this.model.indexOf(filter.except, editorsToClose)) : + editorsToClose.slice(this.model.indexOf(filter.except, editorsToClose) + 1); } // Filter: except @@ -1580,7 +1604,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { } // Check for dirty and veto - const veto = await this.handleDirtyClosing(this._group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE, options)); + const veto = await this.handleDirtyClosing(this.model.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE, options)); if (veto) { return; } @@ -1593,7 +1617,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Close all inactive editors first const editorsToClose: EditorInput[] = []; - for (const editor of this._group.getEditors(EditorsOrder.SEQUENTIAL, options)) { + for (const editor of this.model.getEditors(EditorsOrder.SEQUENTIAL, options)) { if (!this.isActive(editor)) { this.doCloseInactiveEditor(editor); } @@ -1689,7 +1713,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { //#region Themable - protected updateStyles(): void { + protected override updateStyles(): void { const isEmpty = this.isEmpty; // Container @@ -1752,13 +1776,13 @@ export class EditorGroupView extends Themable implements IEditorGroupView { } } - toJSON(): ISerializedEditorGroup { - return this._group.serialize(); + toJSON(): ISerializedEditorGroupModel { + return this.model.serialize(); } //#endregion - dispose(): void { + override dispose(): void { this._disposed = true; this._onWillDispose.fire(); @@ -1769,36 +1793,9 @@ export class EditorGroupView extends Themable implements IEditorGroupView { } } -class EditorOpeningEvent implements IEditorOpeningEvent { - private override: (() => Promise) | undefined = undefined; - - constructor( - public readonly groupId: GroupIdentifier, - public readonly editor: EditorInput, - private _options: EditorOptions | undefined, - public readonly context: OpenEditorContext | undefined - ) { - } - - get options(): EditorOptions | undefined { - return this._options; - } - - - prevent(callback: () => Promise): void { - this.override = callback; - } - - isPrevented(): (() => Promise) | undefined { - return this.override; - } -} - -export interface EditorReplacement { +export interface EditorReplacement extends IEditorReplacement { readonly editor: EditorInput; readonly replacement: EditorInput; - /** Skips asking the user for confirmation and doesn't save the document. Only use this if you really need to! */ - readonly forceReplaceDirty?: boolean; readonly options?: EditorOptions; } diff --git a/src/vs/workbench/browser/parts/editor/editorPane.ts b/src/vs/workbench/browser/parts/editor/editorPane.ts index b17f530e..081a0a8a 100644 --- a/src/vs/workbench/browser/parts/editor/editorPane.ts +++ b/src/vs/workbench/browser/parts/editor/editorPane.ts @@ -76,7 +76,7 @@ export abstract class EditorPane extends Composite implements IEditorPane { super(id, telemetryService, themeService, storageService); } - create(parent: HTMLElement): void { + override create(parent: HTMLElement): void { super.create(parent); // Create Editor @@ -133,7 +133,7 @@ export abstract class EditorPane extends Composite implements IEditorPane { this._options = options; } - setVisible(visible: boolean, group?: IEditorGroup): void { + override setVisible(visible: boolean, group?: IEditorGroup): void { super.setVisible(visible); // Propagate to Editor @@ -163,7 +163,7 @@ export abstract class EditorPane extends Composite implements IEditorPane { return editorMemento; } - protected saveState(): void { + protected override saveState(): void { // Save all editor memento for this editor type for (const [, editorMemento] of EditorPane.EDITOR_MEMENTOS) { @@ -175,7 +175,7 @@ export abstract class EditorPane extends Composite implements IEditorPane { super.saveState(); } - dispose(): void { + override dispose(): void { this._input = undefined; this._options = undefined; @@ -274,7 +274,7 @@ export class EditorMemento implements IEditorMemento { } if (!this.editorDisposables.has(editor)) { - this.editorDisposables.set(editor, Event.once(editor.onDispose)(() => { + this.editorDisposables.set(editor, Event.once(editor.onWillDispose)(() => { this.clearEditorState(resource); this.editorDisposables?.delete(editor); })); diff --git a/src/vs/workbench/browser/parts/editor/editorPart.ts b/src/vs/workbench/browser/parts/editor/editorPart.ts index 01dc3928..1383de7a 100644 --- a/src/vs/workbench/browser/parts/editor/editorPart.ts +++ b/src/vs/workbench/browser/parts/editor/editorPart.ts @@ -19,7 +19,7 @@ import { EditorGroupView } from 'vs/workbench/browser/parts/editor/editorGroupVi import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; import { IDisposable, dispose, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; -import { ISerializedEditorGroup, isSerializedEditorGroup } from 'vs/workbench/common/editor/editorGroup'; +import { ISerializedEditorGroupModel, isSerializedEditorGroupModel } from 'vs/workbench/common/editor/editorGroupModel'; import { EditorDropTarget, IEditorDropTargetDelegate } from 'vs/workbench/browser/parts/editor/editorDropTarget'; import { IEditorDropService } from 'vs/workbench/services/editor/browser/editorDropService'; import { Color } from 'vs/base/common/color'; @@ -27,7 +27,6 @@ import { CenteredViewLayout } from 'vs/base/browser/ui/centered/centeredViewLayo import { onUnexpectedError } from 'vs/base/common/errors'; import { Parts, IWorkbenchLayoutService, Position } from 'vs/workbench/services/layout/browser/layoutService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { MementoObject } from 'vs/workbench/common/memento'; import { assertIsDefined } from 'vs/base/common/types'; import { IBoundarySashes } from 'vs/base/browser/ui/grid/gridview'; import { CompositeDragAndDropObserver } from 'vs/workbench/browser/dnd'; @@ -121,8 +120,8 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro //#endregion - private readonly workspaceMemento: MementoObject; - private readonly globalMemento: MementoObject; + private readonly workspaceMemento = this.getMemento(StorageScope.WORKSPACE, StorageTarget.MACHINE); + private readonly globalMemento = this.getMemento(StorageScope.GLOBAL, StorageTarget.MACHINE); private readonly groupViews = new Map(); private mostRecentActiveGroups: GroupIdentifier[] = []; @@ -132,10 +131,7 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro private centeredLayoutWidget!: CenteredViewLayout; private gridWidget!: SerializableGrid; - private gridWidgetView: GridWidgetView; - - private _whenRestored: Promise; - private whenRestoredResolve: (() => void) | undefined; + private readonly gridWidgetView = this._register(new GridWidgetView()); constructor( @IInstantiationService private readonly instantiationService: IInstantiationService, @@ -146,13 +142,6 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro ) { super(Parts.EDITOR_PART, { hasTitle: false }, themeService, storageService, layoutService); - this.gridWidgetView = new GridWidgetView(); - - this.workspaceMemento = this.getMemento(StorageScope.WORKSPACE, StorageTarget.MACHINE); - this.globalMemento = this.getMemento(StorageScope.GLOBAL, StorageTarget.MACHINE); - - this._whenRestored = new Promise(resolve => (this.whenRestoredResolve = resolve)); - this.registerListeners(); } @@ -217,11 +206,18 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro return (this.gridWidget && this.gridWidget.orientation === Orientation.VERTICAL) ? GroupOrientation.VERTICAL : GroupOrientation.HORIZONTAL; } - get whenRestored(): Promise { - return this._whenRestored; + private whenReadyResolve: (() => void) | undefined; + readonly whenReady = new Promise(resolve => (this.whenReadyResolve = resolve)); + + private whenRestoredResolve: (() => void) | undefined; + readonly whenRestored = new Promise(resolve => (this.whenRestoredResolve = resolve)); + private restored = false; + + isRestored(): boolean { + return this.restored; } - get willRestoreEditors(): boolean { + get hasRestorableState(): boolean { return !!this.workspaceMemento[EditorPart.EDITOR_PART_UI_STATE_STORAGE_KEY]; } @@ -522,13 +518,13 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro return this._partOptions.splitSizing === 'split' ? Sizing.Split : Sizing.Distribute; } - private doCreateGroupView(from?: IEditorGroupView | ISerializedEditorGroup | null): IEditorGroupView { + private doCreateGroupView(from?: IEditorGroupView | ISerializedEditorGroupModel | null): IEditorGroupView { // Create group view let groupView: IEditorGroupView; if (from instanceof EditorGroupView) { groupView = EditorGroupView.createCopy(from, this, this.count, this.instantiationService); - } else if (isSerializedEditorGroup(from)) { + } else if (isSerializedEditorGroupModel(from)) { groupView = EditorGroupView.createFromSerialized(from, this, this.count, this.instantiationService); } else { groupView = EditorGroupView.createNew(this, this.count, this.instantiationService); @@ -811,14 +807,14 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro readonly snap = true; - get onDidChange(): Event { return Event.any(this.centeredLayoutWidget.onDidChange, this.onDidSetGridWidget.event); } + override get onDidChange(): Event { return Event.any(this.centeredLayoutWidget.onDidChange, this.onDidSetGridWidget.event); } readonly priority: LayoutPriority = LayoutPriority.High; private get gridSeparatorBorder(): Color { return this.theme.getColor(EDITOR_GROUP_BORDER) || this.theme.getColor(contrastBorder) || Color.transparent; } - updateStyles(): void { + override updateStyles(): void { const container = assertIsDefined(this.container); container.style.backgroundColor = this.getColor(editorBackground) || ''; @@ -827,7 +823,7 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro this.centeredLayoutWidget.styles(separatorBorderStyle); } - createContentArea(parent: HTMLElement, options?: IEditorPartCreationOptions): HTMLElement { + override createContentArea(parent: HTMLElement, options?: IEditorPartCreationOptions): HTMLElement { // Container this.element = parent; @@ -835,13 +831,31 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro this.container.classList.add('content'); parent.appendChild(this.container); - // Grid control with center layout + // Grid control this.doCreateGridControl(options); + // Centered layout widget this.centeredLayoutWidget = this._register(new CenteredViewLayout(this.container, this.gridWidgetView, this.globalMemento[EditorPart.EDITOR_PART_CENTERED_VIEW_STORAGE_KEY])); - // Drop support - this._register(this.createEditorDropTarget(this.container, Object.create(null))); + // Drag & Drop support + this.setupDragAndDropSupport(parent, this.container); + + // Signal ready + this.whenReadyResolve?.(); + + // Signal restored + Promises.settled(this.groups.map(group => group.whenRestored)).finally(() => { + this.restored = true; + this.whenRestoredResolve?.(); + }); + + return this.container; + } + + private setupDragAndDropSupport(parent: HTMLElement, container: HTMLElement): void { + + // Editor drop target + this._register(this.createEditorDropTarget(container, Object.create(null))); // No drop in the editor const overlay = document.createElement('div'); @@ -849,17 +863,11 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro parent.appendChild(overlay); // Hide the block if a mouse down event occurs #99065 - this._register(addDisposableGenericMouseDownListner(overlay, () => { - overlay.classList.remove('visible'); - })); + this._register(addDisposableGenericMouseDownListner(overlay, () => overlay.classList.remove('visible'))); this._register(CompositeDragAndDropObserver.INSTANCE.registerTarget(this.element, { - onDragStart: e => { - overlay.classList.add('visible'); - }, - onDragEnd: e => { - overlay.classList.remove('visible'); - } + onDragStart: e => overlay.classList.add('visible'), + onDragEnd: e => overlay.classList.remove('visible') })); let panelOpenerTimeout: any; @@ -920,8 +928,6 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro } } })); - - return this.container; } centerLayout(active: boolean): void { @@ -955,13 +961,6 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro this.doSetGroupActive(initialGroup); } - // Signal restored - Promises.settled(this.groups.map(group => group.whenRestored)).finally(() => { - if (this.whenRestoredResolve) { - this.whenRestoredResolve(); - } - }); - // Update container this.updateContainer(); @@ -1012,7 +1011,7 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro // Create new const groupViews: IEditorGroupView[] = []; const gridWidget = SerializableGrid.deserialize(serializedGrid, { - fromJSON: (serializedEditorGroup: ISerializedEditorGroup | null) => { + fromJSON: (serializedEditorGroup: ISerializedEditorGroupModel | null) => { let groupView: IEditorGroupView; if (reuseGroupViews.length > 0) { groupView = reuseGroupViews.shift()!; @@ -1081,7 +1080,7 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro this.centeredLayoutWidget.boundarySashes = sashes; } - layout(width: number, height: number): void { + override layout(width: number, height: number): void { // Layout contents const contentAreaSize = super.layoutContents(width, height).contentSize; @@ -1100,7 +1099,7 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro this._onDidLayout.fire(dimension); } - protected saveState(): void { + protected override saveState(): void { // Persist grid UI state if (this.gridWidget) { @@ -1136,16 +1135,14 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro }; } - dispose(): void { + override dispose(): void { // Forward to all groups this.groupViews.forEach(group => group.dispose()); this.groupViews.clear(); // Grid widget - if (this.gridWidget) { - this.gridWidget.dispose(); - } + this.gridWidget?.dispose(); super.dispose(); } diff --git a/src/vs/workbench/browser/parts/editor/editorQuickAccess.ts b/src/vs/workbench/browser/parts/editor/editorQuickAccess.ts index ca4c7f6f..a2e88453 100644 --- a/src/vs/workbench/browser/parts/editor/editorQuickAccess.ts +++ b/src/vs/workbench/browser/parts/editor/editorQuickAccess.ts @@ -59,7 +59,7 @@ export abstract class BaseEditorQuickAccessProvider extends PickerQuickAccessPro ); } - provide(picker: IQuickPick, token: CancellationToken): IDisposable { + override provide(picker: IQuickPick, token: CancellationToken): IDisposable { // Reset the pick state for this run this.pickState.reset(!!picker.quickNavigate); @@ -172,7 +172,7 @@ export abstract class BaseEditorQuickAccessProvider extends PickerQuickAccessPro if (group) { await group.closeEditor(editor, { preserveFocus: true }); - if (!group.isOpened(editor)) { + if (!group.contains(editor)) { return TriggerAction.REMOVE_ITEM; } } diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index adfc7779..4c2e352c 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -13,7 +13,7 @@ import { URI } from 'vs/base/common/uri'; import { Action, WorkbenchActionExecutedClassification, WorkbenchActionExecutedEvent } from 'vs/base/common/actions'; import { Language } from 'vs/base/common/platform'; import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; -import { IFileEditorInput, EncodingMode, IEncodingSupport, EditorResourceAccessor, SideBySideEditorInput, IEditorPane, IEditorInput, SideBySideEditor, IModeSupport } from 'vs/workbench/common/editor'; +import { IFileEditorInput, EditorResourceAccessor, SideBySideEditorInput, IEditorPane, IEditorInput, SideBySideEditor } from 'vs/workbench/common/editor'; import { Disposable, MutableDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IEditorAction } from 'vs/editor/common/editorCommon'; import { EndOfLineSequence } from 'vs/editor/common/model'; @@ -31,7 +31,7 @@ import { Selection } from 'vs/editor/common/core/selection'; import { TabFocus } from 'vs/editor/common/config/commonEditorConfig'; import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { EncodingMode, IEncodingSupport, IModeSupport, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { SUPPORTED_ENCODINGS } from 'vs/workbench/services/textfile/common/encoding'; import { ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorEvents'; import { ConfigurationChangedEvent, IEditorOptions, EditorOption } from 'vs/editor/common/config/editorOptions'; @@ -43,7 +43,7 @@ import { Schemas } from 'vs/base/common/network'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { IQuickInputService, IQuickPickItem, QuickPickInput } from 'vs/platform/quickinput/common/quickInput'; import { getIconClassesForModeId } from 'vs/editor/common/services/getIconClasses'; -import { timeout } from 'vs/base/common/async'; +import { Promises, timeout } from 'vs/base/common/async'; import { INotificationHandle, INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { Event } from 'vs/base/common/event'; import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; @@ -61,8 +61,8 @@ class SideBySideEditorEncodingSupport implements IEncodingSupport { return this.primary.getEncoding(); // always report from modified (right hand) side } - setEncoding(encoding: string, mode: EncodingMode): void { - [this.primary, this.secondary].forEach(editor => editor.setEncoding(encoding, mode)); + async setEncoding(encoding: string, mode: EncodingMode): Promise { + await Promises.settled([this.primary, this.secondary].map(editor => editor.setEncoding(encoding, mode))); } } @@ -976,7 +976,7 @@ class ShowCurrentMarkerInStatusbarContribution extends Disposable { return this.markers.find(marker => Range.containsPosition(marker, position)) || null; } - private onMarkerChanged(changedResources: ReadonlyArray): void { + private onMarkerChanged(changedResources: readonly URI[]): void { if (!this.editor) { return; } @@ -1044,7 +1044,7 @@ export class ShowLanguageExtensionsAction extends Action { this.enabled = galleryService.isEnabled(); } - async run(): Promise { + override async run(): Promise { await this.commandService.executeCommand('workbench.extensions.action.showExtensionsForLanguage', this.fileExtension); } } @@ -1069,7 +1069,7 @@ export class ChangeModeAction extends Action { super(actionId, actionLabel); } - async run(event: any, data: ITelemetryData): Promise { + override async run(event: unknown, data?: ITelemetryData): Promise { const activeTextEditorControl = getCodeEditor(this.editorService.activeTextEditorControl); if (!activeTextEditorControl) { await this.quickInputService.pick([{ label: localize('noEditor', "No text editor active at this time") }]); @@ -1094,7 +1094,7 @@ export class ChangeModeAction extends Action { // All languages are valid picks const languages = this.modeService.getRegisteredLanguageNames(); - const picks: QuickPickInput[] = languages.sort().map((lang, index) => { + const picks: QuickPickInput[] = languages.sort().map(lang => { const modeId = this.modeService.getModeIdForLanguageName(lang.toLowerCase()) || 'unknown'; const extensions = this.modeService.getExtensions(lang).join(' '); let description: string; @@ -1263,7 +1263,7 @@ export class ChangeEOLAction extends Action { super(actionId, actionLabel); } - async run(): Promise { + override async run(): Promise { const activeTextEditorControl = getCodeEditor(this.editorService.activeTextEditorControl); if (!activeTextEditorControl) { await this.quickInputService.pick([{ label: localize('noEditor', "No text editor active at this time") }]); @@ -1316,7 +1316,7 @@ export class ChangeEncodingAction extends Action { super(actionId, actionLabel); } - async run(): Promise { + override async run(): Promise { const activeTextEditorControl = getCodeEditor(this.editorService.activeTextEditorControl); if (!activeTextEditorControl) { await this.quickInputService.pick([{ label: localize('noEditor', "No text editor active at this time") }]); @@ -1434,7 +1434,7 @@ export class ChangeEncodingAction extends Action { const activeEncodingSupport = toEditorWithEncodingSupport(this.editorService.activeEditorPane.input); if (typeof encoding.id !== 'undefined' && activeEncodingSupport && activeEncodingSupport.getEncoding() !== encoding.id) { - activeEncodingSupport.setEncoding(encoding.id, isReopenWithEncoding ? EncodingMode.Decode : EncodingMode.Encode); // Set new encoding + await activeEncodingSupport.setEncoding(encoding.id, isReopenWithEncoding ? EncodingMode.Decode : EncodingMode.Encode); // Set new encoding } activeTextEditorControl.focus(); diff --git a/src/vs/workbench/browser/parts/editor/editorsObserver.ts b/src/vs/workbench/browser/parts/editor/editorsObserver.ts index 4c68ea92..1abe2fd4 100644 --- a/src/vs/workbench/browser/parts/editor/editorsObserver.ts +++ b/src/vs/workbench/browser/parts/editor/editorsObserver.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IEditorInput, IEditorInputFactoryRegistry, IEditorIdentifier, GroupIdentifier, Extensions, IEditorPartOptionsChangeEvent, EditorsOrder, EditorResourceAccessor, SideBySideEditor } from 'vs/workbench/common/editor'; +import { IEditorInput, IEditorInputFactoryRegistry, IEditorIdentifier, GroupIdentifier, EditorExtensions, IEditorPartOptionsChangeEvent, EditorsOrder, SideBySideEditorInput } from 'vs/workbench/common/editor'; import { dispose, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -12,6 +12,7 @@ import { IEditorGroupsService, IEditorGroup, GroupChangeKind, GroupsOrder } from import { coalesce } from 'vs/base/common/arrays'; import { LinkedMap, Touch, ResourceMap } from 'vs/base/common/map'; import { equals } from 'vs/base/common/objects'; +import { IResourceEditorInputIdentifier } from 'vs/platform/editor/common/editor'; import { URI } from 'vs/base/common/uri'; interface ISerializedEditorsList { @@ -38,7 +39,7 @@ export class EditorsObserver extends Disposable { private readonly keyMap = new Map>(); private readonly mostRecentEditorsMap = new LinkedMap(); - private readonly editorResourcesMap = new ResourceMap(); + private readonly editorsPerResourceCounter = new ResourceMap>(); private readonly _onDidMostRecentlyActiveEditorsChange = this._register(new Emitter()); readonly onDidMostRecentlyActiveEditorsChange = this._onDidMostRecentlyActiveEditorsChange.event; @@ -51,8 +52,14 @@ export class EditorsObserver extends Disposable { return [...this.mostRecentEditorsMap.values()]; } - hasEditor(resource: URI): boolean { - return this.editorResourcesMap.has(resource); + hasEditor(editor: IResourceEditorInputIdentifier): boolean { + const editors = this.editorsPerResourceCounter.get(editor.resource); + + return editors?.has(editor.typeId) ?? false; + } + + hasEditors(resource: URI): boolean { + return this.editorsPerResourceCounter.has(resource); } constructor( @@ -69,7 +76,7 @@ export class EditorsObserver extends Disposable { this._register(this.editorGroupsService.onDidAddGroup(group => this.onGroupAdded(group))); this._register(this.editorGroupsService.onDidChangeEditorPartOptions(e => this.onDidChangeEditorPartOptions(e))); - this.editorGroupsService.whenRestored.then(() => this.loadState()); + this.editorGroupsService.whenReady.then(() => this.loadState()); } private onGroupAdded(group: IEditorGroup): void { @@ -185,19 +192,48 @@ export class EditorsObserver extends Disposable { } private updateEditorResourcesMap(editor: IEditorInput, add: boolean): void { - const resource = EditorResourceAccessor.getCanonicalUri(editor, { supportSideBySide: SideBySideEditor.PRIMARY }); + + // Distill the editor resource and type id with support + // for side by side editor's primary side too. + let resource: URI | undefined = undefined; + let typeId: string | undefined = undefined; + if (editor instanceof SideBySideEditorInput) { + resource = editor.primary.resource; + typeId = editor.primary.typeId; + } else { + resource = editor.resource; + typeId = editor.typeId; + } + if (!resource) { return; // require a resource } + // Add entry if (add) { - this.editorResourcesMap.set(resource, (this.editorResourcesMap.get(resource) ?? 0) + 1); - } else { - const counter = this.editorResourcesMap.get(resource) ?? 0; - if (counter > 1) { - this.editorResourcesMap.set(resource, counter - 1); - } else { - this.editorResourcesMap.delete(resource); + let editorsPerResource = this.editorsPerResourceCounter.get(resource); + if (!editorsPerResource) { + editorsPerResource = new Map(); + this.editorsPerResourceCounter.set(resource, editorsPerResource); + } + + editorsPerResource.set(typeId, (editorsPerResource.get(typeId) ?? 0) + 1); + } + + // Remove entry + else { + const editorsPerResource = this.editorsPerResourceCounter.get(resource); + if (editorsPerResource) { + const counter = editorsPerResource.get(typeId) ?? 0; + if (counter > 1) { + editorsPerResource.set(typeId, counter - 1); + } else { + editorsPerResource.delete(typeId); + + if (editorsPerResource.size === 0) { + this.editorsPerResourceCounter.delete(resource); + } + } } } } @@ -344,7 +380,7 @@ export class EditorsObserver extends Disposable { } private serialize(): ISerializedEditorsList { - const registry = Registry.as(Extensions.EditorInputFactories); + const registry = Registry.as(EditorExtensions.EditorInputFactories); const entries = [...this.mostRecentEditorsMap.values()]; const mapGroupToSerializableEditorsOfGroup = new Map(); @@ -362,9 +398,9 @@ export class EditorsObserver extends Disposable { let serializableEditorsOfGroup = mapGroupToSerializableEditorsOfGroup.get(group); if (!serializableEditorsOfGroup) { serializableEditorsOfGroup = group.getEditors(EditorsOrder.SEQUENTIAL).filter(editor => { - const factory = registry.getEditorInputFactory(editor.getTypeId()); + const editorSerializer = registry.getEditorInputSerializer(editor); - return factory?.canSerialize(editor); + return editorSerializer?.canSerialize(editor); }); mapGroupToSerializableEditorsOfGroup.set(group, serializableEditorsOfGroup); } @@ -384,10 +420,8 @@ export class EditorsObserver extends Disposable { private loadState(): void { const serialized = this.storageService.get(EditorsObserver.STORAGE_KEY, StorageScope.WORKSPACE); - // Previous state: + // Previous state: Load editors map from persisted state if (serialized) { - - // Load editors map from persisted state this.deserialize(JSON.parse(serialized)); } diff --git a/src/vs/workbench/browser/parts/editor/media/editorgroupview.css b/src/vs/workbench/browser/parts/editor/media/editorgroupview.css index 582eac23..8018ba37 100644 --- a/src/vs/workbench/browser/parts/editor/media/editorgroupview.css +++ b/src/vs/workbench/browser/parts/editor/media/editorgroupview.css @@ -73,20 +73,19 @@ .monaco-workbench .part.editor > .content .editor-group-container > .editor-group-container-toolbar { display: none; + height: 35px; } .monaco-workbench .part.editor > .content:not(.empty) .editor-group-container.empty > .editor-group-container-toolbar { display: block; } -.monaco-workbench .part.editor > .content .editor-group-container > .editor-group-container-toolbar .action-label { - display: block; - height: 35px; - line-height: 35px; - min-width: 28px; - background-size: 16px; - background-position: center center; - background-repeat: no-repeat; +.monaco-workbench .part.editor > .content .editor-group-container > .editor-group-container-toolbar .actions-container { + justify-content: flex-end; +} + +.monaco-workbench .part.editor > .content .editor-group-container > .editor-group-container-toolbar .action-item { + margin-right: 6px; } /* Editor */ diff --git a/src/vs/workbench/browser/parts/editor/media/notabstitlecontrol.css b/src/vs/workbench/browser/parts/editor/media/notabstitlecontrol.css index 366f6dbe..21848636 100644 --- a/src/vs/workbench/browser/parts/editor/media/notabstitlecontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/notabstitlecontrol.css @@ -97,6 +97,10 @@ height: 35px; } +.monaco-workbench .part.editor > .content .editor-group-container > .title > .title-actions .action-item { + margin-right: 4px; +} + .monaco-workbench .part.editor > .content .editor-group-container.active > .title > .title-actions { opacity: 1; } diff --git a/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css b/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css index fdd7368d..1e77dde5 100644 --- a/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css @@ -281,6 +281,7 @@ .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink > .monaco-icon-label > .monaco-icon-label-container { text-overflow: clip; + flex: none; } .monaco-workbench.hc-black .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink > .monaco-icon-label > .monaco-icon-label-container { @@ -295,6 +296,10 @@ width: 28px; } +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab > .tab-actions > .monaco-action-bar { + width: 28px; +} + .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.tab-actions-right.sizing-shrink > .tab-actions { flex: 0; overflow: hidden; /* let the tab actions be pushed out of view when sizing is set to shrink to make more room */ @@ -321,9 +326,16 @@ opacity: 1; } +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab > .tab-actions .actions-container { + justify-content: center; +} + .monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab > .tab-actions .action-label.codicon { color: inherit; font-size: 16px; + padding: 2px; + width: 16px; + height: 16px; } .monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab.sticky.dirty > .tab-actions .action-label:not(:hover)::before, @@ -346,13 +358,6 @@ .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab > .tab-actions .action-label { opacity: 0; - display: block; - height: 16px; - width: 16px; - background-size: 16px; - background-position: center center; - background-repeat: no-repeat; - margin-right: 0.5em; } /* Tab Actions: Off */ @@ -430,6 +435,10 @@ height: 35px; } +.monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-item { + margin-right: 4px; +} + .monaco-workbench .part.editor > .content .editor-group-container > .title > .tabs-and-actions-container.wrapping .editor-actions { /* When tabs are wrapped, position the editor actions at the end of the very last row */ diff --git a/src/vs/workbench/browser/parts/editor/media/titlecontrol.css b/src/vs/workbench/browser/parts/editor/media/titlecontrol.css index 420f4031..28af3045 100644 --- a/src/vs/workbench/browser/parts/editor/media/titlecontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/titlecontrol.css @@ -35,39 +35,6 @@ padding-right: 0; /* by default the icon label has a padding right and this isn't wanted when not showing tabs and not showing breadcrumbs */ } -/* Title Actions */ - -.monaco-workbench .part.editor > .content .editor-group-container > .title .title-actions .action-label:not(span), -.monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label:not(span) { - display: flex; - height: 35px; - min-width: 28px; - align-items: center; - justify-content: center; - background-size: 16px; - background-position: center center; - background-repeat: no-repeat; -} - -.monaco-workbench.hc-black .part.editor > .content .editor-group-container > .title .title-actions .action-label, -.monaco-workbench.hc-black .part.editor > .content .editor-group-container > .title .editor-actions .action-label:not(.codicon) { - line-height: initial; -} - -.monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label .label, -.monaco-workbench .part.editor > .content .editor-group-container > .title .title-actions .action-label .label { - display: none; -} - -.monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label.codicon { - color: inherit; -} - -.monaco-workbench .part.editor > .content .editor-group-container > .title .title-actions .action-label.disabled, -.monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label.disabled { - opacity: 0.4; -} - /* Drag and Drop */ .monaco-workbench .part.editor > .content .editor-group-container > .title { diff --git a/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts index 9fbb708c..8d3ae15e 100644 --- a/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts @@ -50,7 +50,7 @@ export class NoTabsTitleControl extends TitleControl { this._register(addDisposableListener(this.editorLabel.element, EventType.CLICK, e => this.onTitleLabelClick(e))); // Breadcrumbs - this.createBreadcrumbsControl(labelContainer, { showFileIcons: false, showSymbolIcons: true, showDecorationColors: false, breadcrumbsBackground: () => Color.transparent }); + this.createBreadcrumbsControl(labelContainer, { showFileIcons: false, showSymbolIcons: true, showDecorationColors: false, breadcrumbsBackground: () => Color.transparent, showPlaceholder: false }); titleContainer.classList.toggle('breadcrumbs', Boolean(this.breadcrumbsControl)); this._register(toDisposable(() => titleContainer.classList.remove('breadcrumbs'))); // important to remove because the container is a shared dom node @@ -195,7 +195,7 @@ export class NoTabsTitleControl extends TitleControl { } } - updateStyles(): void { + override updateStyles(): void { this.redraw(); } @@ -321,7 +321,7 @@ export class NoTabsTitleControl extends TitleControl { } } - protected prepareEditorActions(editorActions: IToolbarActions): { primaryEditorActions: IAction[], secondaryEditorActions: IAction[] } { + protected override prepareEditorActions(editorActions: IToolbarActions): { primaryEditorActions: IAction[], secondaryEditorActions: IAction[] } { const isGroupActive = this.accessor.activeGroup === this.group; // Group active: show all actions diff --git a/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts b/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts index dcfda946..5a857f4c 100644 --- a/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts +++ b/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts @@ -5,13 +5,13 @@ import { Dimension, $, clearNode } from 'vs/base/browser/dom'; import { Registry } from 'vs/platform/registry/common/platform'; -import { EditorInput, EditorOptions, SideBySideEditorInput, IEditorControl, IEditorPane, IEditorOpenContext } from 'vs/workbench/common/editor'; +import { EditorInput, EditorOptions, SideBySideEditorInput, IEditorControl, IEditorPane, IEditorOpenContext, EditorExtensions } from 'vs/workbench/common/editor'; import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { scrollbarShadow } from 'vs/platform/theme/common/colorRegistry'; -import { IEditorRegistry, Extensions as EditorExtensions } from 'vs/workbench/browser/editor'; +import { IEditorRegistry } from 'vs/workbench/browser/editor'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { SplitView, Sizing, Orientation } from 'vs/base/browser/ui/splitview/splitview'; @@ -34,15 +34,15 @@ export class SideBySideEditor extends EditorPane { private get maximumSecondaryHeight() { return this.secondaryEditorPane ? this.secondaryEditorPane.maximumHeight : Number.POSITIVE_INFINITY; } // these setters need to exist because this extends from EditorPane - set minimumWidth(value: number) { /* noop */ } - set maximumWidth(value: number) { /* noop */ } - set minimumHeight(value: number) { /* noop */ } - set maximumHeight(value: number) { /* noop */ } + override set minimumWidth(value: number) { /* noop */ } + override set maximumWidth(value: number) { /* noop */ } + override set minimumHeight(value: number) { /* noop */ } + override set maximumHeight(value: number) { /* noop */ } - get minimumWidth() { return this.minimumPrimaryWidth + this.minimumSecondaryWidth; } - get maximumWidth() { return this.maximumPrimaryWidth + this.maximumSecondaryWidth; } - get minimumHeight() { return this.minimumPrimaryHeight + this.minimumSecondaryHeight; } - get maximumHeight() { return this.maximumPrimaryHeight + this.maximumSecondaryHeight; } + override get minimumWidth() { return this.minimumPrimaryWidth + this.minimumSecondaryWidth; } + override get maximumWidth() { return this.maximumPrimaryWidth + this.maximumSecondaryWidth; } + override get minimumHeight() { return this.minimumPrimaryHeight + this.minimumSecondaryHeight; } + override get maximumHeight() { return this.maximumPrimaryHeight + this.maximumSecondaryHeight; } protected primaryEditorPane?: EditorPane; protected secondaryEditorPane?: EditorPane; @@ -56,7 +56,7 @@ export class SideBySideEditor extends EditorPane { private onDidCreateEditors = this._register(new Emitter<{ width: number; height: number; } | undefined>()); private _onDidChangeSizeConstraints = this._register(new Relay<{ width: number; height: number; } | undefined>()); - readonly onDidChangeSizeConstraints = Event.any(this.onDidCreateEditors.event, this._onDidChangeSizeConstraints.event); + override readonly onDidChangeSizeConstraints = Event.any(this.onDidCreateEditors.event, this._onDidChangeSizeConstraints.event); constructor( @ITelemetryService telemetryService: ITelemetryService, @@ -94,20 +94,20 @@ export class SideBySideEditor extends EditorPane { this.updateStyles(); } - async setInput(newInput: EditorInput, options: EditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise { + override async setInput(newInput: EditorInput, options: EditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise { const oldInput = this.input as SideBySideEditorInput; await super.setInput(newInput, options, context, token); return this.updateInput(oldInput, (newInput as SideBySideEditorInput), options, context, token); } - setOptions(options: EditorOptions | undefined): void { + override setOptions(options: EditorOptions | undefined): void { if (this.primaryEditorPane) { this.primaryEditorPane.setOptions(options); } } - protected setEditorVisible(visible: boolean, group: IEditorGroup | undefined): void { + protected override setEditorVisible(visible: boolean, group: IEditorGroup | undefined): void { if (this.primaryEditorPane) { this.primaryEditorPane.setVisible(visible, group); } @@ -119,7 +119,7 @@ export class SideBySideEditor extends EditorPane { super.setEditorVisible(visible, group); } - clearInput(): void { + override clearInput(): void { if (this.primaryEditorPane) { this.primaryEditorPane.clearInput(); } @@ -133,7 +133,7 @@ export class SideBySideEditor extends EditorPane { super.clearInput(); } - focus(): void { + override focus(): void { if (this.primaryEditorPane) { this.primaryEditorPane.focus(); } @@ -146,7 +146,7 @@ export class SideBySideEditor extends EditorPane { splitview.layout(dimension.width); } - getControl(): IEditorControl | undefined { + override getControl(): IEditorControl | undefined { if (this.primaryEditorPane) { return this.primaryEditorPane.getControl(); } @@ -218,7 +218,7 @@ export class SideBySideEditor extends EditorPane { ); } - updateStyles(): void { + override updateStyles(): void { super.updateStyles(); if (this.primaryEditorContainer) { @@ -246,7 +246,7 @@ export class SideBySideEditor extends EditorPane { } } - dispose(): void { + override dispose(): void { this.disposeEditors(); super.dispose(); diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index 1f49ffb2..5ca4311b 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -6,7 +6,8 @@ import 'vs/css!./media/tabstitlecontrol'; import { isMacintosh, isWindows } from 'vs/base/common/platform'; import { shorten } from 'vs/base/common/labels'; -import { EditorResourceAccessor, GroupIdentifier, IEditorInput, Verbosity, EditorCommandsContextActionRunner, IEditorPartOptions, SideBySideEditor, computeEditorAriaLabel } from 'vs/workbench/common/editor'; +import { EditorResourceAccessor, GroupIdentifier, IEditorInput, Verbosity, EditorCommandsContextActionRunner, IEditorPartOptions, SideBySideEditor } from 'vs/workbench/common/editor'; +import { computeEditorAriaLabel } from 'vs/workbench/browser/editor'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { EventType as TouchEventType, GestureEvent, Gesture } from 'vs/base/browser/touch'; import { KeyCode } from 'vs/base/common/keyCodes'; @@ -166,7 +167,7 @@ export class TabsTitleControl extends TitleControl { const breadcrumbsContainer = document.createElement('div'); breadcrumbsContainer.classList.add('tabs-breadcrumbs'); this.titleContainer.appendChild(breadcrumbsContainer); - this.createBreadcrumbsControl(breadcrumbsContainer, { showFileIcons: true, showSymbolIcons: true, showDecorationColors: false, breadcrumbsBackground: breadcrumbsBackground }); + this.createBreadcrumbsControl(breadcrumbsContainer, { showFileIcons: true, showSymbolIcons: true, showDecorationColors: false, showPlaceholder: true, breadcrumbsBackground: breadcrumbsBackground }); } private createTabsScrollbar(scrollable: HTMLElement): ScrollableElement { @@ -378,7 +379,7 @@ export class TabsTitleControl extends TitleControl { this.layout(this.dimensions); } - protected updateEditorActionsToolbar(): void { + protected override updateEditorActionsToolbar(): void { super.updateEditorActionsToolbar(); // Changing the actions in the toolbar can have an impact on the size of the @@ -570,7 +571,7 @@ export class TabsTitleControl extends TitleControl { } } - updateStyles(): void { + override updateStyles(): void { this.redraw(); } @@ -1771,7 +1772,7 @@ export class TabsTitleControl extends TitleControl { return !isCopy || source === this.group.id; } - dispose(): void { + override dispose(): void { super.dispose(); this.tabDisposables = dispose(this.tabDisposables); diff --git a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts index 54eca9b3..e9a4d85f 100644 --- a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts @@ -9,7 +9,7 @@ import { isFunction, isObject, isArray, assertIsDefined, withUndefinedAsNull } f import { IDiffEditor } from 'vs/editor/browser/editorBrowser'; import { IDiffEditorOptions, IEditorOptions as ICodeEditorOptions } from 'vs/editor/common/config/editorOptions'; import { BaseTextEditor, IEditorConfiguration } from 'vs/workbench/browser/parts/editor/textEditor'; -import { TextEditorOptions, EditorInput, EditorOptions, TEXT_DIFF_EDITOR_ID, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions, ITextDiffEditorPane, IEditorInput, IEditorOpenContext } from 'vs/workbench/common/editor'; +import { TextEditorOptions, EditorInput, EditorOptions, TEXT_DIFF_EDITOR_ID, IEditorInputFactoryRegistry, EditorExtensions, ITextDiffEditorPane, IEditorInput, IEditorOpenContext } from 'vs/workbench/common/editor'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { DiffNavigator } from 'vs/editor/browser/widget/diffNavigator'; import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditorWidget'; @@ -43,7 +43,7 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditorPan private diffNavigator: DiffNavigator | undefined; private readonly diffNavigatorDisposables = this._register(new DisposableStore()); - get scopedContextKeyService(): IContextKeyService | undefined { + override get scopedContextKeyService(): IContextKeyService | undefined { const control = this.getControl(); if (!control) { return undefined; @@ -86,7 +86,7 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditorPan } } - protected onWillCloseEditorInGroup(editor: IEditorInput): void { + protected override onWillCloseEditorInGroup(editor: IEditorInput): void { // React to editors closing to preserve or clear view state. This needs to happen // in the onWillCloseEditor because at that time the editor has not yet @@ -94,7 +94,7 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditorPan this.doSaveOrClearTextDiffEditorViewState(editor); } - getTitle(): string { + override getTitle(): string { if (this.input) { return this.input.getName(); } @@ -102,11 +102,11 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditorPan return localize('textDiffEditor', "Text Diff Editor"); } - createEditorControl(parent: HTMLElement, configuration: ICodeEditorOptions): IDiffEditor { + override createEditorControl(parent: HTMLElement, configuration: ICodeEditorOptions): IDiffEditor { return this.instantiationService.createInstance(DiffEditorWidget, parent, configuration, {}); } - async setInput(input: EditorInput, options: EditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise { + override async setInput(input: EditorInput, options: EditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise { // Dispose previous diff navigator this.diffNavigatorDisposables.clear(); @@ -197,7 +197,7 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditorPan const binaryDiffInput = this.instantiationService.createInstance(DiffEditorInput, input.getName(), input.getDescription(), originalInput, modifiedInput, true); // Forward binary flag to input if supported - const fileEditorInputFactory = Registry.as(EditorInputExtensions.EditorInputFactories).getFileEditorInputFactory(); + const fileEditorInputFactory = Registry.as(EditorExtensions.EditorInputFactories).getFileEditorInputFactory(); if (fileEditorInputFactory.isFileEditorInput(originalInput)) { originalInput.setForceOpenAsBinary(); } @@ -231,7 +231,7 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditorPan return false; } - protected computeConfiguration(configuration: IEditorConfiguration): ICodeEditorOptions { + protected override computeConfiguration(configuration: IEditorConfiguration): ICodeEditorOptions { const editorConfiguration = super.computeConfiguration(configuration); // Handle diff editor specially by merging in diffEditor configuration @@ -252,7 +252,7 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditorPan return editorConfiguration; } - protected getConfigurationOverrides(): ICodeEditorOptions { + protected override getConfigurationOverrides(): ICodeEditorOptions { const options: IDiffEditorOptions = super.getConfigurationOverrides(); options.readOnly = this.input instanceof DiffEditorInput && this.input.modifiedInput.isReadonly(); @@ -274,7 +274,7 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditorPan return (error).textFileOperationResult === TextFileOperationResult.FILE_IS_BINARY; } - clearInput(): void { + override clearInput(): void { // Dispose previous diff navigator this.diffNavigatorDisposables.clear(); @@ -296,15 +296,15 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditorPan return this.diffNavigator; } - getControl(): IDiffEditor | undefined { + override getControl(): IDiffEditor | undefined { return super.getControl() as IDiffEditor | undefined; } - protected loadTextEditorViewState(resource: URI): IDiffEditorViewState { + protected override loadTextEditorViewState(resource: URI): IDiffEditorViewState { return super.loadTextEditorViewState(resource) as IDiffEditorViewState; // overridden for text diff editor support } - protected saveState(): void { + protected override saveState(): void { // Update/clear editor view State this.doSaveOrClearTextDiffEditorViewState(this.input); @@ -323,7 +323,7 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditorPan } // Clear view state if input is disposed or we are configured to not storing any state - if (input.isDisposed() || (!this.shouldRestoreTextEditorViewState(input) && (!this.group || !this.group.isOpened(input)))) { + if (input.isDisposed() || (!this.shouldRestoreTextEditorViewState(input) && (!this.group || !this.group.contains(input)))) { super.clearTextEditorViewState([resource], this.group); } @@ -333,7 +333,7 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditorPan } } - protected retrieveTextEditorViewState(resource: URI): IDiffEditorViewState | null { + protected override retrieveTextEditorViewState(resource: URI): IDiffEditorViewState | null { return this.retrieveTextDiffEditorViewState(resource); // overridden for text diff editor support } diff --git a/src/vs/workbench/browser/parts/editor/textEditor.ts b/src/vs/workbench/browser/parts/editor/textEditor.ts index 7ec16141..bf11b188 100644 --- a/src/vs/workbench/browser/parts/editor/textEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textEditor.ts @@ -10,7 +10,8 @@ import { Event } from 'vs/base/common/event'; import { isObject, assertIsDefined, withNullAsUndefined, isFunction } from 'vs/base/common/types'; import { Dimension } from 'vs/base/browser/dom'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; -import { EditorInput, EditorOptions, IEditorMemento, ITextEditorPane, TextEditorOptions, IEditorCloseEvent, IEditorInput, computeEditorAriaLabel, IEditorOpenContext, EditorResourceAccessor, SideBySideEditor } from 'vs/workbench/common/editor'; +import { EditorInput, EditorOptions, IEditorMemento, ITextEditorPane, TextEditorOptions, IEditorCloseEvent, IEditorInput, IEditorOpenContext, EditorResourceAccessor, SideBySideEditor } from 'vs/workbench/common/editor'; +import { computeEditorAriaLabel } from 'vs/workbench/browser/editor'; import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; import { IEditorViewState, IEditor, ScrollType } from 'vs/editor/common/editorCommon'; import { IStorageService } from 'vs/platform/storage/common/storage'; @@ -52,7 +53,7 @@ export abstract class BaseTextEditor extends EditorPane implements ITextEditorPa protected get instantiationService(): IInstantiationService { return this._instantiationService; } protected set instantiationService(value: IInstantiationService) { this._instantiationService = value; } - get scopedContextKeyService(): IContextKeyService | undefined { + override get scopedContextKeyService(): IContextKeyService | undefined { return isCodeEditor(this.editorControl) ? this.editorControl.invokeWithinContext(accessor => accessor.get(IContextKeyService)) : undefined; } @@ -62,7 +63,7 @@ export abstract class BaseTextEditor extends EditorPane implements ITextEditorPa @IInstantiationService instantiationService: IInstantiationService, @IStorageService storageService: IStorageService, @ITextResourceConfigurationService protected readonly textResourceConfigurationService: ITextResourceConfigurationService, - @IThemeService protected themeService: IThemeService, + @IThemeService themeService: IThemeService, @IEditorService protected editorService: IEditorService, @IEditorGroupsService protected editorGroupService: IEditorGroupsService ) { @@ -158,7 +159,7 @@ export abstract class BaseTextEditor extends EditorPane implements ITextEditorPa return this.instantiationService.createInstance(CodeEditorWidget, parent, configuration, {}); } - async setInput(input: EditorInput, options: EditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise { + override async setInput(input: EditorInput, options: EditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise { await super.setInput(input, options, context, token); // Update editor options after having set the input. We do this because there can be @@ -170,7 +171,7 @@ export abstract class BaseTextEditor extends EditorPane implements ITextEditorPa editorContainer.setAttribute('aria-label', this.computeAriaLabel()); } - setOptions(options: EditorOptions | undefined): void { + override setOptions(options: EditorOptions | undefined): void { const textOptions = options as TextEditorOptions; if (textOptions && isFunction(textOptions.apply)) { const textEditor = assertIsDefined(this.getControl()); @@ -178,7 +179,7 @@ export abstract class BaseTextEditor extends EditorPane implements ITextEditorPa } } - protected setEditorVisible(visible: boolean, group: IEditorGroup | undefined): void { + protected override setEditorVisible(visible: boolean, group: IEditorGroup | undefined): void { // Pass on to Editor const editorControl = assertIsDefined(this.editorControl); @@ -206,7 +207,7 @@ export abstract class BaseTextEditor extends EditorPane implements ITextEditorPa // Subclasses can override } - focus(): void { + override focus(): void { // Pass on to Editor const editorControl = assertIsDefined(this.editorControl); @@ -220,7 +221,7 @@ export abstract class BaseTextEditor extends EditorPane implements ITextEditorPa editorControl.layout(dimension); } - getControl(): IEditor | undefined { + override getControl(): IEditor | undefined { return this.editorControl; } @@ -338,7 +339,7 @@ export abstract class BaseTextEditor extends EditorPane implements ITextEditorPa return undefined; } - dispose(): void { + override dispose(): void { this.lastAppliedEditorOptions = undefined; super.dispose(); diff --git a/src/vs/workbench/browser/parts/editor/textResourceEditor.ts b/src/vs/workbench/browser/parts/editor/textResourceEditor.ts index 63135239..5105d227 100644 --- a/src/vs/workbench/browser/parts/editor/textResourceEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textResourceEditor.ts @@ -45,7 +45,7 @@ export class AbstractTextResourceEditor extends BaseTextEditor { super(id, telemetryService, instantiationService, storageService, textResourceConfigurationService, themeService, editorService, editorGroupService); } - getTitle(): string | undefined { + override getTitle(): string | undefined { if (this.input) { return this.input.getName(); } @@ -53,7 +53,7 @@ export class AbstractTextResourceEditor extends BaseTextEditor { return localize('textEditor', "Text Editor"); } - async setInput(input: EditorInput, options: EditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise { + override async setInput(input: EditorInput, options: EditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise { // Remember view settings if input changes this.saveTextResourceEditorViewState(this.input); @@ -119,7 +119,7 @@ export class AbstractTextResourceEditor extends BaseTextEditor { } } - clearInput(): void { + override clearInput(): void { // Keep editor view state in settings to restore when coming back this.saveTextResourceEditorViewState(this.input); @@ -133,7 +133,7 @@ export class AbstractTextResourceEditor extends BaseTextEditor { super.clearInput(); } - protected saveState(): void { + protected override saveState(): void { // Save View State (only for untitled) if (this.input instanceof UntitledTextEditorInput) { @@ -180,7 +180,7 @@ export class TextResourceEditor extends AbstractTextResourceEditor { super(TextResourceEditor.ID, telemetryService, instantiationService, storageService, textResourceConfigurationService, themeService, editorGroupService, editorService); } - protected createEditorControl(parent: HTMLElement, configuration: IEditorOptions): IEditor { + protected override createEditorControl(parent: HTMLElement, configuration: IEditorOptions): IEditor { const control = super.createEditorControl(parent, configuration); // Install a listener for paste to update this editors diff --git a/src/vs/workbench/browser/parts/editor/titleControl.ts b/src/vs/workbench/browser/parts/editor/titleControl.ts index 7acaf58f..e2d0ed9a 100644 --- a/src/vs/workbench/browser/parts/editor/titleControl.ts +++ b/src/vs/workbench/browser/parts/editor/titleControl.ts @@ -38,6 +38,7 @@ import { IFileService } from 'vs/platform/files/common/files'; import { withNullAsUndefined, withUndefinedAsNull, assertIsDefined } from 'vs/base/common/types'; import { isFirefox } from 'vs/base/browser/browser'; import { ITextEditorOptions } from 'vs/platform/editor/common/editor'; +import { isPromiseCanceledError } from 'vs/base/common/errors'; export interface IToolbarActions { primary: IAction[]; @@ -151,12 +152,12 @@ export abstract class TitleControl extends Themable { this._register(this.editorActionsToolbar.actionRunner.onDidRun(e => { // Notify for Error - this.notificationService.error(e.error); + if (e.error && !isPromiseCanceledError(e.error)) { + this.notificationService.error(e.error); + } // Log in telemetry - if (this.telemetryService) { - this.telemetryService.publicLog2('workbenchActionExecuted', { id: e.action.id, from: 'editorPart' }); - } + this.telemetryService.publicLog2('workbenchActionExecuted', { id: e.action.id, from: 'editorPart' }); })); } @@ -385,13 +386,11 @@ export abstract class TitleControl extends Themable { abstract updateOptions(oldOptions: IEditorPartOptions, newOptions: IEditorPartOptions): void; - abstract updateStyles(): void; - abstract layout(dimensions: ITitleControlDimensions): Dimension; abstract getHeight(): IEditorGroupTitleHeight; - dispose(): void { + override dispose(): void { dispose(this.breadcrumbsControl); this.breadcrumbsControl = undefined; diff --git a/src/vs/workbench/browser/parts/editor/untitledHint.ts b/src/vs/workbench/browser/parts/editor/untitledHint.ts index f06f57d7..04a3dc37 100644 --- a/src/vs/workbench/browser/parts/editor/untitledHint.ts +++ b/src/vs/workbench/browser/parts/editor/untitledHint.ts @@ -7,18 +7,16 @@ import * as dom from 'vs/base/browser/dom'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser'; import { localize } from 'vs/nls'; -import { DEFAULT_FONT_FAMILY } from 'vs/workbench/browser/style'; -import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; -import { inputPlaceholderForeground } from 'vs/platform/theme/common/colorRegistry'; +import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { inputPlaceholderForeground, textLinkForeground } from 'vs/platform/theme/common/colorRegistry'; import { ChangeModeAction } from 'vs/workbench/browser/parts/editor/editorStatus'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { Schemas } from 'vs/base/common/network'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { FloatingClickWidget } from 'vs/workbench/browser/codeeditor'; import { ITASExperimentService } from 'vs/workbench/services/experiment/common/experimentService'; +import { ConfigurationChangedEvent, EditorOption } from 'vs/editor/common/config/editorOptions'; const $ = dom.$; const untitledHintSetting = 'workbench.editor.untitled.hint'; @@ -28,16 +26,15 @@ export class UntitledHintContribution implements IEditorContribution { private toDispose: IDisposable[]; private untitledHintContentWidget: UntitledHintContentWidget | undefined; - private button: FloatingClickWidget | undefined; - private experimentTreatment: 'text' | 'button' | 'hidden' | undefined; + private experimentTreatment: 'text' | 'hidden' | undefined; + constructor( private editor: ICodeEditor, @ICommandService private readonly commandService: ICommandService, @IConfigurationService private readonly configurationService: IConfigurationService, - @IKeybindingService private readonly keybindingService: IKeybindingService, - @IThemeService private readonly themeService: IThemeService, @ITASExperimentService private readonly experimentService: ITASExperimentService + ) { this.toDispose = []; this.toDispose.push(this.editor.onDidChangeModel(() => this.update())); @@ -47,7 +44,7 @@ export class UntitledHintContribution implements IEditorContribution { this.update(); } })); - this.experimentService.getTreatment<'text' | 'button'>('untitledhint').then(treatment => { + this.experimentService.getTreatment<'text' | 'hidden'>('untitledhint').then(treatment => { this.experimentTreatment = treatment; this.update(); }); @@ -55,25 +52,13 @@ export class UntitledHintContribution implements IEditorContribution { private update(): void { this.untitledHintContentWidget?.dispose(); - this.button?.dispose(); - const configValue = this.configurationService.getValue<'text' | 'button' | 'hidden' | 'default'>(untitledHintSetting); - const untitledHintMode = configValue === 'default' ? (this.experimentTreatment || 'hidden') : configValue; + const configValue = this.configurationService.getValue<'text' | 'hidden' | 'default'>(untitledHintSetting); + const untitledHintMode = configValue === 'default' ? (this.experimentTreatment || 'text') : configValue; + const model = this.editor.getModel(); - if (model && model.uri.scheme === Schemas.untitled && model.getModeId() === PLAINTEXT_MODE_ID) { - if (untitledHintMode === 'text') { - this.untitledHintContentWidget = new UntitledHintContentWidget(this.editor, this.commandService, this.configurationService, this.keybindingService); - } - if (untitledHintMode === 'button') { - this.button = new FloatingClickWidget(this.editor, localize('selectALanguage', "Select a Language"), ChangeModeAction.ID, this.keybindingService, this.themeService); - this.toDispose.push(this.button.onClick(async () => { - // Need to focus editor before so current editor becomes active and the command is properly executed - this.editor.focus(); - await this.commandService.executeCommand(ChangeModeAction.ID, { from: 'button' }); - this.editor.focus(); - })); - this.button.render(); - } + if (model && model.uri.scheme === Schemas.untitled && model.getModeId() === PLAINTEXT_MODE_ID && untitledHintMode === 'text') { + this.untitledHintContentWidget = new UntitledHintContentWidget(this.editor, this.commandService, this.configurationService); } } @@ -94,10 +79,14 @@ class UntitledHintContentWidget implements IContentWidget { private readonly editor: ICodeEditor, private readonly commandService: ICommandService, private readonly configurationService: IConfigurationService, - private readonly keybindingService: IKeybindingService ) { this.toDispose = []; this.toDispose.push(editor.onDidChangeModelContent(() => this.onDidChangeModelContent())); + this.toDispose.push(this.editor.onDidChangeConfiguration((e: ConfigurationChangedEvent) => { + if (this.domNode && e.hasChanged(EditorOption.fontInfo)) { + this.editor.applyFontInfo(this.domNode); + } + })); this.onDidChangeModelContent(); } @@ -118,17 +107,16 @@ class UntitledHintContentWidget implements IContentWidget { if (!this.domNode) { this.domNode = $('.untitled-hint'); this.domNode.style.width = 'max-content'; - const language = $('span.language-mode.detected-link-active'); - const keybinding = this.keybindingService.lookupKeybinding(ChangeModeAction.ID); - const keybindingLabel = keybinding?.getLabel(); - const keybindingWithBrackets = keybindingLabel ? `(${keybindingLabel})` : ''; - language.innerText = localize('selectAlanguage', "Select a language {0}", keybindingWithBrackets); + const language = $('a.language-mode'); + language.style.cursor = 'pointer'; + language.innerText = localize('selectAlanguage', "Select a language"); this.domNode.appendChild(language); const toGetStarted = $('span'); toGetStarted.innerText = localize('toGetStarted', " to get started. Start typing to dismiss, or ",); this.domNode.appendChild(toGetStarted); - const dontShow = $('span.detected-link-active'); + const dontShow = $('a'); + dontShow.style.cursor = 'pointer'; dontShow.innerText = localize('dontshow', "don't show"); this.domNode.appendChild(dontShow); @@ -154,9 +142,9 @@ class UntitledHintContentWidget implements IContentWidget { this.editor.focus(); })); - this.domNode.style.fontFamily = DEFAULT_FONT_FAMILY; this.domNode.style.fontStyle = 'italic'; this.domNode.style.paddingLeft = '4px'; + this.editor.applyFontInfo(this.domNode); } return this.domNode; @@ -180,4 +168,8 @@ registerThemingParticipant((theme, collector) => { if (inputPlaceholderForegroundColor) { collector.addRule(`.monaco-editor .contentWidgets .untitled-hint { color: ${inputPlaceholderForegroundColor}; }`); } + const textLinkForegroundColor = theme.getColor(textLinkForeground); + if (textLinkForegroundColor) { + collector.addRule(`.monaco-editor .contentWidgets .untitled-hint a { color: ${textLinkForegroundColor}; }`); + } }); diff --git a/src/vs/workbench/browser/parts/notifications/media/notificationsActions.css b/src/vs/workbench/browser/parts/notifications/media/notificationsActions.css index a8363373..409a0ee2 100644 --- a/src/vs/workbench/browser/parts/notifications/media/notificationsActions.css +++ b/src/vs/workbench/browser/parts/notifications/media/notificationsActions.css @@ -3,15 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -.monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-toolbar-container .action-label, -.monaco-workbench > .notifications-center > .notifications-center-header > .notifications-center-header-toolbar .action-label { - display: flex; - align-items: center; - width: 16px; - height: 22px; +.monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-toolbar-container .action-item, +.monaco-workbench > .notifications-center > .notifications-center-header > .notifications-center-header-toolbar .action-item { margin-right: 4px; - margin-left: 4px; - color: inherit; - background-position: center; - background-repeat: no-repeat; +} + +.monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-toolbar-container .action-item:first-child, +.monaco-workbench > .notifications-center > .notifications-center-header > .notifications-center-header-toolbar .action-item:first-child { + margin-left: 4px; } diff --git a/src/vs/workbench/browser/parts/notifications/media/notificationsCenter.css b/src/vs/workbench/browser/parts/notifications/media/notificationsCenter.css index a6fe87f7..bd6c9dd9 100644 --- a/src/vs/workbench/browser/parts/notifications/media/notificationsCenter.css +++ b/src/vs/workbench/browser/parts/notifications/media/notificationsCenter.css @@ -37,4 +37,8 @@ .monaco-workbench > .notifications-center > .notifications-center-header > .notifications-center-header-toolbar { flex: 1; -} \ No newline at end of file +} + +.monaco-workbench > .notifications-center > .notifications-center-header > .notifications-center-header-toolbar .actions-container { + justify-content: flex-end; +} diff --git a/src/vs/workbench/browser/parts/notifications/notificationsActions.ts b/src/vs/workbench/browser/parts/notifications/notificationsActions.ts index 71c7ad75..a7af76e8 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsActions.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsActions.ts @@ -37,7 +37,7 @@ export class ClearNotificationAction extends Action { super(id, label, ThemeIcon.asClassName(clearIcon)); } - async run(notification: INotificationViewItem): Promise { + override async run(notification: INotificationViewItem): Promise { this.commandService.executeCommand(CLEAR_NOTIFICATION, notification); } } @@ -55,7 +55,7 @@ export class ClearAllNotificationsAction extends Action { super(id, label, ThemeIcon.asClassName(clearAllIcon)); } - async run(): Promise { + override async run(): Promise { this.commandService.executeCommand(CLEAR_ALL_NOTIFICATIONS); } } @@ -73,7 +73,7 @@ export class HideNotificationsCenterAction extends Action { super(id, label, ThemeIcon.asClassName(hideIcon)); } - async run(): Promise { + override async run(): Promise { this.commandService.executeCommand(HIDE_NOTIFICATIONS_CENTER); } } @@ -91,7 +91,7 @@ export class ExpandNotificationAction extends Action { super(id, label, ThemeIcon.asClassName(expandIcon)); } - async run(notification: INotificationViewItem): Promise { + override async run(notification: INotificationViewItem): Promise { this.commandService.executeCommand(EXPAND_NOTIFICATION, notification); } } @@ -109,7 +109,7 @@ export class CollapseNotificationAction extends Action { super(id, label, ThemeIcon.asClassName(collapseIcon)); } - async run(notification: INotificationViewItem): Promise { + override async run(notification: INotificationViewItem): Promise { this.commandService.executeCommand(COLLAPSE_NOTIFICATION, notification); } } @@ -122,7 +122,7 @@ export class ConfigureNotificationAction extends Action { constructor( id: string, label: string, - public readonly configurationActions: ReadonlyArray + public readonly configurationActions: readonly IAction[] ) { super(id, label, ThemeIcon.asClassName(configureIcon)); } @@ -141,7 +141,7 @@ export class CopyNotificationMessageAction extends Action { super(id, label); } - run(notification: INotificationViewItem): Promise { + override run(notification: INotificationViewItem): Promise { return this.clipboardService.writeText(notification.message.raw); } } @@ -149,12 +149,15 @@ export class CopyNotificationMessageAction extends Action { interface NotificationActionMetrics { id: string; actionLabel: string; - source: string | undefined; + source: string; + silent: boolean; } + type NotificationActionMetricsClassification = { id: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; actionLabel: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; source: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; + silent: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; }; export class NotificationActionRunner extends ActionRunner { @@ -166,11 +169,11 @@ export class NotificationActionRunner extends ActionRunner { super(); } - protected async runAction(action: IAction, context: INotificationViewItem | undefined): Promise { + protected override async runAction(action: IAction, context: INotificationViewItem | undefined): Promise { this.telemetryService.publicLog2('workbenchActionExecuted', { id: action.id, from: 'message' }); if (context) { // If the context is not present it is a "global" notification action. Will be captured by other events - this.telemetryService.publicLog2('notification:actionExecuted', { id: hash(context.message.original.toString()).toString(), actionLabel: action.label, source: context.sourceId }); + this.telemetryService.publicLog2('notification:actionExecuted', { id: hash(context.message.original.toString()).toString(), actionLabel: action.label, source: context.sourceId || 'core', silent: context.silent }); } // Run and make sure to notify on any error again diff --git a/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts b/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts index 19958434..2c1d47f2 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts @@ -248,7 +248,7 @@ export class NotificationsCenter extends Themable implements INotificationsCente } } - protected updateStyles(): void { + protected override updateStyles(): void { if (this.notificationsCenterContainer && this.notificationsCenterHeader) { const widgetShadowColor = this.getColor(widgetShadow); this.notificationsCenterContainer.style.boxShadow = widgetShadowColor ? `0 0 8px 2px ${widgetShadowColor}` : ''; diff --git a/src/vs/workbench/browser/parts/notifications/notificationsList.ts b/src/vs/workbench/browser/parts/notifications/notificationsList.ts index b1e8112e..ec27091c 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsList.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsList.ts @@ -246,7 +246,7 @@ export class NotificationsList extends Themable { return isAncestor(document.activeElement, this.listContainer); } - protected updateStyles(): void { + protected override updateStyles(): void { if (this.listContainer) { const foreground = this.getColor(NOTIFICATIONS_FOREGROUND); this.listContainer.style.color = foreground ? foreground : ''; @@ -271,7 +271,7 @@ export class NotificationsList extends Themable { } } - dispose(): void { + override dispose(): void { this.hide(); super.dispose(); diff --git a/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts b/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts index 52cb176c..f8482094 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts @@ -21,7 +21,7 @@ import { Severity, NotificationsFilter } from 'vs/platform/notification/common/n import { ScrollbarVisibility } from 'vs/base/common/scrollable'; import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IHostService } from 'vs/workbench/services/host/browser/host'; -import { IntervalCounter, timeout } from 'vs/base/common/async'; +import { IntervalCounter } from 'vs/base/common/async'; import { assertIsDefined } from 'vs/base/common/types'; interface INotificationToast { @@ -93,8 +93,9 @@ export class NotificationsToasts extends Themable implements INotificationsToast // Layout this._register(this.layoutService.onDidLayout(dimension => this.layout(Dimension.lift(dimension)))); - // Delay some tasks until after we can show notifications - this.onCanShowNotifications().then(() => { + // Delay some tasks until after we have restored + // to reduce UI pressure from the startup phase + this.lifecycleService.when(LifecyclePhase.Restored).then(() => { // Show toast for initial notifications if any this.model.notifications.forEach(notification => this.addToast(notification)); @@ -111,19 +112,6 @@ export class NotificationsToasts extends Themable implements INotificationsToast })); } - private async onCanShowNotifications(): Promise { - - // Wait for the running phase to ensure we can draw notifications properly - await this.lifecycleService.when(LifecyclePhase.Ready); - - // Push notificiations out until either workbench is restored - // or some time has ellapsed to reduce pressure on the startup - return Promise.race([ - this.lifecycleService.when(LifecyclePhase.Restored), - timeout(2000) - ]); - } - private onDidChangeNotification(e: INotificationChangeEvent): void { switch (e.kind) { case NotificationChangeType.ADD: @@ -476,7 +464,7 @@ export class NotificationsToasts extends Themable implements INotificationsToast } } - protected updateStyles(): void { + protected override updateStyles(): void { this.mapNotificationToToast.forEach(({ toast }) => { const backgroundColor = this.getColor(NOTIFICATIONS_BACKGROUND); toast.style.background = backgroundColor ? backgroundColor : ''; diff --git a/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts b/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts index 3255a708..1a316c06 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts @@ -85,7 +85,7 @@ export class NotificationsListDelegate implements IListVirtualDelegate this.openerService.open(URI.parse(link)), + callback: link => this.openerService.open(URI.parse(link), { allowCommands: true }), toDispose: this.inputDisposables })); @@ -444,7 +444,7 @@ export class NotificationTemplateRenderer extends Disposable { if (notification.expanded && isNonEmptyArray(primaryActions)) { const that = this; const actionRunner: IActionRunner = new class extends ActionRunner { - protected async runAction(action: IAction): Promise { + protected override async runAction(action: IAction): Promise { // Run action that.actionRunner.run(action, notification); diff --git a/src/vs/workbench/browser/parts/panel/media/panelpart.css b/src/vs/workbench/browser/parts/panel/media/panelpart.css index f1c7734e..17254ee7 100644 --- a/src/vs/workbench/browser/parts/panel/media/panelpart.css +++ b/src/vs/workbench/browser/parts/panel/media/panelpart.css @@ -46,6 +46,14 @@ border-right-width: 0; /* no border when editor area is hiden */ } +.monaco-workbench .part.panel > .composite.title > .title-actions .monaco-action-bar .actions-container { + justify-content: flex-end; +} + +.monaco-workbench .part.panel > .composite.title > .title-actions .monaco-action-bar .action-item { + margin-right: 4px; +} + .monaco-workbench .part.panel > .composite.title > .title-actions .monaco-action-bar .action-item .action-label { outline-offset: -2px; } @@ -144,14 +152,24 @@ opacity: 1; } -.monaco-workbench .part.panel > .composite.title> .panel-switcher-container > .monaco-action-bar .action-item .action-label{ +.monaco-workbench .part.panel > .composite.title> .panel-switcher-container > .monaco-action-bar .action-item .action-label { margin-right: 0; + padding: 2px; +} + +.monaco-workbench .part.panel > .composite.title> .panel-switcher-container > .monaco-action-bar .action-item .action-label:not(.codicon) { + background: none !important; + border-radius: 0; } .monaco-workbench .part.panel > .composite.title> .panel-switcher-container > .monaco-action-bar .action-item:last-child { padding-right: 10px; } +.monaco-workbench .part.panel > .composite.title> .panel-switcher-container > .monaco-action-bar .action-item:not(.checked) .action-label { + margin-bottom: 1px; +} + .monaco-workbench .part.panel > .composite.title> .panel-switcher-container > .monaco-action-bar .action-item.checked .action-label { border-bottom: 1px solid; margin-right: 0; diff --git a/src/vs/workbench/browser/parts/panel/panelActions.ts b/src/vs/workbench/browser/parts/panel/panelActions.ts index 0d447ee9..3f4cc41b 100644 --- a/src/vs/workbench/browser/parts/panel/panelActions.ts +++ b/src/vs/workbench/browser/parts/panel/panelActions.ts @@ -38,7 +38,7 @@ export class TogglePanelAction extends Action { super(id, name, layoutService.isVisible(Parts.PANEL_PART) ? 'panel expanded' : 'panel'); } - async run(): Promise { + override async run(): Promise { this.layoutService.setPanelHidden(this.layoutService.isVisible(Parts.PANEL_PART)); } } @@ -57,7 +57,7 @@ class FocusPanelAction extends Action { super(id, label); } - async run(): Promise { + override async run(): Promise { // Show panel if (!this.layoutService.isVisible(Parts.PANEL_PART)) { @@ -113,7 +113,7 @@ export class SetPanelPositionAction extends Action { super(id, label); } - async run(): Promise { + override async run(): Promise { const position = positionByActionId.get(this.id); this.layoutService.setPanelPosition(position === undefined ? Position.BOTTOM : position); } @@ -128,7 +128,7 @@ export class PanelActivityAction extends ActivityAction { super(activity); } - async run(): Promise { + override async run(): Promise { await this.panelService.openPanel(this.activity.id, true); this.activate(); } @@ -169,7 +169,7 @@ export class SwitchPanelViewAction extends Action { super(id, name); } - async run(offset: number): Promise { + override async run(offset: number): Promise { const pinnedPanels = this.panelService.getPinnedPanels(); const activePanel = this.panelService.getActivePanel(); if (!activePanel) { @@ -201,7 +201,7 @@ export class PreviousPanelViewAction extends SwitchPanelViewAction { super(id, name, panelService); } - run(): Promise { + override run(): Promise { return super.run(-1); } } @@ -219,7 +219,7 @@ export class NextPanelViewAction extends SwitchPanelViewAction { super(id, name, panelService); } - run(): Promise { + override run(): Promise { return super.run(1); } } diff --git a/src/vs/workbench/browser/parts/panel/panelPart.ts b/src/vs/workbench/browser/parts/panel/panelPart.ts index c2cf7ea3..49a9fdd7 100644 --- a/src/vs/workbench/browser/parts/panel/panelPart.ts +++ b/src/vs/workbench/browser/parts/panel/panelPart.ts @@ -37,6 +37,7 @@ import { ViewContainer, IViewDescriptorService, IViewContainerModel, ViewContain import { IPaneComposite } from 'vs/workbench/common/panecomposite'; import { Before2D, CompositeDragAndDropObserver, ICompositeDragAndDrop, toggleDropEffect } from 'vs/workbench/browser/dnd'; import { IActivity } from 'vs/workbench/common/activity'; +import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget'; interface ICachedPanel { id: string; @@ -148,6 +149,10 @@ export class PanelPart extends CompositePart implements IPanelService { this.compositeBar = this._register(this.instantiationService.createInstance(CompositeBar, this.getCachedPanels(), { icon: false, orientation: ActionsOrientation.HORIZONTAL, + activityHoverOptions: { + position: () => this.layoutService.getPanelPosition() === Position.BOTTOM && !this.layoutService.isPanelMaximized() ? HoverPosition.ABOVE : HoverPosition.BELOW, + delay: () => 0 + }, openComposite: compositeId => this.openPanel(compositeId, true).then(panel => panel || null), getActivityAction: compositeId => this.getCompositeActions(compositeId).activityAction, getCompositePinnedAction: compositeId => this.getCompositeActions(compositeId).pinnedAction, @@ -415,7 +420,7 @@ export class PanelPart extends CompositePart implements IPanelService { this.layoutEmptyMessage(); } - create(parent: HTMLElement): void { + override create(parent: HTMLElement): void { this.element = parent; super.create(parent); @@ -468,7 +473,7 @@ export class PanelPart extends CompositePart implements IPanelService { })); } - updateStyles(): void { + override updateStyles(): void { super.updateStyles(); const container = assertIsDefined(this.getContainer()); @@ -562,7 +567,7 @@ export class PanelPart extends CompositePart implements IPanelService { this.hideActiveComposite(); } - protected createTitleLabel(parent: HTMLElement): ICompositeTitleLabel { + protected override createTitleLabel(parent: HTMLElement): ICompositeTitleLabel { const titleArea = this.compositeBar.create(parent); titleArea.classList.add('panel-switcher-container'); @@ -579,7 +584,7 @@ export class PanelPart extends CompositePart implements IPanelService { }; } - layout(width: number, height: number): void { + override layout(width: number, height: number): void { if (!this.layoutService.isVisible(Parts.PANEL_PART)) { return; } @@ -646,7 +651,7 @@ export class PanelPart extends CompositePart implements IPanelService { return compositeActions; } - protected removeComposite(compositeId: string): boolean { + protected override removeComposite(compositeId: string): boolean { if (super.removeComposite(compositeId)) { this.compositeBar.removeComposite(compositeId); const compositeActions = this.compositeActions.get(compositeId); @@ -857,16 +862,15 @@ registerThemingParticipant((theme, collector) => { if (outline) { collector.addRule(` .monaco-workbench .part.panel > .title > .panel-switcher-container > .monaco-action-bar .action-item.checked .action-label, - .monaco-workbench .part.panel > .title > .panel-switcher-container > .monaco-action-bar .action-item .action-label:hover { + .monaco-workbench .part.panel > .title > .panel-switcher-container > .monaco-action-bar .action-item:hover .action-label { outline-color: ${outline}; outline-width: 1px; outline-style: solid; border-bottom: none; - padding-bottom: 0; - outline-offset: 1px; + outline-offset: -2px; } - .monaco-workbench .part.panel > .title > .panel-switcher-container > .monaco-action-bar .action-item:not(.checked) .action-label:hover { + .monaco-workbench .part.panel > .title > .panel-switcher-container > .monaco-action-bar .action-item:not(.checked):hover .action-label { outline-style: dashed; } `); diff --git a/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css b/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css index c581f528..ed995215 100644 --- a/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css +++ b/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css @@ -8,6 +8,14 @@ visibility: hidden !important; } +.monaco-workbench .part.sidebar .title-actions .actions-container { + justify-content: flex-end; +} + +.monaco-workbench .part.sidebar .title-actions .action-item { + margin-right: 4px; +} + .monaco-workbench .part.sidebar > .title > .title-label h2 { text-transform: uppercase; } diff --git a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts index ed77ad7b..b08f327c 100644 --- a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts +++ b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts @@ -155,7 +155,7 @@ export class SidebarPart extends CompositePart implements IViewletServi })); } - create(parent: HTMLElement): void { + override create(parent: HTMLElement): void { this.element = parent; super.create(parent); @@ -165,7 +165,7 @@ export class SidebarPart extends CompositePart implements IViewletServi this._register(focusTracker.onDidBlur(() => this.sideBarFocusContextKey.set(false))); } - createTitleArea(parent: HTMLElement): HTMLElement { + override createTitleArea(parent: HTMLElement): HTMLElement { const titleArea = super.createTitleArea(parent); this._register(addDisposableListener(titleArea, EventType.CONTEXT_MENU, e => { @@ -183,7 +183,7 @@ export class SidebarPart extends CompositePart implements IViewletServi return titleArea; } - updateStyles(): void { + override updateStyles(): void { super.updateStyles(); // Part container @@ -203,7 +203,7 @@ export class SidebarPart extends CompositePart implements IViewletServi container.style.outlineColor = this.getColor(SIDE_BAR_DRAG_AND_DROP_BACKGROUND) ?? ''; } - layout(width: number, height: number): void { + override layout(width: number, height: number): void { if (!this.layoutService.isVisible(Parts.SIDEBAR_PART)) { return; } @@ -275,7 +275,7 @@ export class SidebarPart extends CompositePart implements IViewletServi return this.openComposite(id, focus) as Viewlet; } - protected getTitleAreaDropDownAnchorAlignment(): AnchorAlignment { + protected override getTitleAreaDropDownAnchorAlignment(): AnchorAlignment { return this.layoutService.getSideBarPosition() === SideBarPosition.LEFT ? AnchorAlignment.LEFT : AnchorAlignment.RIGHT; } diff --git a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts index 146c0458..20fe201c 100644 --- a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts +++ b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts @@ -39,6 +39,9 @@ import { RawContextKey, IContextKeyService } from 'vs/platform/contextkey/common import { ColorScheme } from 'vs/platform/theme/common/theme'; import { renderIcon, renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels'; import { syncing } from 'vs/platform/theme/common/iconRegistry'; +import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; +import { CATEGORIES } from 'vs/workbench/common/actions'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; interface IPendingStatusbarEntry { id: string; @@ -208,6 +211,11 @@ class StatusbarViewModel extends Disposable { this.focusEntry(-1, this.entries.length - 1); } + isEntryFocused(): boolean { + const focused = this._entries.find(entry => isAncestor(document.activeElement, entry.container)); + return !!focused; + } + private focusEntry(delta: number, restartPosition: number): void { const getVisibleEntry = (start: number) => { let indexToFocus = start; @@ -352,7 +360,7 @@ class ToggleStatusbarEntryVisibilityAction extends Action { this.checked = !model.isHidden(id); } - async run(): Promise { + override async run(): Promise { if (this.model.isHidden(this.id)) { this.model.show(this.id); } else { @@ -367,7 +375,7 @@ class HideStatusbarEntryAction extends Action { super(id, localize('hide', "Hide '{0}'", name), undefined, true); } - async run(): Promise { + override async run(): Promise { this.model.hide(this.id); } } @@ -496,6 +504,10 @@ export class StatusbarPart extends Part implements IStatusbarService { this.viewModel.focusPreviousEntry(); } + isEntryFocused(): boolean { + return this.viewModel.isEntryFocused(); + } + focus(preserveEntryFocus = true): void { this.getContainer()?.focus(); const lastFocusedEntry = this.viewModel.lastFocusedEntry; @@ -505,7 +517,7 @@ export class StatusbarPart extends Part implements IStatusbarService { } } - createContentArea(parent: HTMLElement): HTMLElement { + override createContentArea(parent: HTMLElement): HTMLElement { this.element = parent; // Track focus within container @@ -516,7 +528,7 @@ export class StatusbarPart extends Part implements IStatusbarService { this.leftItemsContainer = document.createElement('div'); this.leftItemsContainer.classList.add('left-items', 'items-container'); this.element.appendChild(this.leftItemsContainer); - this.element.tabIndex = -1; + this.element.tabIndex = 0; // Right items container this.rightItemsContainer = document.createElement('div'); @@ -646,7 +658,7 @@ export class StatusbarPart extends Part implements IStatusbarService { return actions; } - updateStyles(): void { + override updateStyles(): void { super.updateStyles(); const container = assertIsDefined(this.getContainer()); @@ -692,7 +704,7 @@ export class StatusbarPart extends Part implements IStatusbarService { return itemContainer; } - layout(width: number, height: number): void { + override layout(width: number, height: number): void { super.layout(width, height); super.layoutContents(width, height); } @@ -724,7 +736,7 @@ class StatusBarCodiconLabel extends SimpleIconLabel { } } - set text(text: string) { + override set text(text: string) { // Progress: insert progress codicon as first element as needed // but keep it stable so that the animation does not reset @@ -928,7 +940,7 @@ class StatusbarEntryItem extends Disposable { } } - dispose(): void { + override dispose(): void { super.dispose(); dispose(this.foregroundListener); @@ -1042,6 +1054,30 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ when: CONTEXT_STATUS_BAR_FOCUSED, handler: (accessor: ServicesAccessor) => { const statusBarService = accessor.get(IStatusbarService); - statusBarService.focus(false); + const editorService = accessor.get(IEditorService); + if (statusBarService.isEntryFocused()) { + statusBarService.focus(false); + } else if (editorService.activeEditorPane) { + editorService.activeEditorPane.focus(); + } } }); + +class FocusStatusBarAction extends Action2 { + + constructor() { + super({ + id: 'workbench.action.focusStatusBar', + title: { value: localize('focusStatusBar', "Focus Status Bar"), original: 'Focus Status Bar' }, + category: CATEGORIES.View, + f1: true + }); + } + + async run(accessor: ServicesAccessor): Promise { + const layoutService = accessor.get(IWorkbenchLayoutService); + layoutService.focusPart(Parts.STATUSBAR_PART); + } +} + +registerAction2(FocusStatusBarAction); diff --git a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts index bc20eca2..ac94cca1 100644 --- a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts +++ b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts @@ -251,11 +251,12 @@ export abstract class MenubarControl extends Disposable { openable = { fileUri: uri }; } - const ret: IAction = new Action(commandId, unmnemonicLabel(label), undefined, undefined, (event) => { - const openInNewWindow = event && ((!isMacintosh && (event.ctrlKey || event.shiftKey)) || (isMacintosh && (event.metaKey || event.altKey))); + const ret: IAction = new Action(commandId, unmnemonicLabel(label), undefined, undefined, event => { + const browserEvent = event as KeyboardEvent; + const openInNewWindow = event && ((!isMacintosh && (browserEvent.ctrlKey || browserEvent.shiftKey)) || (isMacintosh && (browserEvent.metaKey || browserEvent.altKey))); return this.hostService.openWindow([openable], { - forceNewWindow: openInNewWindow, + forceNewWindow: !!openInNewWindow, remoteAuthority }); }); @@ -311,11 +312,11 @@ export class CustomMenubarControl extends MenubarControl { @IStorageService storageService: IStorageService, @INotificationService notificationService: INotificationService, @IPreferencesService preferencesService: IPreferencesService, - @IWorkbenchEnvironmentService protected readonly environmentService: IWorkbenchEnvironmentService, + @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, @IAccessibilityService accessibilityService: IAccessibilityService, @IThemeService private readonly themeService: IThemeService, @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, - @IHostService protected readonly hostService: IHostService, + @IHostService hostService: IHostService, @ICommandService commandService: ICommandService ) { super(menuService, workspacesService, contextKeyService, keybindingService, configurationService, labelService, updateService, storageService, notificationService, preferencesService, environmentService, accessibilityService, hostService, commandService); @@ -463,7 +464,7 @@ export class CustomMenubarControl extends MenubarControl { case StateType.Idle: return new Action('update.check', localize({ key: 'checkForUpdates', comment: ['&& denotes a mnemonic'] }, "Check for &&Updates..."), undefined, true, () => - this.updateService.checkForUpdates(this.environmentService.sessionId)); + this.updateService.checkForUpdates(true)); case StateType.CheckingForUpdates: return new Action('update.checking', localize('checkingForUpdates', "Checking for Updates..."), undefined, false); @@ -739,7 +740,7 @@ export class CustomMenubarControl extends MenubarControl { }; } - protected onDidChangeWindowFocus(hasFocus: boolean): void { + protected override onDidChangeWindowFocus(hasFocus: boolean): void { if (!this.visible) { return; } @@ -758,7 +759,7 @@ export class CustomMenubarControl extends MenubarControl { } } - protected onUpdateStateChange(): void { + protected override onUpdateStateChange(): void { if (!this.visible) { return; } @@ -766,7 +767,7 @@ export class CustomMenubarControl extends MenubarControl { super.onUpdateStateChange(); } - protected onDidChangeRecentlyOpened(): void { + protected override onDidChangeRecentlyOpened(): void { if (!this.visible) { return; } @@ -774,7 +775,7 @@ export class CustomMenubarControl extends MenubarControl { super.onDidChangeRecentlyOpened(); } - protected onUpdateKeybindings(): void { + protected override onUpdateKeybindings(): void { if (!this.visible) { return; } @@ -782,7 +783,7 @@ export class CustomMenubarControl extends MenubarControl { super.onUpdateKeybindings(); } - protected registerListeners(): void { + protected override registerListeners(): void { super.registerListeners(); this._register(addDisposableListener(window, EventType.RESIZE, () => { diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index 94f766ad..8572b7f4 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -42,6 +42,7 @@ import { IProductService } from 'vs/platform/product/common/productService'; import { Schemas } from 'vs/base/common/network'; import { withNullAsUndefined } from 'vs/base/common/types'; import { Codicon, iconRegistry } from 'vs/base/common/codicons'; +import { getVirtualWorkspaceLocation } from 'vs/platform/remote/common/remoteHosts'; export class TitlebarPart extends Part implements ITitleService { @@ -278,6 +279,19 @@ export class TitlebarPart extends Part implements ITitleService { folder = withNullAsUndefined(this.contextService.getWorkspaceFolder(editorResource)); } + // Compute remote + // vscode-remtoe: use as is + // otherwise figure out if we have a virtual folder opened + let remoteName: string | undefined = undefined; + if (this.environmentService.remoteAuthority) { + remoteName = this.labelService.getHostLabel(Schemas.vscodeRemote, this.environmentService.remoteAuthority); + } else { + const virtualWorkspaceLocation = getVirtualWorkspaceLocation(workspace); + if (virtualWorkspaceLocation) { + remoteName = this.labelService.getHostLabel(virtualWorkspaceLocation.scheme, virtualWorkspaceLocation.authority); + } + } + // Variables const activeEditorShort = editor ? editor.getTitle(Verbosity.SHORT) : ''; const activeEditorMedium = editor ? editor.getTitle(Verbosity.MEDIUM) : activeEditorShort; @@ -291,7 +305,6 @@ export class TitlebarPart extends Part implements ITitleService { const folderPath = folder ? this.labelService.getUriLabel(folder.uri) : ''; const dirty = editor?.isDirty() && !editor.isSaving() ? TitlebarPart.TITLE_DIRTY : ''; const appName = this.productService.nameLong; - const remoteName = this.labelService.getHostLabel(Schemas.vscodeRemote, this.environmentService.remoteAuthority); const separator = this.configurationService.getValue('window.titleSeparator'); const titleTemplate = this.configurationService.getValue('window.title'); @@ -341,7 +354,7 @@ export class TitlebarPart extends Part implements ITitleService { this._register(this.customMenubar.onVisibilityChange(e => this.onMenubarVisibilityChanged(e))); } - createContentArea(parent: HTMLElement): HTMLElement { + override createContentArea(parent: HTMLElement): HTMLElement { this.element = parent; // App Icon (Native Windows/Linux and Web) @@ -413,7 +426,7 @@ export class TitlebarPart extends Part implements ITitleService { return this.element; } - updateStyles(): void { + override updateStyles(): void { super.updateStyles(); // Part container @@ -514,7 +527,7 @@ export class TitlebarPart extends Part implements ITitleService { } } - layout(width: number, height: number): void { + override layout(width: number, height: number): void { this.updateLayout(new Dimension(width, height)); super.layoutContents(width, height); diff --git a/src/vs/workbench/browser/parts/views/media/paneviewlet.css b/src/vs/workbench/browser/parts/views/media/paneviewlet.css index f724b937..19dc50ab 100644 --- a/src/vs/workbench/browser/parts/views/media/paneviewlet.css +++ b/src/vs/workbench/browser/parts/views/media/paneviewlet.css @@ -18,13 +18,14 @@ .monaco-pane-view .pane > .pane-header > .actions.show { display: initial; } -.monaco-pane-view .pane > .pane-header .icon { + +.monaco-pane-view .pane > .pane-header > .icon { display: none; width: 16px; height: 16px; } -.monaco-pane-view .pane.pane.horizontal:not(.expanded) > .pane-header .icon { +.monaco-pane-view .pane.pane.horizontal:not(.expanded) > .pane-header > .icon { display: inline; margin-top: 4px; } diff --git a/src/vs/workbench/browser/parts/views/media/views.css b/src/vs/workbench/browser/parts/views/media/views.css index ff7bbb6c..d82afb63 100644 --- a/src/vs/workbench/browser/parts/views/media/views.css +++ b/src/vs/workbench/browser/parts/views/media/views.css @@ -131,6 +131,11 @@ overflow: hidden; } +.customview-tree .monaco-list .monaco-list-row .custom-view-tree-node-item .monaco-icon-label-container::after { + content: ''; + display: block; +} + .customview-tree .monaco-list .monaco-list-row .custom-view-tree-node-item > .custom-view-tree-node-item-icon { background-size: 16px; background-position: left center; @@ -162,25 +167,12 @@ display: none; } +.customview-tree .monaco-list .monaco-list-row .custom-view-tree-node-item .actions .action-label { + padding: 2px; +} + .customview-tree .monaco-list .monaco-list-row:hover .custom-view-tree-node-item .actions, .customview-tree .monaco-list .monaco-list-row.selected .custom-view-tree-node-item .actions, .customview-tree .monaco-list .monaco-list-row.focused .custom-view-tree-node-item .actions { display: block; } - -.customview-tree .monaco-list .custom-view-tree-node-item .actions .action-label { - width: 16px; - height: 100%; - background-size: 16px; - background-position: 50% 50%; - background-repeat: no-repeat; -} - -.customview-tree .monaco-list .custom-view-tree-node-item .actions .action-label.codicon { - line-height: 22px; - height: 22px; -} - -.customview-tree .monaco-list .custom-view-tree-node-item .actions .action-label.codicon::before { - vertical-align: middle; -} diff --git a/src/vs/workbench/browser/parts/views/treeView.ts b/src/vs/workbench/browser/parts/views/treeView.ts index e6b07691..075cf857 100644 --- a/src/vs/workbench/browser/parts/views/treeView.ts +++ b/src/vs/workbench/browser/parts/views/treeView.ts @@ -54,6 +54,7 @@ import { API_OPEN_DIFF_EDITOR_COMMAND_ID, API_OPEN_EDITOR_COMMAND_ID } from 'vs/ import { Codicon } from 'vs/base/common/codicons'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { Command } from 'vs/editor/common/modes'; +import { isPromiseCanceledError } from 'vs/base/common/errors'; export class TreeViewPane extends ViewPane { @@ -71,7 +72,7 @@ export class TreeViewPane extends ViewPane { @IThemeService themeService: IThemeService, @ITelemetryService telemetryService: ITelemetryService, ) { - super({ ...(options as IViewPaneOptions), titleMenuId: MenuId.ViewTitle }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); + super({ ...(options as IViewPaneOptions), titleMenuId: MenuId.ViewTitle, donotForwardArgs: true }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); const { treeView } = (Registry.as(Extensions.ViewsRegistry).getView(options.id)); this.treeView = treeView; this._register(this.treeView.onDidChangeActions(() => this.updateActions(), this)); @@ -89,26 +90,26 @@ export class TreeViewPane extends ViewPane { this.updateTreeVisibility(); } - focus(): void { + override focus(): void { super.focus(); this.treeView.focus(); } - renderBody(container: HTMLElement): void { + override renderBody(container: HTMLElement): void { super.renderBody(container); this.renderTreeView(container); } - shouldShowWelcome(): boolean { + override shouldShowWelcome(): boolean { return ((this.treeView.dataProvider === undefined) || !!this.treeView.dataProvider.isTreeEmpty) && (this.treeView.message === undefined); } - layoutBody(height: number, width: number): void { + override layoutBody(height: number, width: number): void { super.layoutBody(height, width); this.layoutTreeView(height, width); } - getOptimalWidth(): number { + override getOptimalWidth(): number { return this.treeView.getOptimalWidth(); } @@ -1071,13 +1072,13 @@ class MultipleSelectionActionRunner extends ActionRunner { constructor(notificationService: INotificationService, private getSelectedResources: (() => ITreeItem[])) { super(); this._register(this.onDidRun(e => { - if (e.error) { + if (e.error && !isPromiseCanceledError(e.error)) { notificationService.error(localize('command-error', 'Error running command {1}: {0}. This is likely caused by the extension that contributes {1}.', e.error.message, e.action.id)); } })); } - runAction(action: IAction, context: TreeViewItemHandleArg): Promise { + override async runAction(action: IAction, context: TreeViewItemHandleArg): Promise { const selection = this.getSelectedResources(); let selectionHandleArgs: TreeViewItemHandleArg[] | undefined = undefined; let actionInSelected: boolean = false; @@ -1094,7 +1095,7 @@ class MultipleSelectionActionRunner extends ActionRunner { selectionHandleArgs = undefined; } - return action.run(...[context, selectionHandleArgs]); + await action.run(...[context, selectionHandleArgs]); } } @@ -1164,7 +1165,7 @@ export class CustomTreeView extends TreeView { super(id, title, themeService, instantiationService, commandService, configurationService, progressService, contextMenuService, keybindingService, notificationService, viewDescriptorService, hoverService, contextKeyService); } - setVisibility(isVisible: boolean): void { + override setVisibility(isVisible: boolean): void { super.setVisibility(isVisible); if (this.visible) { this.activate(); diff --git a/src/vs/workbench/browser/parts/views/viewPane.ts b/src/vs/workbench/browser/parts/views/viewPane.ts index f53737da..7b20423c 100644 --- a/src/vs/workbench/browser/parts/views/viewPane.ts +++ b/src/vs/workbench/browser/parts/views/viewPane.ts @@ -46,6 +46,7 @@ export interface IViewPaneOptions extends IPaneOptions { id: string; showActionsAlways?: boolean; titleMenuId?: MenuId; + donotForwardArgs?: boolean; } type WelcomeActionClassification = { @@ -142,6 +143,7 @@ class ViewMenuActions extends CompositeMenuActions { viewId: string, menuId: MenuId, contextMenuId: MenuId, + donotForwardArgs: boolean, @IContextKeyService contextKeyService: IContextKeyService, @IMenuService menuService: IMenuService, @IViewDescriptorService viewDescriptorService: IViewDescriptorService, @@ -149,7 +151,7 @@ class ViewMenuActions extends CompositeMenuActions { const scopedContextKeyService = contextKeyService.createScoped(element); scopedContextKeyService.createKey('view', viewId); const viewLocationKey = scopedContextKeyService.createKey('viewLocation', ViewContainerLocationToString(viewDescriptorService.getViewLocationById(viewId)!)); - super(menuId, contextMenuId, { shouldForwardArgs: true }, scopedContextKeyService, menuService); + super(menuId, contextMenuId, { shouldForwardArgs: !donotForwardArgs }, scopedContextKeyService, menuService); this._register(scopedContextKeyService); this._register(Event.filter(viewDescriptorService.onDidChangeLocation, e => e.views.some(view => view.id === viewId))(() => viewLocationKey.set(ViewContainerLocationToString(viewDescriptorService.getViewLocationById(viewId)!)))); } @@ -225,17 +227,17 @@ export abstract class ViewPane extends Pane implements IView { this._titleDescription = options.titleDescription; this.showActionsAlways = !!options.showActionsAlways; - this.menuActions = this._register(this.instantiationService.createInstance(ViewMenuActions, this.element, this.id, options.titleMenuId || MenuId.ViewTitle, MenuId.ViewTitleContext)); + this.menuActions = this._register(this.instantiationService.createInstance(ViewMenuActions, this.element, this.id, options.titleMenuId || MenuId.ViewTitle, MenuId.ViewTitleContext, !!options.donotForwardArgs)); this._register(this.menuActions.onDidChange(() => this.updateActions())); this.viewWelcomeController = new ViewWelcomeController(this.id, contextKeyService); } - get headerVisible(): boolean { + override get headerVisible(): boolean { return super.headerVisible; } - set headerVisible(visible: boolean) { + override set headerVisible(visible: boolean) { super.headerVisible = visible; this.element.classList.toggle('merged-header', !visible); } @@ -258,7 +260,7 @@ export abstract class ViewPane extends Pane implements IView { return this._isVisible && this.isExpanded(); } - setExpanded(expanded: boolean): boolean { + override setExpanded(expanded: boolean): boolean { const changed = super.setExpanded(expanded); if (changed) { this._onDidChangeBodyVisibility.fire(expanded); @@ -270,7 +272,7 @@ export abstract class ViewPane extends Pane implements IView { return changed; } - render(): void { + override render(): void { super.render(); const focusTracker = trackFocus(this.element); @@ -314,7 +316,7 @@ export abstract class ViewPane extends Pane implements IView { return expanded ? viewPaneContainerExpandedIcon : viewPaneContainerCollapsedIcon; } - style(styles: IPaneStyles): void { + override style(styles: IPaneStyles): void { super.style(styles); const icon = this.getIcon(); @@ -552,7 +554,7 @@ export abstract class ViewPane extends Pane implements IView { button.label = node.label; button.onDidClick(_ => { this.telemetryService.publicLog2<{ viewId: string, uri: string }, WelcomeActionClassification>('views.welcomeAction', { viewId: this.id, uri: node.href }); - this.openerService.open(node.href); + this.openerService.open(node.href, { allowCommands: true }); }, null, disposables); disposables.add(button); disposables.add(attachButtonStyler(button, this.themeService)); @@ -603,8 +605,10 @@ export abstract class ViewPane extends Pane implements IView { } export abstract class ViewAction extends Action2 { - constructor(readonly desc: Readonly & { viewId: string }) { + override readonly desc: Readonly & { viewId: string }; + constructor(desc: Readonly & { viewId: string }) { super(desc); + this.desc = desc; } run(accessor: ServicesAccessor, ...args: any[]) { diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index 32db51de..255e8eab 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -96,7 +96,7 @@ class ViewPaneDropOverlay extends Themable { private orientation: Orientation | undefined, private bounds: BoundingRect | undefined, protected location: ViewContainerLocation, - protected themeService: IThemeService, + themeService: IThemeService, ) { super(themeService); this.cleanupOverlayScheduler = this._register(new RunOnceScheduler(() => this.dispose(), 300)); @@ -134,7 +134,7 @@ class ViewPaneDropOverlay extends Themable { this.updateStyles(); } - protected updateStyles(): void { + protected override updateStyles(): void { // Overlay drop background this.overlay.style.backgroundColor = this.getColor(this.location === ViewContainerLocation.Panel ? PANEL_SECTION_DRAG_AND_DROP_BACKGROUND : SIDE_BAR_DRAG_AND_DROP_BACKGROUND) || ''; @@ -287,7 +287,7 @@ class ViewPaneDropOverlay extends Themable { return element === this.container || element === this.overlay; } - dispose(): void { + override dispose(): void { super.dispose(); this._disposed = true; @@ -381,7 +381,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { @IContextMenuService protected contextMenuService: IContextMenuService, @ITelemetryService protected telemetryService: ITelemetryService, @IExtensionService protected extensionService: IExtensionService, - @IThemeService protected themeService: IThemeService, + @IThemeService themeService: IThemeService, @IStorageService protected storageService: IStorageService, @IWorkspaceContextService protected contextService: IWorkspaceContextService, @IViewDescriptorService protected viewDescriptorService: IViewDescriptorService, @@ -720,7 +720,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { return sizes; } - saveState(): void { + override saveState(): void { this.panes.forEach((view) => view.saveState()); this.storageService.store(this.visibleViewsStorageId, this.length, StorageScope.WORKSPACE, StorageTarget.USER); } @@ -1064,7 +1064,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { return true; } - dispose(): void { + override dispose(): void { super.dispose(); this.paneItems.forEach(i => i.disposable.dispose()); if (this.paneview) { @@ -1074,8 +1074,10 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { } export abstract class ViewPaneContainerAction extends Action2 { - constructor(readonly desc: Readonly & { viewPaneContainerId: string }) { + override readonly desc: Readonly & { viewPaneContainerId: string }; + constructor(desc: Readonly & { viewPaneContainerId: string }) { super(desc); + this.desc = desc; } run(accessor: ServicesAccessor, ...args: any[]) { diff --git a/src/vs/workbench/browser/parts/views/viewsViewlet.ts b/src/vs/workbench/browser/parts/views/viewsViewlet.ts index 00b65ec2..6ea2e1ee 100644 --- a/src/vs/workbench/browser/parts/views/viewsViewlet.ts +++ b/src/vs/workbench/browser/parts/views/viewsViewlet.ts @@ -121,7 +121,7 @@ export abstract class FilterViewPaneContainer extends ViewPaneContainer { return views; } - onDidAddViewDescriptors(added: IAddedViewDescriptorRef[]): ViewPane[] { + override onDidAddViewDescriptors(added: IAddedViewDescriptorRef[]): ViewPane[] { const panes: ViewPane[] = super.onDidAddViewDescriptors(added); for (let i = 0; i < added.length; i++) { if (this.constantViewDescriptors.has(added[i].viewDescriptor.id)) { @@ -135,6 +135,6 @@ export abstract class FilterViewPaneContainer extends ViewPaneContainer { return panes; } - abstract getTitle(): string; + abstract override getTitle(): string; } diff --git a/src/vs/workbench/browser/style.ts b/src/vs/workbench/browser/style.ts index 5028f89c..c83278bd 100644 --- a/src/vs/workbench/browser/style.ts +++ b/src/vs/workbench/browser/style.ts @@ -5,7 +5,7 @@ import 'vs/css!./media/style'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; -import { iconForeground, foreground, selectionBackground, focusBorder, scrollbarShadow, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, listHighlightForeground, inputPlaceholderForeground } from 'vs/platform/theme/common/colorRegistry'; +import { iconForeground, foreground, selectionBackground, focusBorder, scrollbarShadow, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, listHighlightForeground, inputPlaceholderForeground, toolbarHoverBackground, toolbarActiveBackground, toolbarHoverOutline } from 'vs/platform/theme/common/colorRegistry'; import { WORKBENCH_BACKGROUND, TITLE_BAR_ACTIVE_BACKGROUND } from 'vs/workbench/common/theme'; import { isWeb, isIOS, isMacintosh, isWindows } from 'vs/base/common/platform'; import { createMetaElement } from 'vs/base/browser/dom'; @@ -182,6 +182,39 @@ registerThemingParticipant((theme, collector) => { if (isIOS && isStandalone) { collector.addRule(`body { background-color: ${workbenchBackground}; }`); } + + // Action bars + const toolbarHoverBackgroundColor = theme.getColor(toolbarHoverBackground); + if (toolbarHoverBackgroundColor) { + collector.addRule(` + .monaco-action-bar:not(.vertical) .action-label:not(.disabled):hover { + background-color: ${toolbarHoverBackgroundColor}; + } + .monaco-action-bar:not(.vertical) .monaco-dropdown-with-primary:not(.disabled):hover { + background-color: ${toolbarHoverBackgroundColor}; + } + `); + } + + const toolbarActiveBackgroundColor = theme.getColor(toolbarActiveBackground); + if (toolbarActiveBackgroundColor) { + collector.addRule(` + .monaco-action-bar:not(.vertical) .action-item.active .action-label:not(.disabled), + .monaco-action-bar:not(.vertical) .monaco-dropdown.active .action-label:not(.disabled) { + background-color: ${toolbarActiveBackgroundColor}; + } + `); + } + + const toolbarHoverOutlineColor = theme.getColor(toolbarHoverOutline); + if (toolbarHoverOutlineColor) { + collector.addRule(` + .monaco-action-bar:not(.vertical) .action-item .action-label:hover:not(.disabled) { + outline: 1px dashed ${toolbarHoverOutlineColor}; + outline-offset: -1px; + } + `); + } }); /** diff --git a/src/vs/workbench/browser/viewlet.ts b/src/vs/workbench/browser/viewlet.ts index 4d1fe977..31d4a000 100644 --- a/src/vs/workbench/browser/viewlet.ts +++ b/src/vs/workbench/browser/viewlet.ts @@ -22,12 +22,12 @@ export abstract class Viewlet extends PaneComposite implements IViewlet { constructor(id: string, @ITelemetryService telemetryService: ITelemetryService, - @IStorageService protected storageService: IStorageService, - @IInstantiationService protected instantiationService: IInstantiationService, + @IStorageService storageService: IStorageService, + @IInstantiationService instantiationService: IInstantiationService, @IThemeService themeService: IThemeService, - @IContextMenuService protected contextMenuService: IContextMenuService, - @IExtensionService protected extensionService: IExtensionService, - @IWorkspaceContextService protected contextService: IWorkspaceContextService, + @IContextMenuService contextMenuService: IContextMenuService, + @IExtensionService extensionService: IExtensionService, + @IWorkspaceContextService contextService: IWorkspaceContextService, @IWorkbenchLayoutService protected layoutService: IWorkbenchLayoutService, @IConfigurationService protected configurationService: IConfigurationService ) { diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts index 9aea863b..d5bf2097 100644 --- a/src/vs/workbench/browser/web.main.ts +++ b/src/vs/workbench/browser/web.main.ts @@ -60,6 +60,9 @@ import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/ur import { UriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentityService'; import { BrowserWindow } from 'vs/workbench/browser/window'; import { ITimerService } from 'vs/workbench/services/timer/browser/timerService'; +import { WorkspaceTrustManagementService } from 'vs/workbench/services/workspaces/common/workspaceTrust'; +import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust'; +import { HTMLFileSystemProvider } from 'vs/platform/files/browser/htmlFileSystemProvider'; class BrowserMain extends Disposable { @@ -79,10 +82,9 @@ class BrowserMain extends Disposable { } async open(): Promise { - const services = await this.initServices(); - await domContentLoaded(); - mark('code/willStartWorkbench'); + // Init services and wait for DOM to be ready in parallel + const [services] = await Promise.all([this.initServices(), domContentLoaded()]); // Create Workbench const workbench = new Workbench(this.domElement, services.serviceCollection, services.logService); @@ -130,7 +132,7 @@ class BrowserMain extends Disposable { } })); this._register(workbench.onWillShutdown(() => storageService.close())); - this._register(workbench.onShutdown(() => this.dispose())); + this._register(workbench.onDidShutdown(() => this.dispose())); } private async initServices(): Promise<{ serviceCollection: ServiceCollection, configurationService: IWorkbenchConfigurationService, logService: ILogService, storageService: BrowserStorageService }> { @@ -200,6 +202,14 @@ class BrowserMain extends Disposable { }) ]); + // Workspace Trust Service + const workspaceTrustManagementService = new WorkspaceTrustManagementService(configurationService, environmentService, storageService, uriIdentityService, configurationService); + serviceCollection.set(IWorkspaceTrustManagementService, workspaceTrustManagementService); + + // Update workspace trust so that configuration is updated accordingly + configurationService.updateWorkspaceTrust(workspaceTrustManagementService.isWorkpaceTrusted()); + this._register(workspaceTrustManagementService.onDidChangeTrust(() => configurationService.updateWorkspaceTrust(workspaceTrustManagementService.isWorkpaceTrusted()))); + // Request Service const requestService = new BrowserRequestService(remoteAgentService, configurationService, logService); serviceCollection.set(IRequestService, requestService); @@ -269,7 +279,16 @@ class BrowserMain extends Disposable { } catch (error) { onUnexpectedError(error); } - fileService.registerProvider(Schemas.userData, indexedDBUserDataProvider || new InMemoryFileSystemProvider()); + + let userDataProvider: IFileSystemProvider | undefined; + if (indexedDBUserDataProvider) { + userDataProvider = indexedDBUserDataProvider; + } else { + logService.info('using in-memory user data provider'); + userDataProvider = new InMemoryFileSystemProvider(); + } + + fileService.registerProvider(Schemas.userData, userDataProvider); if (indexedDBUserDataProvider) { registerAction2(class ResetUserDataAction extends Action2 { @@ -299,6 +318,9 @@ class BrowserMain extends Disposable { } }); } + + fileService.registerProvider(Schemas.file, new HTMLFileSystemProvider()); + fileService.registerProvider(Schemas.tmp, new InMemoryFileSystemProvider()); } private async createStorageService(payload: IWorkspaceInitializationPayload, environmentService: IWorkbenchEnvironmentService, fileService: IFileService, logService: ILogService): Promise { diff --git a/src/vs/workbench/browser/window.ts b/src/vs/workbench/browser/window.ts index 56782daa..fa5f82cd 100644 --- a/src/vs/workbench/browser/window.ts +++ b/src/vs/workbench/browser/window.ts @@ -12,6 +12,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import { isIOS, isMacintosh } from 'vs/base/common/platform'; import Severity from 'vs/base/common/severity'; +import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { registerWindowDriver } from 'vs/platform/driver/browser/driver'; @@ -115,7 +116,7 @@ export class BrowserWindow extends Disposable { private create(): void { // Driver - if (this.environmentService.options?.driver) { + if (this.environmentService.options?.developmentOptions?.enableSmokeTestDriver) { (async () => this._register(await registerWindowDriver()))(); } @@ -138,7 +139,17 @@ export class BrowserWindow extends Disposable { this.openerService.setDefaultExternalOpener({ openExternal: async (href: string) => { if (matchesScheme(href, Schemas.http) || matchesScheme(href, Schemas.https)) { - windowOpenNoOpener(href); + const opened = windowOpenNoOpener(href); + if (!opened) { + const showResult = await this.dialogService.show(Severity.Warning, localize('unableToOpenExternal', "The browser interrupted the opening of a new tab or window. Press 'Open' to open it anyway."), + [localize('open', "Open"), localize('learnMore', "Learn More"), localize('cancel', "Cancel")], { cancelId: 2, detail: href }); + if (showResult.choice === 0) { + windowOpenNoOpener(href); + } + if (showResult.choice === 1) { + await this.openerService.open(URI.parse('https://aka.ms/allow-vscode-popup')); + } + } } else { this.lifecycleService.withExpectedUnload(() => window.location.href = href); } diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index 7e949a0b..30687a95 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -9,10 +9,14 @@ import { IConfigurationRegistry, Extensions as ConfigurationExtensions, Configur import { isMacintosh, isWindows, isLinux, isWeb, isNative } from 'vs/base/common/platform'; import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration'; import { isStandalone } from 'vs/base/browser/browser'; +import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { ITASExperimentService } from 'vs/workbench/services/experiment/common/experimentService'; +import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; + +const registry = Registry.as(ConfigurationExtensions.Configuration); // Configuration (function registerConfiguration(): void { - const registry = Registry.as(ConfigurationExtensions.Configuration); // Workbench registry.registerConfiguration({ @@ -88,7 +92,7 @@ import { isStandalone } from 'vs/base/browser/browser'; }, 'workbench.editor.untitled.hint': { 'type': 'string', - 'enum': ['text', 'button', 'hidden', 'default'], + 'enum': ['text', '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.") }, @@ -511,3 +515,23 @@ import { isStandalone } from 'vs/base/browser/browser'; } }); })(); + +class ExperimentalCustomHoverConfigContribution implements IWorkbenchContribution { + constructor(@ITASExperimentService tasExperimentService: ITASExperimentService) { + tasExperimentService.getTreatment('customHovers').then(useCustomHoversAsDefault => { + registry.registerConfiguration({ + ...workbenchConfigurationNodeBase, + 'properties': { + 'workbench.experimental.useCustomHover': { + 'type': 'boolean', + 'description': localize('workbench.experimental.useCustomHover', "Enable/disable custom hovers on Activity Bar & Panel. Note this configuration is experimental and subjected to be removed at any time."), + 'default': !!useCustomHoversAsDefault + } + } + }); + }); + } +} + +const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); +workbenchRegistry.registerWorkbenchContribution(ExperimentalCustomHoverConfigContribution, LifecyclePhase.Starting); diff --git a/src/vs/workbench/browser/workbench.ts b/src/vs/workbench/browser/workbench.ts index 64d9a7f9..2074e0c7 100644 --- a/src/vs/workbench/browser/workbench.ts +++ b/src/vs/workbench/browser/workbench.ts @@ -6,14 +6,14 @@ import 'vs/workbench/browser/style'; import { localize } from 'vs/nls'; import { Event, Emitter, setGlobalLeakWarningThreshold } from 'vs/base/common/event'; -import { runWhenIdle } from 'vs/base/common/async'; +import { RunOnceScheduler, runWhenIdle, timeout } from 'vs/base/common/async'; import { getZoomLevel, isFirefox, isSafari, isChrome, getPixelRatio } from 'vs/base/browser/browser'; import { mark } from 'vs/base/common/performance'; import { onUnexpectedError, setUnexpectedErrorHandler } from 'vs/base/common/errors'; import { Registry } from 'vs/platform/registry/common/platform'; import { isWindows, isLinux, isWeb, isNative, isMacintosh } from 'vs/base/common/platform'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; -import { IEditorInputFactoryRegistry, Extensions as EditorExtensions } from 'vs/workbench/common/editor'; +import { IEditorInputFactoryRegistry, EditorExtensions } from 'vs/workbench/common/editor'; import { getSingletonServiceDescriptors } from 'vs/platform/instantiation/common/extensions'; import { Position, Parts, IWorkbenchLayoutService, positionToString } from 'vs/workbench/services/layout/browser/layoutService'; import { IStorageService, WillSaveStateReason, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; @@ -48,8 +48,8 @@ export class Workbench extends Layout { private readonly _onWillShutdown = this._register(new Emitter()); readonly onWillShutdown = this._onWillShutdown.event; - private readonly _onShutdown = this._register(new Emitter()); - readonly onShutdown = this._onShutdown.event; + private readonly _onDidShutdown = this._register(new Emitter()); + readonly onDidShutdown = this._onDidShutdown.event; constructor( parent: HTMLElement, @@ -58,6 +58,9 @@ export class Workbench extends Layout { ) { super(parent); + // Perf: measure workbench startup time + mark('code/willStartWorkbench'); + this.registerErrorHandler(logService); } @@ -128,7 +131,7 @@ export class Workbench extends Layout { // Services const instantiationService = this.initServices(this.serviceCollection); - instantiationService.invokeFunction(async accessor => { + instantiationService.invokeFunction(accessor => { const lifecycleService = accessor.get(ILifecycleService); const storageService = accessor.get(IStorageService); const configurationService = accessor.get(IConfigurationService); @@ -157,11 +160,7 @@ export class Workbench extends Layout { this.layout(); // Restore - try { - await this.restoreWorkbench(accessor.get(ILogService), lifecycleService); - } catch (error) { - onUnexpectedError(error); - } + this.restore(lifecycleService); }); return instantiationService; @@ -216,12 +215,7 @@ export class Workbench extends Layout { return instantiationService; } - private registerListeners( - lifecycleService: ILifecycleService, - storageService: IStorageService, - configurationService: IConfigurationService, - hostService: IHostService - ): void { + private registerListeners(lifecycleService: ILifecycleService, storageService: IStorageService, configurationService: IConfigurationService, hostService: IHostService): void { // Configuration changes this._register(configurationService.onDidChangeConfiguration(() => this.setFontAliasing(configurationService))); @@ -240,8 +234,8 @@ export class Workbench extends Layout { // Lifecycle this._register(lifecycleService.onBeforeShutdown(event => this._onBeforeShutdown.fire(event))); this._register(lifecycleService.onWillShutdown(event => this._onWillShutdown.fire(event))); - this._register(lifecycleService.onShutdown(() => { - this._onShutdown.fire(); + this._register(lifecycleService.onDidShutdown(() => { + this._onDidShutdown.fire(); this.dispose(); })); @@ -250,7 +244,11 @@ export class Workbench extends Layout { // the chance of loosing any state. // The window loosing focus is a good indication that the user has stopped working // in that window so we pick that at a time to collect state. - this._register(hostService.onDidChangeFocus(focus => { if (!focus) { storageService.flush(); } })); + this._register(hostService.onDidChangeFocus(focus => { + if (!focus) { + storageService.flush(); + } + })); } private fontAliasing: 'default' | 'antialiased' | 'none' | 'auto' | undefined; @@ -361,7 +359,7 @@ export class Workbench extends Layout { } private createPart(id: string, role: string, classes: string[]): HTMLElement { - const part = document.createElement(role === 'status' ? 'footer' : 'div'); // Use footer element for status bar #98376 + const part = document.createElement(role === 'status' ? 'footer' /* Use footer element for status bar #98376 */ : 'div'); part.classList.add('part', ...classes); part.id = id; part.setAttribute('role', role); @@ -400,35 +398,54 @@ export class Workbench extends Layout { }); } - private async restoreWorkbench( - logService: ILogService, - lifecycleService: ILifecycleService - ): Promise { - - // Emit a warning after 10s if restore does not complete - const restoreTimeoutHandle = setTimeout(() => logService.warn('Workbench did not finish loading in 10 seconds, that might be a problem that should be reported.'), 10000); + private restore(lifecycleService: ILifecycleService): void { + // Ask each part to restore try { - await super.restoreWorkbenchLayout(); - - clearTimeout(restoreTimeoutHandle); + this.restoreParts(); } catch (error) { onUnexpectedError(error); - } finally { - - // Set lifecycle phase to `Restored` - lifecycleService.phase = LifecyclePhase.Restored; - - // Set lifecycle phase to `Eventually` after a short delay and when idle (min 2.5sec, max 5sec) - setTimeout(() => { - this._register(runWhenIdle(() => lifecycleService.phase = LifecyclePhase.Eventually, 2500)); - }, 2500); - - // Telemetry: startup metrics - mark('code/didStartWorkbench'); - - // Perf reporting (devtools) - performance.measure('perf: workbench create & restore', 'code/didLoadWorkbenchMain', 'code/didStartWorkbench'); } + + // Transition into restored phase after layout has restored + // but do not wait indefinitly on this to account for slow + // editors restoring. Since the workbench is fully functional + // even when the visible editors have not resolved, we still + // want contributions on the `Restored` phase to work before + // slow editors have resolved. But we also do not want fast + // editors to resolve slow when too many contributions get + // instantiated, so we find a middle ground solution via + // `Promise.race` + this.whenReady.finally(() => + Promise.race([ + this.whenRestored, + timeout(2000) + ]).finally(() => { + + // Set lifecycle phase to `Restored` + lifecycleService.phase = LifecyclePhase.Restored; + + // Set lifecycle phase to `Eventually` after a short delay and when idle (min 2.5sec, max 5sec) + const eventuallyPhaseScheduler = this._register(new RunOnceScheduler(() => { + this._register(runWhenIdle(() => lifecycleService.phase = LifecyclePhase.Eventually, 2500)); + }, 2500)); + eventuallyPhaseScheduler.schedule(); + + // Update perf marks only when the layout is fully + // restored. We want the time it takes to restore + // editors to be included in these numbers + + function markDidStartWorkbench() { + mark('code/didStartWorkbench'); + performance.measure('perf: workbench create & restore', 'code/didLoadWorkbenchMain', 'code/didStartWorkbench'); + } + + if (this.isRestored()) { + markDidStartWorkbench(); + } else { + this.whenRestored.finally(() => markDidStartWorkbench()); + } + }) + ); } } diff --git a/src/vs/workbench/common/composite.ts b/src/vs/workbench/common/composite.ts index b2bd4470..58bc7258 100644 --- a/src/vs/workbench/common/composite.ts +++ b/src/vs/workbench/common/composite.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IAction } from 'vs/base/common/actions'; import { Event } from 'vs/base/common/event'; export interface IComposite { @@ -33,21 +32,6 @@ export interface IComposite { */ getTitle(): string | undefined; - /** - * Returns the primary actions of the composite. - */ - getActions(): ReadonlyArray; - - /** - * Returns the secondary actions of the composite. - */ - getSecondaryActions(): ReadonlyArray; - - /** - * Returns an array of actions to show in the context menu of the composite - */ - getContextMenuActions(): ReadonlyArray; - /** * Returns the underlying control of this composite. */ diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index 3b9d0657..d33a1daa 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -13,7 +13,7 @@ import { IEditorModel, IEditorOptions, ITextEditorOptions, IBaseResourceEditorIn import { IInstantiationService, IConstructorSignature0, ServicesAccessor, BrandedService } from 'vs/platform/instantiation/common/instantiation'; import { IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { Registry } from 'vs/platform/registry/common/platform'; -import { ITextModel } from 'vs/editor/common/model'; +import { IEncodingSupport, IModeSupport } from 'vs/workbench/services/textfile/common/textfiles'; import { GroupsOrder, IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ICompositeControl, IComposite } from 'vs/workbench/common/composite'; import { ActionRunner, IAction } from 'vs/base/common/actions'; @@ -24,6 +24,13 @@ import { ACTIVE_GROUP, IResourceEditorInputType, SIDE_GROUP } from 'vs/workbench import { IRange } from 'vs/editor/common/core/range'; import { IExtUri } from 'vs/base/common/resources'; +// Static values for editor contributions +export const EditorExtensions = { + Editors: 'workbench.contributions.editors', + Associations: 'workbench.editors.associations', + EditorInputFactories: 'workbench.contributions.editor.inputFactories' +}; + // Editor State Context Keys export const ActiveEditorDirtyContext = new RawContextKey('activeEditorIsDirty', false, localize('activeEditorIsDirty', "Whether the active editor is dirty")); export const ActiveEditorPinnedContext = new RawContextKey('activeEditorIsNotPreview', false, localize('activeEditorIsNotPreview', "Whether the active editor is not in preview mode")); @@ -175,6 +182,11 @@ export interface IEditorControl extends ICompositeControl { } export interface IFileEditorInputFactory { + /** + * The type identifier of the file editor input. + */ + typeId: string; + /** * Creates new new editor input capable of showing files. */ @@ -186,8 +198,19 @@ export interface IFileEditorInputFactory { isFileEditorInput(obj: unknown): obj is IFileEditorInput; } +/** + * @deprecated obsolete + * + * TODO@bpasero remove this API and users once the generic backup restorer has been removed + */ export interface ICustomEditorInputFactory { + /** + * @deprecated obsolete + */ createCustomEditorInput(resource: URI, instantiationService: IInstantiationService): Promise; + /** + * @deprecated obsolete + */ canResolveBackup(editorInput: IEditorInput, backupResource: URI): boolean; } @@ -205,29 +228,33 @@ export interface IEditorInputFactoryRegistry { /** * Registers the custom editor input factory to use for custom inputs. + * + * @deprecated obsolete */ registerCustomEditorInputFactory(scheme: string, factory: ICustomEditorInputFactory): void; /** * Returns the custom editor input factory to use for custom inputs. + * + * @deprecated obsolete */ getCustomEditorInputFactory(scheme: string): ICustomEditorInputFactory | undefined; /** - * Registers a editor input factory for the given editor input to the registry. An editor input factory - * is capable of serializing and deserializing editor inputs from string data. + * Registers a editor input serializer for the given editor input to the registry. + * An editor input serializer is capable of serializing and deserializing editor + * inputs from string data. * - * @param editorInputId the identifier of the editor input - * @param factory the editor input factory for serialization/deserialization + * @param editorInputTypeId the type identifier of the editor input + * @param serializer the editor input serializer for serialization/deserialization */ - registerEditorInputFactory(editorInputId: string, ctor: { new(...Services: Services): IEditorInputFactory }): IDisposable; + registerEditorInputSerializer(editorInputTypeId: string, ctor: { new(...Services: Services): IEditorInputSerializer }): IDisposable; /** - * Returns the editor input factory for the given editor input. - * - * @param editorInputId the identifier of the editor input + * Returns the editor input serializer for the given editor input. */ - getEditorInputFactory(editorInputId: string): IEditorInputFactory | undefined; + getEditorInputSerializer(editorInput: IEditorInput): IEditorInputSerializer | undefined; + getEditorInputSerializer(editorInputTypeId: string): IEditorInputSerializer | undefined; /** * Starts the registry by providing the required services. @@ -235,10 +262,10 @@ export interface IEditorInputFactoryRegistry { start(accessor: ServicesAccessor): void; } -export interface IEditorInputFactory { +export interface IEditorInputSerializer { /** - * Determines whether the given editor input can be serialized by the factory. + * Determines whether the given editor input can be serialized by the serializer. */ canSerialize(editorInput: IEditorInput): boolean; @@ -373,9 +400,9 @@ export interface IMoveResult { export interface IEditorInput extends IDisposable { /** - * Triggered when this input is disposed. + * Triggered when this input is about to be disposed. */ - readonly onDispose: Event; + readonly onWillDispose: Event; /** * Triggered when this input changes its dirty state. @@ -387,6 +414,14 @@ export interface IEditorInput extends IDisposable { */ readonly onDidChangeLabel: Event; + /** + * Unique type identifier for this inpput. Every editor input of the + * same class should share the same type identifier. The type identifier + * is used for example for serialising/deserialising editor inputs + * via the serialisers of the `IEditorInputFactoryRegistry`. + */ + readonly typeId: string; + /** * Returns the optional associated resource of this input. * @@ -400,11 +435,6 @@ export interface IEditorInput extends IDisposable { */ readonly resource: URI | undefined; - /** - * Unique type identifier for this inpput. - */ - getTypeId(): string; - /** * Returns the display name of this input. */ @@ -495,7 +525,7 @@ export interface IEditorInput extends IDisposable { /** * Subclasses can set this to false if it does not make sense to split the editor input. */ - supportsSplitEditor(): boolean; + canSplit(): boolean; /** * Returns if the other object matches this input. @@ -506,6 +536,11 @@ export interface IEditorInput extends IDisposable { * Returns if this editor is disposed. */ isDisposed(): boolean; + + /** + * Returns a copy of the current editor input. Used when we can't just reuse the input + */ + copy(): IEditorInput; } /** @@ -520,17 +555,17 @@ export abstract class EditorInput extends Disposable implements IEditorInput { protected readonly _onDidChangeLabel = this._register(new Emitter()); readonly onDidChangeLabel = this._onDidChangeLabel.event; - private readonly _onDispose = this._register(new Emitter()); - readonly onDispose = this._onDispose.event; + private readonly _onWillDispose = this._register(new Emitter()); + readonly onWillDispose = this._onWillDispose.event; private disposed: boolean = false; + abstract get typeId(): string; + abstract get resource(): URI | undefined; - abstract getTypeId(): string; - getName(): string { - return `Editor ${this.getTypeId()}`; + return `Editor ${this.typeId}`; } getDescription(verbosity?: Verbosity): string | undefined { @@ -564,7 +599,7 @@ export abstract class EditorInput extends Disposable implements IEditorInput { "typeId" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } } */ - return { typeId: this.getTypeId() }; + return { typeId: this.typeId }; } isReadonly(): boolean { @@ -601,7 +636,7 @@ export abstract class EditorInput extends Disposable implements IEditorInput { return undefined; } - supportsSplitEditor(): boolean { + canSplit(): boolean { return true; } @@ -609,54 +644,24 @@ export abstract class EditorInput extends Disposable implements IEditorInput { return this === otherInput; } + copy(): IEditorInput { + return this; + } + isDisposed(): boolean { return this.disposed; } - dispose(): void { + override dispose(): void { if (!this.disposed) { this.disposed = true; - this._onDispose.fire(); + this._onWillDispose.fire(); } super.dispose(); } } -export const enum EncodingMode { - - /** - * Instructs the encoding support to encode the current input with the provided encoding - */ - Encode, - - /** - * Instructs the encoding support to decode the current input with the provided encoding - */ - Decode -} - -export interface IEncodingSupport { - - /** - * Gets the encoding of the type if known. - */ - getEncoding(): string | undefined; - - /** - * Sets the encoding for the type for saving. - */ - setEncoding(encoding: string, mode: EncodingMode): void; -} - -export interface IModeSupport { - - /** - * Sets the language mode of the type. - */ - setMode(mode: string): void; -} - export interface IEditorInputWithPreferredResource { /** @@ -750,6 +755,10 @@ export class SideBySideEditorInput extends EditorInput { static readonly ID: string = 'workbench.editorinputs.sidebysideEditorInput'; + override get typeId(): string { + return SideBySideEditorInput.ID; + } + constructor( protected readonly name: string | undefined, protected readonly description: string | undefined, @@ -764,14 +773,14 @@ export class SideBySideEditorInput extends EditorInput { private registerListeners(): void { // When the primary or secondary input gets disposed, dispose this diff editor input - const onceSecondaryDisposed = Event.once(this.secondary.onDispose); + const onceSecondaryDisposed = Event.once(this.secondary.onWillDispose); this._register(onceSecondaryDisposed(() => { if (!this.isDisposed()) { this.dispose(); } })); - const oncePrimaryDisposed = Event.once(this.primary.onDispose); + const oncePrimaryDisposed = Event.once(this.primary.onWillDispose); this._register(oncePrimaryDisposed(() => { if (!this.isDisposed()) { this.dispose(); @@ -799,11 +808,7 @@ export class SideBySideEditorInput extends EditorInput { return this._secondary; } - getTypeId(): string { - return SideBySideEditorInput.ID; - } - - getName(): string { + override getName(): string { if (!this.name) { return localize('sideBySideLabels', "{0} - {1}", this._secondary.getName(), this._primary.getName()); } @@ -811,45 +816,45 @@ export class SideBySideEditorInput extends EditorInput { return this.name; } - getDescription(): string | undefined { + override getDescription(): string | undefined { return this.description; } - isReadonly(): boolean { + override isReadonly(): boolean { return this.primary.isReadonly(); } - isUntitled(): boolean { + override isUntitled(): boolean { return this.primary.isUntitled(); } - isDirty(): boolean { + override isDirty(): boolean { return this.primary.isDirty(); } - isSaving(): boolean { + override isSaving(): boolean { return this.primary.isSaving(); } - save(group: GroupIdentifier, options?: ISaveOptions): Promise { + override save(group: GroupIdentifier, options?: ISaveOptions): Promise { return this.primary.save(group, options); } - saveAs(group: GroupIdentifier, options?: ISaveOptions): Promise { + override saveAs(group: GroupIdentifier, options?: ISaveOptions): Promise { return this.primary.saveAs(group, options); } - revert(group: GroupIdentifier, options?: IRevertOptions): Promise { + override revert(group: GroupIdentifier, options?: IRevertOptions): Promise { return this.primary.revert(group, options); } - getTelemetryDescriptor(): { [key: string]: unknown } { + override getTelemetryDescriptor(): { [key: string]: unknown } { const descriptor = this.primary.getTelemetryDescriptor(); return Object.assign(descriptor, super.getTelemetryDescriptor()); } - matches(otherInput: unknown): boolean { + override matches(otherInput: unknown): boolean { if (otherInput === this) { return true; } @@ -862,34 +867,31 @@ export class SideBySideEditorInput extends EditorInput { } } -export interface ITextEditorModel extends IEditorModel { - textEditorModel: ITextModel; -} - /** * The editor model is the heavyweight counterpart of editor input. Depending on the editor input, it - * connects to the disk to retrieve content and may allow for saving it back or reverting it. Editor models - * are typically cached for some while because they are expensive to construct. + * resolves from a file system retrieve content and may allow for saving it back or reverting it. + * Editor models are typically cached for some while because they are expensive to construct. */ export class EditorModel extends Disposable implements IEditorModel { - private readonly _onDispose = this._register(new Emitter()); - readonly onDispose = this._onDispose.event; + private readonly _onWillDispose = this._register(new Emitter()); + readonly onWillDispose = this._onWillDispose.event; private disposed = false; + private resolved = false; /** - * Causes this model to load returning a promise when loading is completed. + * Causes this model to resolve returning a promise when loading is completed. */ - async load(): Promise { - return this; + async resolve(): Promise { + this.resolved = true; } /** * Returns whether this model was loaded or not. */ isResolved(): boolean { - return true; + return this.resolved; } /** @@ -902,9 +904,9 @@ export class EditorModel extends Disposable implements IEditorModel { /** * Subclasses should implement to free resources that have been claimed through loading. */ - dispose(): void { + override dispose(): void { this.disposed = true; - this._onDispose.fire(); + this._onWillDispose.fire(); super.dispose(); } @@ -915,6 +917,10 @@ export interface IEditorInputWithOptions { options?: IEditorOptions | ITextEditorOptions; } +export interface IEditorInputWithOptionsAndGroup extends IEditorInputWithOptions { + group?: IEditorGroup; +} + export function isEditorInputWithOptions(obj: unknown): obj is IEditorInputWithOptions { const editorInputWithOptions = obj as IEditorInputWithOptions; @@ -1110,7 +1116,7 @@ export class TextEditorOptions extends EditorOptions implements ITextEditorOptio /** * Helper to convert options bag to real class */ - static create(options: ITextEditorOptions = Object.create(null)): TextEditorOptions { + static override create(options: ITextEditorOptions = Object.create(null)): TextEditorOptions { const textEditorOptions = new TextEditorOptions(); textEditorOptions.overwrite(options); @@ -1120,7 +1126,7 @@ export class TextEditorOptions extends EditorOptions implements ITextEditorOptio /** * Overwrites option values from the provided bag. */ - overwrite(options: ITextEditorOptions): TextEditorOptions { + override overwrite(options: ITextEditorOptions): TextEditorOptions { super.overwrite(options); if (options.selection) { @@ -1243,7 +1249,7 @@ export class EditorCommandsContextActionRunner extends ActionRunner { super(); } - run(action: IAction): Promise { + override run(action: IAction): Promise { return super.run(action, this.context); } } @@ -1254,6 +1260,10 @@ export interface IEditorCloseEvent extends IEditorIdentifier { sticky: boolean; } +export interface IEditorMoveEvent extends IEditorIdentifier { + target: GroupIdentifier; +} + export type GroupIdentifier = number; export interface IWorkbenchEditorConfiguration { @@ -1463,29 +1473,33 @@ export interface IEditorMemento { class EditorInputFactoryRegistry implements IEditorInputFactoryRegistry { private instantiationService: IInstantiationService | undefined; + private fileEditorInputFactory: IFileEditorInputFactory | undefined; - - private readonly editorInputFactoryConstructors: Map> = new Map(); - private readonly editorInputFactoryInstances: Map = new Map(); - private readonly customEditorInputFactoryInstances: Map = new Map(); + private readonly editorInputSerializerConstructors: Map> = new Map(); + private readonly editorInputSerializerInstances: Map = new Map(); + start(accessor: ServicesAccessor): void { const instantiationService = this.instantiationService = accessor.get(IInstantiationService); - this.editorInputFactoryConstructors.forEach((ctor, key) => { - this.createEditorInputFactory(key, ctor, instantiationService); - }); + for (const [key, ctor] of this.editorInputSerializerConstructors) { + this.createEditorInputSerializer(key, ctor, instantiationService); + } - this.editorInputFactoryConstructors.clear(); + this.editorInputSerializerConstructors.clear(); } - private createEditorInputFactory(editorInputId: string, ctor: IConstructorSignature0, instantiationService: IInstantiationService): void { + private createEditorInputSerializer(editorInputTypeId: string, ctor: IConstructorSignature0, instantiationService: IInstantiationService): void { const instance = instantiationService.createInstance(ctor); - this.editorInputFactoryInstances.set(editorInputId, instance); + this.editorInputSerializerInstances.set(editorInputTypeId, instance); } registerFileEditorInputFactory(factory: IFileEditorInputFactory): void { + if (this.fileEditorInputFactory) { + throw new Error('Can only register one file editor input factory.'); + } + this.fileEditorInputFactory = factory; } @@ -1493,6 +1507,29 @@ class EditorInputFactoryRegistry implements IEditorInputFactoryRegistry { return assertIsDefined(this.fileEditorInputFactory); } + registerEditorInputSerializer(editorInputTypeId: string, ctor: IConstructorSignature0): IDisposable { + if (this.editorInputSerializerConstructors.has(editorInputTypeId) || this.editorInputSerializerInstances.has(editorInputTypeId)) { + throw new Error(`A editor input serializer with type ID '${editorInputTypeId}' was already registered.`); + } + + if (!this.instantiationService) { + this.editorInputSerializerConstructors.set(editorInputTypeId, ctor); + } else { + this.createEditorInputSerializer(editorInputTypeId, ctor, this.instantiationService); + } + + return toDisposable(() => { + this.editorInputSerializerConstructors.delete(editorInputTypeId); + this.editorInputSerializerInstances.delete(editorInputTypeId); + }); + } + + getEditorInputSerializer(editorInput: IEditorInput): IEditorInputSerializer | undefined; + getEditorInputSerializer(editorInputTypeId: string): IEditorInputSerializer | undefined; + getEditorInputSerializer(arg1: string | IEditorInput): IEditorInputSerializer | undefined { + return this.editorInputSerializerInstances.get(typeof arg1 === 'string' ? arg1 : arg1.typeId); + } + registerCustomEditorInputFactory(scheme: string, factory: ICustomEditorInputFactory): void { this.customEditorInputFactoryInstances.set(scheme, factory); } @@ -1500,30 +1537,9 @@ class EditorInputFactoryRegistry implements IEditorInputFactoryRegistry { getCustomEditorInputFactory(scheme: string): ICustomEditorInputFactory | undefined { return this.customEditorInputFactoryInstances.get(scheme); } - - registerEditorInputFactory(editorInputId: string, ctor: IConstructorSignature0): IDisposable { - if (!this.instantiationService) { - this.editorInputFactoryConstructors.set(editorInputId, ctor); - } else { - this.createEditorInputFactory(editorInputId, ctor, this.instantiationService); - } - - return toDisposable(() => { - this.editorInputFactoryConstructors.delete(editorInputId); - this.editorInputFactoryInstances.delete(editorInputId); - }); - } - - getEditorInputFactory(editorInputId: string): IEditorInputFactory | undefined { - return this.editorInputFactoryInstances.get(editorInputId); - } } -export const Extensions = { - EditorInputFactories: 'workbench.contributions.editor.inputFactories' -}; - -Registry.add(Extensions.EditorInputFactories, new EditorInputFactoryRegistry()); +Registry.add(EditorExtensions.EditorInputFactories, new EditorInputFactoryRegistry()); export async function pathsToEditors(paths: IPathData[] | undefined, fileService: IFileService): Promise<(IResourceEditorInput | IUntitledTextResourceEditorInput)[]> { if (!paths || !paths.length) { @@ -1579,29 +1595,6 @@ export const enum EditorsOrder { SEQUENTIAL } -export function computeEditorAriaLabel(input: IEditorInput, index: number | undefined, group: IEditorGroup | undefined, groupCount: number): string { - let ariaLabel = input.getAriaLabel(); - if (group && !group.isPinned(input)) { - ariaLabel = localize('preview', "{0}, preview", ariaLabel); - } - - if (group?.isSticky(index ?? input)) { - ariaLabel = localize('pinned', "{0}, pinned", ariaLabel); - } - - // Apply group information to help identify in - // which group we are (only if more than one group - // is actually opened) - if (group && groupCount > 1) { - ariaLabel = `${ariaLabel}, ${group.ariaLabel}`; - } - - return ariaLabel; -} - - -//#region Editor Group Column - /** * A way to address editor groups through a column based system * where `0` is the first column. Will fallback to `SIDE_GROUP` @@ -1635,5 +1628,3 @@ export function editorGroupToViewColumn(editorGroupService: IEditorGroupsService return editorGroupService.getGroups(GroupsOrder.GRID_APPEARANCE).indexOf(group); } - -//#endregion diff --git a/src/vs/workbench/common/editor/binaryEditorModel.ts b/src/vs/workbench/common/editor/binaryEditorModel.ts index edf4d54b..98bbf936 100644 --- a/src/vs/workbench/common/editor/binaryEditorModel.ts +++ b/src/vs/workbench/common/editor/binaryEditorModel.ts @@ -56,7 +56,7 @@ export class BinaryEditorModel extends EditorModel { return this.etag; } - async load(): Promise { + override async resolve(): Promise { // Make sure to resolve up to date stat for file resources if (this.fileService.canHandleResource(this.resource)) { @@ -66,7 +66,5 @@ export class BinaryEditorModel extends EditorModel { this.size = stat.size; } } - - return this; } } diff --git a/src/vs/workbench/common/editor/diffEditorInput.ts b/src/vs/workbench/common/editor/diffEditorInput.ts index 1b0eed95..93c0ed19 100644 --- a/src/vs/workbench/common/editor/diffEditorInput.ts +++ b/src/vs/workbench/common/editor/diffEditorInput.ts @@ -21,13 +21,17 @@ import { withNullAsUndefined } from 'vs/base/common/types'; */ export class DiffEditorInput extends SideBySideEditorInput { - static readonly ID = 'workbench.editors.diffEditorInput'; + static override readonly ID = 'workbench.editors.diffEditorInput'; + + override get typeId(): string { + return DiffEditorInput.ID; + } private cachedModel: DiffEditorModel | undefined = undefined; constructor( - protected name: string | undefined, - protected description: string | undefined, + name: string | undefined, + description: string | undefined, public readonly originalInput: EditorInput, public readonly modifiedInput: EditorInput, private readonly forceOpenAsBinary: boolean | undefined, @@ -37,11 +41,7 @@ export class DiffEditorInput extends SideBySideEditorInput { super(name, description, originalInput, modifiedInput); } - getTypeId(): string { - return DiffEditorInput.ID; - } - - getName(): string { + override getName(): string { if (!this.name) { // Craft a name from original and modified input that includes the @@ -58,7 +58,7 @@ export class DiffEditorInput extends SideBySideEditorInput { return this.name; } - getDescription(verbosity: Verbosity = Verbosity.MEDIUM): string | undefined { + override getDescription(verbosity: Verbosity = Verbosity.MEDIUM): string | undefined { if (typeof this.description !== 'string') { // Pass the description of the modified side in case both original @@ -88,7 +88,7 @@ export class DiffEditorInput extends SideBySideEditorInput { return undefined; } - async resolve(): Promise { + override async resolve(): Promise { // Create Model - we never reuse our cached model if refresh is true because we cannot // decide for the inputs within if the cached model can be reused or not. There may be @@ -104,7 +104,7 @@ export class DiffEditorInput extends SideBySideEditorInput { return this.cachedModel; } - getPreferredEditorId(candidates: string[]): string { + override getPreferredEditorId(candidates: string[]): string { return this.forceOpenAsBinary ? BINARY_DIFF_EDITOR_ID : TEXT_DIFF_EDITOR_ID; } @@ -125,7 +125,7 @@ export class DiffEditorInput extends SideBySideEditorInput { return new DiffEditorModel(withNullAsUndefined(originalEditorModel), withNullAsUndefined(modifiedEditorModel)); } - matches(otherInput: unknown): boolean { + override matches(otherInput: unknown): boolean { if (!super.matches(otherInput)) { return false; } @@ -133,7 +133,7 @@ export class DiffEditorInput extends SideBySideEditorInput { return otherInput instanceof DiffEditorInput && otherInput.forceOpenAsBinary === this.forceOpenAsBinary; } - dispose(): void { + override dispose(): void { // Free the diff editor model but do not propagate the dispose() call to the two inputs // We never created the two inputs (original and modified) so we can not dispose diff --git a/src/vs/workbench/common/editor/diffEditorModel.ts b/src/vs/workbench/common/editor/diffEditorModel.ts index 5550bf99..9de37bf7 100644 --- a/src/vs/workbench/common/editor/diffEditorModel.ts +++ b/src/vs/workbench/common/editor/diffEditorModel.ts @@ -25,20 +25,18 @@ export class DiffEditorModel extends EditorModel { this._modifiedModel = modifiedModel; } - async load(): Promise { + override async resolve(): Promise { await Promise.all([ - this._originalModel?.load(), - this._modifiedModel?.load() + this._originalModel?.resolve(), + this._modifiedModel?.resolve() ]); - - return this; } - isResolved(): boolean { - return this.originalModel instanceof EditorModel && this.originalModel.isResolved() && this.modifiedModel instanceof EditorModel && this.modifiedModel.isResolved(); + override isResolved(): boolean { + return !!(this.originalModel?.isResolved() && this.modifiedModel?.isResolved()); } - dispose(): void { + override dispose(): void { // Do not propagate the dispose() call to the two models inside. We never created the two models // (original and modified) so we can not dispose them without sideeffects. Rather rely on the diff --git a/src/vs/workbench/common/editor/editorGroup.ts b/src/vs/workbench/common/editor/editorGroupModel.ts similarity index 90% rename from src/vs/workbench/common/editor/editorGroup.ts rename to src/vs/workbench/common/editor/editorGroupModel.ts index d1580899..85076d88 100644 --- a/src/vs/workbench/common/editor/editorGroup.ts +++ b/src/vs/workbench/common/editor/editorGroupModel.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Event, Emitter } from 'vs/base/common/event'; -import { Extensions, IEditorInputFactoryRegistry, EditorInput, IEditorIdentifier, IEditorCloseEvent, GroupIdentifier, SideBySideEditorInput, IEditorInput, EditorsOrder } from 'vs/workbench/common/editor'; +import { IEditorInputFactoryRegistry, EditorInput, IEditorIdentifier, IEditorCloseEvent, GroupIdentifier, SideBySideEditorInput, IEditorInput, EditorsOrder, EditorExtensions } from 'vs/workbench/common/editor'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { dispose, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; @@ -44,7 +44,7 @@ export interface ISerializedEditorInput { readonly value: string; } -export interface ISerializedEditorGroup { +export interface ISerializedEditorGroupModel { readonly id: number; readonly editors: ISerializedEditorInput[]; readonly mru: number[]; @@ -52,13 +52,13 @@ export interface ISerializedEditorGroup { sticky?: number; } -export function isSerializedEditorGroup(obj?: unknown): obj is ISerializedEditorGroup { - const group = obj as ISerializedEditorGroup; +export function isSerializedEditorGroupModel(obj?: unknown): obj is ISerializedEditorGroupModel { + const group = obj as ISerializedEditorGroupModel; return !!(obj && typeof obj === 'object' && Array.isArray(group.editors) && Array.isArray(group.mru)); } -export class EditorGroup extends Disposable { +export class EditorGroupModel extends Disposable { private static IDS = 0; @@ -73,8 +73,8 @@ export class EditorGroup extends Disposable { private readonly _onDidCloseEditor = this._register(new Emitter()); readonly onDidCloseEditor = this._onDidCloseEditor.event; - private readonly _onDidDisposeEditor = this._register(new Emitter()); - readonly onDidDisposeEditor = this._onDidDisposeEditor.event; + private readonly _onWillDisposeEditor = this._register(new Emitter()); + readonly onWillDisposeEditor = this._onWillDisposeEditor.event; private readonly _onDidChangeEditorDirty = this._register(new Emitter()); readonly onDidChangeEditorDirty = this._onDidChangeEditorDirty.event; @@ -107,16 +107,16 @@ export class EditorGroup extends Disposable { private focusRecentEditorAfterClose: boolean | undefined; constructor( - labelOrSerializedGroup: ISerializedEditorGroup | undefined, + labelOrSerializedGroup: ISerializedEditorGroupModel | undefined, @IInstantiationService private readonly instantiationService: IInstantiationService, @IConfigurationService private readonly configurationService: IConfigurationService ) { super(); - if (isSerializedEditorGroup(labelOrSerializedGroup)) { + if (isSerializedEditorGroupModel(labelOrSerializedGroup)) { this._id = this.deserialize(labelOrSerializedGroup); } else { - this._id = EditorGroup.IDS++; + this._id = EditorGroupModel.IDS++; } this.onConfigurationUpdated(); @@ -315,9 +315,9 @@ export class EditorGroup extends Disposable { const listeners = new DisposableStore(); // Re-emit disposal of editor input as our own event - listeners.add(Event.once(editor.onDispose)(() => { + listeners.add(Event.once(editor.onWillDispose)(() => { if (this.indexOf(editor) >= 0) { - this._onDidDisposeEditor.fire(editor); + this._onWillDisposeEditor.fire(editor); } })); @@ -722,26 +722,26 @@ export class EditorGroup extends Disposable { return editor.matches(candidate); } - clone(): EditorGroup { - const group = this.instantiationService.createInstance(EditorGroup, undefined); + clone(): EditorGroupModel { + const clone = this.instantiationService.createInstance(EditorGroupModel, undefined); // Copy over group properties - group.editors = this.editors.slice(0); - group.mru = this.mru.slice(0); - group.preview = this.preview; - group.active = this.active; - group.sticky = this.sticky; + clone.editors = this.editors.slice(0); + clone.mru = this.mru.slice(0); + clone.preview = this.preview; + clone.active = this.active; + clone.sticky = this.sticky; // Ensure to register listeners for each editor - for (const editor of group.editors) { - group.registerEditorListeners(editor); + for (const editor of clone.editors) { + clone.registerEditorListeners(editor); } - return group; + return clone; } - serialize(): ISerializedEditorGroup { - const registry = Registry.as(Extensions.EditorInputFactories); + serialize(): ISerializedEditorGroupModel { + const registry = Registry.as(EditorExtensions.EditorInputFactories); // Serialize all editor inputs so that we can store them. // Editors that cannot be serialized need to be ignored @@ -755,15 +755,15 @@ export class EditorGroup extends Disposable { const editor = this.editors[i]; let canSerializeEditor = false; - const factory = registry.getEditorInputFactory(editor.getTypeId()); - if (factory) { - const value = factory.serialize(editor); + const editorSerializer = registry.getEditorInputSerializer(editor); + if (editorSerializer) { + const value = editorSerializer.serialize(editor); // Editor can be serialized if (typeof value === 'string') { canSerializeEditor = true; - serializedEditors.push({ id: editor.getTypeId(), value }); + serializedEditors.push({ id: editor.typeId, value }); serializableEditors.push(editor); if (this.preview === editor) { @@ -794,23 +794,23 @@ export class EditorGroup extends Disposable { }; } - private deserialize(data: ISerializedEditorGroup): number { - const registry = Registry.as(Extensions.EditorInputFactories); + private deserialize(data: ISerializedEditorGroupModel): number { + const registry = Registry.as(EditorExtensions.EditorInputFactories); if (typeof data.id === 'number') { this._id = data.id; - EditorGroup.IDS = Math.max(data.id + 1, EditorGroup.IDS); // make sure our ID generator is always larger + EditorGroupModel.IDS = Math.max(data.id + 1, EditorGroupModel.IDS); // make sure our ID generator is always larger } else { - this._id = EditorGroup.IDS++; // backwards compatibility + this._id = EditorGroupModel.IDS++; // backwards compatibility } this.editors = coalesce(data.editors.map((e, index) => { let editor: EditorInput | undefined = undefined; - const factory = registry.getEditorInputFactory(e.id); - if (factory) { - editor = factory.deserialize(this.instantiationService, e.value); + const editorSerializer = registry.getEditorInputSerializer(e.id); + if (editorSerializer) { + editor = editorSerializer.deserialize(this.instantiationService, e.value); if (editor) { this.registerEditorListeners(editor); } diff --git a/src/vs/workbench/common/editor/resourceEditorInput.ts b/src/vs/workbench/common/editor/resourceEditorInput.ts index 0167182c..e897355f 100644 --- a/src/vs/workbench/common/editor/resourceEditorInput.ts +++ b/src/vs/workbench/common/editor/resourceEditorInput.ts @@ -3,12 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ITextEditorModel, IModeSupport } from 'vs/workbench/common/editor'; import { URI } from 'vs/base/common/uri'; import { IReference } from 'vs/base/common/lifecycle'; -import { ITextModelService } from 'vs/editor/common/services/resolverService'; +import { ITextEditorModel, ITextModelService } from 'vs/editor/common/services/resolverService'; import { ResourceEditorModel } from 'vs/workbench/common/editor/resourceEditorModel'; -import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { IModeSupport, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IFileService } from 'vs/platform/files/common/files'; @@ -25,6 +24,10 @@ export class ResourceEditorInput extends AbstractTextResourceEditorInput impleme static readonly ID: string = 'workbench.editors.resourceEditorInput'; + override get typeId(): string { + return ResourceEditorInput.ID; + } + private cachedModel: ResourceEditorModel | undefined = undefined; private modelReference: Promise> | undefined = undefined; @@ -44,11 +47,7 @@ export class ResourceEditorInput extends AbstractTextResourceEditorInput impleme super(resource, undefined, editorService, editorGroupService, textFileService, labelService, fileService, filesConfigurationService); } - getTypeId(): string { - return ResourceEditorInput.ID; - } - - getName(): string { + override getName(): string { return this.name || super.getName(); } @@ -60,7 +59,7 @@ export class ResourceEditorInput extends AbstractTextResourceEditorInput impleme } } - getDescription(): string | undefined { + override getDescription(): string | undefined { return this.description; } @@ -84,7 +83,7 @@ export class ResourceEditorInput extends AbstractTextResourceEditorInput impleme this.preferredMode = mode; } - async resolve(): Promise { + override async resolve(): Promise { if (!this.modelReference) { this.modelReference = this.textModelResolverService.createModelReference(this.resource); } @@ -110,7 +109,7 @@ export class ResourceEditorInput extends AbstractTextResourceEditorInput impleme return model; } - matches(otherInput: unknown): boolean { + override matches(otherInput: unknown): boolean { if (otherInput === this) { return true; } @@ -122,7 +121,7 @@ export class ResourceEditorInput extends AbstractTextResourceEditorInput impleme return false; } - dispose(): void { + override dispose(): void { if (this.modelReference) { this.modelReference.then(ref => ref.dispose()); this.modelReference = undefined; diff --git a/src/vs/workbench/common/editor/resourceEditorModel.ts b/src/vs/workbench/common/editor/resourceEditorModel.ts index 56f7aefe..661bbe64 100644 --- a/src/vs/workbench/common/editor/resourceEditorModel.ts +++ b/src/vs/workbench/common/editor/resourceEditorModel.ts @@ -21,7 +21,7 @@ export class ResourceEditorModel extends BaseTextEditorModel { super(modelService, modeService, resource); } - dispose(): void { + override dispose(): void { // force this class to dispose the underlying model if (this.textEditorModelHandle) { diff --git a/src/vs/workbench/common/editor/textDiffEditorModel.ts b/src/vs/workbench/common/editor/textDiffEditorModel.ts index 98528b2e..6ffcdc26 100644 --- a/src/vs/workbench/common/editor/textDiffEditorModel.ts +++ b/src/vs/workbench/common/editor/textDiffEditorModel.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { IDiffEditorModel } from 'vs/editor/common/editorCommon'; -import { EditorModel } from 'vs/workbench/common/editor'; import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel'; import { DiffEditorModel } from 'vs/workbench/common/editor/diffEditorModel'; @@ -14,11 +13,11 @@ import { DiffEditorModel } from 'vs/workbench/common/editor/diffEditorModel'; */ export class TextDiffEditorModel extends DiffEditorModel { - protected readonly _originalModel: BaseTextEditorModel | undefined; - get originalModel(): BaseTextEditorModel | undefined { return this._originalModel; } + protected override readonly _originalModel: BaseTextEditorModel | undefined; + override get originalModel(): BaseTextEditorModel | undefined { return this._originalModel; } - protected readonly _modifiedModel: BaseTextEditorModel | undefined; - get modifiedModel(): BaseTextEditorModel | undefined { return this._modifiedModel; } + protected override readonly _modifiedModel: BaseTextEditorModel | undefined; + override get modifiedModel(): BaseTextEditorModel | undefined { return this._modifiedModel; } private _textDiffEditorModel: IDiffEditorModel | undefined = undefined; get textDiffEditorModel(): IDiffEditorModel | undefined { return this._textDiffEditorModel; } @@ -32,12 +31,10 @@ export class TextDiffEditorModel extends DiffEditorModel { this.updateTextDiffEditorModel(); } - async load(): Promise { - await super.load(); + override async resolve(): Promise { + await super.resolve(); this.updateTextDiffEditorModel(); - - return this; } private updateTextDiffEditorModel(): void { @@ -59,7 +56,7 @@ export class TextDiffEditorModel extends DiffEditorModel { } } - isResolved(): boolean { + override isResolved(): boolean { return !!this._textDiffEditorModel; } @@ -67,7 +64,7 @@ export class TextDiffEditorModel extends DiffEditorModel { return !!this.modifiedModel && this.modifiedModel.isReadonly(); } - dispose(): void { + override dispose(): void { // Free the diff editor model but do not propagate the dispose() call to the two models // inside. We never created the two models (original and modified) so we can not dispose diff --git a/src/vs/workbench/common/editor/textEditorModel.ts b/src/vs/workbench/common/editor/textEditorModel.ts index 3225ea5a..e7bd4873 100644 --- a/src/vs/workbench/common/editor/textEditorModel.ts +++ b/src/vs/workbench/common/editor/textEditorModel.ts @@ -4,7 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { ITextModel, ITextBufferFactory, ITextSnapshot, ModelConstants } from 'vs/editor/common/model'; -import { EditorModel, IModeSupport } from 'vs/workbench/common/editor'; +import { EditorModel } from 'vs/workbench/common/editor'; +import { IModeSupport } from 'vs/workbench/services/textfile/common/textfiles'; import { URI } from 'vs/base/common/uri'; import { ITextEditorModel, IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService'; import { IModeService, ILanguageSelection } from 'vs/editor/common/services/modeService'; @@ -167,11 +168,11 @@ export class BaseTextEditorModel extends EditorModel implements ITextEditorModel return this.textEditorModel.createSnapshot(true /* preserve BOM */); } - isResolved(): this is IResolvedTextEditorModel { + override isResolved(): this is IResolvedTextEditorModel { return !!this.textEditorModelHandle; } - dispose(): void { + override dispose(): void { this.modelDisposeListener.dispose(); // dispose this first because it will trigger another dispose() otherwise if (this.textEditorModelHandle && this.createdEditorModel) { diff --git a/src/vs/workbench/common/editor/textResourceEditorInput.ts b/src/vs/workbench/common/editor/textResourceEditorInput.ts index f8b47aac..26d5f3ff 100644 --- a/src/vs/workbench/common/editor/textResourceEditorInput.ts +++ b/src/vs/workbench/common/editor/textResourceEditorInput.ts @@ -77,7 +77,7 @@ export abstract class AbstractTextResourceEditorInput extends EditorInput implem } private _name: string | undefined = undefined; - getName(): string { + override getName(): string { if (typeof this._name !== 'string') { this._name = this.labelService.getUriBasenameLabel(this._preferredResource); } @@ -85,7 +85,7 @@ export abstract class AbstractTextResourceEditorInput extends EditorInput implem return this._name; } - getDescription(verbosity: Verbosity = Verbosity.MEDIUM): string | undefined { + override getDescription(verbosity: Verbosity = Verbosity.MEDIUM): string | undefined { switch (verbosity) { case Verbosity.SHORT: return this.shortDescription; @@ -151,7 +151,7 @@ export abstract class AbstractTextResourceEditorInput extends EditorInput implem return this._longTitle; } - getTitle(verbosity: Verbosity): string { + override getTitle(verbosity: Verbosity): string { switch (verbosity) { case Verbosity.SHORT: return this.shortTitle; @@ -163,14 +163,14 @@ export abstract class AbstractTextResourceEditorInput extends EditorInput implem } } - isUntitled(): boolean { + override isUntitled(): boolean { // any file: is never untitled as it can be saved // untitled: is untitled by definition // any other: is untitled because it cannot be saved, as such we expect a "Save As" dialog return !this.fileService.canHandleResource(this.resource); } - isReadonly(): boolean { + override isReadonly(): boolean { if (this.isUntitled()) { return false; // untitled is never readonly } @@ -178,7 +178,7 @@ export abstract class AbstractTextResourceEditorInput extends EditorInput implem return this.fileService.hasCapability(this.resource, FileSystemProviderCapabilities.Readonly); } - isSaving(): boolean { + override isSaving(): boolean { if (this.isUntitled()) { return false; // untitled is never saving automatically } @@ -190,7 +190,7 @@ export abstract class AbstractTextResourceEditorInput extends EditorInput implem return false; } - save(group: GroupIdentifier, options?: ITextFileSaveOptions): Promise { + override save(group: GroupIdentifier, options?: ITextFileSaveOptions): Promise { // If this is neither an `untitled` resource, nor a resource // we can handle with the file service, we can only "Save As..." @@ -202,7 +202,7 @@ export abstract class AbstractTextResourceEditorInput extends EditorInput implem return this.doSave(options, false); } - saveAs(group: GroupIdentifier, options?: ITextFileSaveOptions): Promise { + override saveAs(group: GroupIdentifier, options?: ITextFileSaveOptions): Promise { return this.doSave(options, true); } @@ -233,7 +233,7 @@ export abstract class AbstractTextResourceEditorInput extends EditorInput implem return this; } - async revert(group: GroupIdentifier, options?: IRevertOptions): Promise { + override async revert(group: GroupIdentifier, options?: IRevertOptions): Promise { await this.textFileService.revert(this.resource, options); } } diff --git a/src/vs/workbench/common/notifications.ts b/src/vs/workbench/common/notifications.ts index 9f854aa5..fb465bf9 100644 --- a/src/vs/workbench/common/notifications.ts +++ b/src/vs/workbench/common/notifications.ts @@ -275,6 +275,7 @@ export class NotificationsModel extends Disposable implements INotificationsMode } export interface INotificationViewItem { + readonly id: string | undefined; readonly severity: Severity; readonly sticky: boolean; readonly silent: boolean; @@ -468,7 +469,7 @@ export class NotificationViewItem extends Disposable implements INotificationVie actions = { primary: notification.message.actions }; } - return new NotificationViewItem(severity, notification.sticky, notification.silent || filter === NotificationsFilter.SILENT || (filter === NotificationsFilter.ERROR && notification.severity !== Severity.Error), message, notification.source, notification.progress, actions); + return new NotificationViewItem(notification.id, severity, notification.sticky, notification.silent || filter === NotificationsFilter.SILENT || (filter === NotificationsFilter.ERROR && notification.severity !== Severity.Error), message, notification.source, notification.progress, actions); } private static parseNotificationMessage(input: NotificationMessage): INotificationMessage | undefined { @@ -500,6 +501,7 @@ export class NotificationViewItem extends Disposable implements INotificationVie } private constructor( + readonly id: string | undefined, private _severity: Severity, private _sticky: boolean | undefined, private _silent: boolean | undefined, @@ -684,7 +686,15 @@ export class NotificationViewItem extends Disposable implements INotificationVie return false; } - if (this._source !== other.source) { + if (typeof this.id === 'string' || typeof other.id === 'string') { + return this.id === other.id; + } + + if (typeof this._source === 'object') { + if (this._source.label !== other.source || this._source.id !== other.sourceId) { + return false; + } + } else if (this._source !== other.source) { return false; } @@ -694,7 +704,7 @@ export class NotificationViewItem extends Disposable implements INotificationVie const primaryActions = (this._actions && this._actions.primary) || []; const otherPrimaryActions = (other.actions && other.actions.primary) || []; - return equals(primaryActions, otherPrimaryActions, (a, b) => (a.id + a.label) === (b.id + b.label)); + return equals(primaryActions, otherPrimaryActions, (action, otherAction) => (action.id + action.label) === (otherAction.id + otherAction.label)); } } diff --git a/src/vs/workbench/contrib/backup/browser/backup.web.contribution.ts b/src/vs/workbench/contrib/backup/browser/backup.web.contribution.ts deleted file mode 100644 index c6894298..00000000 --- a/src/vs/workbench/contrib/backup/browser/backup.web.contribution.ts +++ /dev/null @@ -1,12 +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 { Registry } from 'vs/platform/registry/common/platform'; -import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; -import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; -import { BrowserBackupTracker } from 'vs/workbench/contrib/backup/browser/backupTracker'; - -// Register Backup Tracker -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(BrowserBackupTracker, LifecyclePhase.Starting); diff --git a/src/vs/workbench/contrib/backup/common/backup.contribution.ts b/src/vs/workbench/contrib/backup/common/backup.contribution.ts deleted file mode 100644 index 0ae3302f..00000000 --- a/src/vs/workbench/contrib/backup/common/backup.contribution.ts +++ /dev/null @@ -1,12 +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 { Registry } from 'vs/platform/registry/common/platform'; -import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; -import { BackupRestorer } from 'vs/workbench/contrib/backup/common/backupRestorer'; -import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; - -// Register Backup Restorer -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(BackupRestorer, LifecyclePhase.Starting); diff --git a/src/vs/workbench/contrib/backup/common/backupRestorer.ts b/src/vs/workbench/contrib/backup/common/backupRestorer.ts deleted file mode 100644 index 42f17676..00000000 --- a/src/vs/workbench/contrib/backup/common/backupRestorer.ts +++ /dev/null @@ -1,122 +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 { URI } from 'vs/base/common/uri'; -import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IResourceEditorInput } from 'vs/platform/editor/common/editor'; -import { Schemas } from 'vs/base/common/network'; -import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; -import { IUntitledTextResourceEditorInput, IEditorInput, IEditorInputFactoryRegistry, Extensions as EditorExtensions, IEditorInputWithOptions } from 'vs/workbench/common/editor'; -import { toLocalResource, isEqual } from 'vs/base/common/resources'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IPathService } from 'vs/workbench/services/path/common/pathService'; -import { ILogService } from 'vs/platform/log/common/log'; -import { Promises } from 'vs/base/common/async'; - -export class BackupRestorer implements IWorkbenchContribution { - - private static readonly UNTITLED_REGEX = /Untitled-\d+/; - - private readonly editorInputFactories = Registry.as(EditorExtensions.EditorInputFactories); - - constructor( - @IEditorService private readonly editorService: IEditorService, - @IBackupFileService private readonly backupFileService: IBackupFileService, - @ILifecycleService private readonly lifecycleService: ILifecycleService, - @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, - @IInstantiationService private readonly instantiationService: IInstantiationService, - @IPathService private readonly pathService: IPathService, - @ILogService private readonly logService: ILogService - ) { - this.restoreBackups(); - } - - private restoreBackups(): void { - this.lifecycleService.when(LifecyclePhase.Restored).then(() => this.doRestoreBackups()); - } - - protected async doRestoreBackups(): Promise { - - // Find all files and untitled with backups - const backups = await this.backupFileService.getBackups(); - const unresolvedBackups = await this.doResolveOpenedBackups(backups); - - // Some failed to restore or were not opened at all so we open and resolve them manually - if (unresolvedBackups.length > 0) { - try { - await this.doOpenEditors(unresolvedBackups); - } catch (error) { - this.logService.error(error); - } - - return this.doResolveOpenedBackups(unresolvedBackups); - } - - return undefined; - } - - private async doResolveOpenedBackups(backups: URI[]): Promise { - const unresolvedBackups: URI[] = []; - - await Promises.settled(backups.map(async backup => { - const openedEditor = this.findEditorByResource(backup); - if (openedEditor) { - try { - await openedEditor.resolve(); // trigger load - } catch (error) { - unresolvedBackups.push(backup); // ignore error and remember as unresolved - } - } else { - unresolvedBackups.push(backup); - } - })); - - return unresolvedBackups; - } - - private findEditorByResource(resource: URI): IEditorInput | undefined { - for (const editor of this.editorService.editors) { - const customFactory = this.editorInputFactories.getCustomEditorInputFactory(resource.scheme); - if (customFactory?.canResolveBackup(editor, resource) || isEqual(editor.resource, resource)) { - return editor; - } - } - - return undefined; - } - - private async doOpenEditors(resources: URI[]): Promise { - const hasOpenedEditors = this.editorService.visibleEditors.length > 0; - const inputs = await Promises.settled(resources.map((resource, index) => this.resolveInput(resource, index, hasOpenedEditors))); - - // Open all remaining backups as editors and resolve them to load their backups - await this.editorService.openEditors(inputs); - } - - private async resolveInput(resource: URI, index: number, hasOpenedEditors: boolean): Promise { - const options = { pinned: true, preserveFocus: true, inactive: index > 0 || hasOpenedEditors }; - - // this is a (weak) strategy to find out if the untitled input had - // an associated file path or not by just looking at the path. and - // if so, we must ensure to restore the local resource it had. - if (resource.scheme === Schemas.untitled && !BackupRestorer.UNTITLED_REGEX.test(resource.path)) { - return { resource: toLocalResource(resource, this.environmentService.remoteAuthority, this.pathService.defaultUriScheme), options, forceUntitled: true }; - } - - // handle custom editors by asking the custom editor input factory - // to create the input. - const customFactory = this.editorInputFactories.getCustomEditorInputFactory(resource.scheme); - if (customFactory) { - const editor = await customFactory.createCustomEditorInput(resource, this.instantiationService); - return { editor, options }; - } - - return { resource, options }; - } -} diff --git a/src/vs/workbench/contrib/backup/electron-sandbox/backup.contribution.ts b/src/vs/workbench/contrib/backup/electron-sandbox/backup.contribution.ts deleted file mode 100644 index cf529c7e..00000000 --- a/src/vs/workbench/contrib/backup/electron-sandbox/backup.contribution.ts +++ /dev/null @@ -1,12 +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 { Registry } from 'vs/platform/registry/common/platform'; -import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; -import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; -import { NativeBackupTracker } from 'vs/workbench/contrib/backup/electron-sandbox/backupTracker'; - -// Register Backup Tracker -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(NativeBackupTracker, LifecyclePhase.Starting); diff --git a/src/vs/workbench/contrib/backup/test/browser/backupTracker.test.ts b/src/vs/workbench/contrib/backup/test/browser/backupTracker.test.ts deleted file mode 100644 index 17c3e8b5..00000000 --- a/src/vs/workbench/contrib/backup/test/browser/backupTracker.test.ts +++ /dev/null @@ -1,153 +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 { URI } from 'vs/base/common/uri'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart'; -import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { EditorService } from 'vs/workbench/services/editor/browser/editorService'; -import { IUntitledTextResourceEditorInput } from 'vs/workbench/common/editor'; -import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; -import { toResource } from 'vs/base/test/common/utils'; -import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; -import { IWorkingCopyBackup, IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; -import { ILogService } from 'vs/platform/log/common/log'; -import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; -import { BackupTracker } from 'vs/workbench/contrib/backup/common/backupTracker'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; -import { createEditorPart, InMemoryTestBackupFileService, registerTestResourceEditor, TestServiceAccessor, workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; -import { TestWorkingCopy } from 'vs/workbench/test/common/workbenchTestServices'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import { timeout } from 'vs/base/common/async'; -import { BrowserBackupTracker } from 'vs/workbench/contrib/backup/browser/backupTracker'; -import { DisposableStore } from 'vs/base/common/lifecycle'; - -suite('BackupTracker (browser)', function () { - let accessor: TestServiceAccessor; - - class TestBackupTracker extends BrowserBackupTracker { - - constructor( - @IBackupFileService backupFileService: IBackupFileService, - @IFilesConfigurationService filesConfigurationService: IFilesConfigurationService, - @IWorkingCopyService workingCopyService: IWorkingCopyService, - @ILifecycleService lifecycleService: ILifecycleService, - @ILogService logService: ILogService, - ) { - super(backupFileService, filesConfigurationService, workingCopyService, lifecycleService, logService); - } - - protected getBackupScheduleDelay(): number { - return 10; // Reduce timeout for tests - } - } - - async function createTracker(): Promise<{ accessor: TestServiceAccessor, part: EditorPart, tracker: BackupTracker, backupFileService: InMemoryTestBackupFileService, instantiationService: IInstantiationService, cleanup: () => void }> { - const disposables = new DisposableStore(); - - const backupFileService = new InMemoryTestBackupFileService(); - const instantiationService = workbenchInstantiationService(); - instantiationService.stub(IBackupFileService, backupFileService); - - const part = createEditorPart(instantiationService, disposables); - - disposables.add(registerTestResourceEditor()); - - instantiationService.stub(IEditorGroupsService, part); - - const editorService: EditorService = instantiationService.createInstance(EditorService); - instantiationService.stub(IEditorService, editorService); - - accessor = instantiationService.createInstance(TestServiceAccessor); - - await part.whenRestored; - - const tracker = disposables.add(instantiationService.createInstance(TestBackupTracker)); - - return { accessor, part, tracker, backupFileService, instantiationService, cleanup: () => disposables.dispose() }; - } - - async function untitledBackupTest(untitled: IUntitledTextResourceEditorInput = {}): Promise { - const { accessor, cleanup, backupFileService } = await createTracker(); - - const untitledEditor = (await accessor.editorService.openEditor(untitled))?.input as UntitledTextEditorInput; - - const untitledModel = await untitledEditor.resolve(); - - if (!untitled?.contents) { - untitledModel.textEditorModel.setValue('Super Good'); - } - - await backupFileService.joinBackupResource(); - - assert.strictEqual(backupFileService.hasBackupSync(untitledEditor.resource), true); - - untitledModel.dispose(); - - await backupFileService.joinDiscardBackup(); - - assert.strictEqual(backupFileService.hasBackupSync(untitledEditor.resource), false); - - cleanup(); - } - - test('Track backups (untitled)', function () { - return untitledBackupTest(); - }); - - test('Track backups (untitled with initial contents)', function () { - return untitledBackupTest({ contents: 'Foo Bar' }); - }); - - test('Track backups (custom)', async function () { - const { accessor, cleanup, backupFileService } = await createTracker(); - - class TestBackupWorkingCopy extends TestWorkingCopy { - - backupDelay = 0; - - constructor(resource: URI) { - super(resource); - - accessor.workingCopyService.registerWorkingCopy(this); - } - - async backup(token: CancellationToken): Promise { - await timeout(this.backupDelay); - - return {}; - } - } - - const resource = toResource.call(this, '/path/custom.txt'); - const customWorkingCopy = new TestBackupWorkingCopy(resource); - - // Normal - customWorkingCopy.setDirty(true); - await backupFileService.joinBackupResource(); - assert.strictEqual(backupFileService.hasBackupSync(resource), true); - - customWorkingCopy.setDirty(false); - customWorkingCopy.setDirty(true); - await backupFileService.joinBackupResource(); - assert.strictEqual(backupFileService.hasBackupSync(resource), true); - - customWorkingCopy.setDirty(false); - await backupFileService.joinDiscardBackup(); - assert.strictEqual(backupFileService.hasBackupSync(resource), false); - - // Cancellation - customWorkingCopy.setDirty(true); - await timeout(0); - customWorkingCopy.setDirty(false); - await backupFileService.joinDiscardBackup(); - assert.strictEqual(backupFileService.hasBackupSync(resource), false); - - customWorkingCopy.dispose(); - cleanup(); - }); -}); diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkCellEdits.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkCellEdits.ts index d633df9b..4c497eea 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkCellEdits.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkCellEdits.ts @@ -20,7 +20,7 @@ export class ResourceNotebookCellEdit extends ResourceEdit { readonly resource: URI, readonly cellEdit: ICellEditOperation, readonly versionId?: number, - readonly metadata?: WorkspaceEditMetadata + metadata?: WorkspaceEditMetadata ) { super(metadata); } diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkFileEdits.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkFileEdits.ts index e15f322a..54266f1c 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkFileEdits.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkFileEdits.ts @@ -76,7 +76,7 @@ class RenameOperation implements IFileOperation { return new Noop(); } - await this._workingCopyFileService.move(moves, this._undoRedoInfo, token); + await this._workingCopyFileService.move(moves, token, this._undoRedoInfo); return new RenameOperation(undoes, { isUndoing: true }, this._workingCopyFileService, this._fileService); } @@ -125,7 +125,7 @@ class CopyOperation implements IFileOperation { } // (2) perform the actual copy and use the return stats to build undo edits - const stats = await this._workingCopyFileService.copy(copies, this._undoRedoInfo, token); + const stats = await this._workingCopyFileService.copy(copies, token, this._undoRedoInfo); const undoes: DeleteEdit[] = []; for (let i = 0; i < stats.length; i++) { @@ -190,8 +190,8 @@ class CreateOperation implements IFileOperation { return new Noop(); } - await this._workingCopyFileService.createFolder(folderCreates, this._undoRedoInfo, token); - await this._workingCopyFileService.create(fileCreates, this._undoRedoInfo, token); + await this._workingCopyFileService.createFolder(folderCreates, token, this._undoRedoInfo); + await this._workingCopyFileService.create(fileCreates, token, this._undoRedoInfo); return this._instaService.createInstance(DeleteOperation, undoes, { isUndoing: true }); } @@ -265,7 +265,7 @@ class DeleteOperation implements IFileOperation { return new Noop(); } - await this._workingCopyFileService.delete(deletes, this._undoRedoInfo, token); + await this._workingCopyFileService.delete(deletes, token, this._undoRedoInfo); if (undoes.length === 0) { return new Noop(); diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkTextEdits.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkTextEdits.ts index 90b8704b..b7c76933 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkTextEdits.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkTextEdits.ts @@ -109,11 +109,11 @@ class EditorEditTask extends ModelEditTask { this._editor = editor; } - getBeforeCursorState(): Selection[] | null { + override getBeforeCursorState(): Selection[] | null { return this._editor.getSelections(); } - apply(): void { + override apply(): void { if (this._edits.length > 0) { this._edits = this._edits.sort((a, b) => Range.compareRangesUsingStarts(a.range, b.range)); this._editor.executeEdits('', this._edits); diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEdit.contribution.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEdit.contribution.ts index 4b59a05c..d7c35afd 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEdit.contribution.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEdit.contribution.ts @@ -14,15 +14,13 @@ import { localize } from 'vs/nls'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { RawContextKey, IContextKeyService, IContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { BulkEditPreviewProvider } from 'vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPreview'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { WorkbenchListFocusContextKey } from 'vs/platform/list/browser/listService'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; -import { URI } from 'vs/base/common/uri'; import { MenuId, registerAction2, Action2 } from 'vs/platform/actions/common/actions'; -import { IEditorInput } from 'vs/workbench/common/editor'; +import { EditorResourceAccessor, IEditorInput, SideBySideEditor } from 'vs/workbench/common/editor'; import type { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; @@ -63,13 +61,7 @@ class UXState { let previewEditors: IEditorInput[] = []; for (let input of group.editors) { - let resource: URI | undefined; - if (input instanceof DiffEditorInput) { - resource = input.modifiedInput.resource; - } else { - resource = input.resource; - } - + let resource = EditorResourceAccessor.getCanonicalUri(input, { supportSideBySide: SideBySideEditor.PRIMARY }); if (resource?.scheme === BulkEditPreviewProvider.Schema) { previewEditors.push(input); } diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts index 8a4997a7..6cf35182 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts @@ -100,12 +100,12 @@ export class BulkEditPane extends ViewPane { this._ctxHasCheckedChanges = BulkEditPane.ctxHasCheckedChanges.bindTo(_contextKeyService); } - dispose(): void { + override dispose(): void { this._tree.dispose(); this._disposables.dispose(); } - protected renderBody(parent: HTMLElement): void { + protected override renderBody(parent: HTMLElement): void { super.renderBody(parent); const resourceLabels = this._instaService.createInstance( @@ -154,7 +154,7 @@ export class BulkEditPane extends ViewPane { this._setState(State.Message); } - protected layoutBody(height: number, width: number): void { + protected override layoutBody(height: number, width: number): void { super.layoutBody(height, width); this._tree.layout(height, width); } diff --git a/src/vs/workbench/contrib/bulkEdit/test/browser/bulkEditPreview.test.ts b/src/vs/workbench/contrib/bulkEdit/test/browser/bulkEditPreview.test.ts index 0aac694e..8df25c9c 100644 --- a/src/vs/workbench/contrib/bulkEdit/test/browser/bulkEditPreview.test.ts +++ b/src/vs/workbench/contrib/bulkEdit/test/browser/bulkEditPreview.test.ts @@ -24,17 +24,17 @@ suite('BulkEditPreview', function () { setup(function () { const fileService: IFileService = new class extends mock() { - onDidFilesChange = Event.None; - async exists() { + override onDidFilesChange = Event.None; + override async exists() { return true; } }; const modelService: IModelService = new class extends mock() { - getModel() { + override getModel() { return null; } - getModels() { + override getModels() { return []; } }; @@ -53,8 +53,8 @@ suite('BulkEditPreview', function () { ]; const ops = await instaService.invokeFunction(BulkFileOperations.create, edits); - assert.equal(ops.fileOperations.length, 1); - assert.equal(ops.checked.isChecked(edits[0]), false); + assert.strictEqual(ops.fileOperations.length, 1); + assert.strictEqual(ops.checked.isChecked(edits[0]), false); }); test('has categories', async function () { @@ -66,9 +66,9 @@ suite('BulkEditPreview', function () { const ops = await instaService.invokeFunction(BulkFileOperations.create, edits); - assert.equal(ops.categories.length, 2); - assert.equal(ops.categories[0].metadata.label, 'uri1'); // unconfirmed! - assert.equal(ops.categories[1].metadata.label, 'uri2'); + assert.strictEqual(ops.categories.length, 2); + assert.strictEqual(ops.categories[0].metadata.label, 'uri1'); // unconfirmed! + assert.strictEqual(ops.categories[1].metadata.label, 'uri2'); }); test('has not categories', async function () { @@ -79,9 +79,9 @@ suite('BulkEditPreview', function () { ]; const ops = await instaService.invokeFunction(BulkFileOperations.create, edits); - assert.equal(ops.categories.length, 1); - assert.equal(ops.categories[0].metadata.label, 'uri1'); // unconfirmed! - assert.equal(ops.categories[0].metadata.label, 'uri1'); + assert.strictEqual(ops.categories.length, 1); + assert.strictEqual(ops.categories[0].metadata.label, 'uri1'); // unconfirmed! + assert.strictEqual(ops.categories[0].metadata.label, 'uri1'); }); test('category selection', async function () { @@ -94,8 +94,8 @@ suite('BulkEditPreview', function () { const ops = await instaService.invokeFunction(BulkFileOperations.create, edits); - assert.equal(ops.checked.isChecked(edits[0]), true); - assert.equal(ops.checked.isChecked(edits[1]), true); + assert.strictEqual(ops.checked.isChecked(edits[0]), true); + assert.strictEqual(ops.checked.isChecked(edits[1]), true); assert.ok(edits === ops.getWorkspaceEdit()); @@ -105,8 +105,8 @@ suite('BulkEditPreview', function () { const newEdits = ops.getWorkspaceEdit(); assert.ok(edits !== newEdits); - assert.equal(edits.length, 2); - assert.equal(newEdits.length, 1); + assert.strictEqual(edits.length, 2); + assert.strictEqual(newEdits.length, 1); }); test('fix bad metadata', async function () { @@ -120,7 +120,7 @@ suite('BulkEditPreview', function () { const ops = await instaService.invokeFunction(BulkFileOperations.create, edits); - assert.equal(ops.checked.isChecked(edits[0]), false); - assert.equal(ops.checked.isChecked(edits[1]), false); + assert.strictEqual(ops.checked.isChecked(edits[0]), false); + assert.strictEqual(ops.checked.isChecked(edits[1]), false); }); }); diff --git a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts index fbf12cd1..96f6d862 100644 --- a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts +++ b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts @@ -101,7 +101,7 @@ export class CallHierarchyTreePeekWidget extends peekView.PeekViewWidget { this._disposables.add(this._previewDisposable); } - dispose(): void { + override dispose(): void { LayoutInfo.store(this._layoutInfo, this._storageService); this._splitView.dispose(); this._tree.dispose(); @@ -124,7 +124,7 @@ export class CallHierarchyTreePeekWidget extends peekView.PeekViewWidget { }); } - protected _fillHead(container: HTMLElement): void { + protected override _fillHead(container: HTMLElement): void { super._fillHead(container, true); const menu = this._menuService.createMenu(CallHierarchyTreePeekWidget.TitleMenu, this._contextKeyService); @@ -410,13 +410,13 @@ export class CallHierarchyTreePeekWidget extends peekView.PeekViewWidget { } } - protected _onWidth(width: number) { + protected override _onWidth(width: number) { if (this._dim) { this._doLayoutBody(this._dim.height, width); } } - protected _doLayoutBody(height: number, width: number): void { + protected override _doLayoutBody(height: number, width: number): void { if (this._dim.height !== height || this._dim.width !== width) { super._doLayoutBody(height, width); this._dim = new Dimension(width, height); diff --git a/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts b/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts index 4f6b7ff0..7522b2f6 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts @@ -143,7 +143,7 @@ class AccessibilityHelpWidget extends Widget implements IOverlayWidget { this._editor.addOverlayWidget(this); } - public dispose(): void { + public override dispose(): void { this._editor.removeOverlayWidget(this); super.dispose(); } diff --git a/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts b/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts index 0f7a8480..2362766c 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts @@ -98,7 +98,7 @@ class DiffEditorHelperContribution extends Disposable implements IDiffEditorCont } } - dispose(): void { + override dispose(): void { super.dispose(); } } diff --git a/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindReplaceWidget.ts b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindReplaceWidget.ts index cb7b1e97..08bc47e5 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindReplaceWidget.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindReplaceWidget.ts @@ -3,25 +3,25 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/css!./simpleFindReplaceWidget'; -import * as nls from 'vs/nls'; import * as dom from 'vs/base/browser/dom'; import { FindInput, IFindInputStyles } from 'vs/base/browser/ui/findinput/findInput'; +import { IReplaceInputStyles, ReplaceInput } from 'vs/base/browser/ui/findinput/replaceInput'; +import { IMessage as InputBoxMessage } from 'vs/base/browser/ui/inputbox/inputBox'; +import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; import { Widget } from 'vs/base/browser/ui/widget'; import { Delayer } from 'vs/base/common/async'; import { KeyCode } from 'vs/base/common/keyCodes'; +import 'vs/css!./simpleFindReplaceWidget'; import { FindReplaceState, FindReplaceStateChangedEvent } from 'vs/editor/contrib/find/findState'; -import { IMessage as InputBoxMessage } from 'vs/base/browser/ui/inputbox/inputBox'; -import { SimpleButton, findNextMatchIcon, findPreviousMatchIcon, findReplaceIcon, findReplaceAllIcon } from 'vs/editor/contrib/find/findWidget'; +import { findNextMatchIcon, findPreviousMatchIcon, findReplaceAllIcon, findReplaceIcon, SimpleButton } from 'vs/editor/contrib/find/findWidget'; +import * as nls from 'vs/nls'; +import { ContextScopedFindInput, ContextScopedReplaceInput } from 'vs/platform/browser/contextScopedHistoryWidget'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; -import { editorWidgetBackground, inputActiveOptionBorder, inputActiveOptionBackground, inputActiveOptionForeground, inputBackground, inputBorder, inputForeground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, inputValidationInfoBackground, inputValidationInfoBorder, inputValidationInfoForeground, inputValidationWarningBackground, inputValidationWarningBorder, inputValidationWarningForeground, widgetShadow, editorWidgetForeground } from 'vs/platform/theme/common/colorRegistry'; -import { IColorTheme, registerThemingParticipant, IThemeService } from 'vs/platform/theme/common/themeService'; -import { ContextScopedFindInput, ContextScopedReplaceInput } from 'vs/platform/browser/contextScopedHistoryWidget'; -import { ReplaceInput, IReplaceInputStyles } from 'vs/base/browser/ui/findinput/replaceInput'; -import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; -import { attachProgressBarStyler } from 'vs/platform/theme/common/styler'; +import { editorWidgetBackground, editorWidgetForeground, inputActiveOptionBackground, inputActiveOptionBorder, inputActiveOptionForeground, inputBackground, inputBorder, inputForeground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, inputValidationInfoBackground, inputValidationInfoBorder, inputValidationInfoForeground, inputValidationWarningBackground, inputValidationWarningBorder, inputValidationWarningForeground, widgetShadow } from 'vs/platform/theme/common/colorRegistry'; import { widgetClose } from 'vs/platform/theme/common/iconRegistry'; +import { attachProgressBarStyler } from 'vs/platform/theme/common/styler'; +import { IColorTheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; const NLS_FIND_INPUT_LABEL = nls.localize('label.find', "Find"); const NLS_FIND_INPUT_PLACEHOLDER = nls.localize('placeholder.find', "Find"); @@ -34,6 +34,7 @@ const NLS_REPLACE_INPUT_PLACEHOLDER = nls.localize('placeholder.replace', "Repla const NLS_REPLACE_BTN_LABEL = nls.localize('label.replaceButton', "Replace"); const NLS_REPLACE_ALL_BTN_LABEL = nls.localize('label.replaceAllButton', "Replace All"); + export abstract class SimpleFindReplaceWidget extends Widget { protected readonly _findInput: FindInput; private readonly _domNode: HTMLElement; @@ -41,6 +42,7 @@ export abstract class SimpleFindReplaceWidget extends Widget { private readonly _focusTracker: dom.IFocusTracker; private readonly _findInputFocusTracker: dom.IFocusTracker; private readonly _updateHistoryDelayer: Delayer; + protected readonly _matchesCount!: HTMLElement; private readonly prevBtn: SimpleButton; private readonly nextBtn: SimpleButton; @@ -145,6 +147,10 @@ export abstract class SimpleFindReplaceWidget extends Widget { this.findFirst(); })); + this._matchesCount = document.createElement('div'); + this._matchesCount.className = 'matchesCount'; + this._updateMatchesCount(); + this.prevBtn = this._register(new SimpleButton({ label: NLS_PREVIOUS_MATCH_BTN_LABEL, icon: findPreviousMatchIcon, @@ -170,6 +176,7 @@ export abstract class SimpleFindReplaceWidget extends Widget { })); this._innerFindDomNode.appendChild(this._findInput.domNode); + this._innerFindDomNode.appendChild(this._matchesCount); this._innerFindDomNode.appendChild(this.prevBtn.domNode); this._innerFindDomNode.appendChild(this.nextBtn.domNode); this._innerFindDomNode.appendChild(closeBtn.domNode); @@ -307,6 +314,7 @@ export abstract class SimpleFindReplaceWidget extends Widget { private _onStateChanged(e: FindReplaceStateChangedEvent): void { this._updateButtons(); + this._updateMatchesCount(); } private _updateButtons(): void { @@ -320,8 +328,10 @@ export abstract class SimpleFindReplaceWidget extends Widget { this._toggleReplaceBtn.setExpanded(this._isReplaceVisible); } + protected _updateMatchesCount(): void { + } - dispose() { + override dispose() { super.dispose(); if (this._domNode && this._domNode.parentElement) { diff --git a/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts index 6226e8fb..dc1c0e06 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts @@ -187,7 +187,7 @@ export abstract class SimpleFindWidget extends Widget { this._findInput.style(inputStyles); } - dispose() { + override dispose() { super.dispose(); if (this._domNode && this._domNode.parentElement) { diff --git a/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts b/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts index 97a0010d..3fd351fc 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts @@ -71,7 +71,7 @@ class InspectEditorTokensController extends Disposable implements IEditorContrib this._register(this._editor.onKeyUp((e) => e.keyCode === KeyCode.Escape && this.stop())); } - public dispose(): void { + public override dispose(): void { this.stop(); super.dispose(); } @@ -214,7 +214,7 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget { this._editor.addContentWidget(this); } - public dispose(): void { + public override dispose(): void { this._isDisposed = true; this._editor.removeContentWidget(this); this._currentRequestCancellationTokenSource.cancel(); diff --git a/src/vs/workbench/contrib/codeEditor/browser/inspectKeybindings.ts b/src/vs/workbench/contrib/codeEditor/browser/inspectKeybindings.ts index d33ec461..f5e0bb15 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/inspectKeybindings.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/inspectKeybindings.ts @@ -47,7 +47,7 @@ class InspectKeyMapJSON extends Action { super(id, label); } - public run(): Promise { + public override run(): Promise { return this._editorService.openEditor({ contents: this._keybindingService._dumpDebugInfoJSON(), options: { pinned: true } }); } } diff --git a/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoLineQuickAccess.ts b/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoLineQuickAccess.ts index 99417655..73843d65 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoLineQuickAccess.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoLineQuickAccess.ts @@ -41,7 +41,7 @@ export class GotoLineQuickAccessProvider extends AbstractGotoLineQuickAccessProv return this.editorService.activeTextEditorControl; } - protected gotoLocation(context: IQuickAccessTextEditorContext, options: { range: IRange, keyMods: IKeyMods, forceSideBySide?: boolean, preserveFocus?: boolean }): void { + protected override gotoLocation(context: IQuickAccessTextEditorContext, options: { range: IRange, keyMods: IKeyMods, forceSideBySide?: boolean, preserveFocus?: boolean }): void { // Check for sideBySide use if ((options.keyMods.alt || (this.configuration.openEditorPinned && options.keyMods.ctrlCmd) || options.forceSideBySide) && this.editorService.activeEditor) { diff --git a/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess.ts b/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess.ts index d3216f61..7766be44 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess.ts @@ -67,7 +67,7 @@ export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccess return this.editorService.activeTextEditorControl; } - protected gotoLocation(context: IQuickAccessTextEditorContext, options: { range: IRange, keyMods: IKeyMods, forceSideBySide?: boolean, preserveFocus?: boolean }): void { + protected override gotoLocation(context: IQuickAccessTextEditorContext, options: { range: IRange, keyMods: IKeyMods, forceSideBySide?: boolean, preserveFocus?: boolean }): void { // Check for sideBySide use if ((options.keyMods.alt || (this.configuration.openEditorPinned && options.keyMods.ctrlCmd) || options.forceSideBySide) && this.editorService.activeEditor) { @@ -110,17 +110,17 @@ export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccess return this.doGetSymbolPicks(this.getDocumentSymbols(model, token), prepareQuery(filter), options, token); } - addDecorations(editor: IEditor, range: IRange): void { + override addDecorations(editor: IEditor, range: IRange): void { super.addDecorations(editor, range); } - clearDecorations(editor: IEditor): void { + override clearDecorations(editor: IEditor): void { super.clearDecorations(editor); } //#endregion - protected provideWithoutTextEditor(picker: IQuickPick): IDisposable { + protected override provideWithoutTextEditor(picker: IQuickPick): IDisposable { if (this.canPickWithOutlineService()) { return this.doGetOutlinePicks(picker); } diff --git a/src/vs/workbench/contrib/codeEditor/browser/saveParticipants.ts b/src/vs/workbench/contrib/codeEditor/browser/saveParticipants.ts index 76ffc0b6..5f4725e8 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/saveParticipants.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/saveParticipants.ts @@ -272,9 +272,11 @@ class CodeActionOnSaveParticipant implements ITextFileSaveParticipant { return; } - if (env.reason === SaveReason.AUTO) { + // Do not run code actions on auto save + if (env.reason !== SaveReason.EXPLICIT) { return undefined; } + const textEditorModel = model.textEditorModel; const settingsOverrides = { overrideIdentifier: textEditorModel.getLanguageIdentifier().language, resource: model.resource }; diff --git a/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts b/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts index 9ae10454..2122c889 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts @@ -88,10 +88,10 @@ type ISuggestEnabledInputStyles = { export function attachSuggestEnabledInputBoxStyler(widget: IThemable, themeService: IThemeService, style?: ISuggestEnabledInputStyleOverrides): IDisposable { return attachStyler(themeService, { - inputBackground: (style && style.inputBackground) || inputBackground, - inputForeground: (style && style.inputForeground) || inputForeground, - inputBorder: (style && style.inputBorder) || inputBorder, - inputPlaceholderForeground: (style && style.inputPlaceholderForeground) || inputPlaceholderForeground, + inputBackground: style?.inputBackground || inputBackground, + inputForeground: style?.inputForeground || inputForeground, + inputBorder: style?.inputBorder || inputBorder, + inputPlaceholderForeground: style?.inputPlaceholderForeground || inputPlaceholderForeground, } as ISuggestEnabledInputStyleOverrides, widget); } diff --git a/src/vs/workbench/contrib/codeEditor/browser/toggleColumnSelection.ts b/src/vs/workbench/contrib/codeEditor/browser/toggleColumnSelection.ts index 858bf7be..5164467c 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/toggleColumnSelection.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/toggleColumnSelection.ts @@ -39,7 +39,7 @@ export class ToggleColumnSelectionAction extends Action { return this._codeEditorService.getActiveCodeEditor(); } - public async run(): Promise { + public override async run(): Promise { const oldValue = this._configurationService.getValue('editor.columnSelection'); const codeEditor = this._getCodeEditor(); await this._configurationService.updateValue('editor.columnSelection', !oldValue); diff --git a/src/vs/workbench/contrib/codeEditor/browser/toggleMinimap.ts b/src/vs/workbench/contrib/codeEditor/browser/toggleMinimap.ts index fbd79e00..1da1c3be 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/toggleMinimap.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/toggleMinimap.ts @@ -23,7 +23,7 @@ export class ToggleMinimapAction extends Action { super(id, label); } - public run(): Promise { + public override run(): Promise { const newValue = !this._configurationService.getValue('editor.minimap.enabled'); return this._configurationService.updateValue('editor.minimap.enabled', newValue); } diff --git a/src/vs/workbench/contrib/codeEditor/browser/toggleMultiCursorModifier.ts b/src/vs/workbench/contrib/codeEditor/browser/toggleMultiCursorModifier.ts index 36a63179..d271522e 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/toggleMultiCursorModifier.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/toggleMultiCursorModifier.ts @@ -29,7 +29,7 @@ export class ToggleMultiCursorModifierAction extends Action { super(id, label); } - public run(): Promise { + public override run(): Promise { const editorConf = this.configurationService.getValue<{ multiCursorModifier: 'ctrlCmd' | 'alt' }>('editor'); const newValue: 'ctrlCmd' | 'alt' = (editorConf.multiCursorModifier === 'ctrlCmd' ? 'alt' : 'ctrlCmd'); diff --git a/src/vs/workbench/contrib/codeEditor/browser/toggleRenderControlCharacter.ts b/src/vs/workbench/contrib/codeEditor/browser/toggleRenderControlCharacter.ts index d530f1a8..ecf2968c 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/toggleRenderControlCharacter.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/toggleRenderControlCharacter.ts @@ -24,7 +24,7 @@ export class ToggleRenderControlCharacterAction extends Action { super(id, label); } - public run(): Promise { + public override run(): Promise { let newRenderControlCharacters = !this._configurationService.getValue('editor.renderControlCharacters'); return this._configurationService.updateValue('editor.renderControlCharacters', newRenderControlCharacters); } diff --git a/src/vs/workbench/contrib/codeEditor/browser/toggleRenderWhitespace.ts b/src/vs/workbench/contrib/codeEditor/browser/toggleRenderWhitespace.ts index 5a5174b8..e9835e21 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/toggleRenderWhitespace.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/toggleRenderWhitespace.ts @@ -24,7 +24,7 @@ export class ToggleRenderWhitespaceAction extends Action { super(id, label); } - public run(): Promise { + public override run(): Promise { const renderWhitespace = this._configurationService.getValue('editor.renderWhitespace'); let newRenderWhitespace: string; diff --git a/src/vs/workbench/contrib/codeEditor/electron-sandbox/selectionClipboard.ts b/src/vs/workbench/contrib/codeEditor/electron-sandbox/selectionClipboard.ts index 015984db..4441bbf6 100644 --- a/src/vs/workbench/contrib/codeEditor/electron-sandbox/selectionClipboard.ts +++ b/src/vs/workbench/contrib/codeEditor/electron-sandbox/selectionClipboard.ts @@ -84,7 +84,7 @@ export class SelectionClipboard extends Disposable implements IEditorContributio } } - public dispose(): void { + public override dispose(): void { super.dispose(); } } diff --git a/src/vs/workbench/contrib/codeEditor/test/browser/saveParticipant.test.ts b/src/vs/workbench/contrib/codeEditor/test/browser/saveParticipant.test.ts index 4e693565..24eb70fa 100644 --- a/src/vs/workbench/contrib/codeEditor/test/browser/saveParticipant.test.ts +++ b/src/vs/workbench/contrib/codeEditor/test/browser/saveParticipant.test.ts @@ -33,7 +33,7 @@ suite('Save Participants', function () { test('insert final new line', async function () { const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/final_new_line.txt'), 'utf8', undefined) as IResolvedTextFileEditorModel; - await model.load(); + await model.resolve(); const configService = new TestConfigurationService(); configService.setUserConfiguration('files', { 'insertFinalNewline': true }); const participant = new FinalNewLineParticipant(configService, undefined!); @@ -66,7 +66,7 @@ suite('Save Participants', function () { test('trim final new lines', async function () { const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/trim_final_new_line.txt'), 'utf8', undefined) as IResolvedTextFileEditorModel; - await model.load(); + await model.resolve(); const configService = new TestConfigurationService(); configService.setUserConfiguration('files', { 'trimFinalNewlines': true }); const participant = new TrimFinalNewLinesParticipant(configService, undefined!); @@ -101,7 +101,7 @@ suite('Save Participants', function () { test('trim final new lines bug#39750', async function () { const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/trim_final_new_line.txt'), 'utf8', undefined) as IResolvedTextFileEditorModel; - await model.load(); + await model.resolve(); const configService = new TestConfigurationService(); configService.setUserConfiguration('files', { 'trimFinalNewlines': true }); const participant = new TrimFinalNewLinesParticipant(configService, undefined!); @@ -128,7 +128,7 @@ suite('Save Participants', function () { test('trim final new lines bug#46075', async function () { const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/trim_final_new_line.txt'), 'utf8', undefined) as IResolvedTextFileEditorModel; - await model.load(); + await model.resolve(); const configService = new TestConfigurationService(); configService.setUserConfiguration('files', { 'trimFinalNewlines': true }); const participant = new TrimFinalNewLinesParticipant(configService, undefined!); @@ -155,7 +155,7 @@ suite('Save Participants', function () { test('trim whitespace', async function () { const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/trim_final_new_line.txt'), 'utf8', undefined) as IResolvedTextFileEditorModel; - await model.load(); + await model.resolve(); const configService = new TestConfigurationService(); configService.setUserConfiguration('files', { 'trimTrailingWhitespace': true }); const participant = new TrimWhitespaceParticipant(configService, undefined!); diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts index 1d4e0e38..42ab2bb4 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts @@ -180,7 +180,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget return undefined; } - protected revealLine(lineNumber: number) { + protected override revealLine(lineNumber: number) { // we don't do anything here as we always do the reveal ourselves. } @@ -397,11 +397,11 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget this.setFocusedComment(this._focusedComment); } - protected _onWidth(widthInPixel: number): void { + protected override _onWidth(widthInPixel: number): void { this._commentReplyComponent?.editor.layout({ height: 5 * 18, width: widthInPixel - 54 /* margin 20px * 10 + scrollbar 14px*/ }); } - protected _doLayout(heightInPixel: number, widthInPixel: number): void { + protected override _doLayout(heightInPixel: number, widthInPixel: number): void { this._commentReplyComponent?.editor.layout({ height: 5 * 18, width: widthInPixel - 54 /* margin 20px * 10 + scrollbar 14px*/ }); } @@ -941,10 +941,17 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget } const fontInfo = this.editor.getOption(EditorOption.fontInfo); + const fontFamilyVar = '--comment-thread-editor-font-family'; + const fontSizeVar = '--comment-thread-editor-font-size'; + const fontWeightVar = '--comment-thread-editor-font-weight'; + this.container?.style.setProperty(fontFamilyVar, fontInfo.fontFamily); + this.container?.style.setProperty(fontSizeVar, `${fontInfo.fontSize}px`); + this.container?.style.setProperty(fontWeightVar, fontInfo.fontWeight); + content.push(`.monaco-editor .review-widget .body code { - font-family: '${fontInfo.fontFamily}'; - font-size: ${fontInfo.fontSize}px; - font-weight: ${fontInfo.fontWeight}; + font-family: var(${fontFamilyVar}); + font-weight: var(${fontWeightVar}); + font-size: var(${fontSizeVar}); }`); this._styleElement.textContent = content.join('\n'); @@ -953,13 +960,13 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget this.setCommentEditorDecorations(); } - show(rangeOrPos: IRange | IPosition, heightInLines: number): void { + override show(rangeOrPos: IRange | IPosition, heightInLines: number): void { this._isExpanded = true; super.show(rangeOrPos, heightInLines); this._refresh(); } - hide() { + override hide() { if (this._isExpanded) { this._isExpanded = false; // Focus the container so that the comment editor will be blurred before it is hidden @@ -968,7 +975,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget super.hide(); } - dispose() { + override dispose() { super.dispose(); if (this._resizeObserver) { this._resizeObserver.disconnect(); diff --git a/src/vs/workbench/contrib/comments/browser/comments.contribution.ts b/src/vs/workbench/contrib/comments/browser/comments.contribution.ts index 4c04857f..d01db82f 100644 --- a/src/vs/workbench/contrib/comments/browser/comments.contribution.ts +++ b/src/vs/workbench/contrib/comments/browser/comments.contribution.ts @@ -23,7 +23,8 @@ Registry.as(ConfigurationExtensions.Configuration).regis 'comments.openPanel': { enum: ['neverOpen', 'openOnSessionStart', 'openOnSessionStartWithComments'], default: 'openOnSessionStartWithComments', - description: nls.localize('openComments', "Controls when the comments panel should open.") + description: nls.localize('openComments', "Controls when the comments panel should open."), + restricted: false } } }); diff --git a/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts b/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts index 0249effb..53b39141 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts @@ -127,7 +127,7 @@ export class CommentNodeRenderer implements IListRenderer inline: true, actionHandler: { callback: (content) => { - this.openerService.open(content).catch(onUnexpectedError); + this.openerService.open(content, { allowCommands: node.element.comment.body.isTrusted }).catch(onUnexpectedError); }, disposeables: disposables } diff --git a/src/vs/workbench/contrib/comments/browser/commentsView.ts b/src/vs/workbench/contrib/comments/browser/commentsView.ts index 9212343c..ae81e533 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsView.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsView.ts @@ -44,7 +44,7 @@ export class CommentsPanel extends ViewPane { constructor( options: IViewPaneOptions, - @IInstantiationService readonly instantiationService: IInstantiationService, + @IInstantiationService instantiationService: IInstantiationService, @IViewDescriptorService viewDescriptorService: IViewDescriptorService, @IEditorService private readonly editorService: IEditorService, @IConfigurationService configurationService: IConfigurationService, @@ -61,7 +61,7 @@ export class CommentsPanel extends ViewPane { this.hasCommentsContextKey = CONTEXT_KEY_HAS_COMMENTS.bindTo(contextKeyService); } - public renderBody(container: HTMLElement): void { + public override renderBody(container: HTMLElement): void { super.renderBody(container); container.classList.add('comments-panel'); @@ -89,7 +89,7 @@ export class CommentsPanel extends ViewPane { this.renderComments(); } - public focus(): void { + public override focus(): void { if (this.tree && this.tree.getHTMLElement() === document.activeElement) { return; } @@ -144,7 +144,7 @@ export class CommentsPanel extends ViewPane { } } - public layoutBody(height: number, width: number): void { + public override layoutBody(height: number, width: number): void { super.layoutBody(height, width); this.tree.layout(height, width); } diff --git a/src/vs/workbench/contrib/comments/browser/media/review.css b/src/vs/workbench/contrib/comments/browser/media/review.css index da9c9f97..3c2c3fcf 100644 --- a/src/vs/workbench/contrib/comments/browser/media/review.css +++ b/src/vs/workbench/contrib/comments/browser/media/review.css @@ -110,10 +110,6 @@ min-height: 25px; } -.monaco-editor .review-widget .body .review-comment .review-comment-contents .comment-reactions .monaco-action-bar .actions-container { - justify-content: flex-start; -} - .monaco-editor .review-widget .body .review-comment .review-comment-contents .comment-reactions .action-item .action-label { padding: 1px 4px; white-space: pre; diff --git a/src/vs/workbench/contrib/comments/browser/reactionsAction.ts b/src/vs/workbench/contrib/comments/browser/reactionsAction.ts index ef091f4d..28215b94 100644 --- a/src/vs/workbench/contrib/comments/browser/reactionsAction.ts +++ b/src/vs/workbench/contrib/comments/browser/reactionsAction.ts @@ -17,7 +17,7 @@ export class ToggleReactionsAction extends Action { super(ToggleReactionsAction.ID, title || nls.localize('pickReactions', "Pick Reactions..."), 'toggle-reactions', true); this.toggleDropdownMenu = toggleDropdownMenu; } - run(): Promise { + override run(): Promise { this.toggleDropdownMenu(); return Promise.resolve(true); } @@ -32,7 +32,7 @@ export class ReactionActionViewItem extends ActionViewItem { constructor(action: ReactionAction) { super(null, action, {}); } - updateLabel(): void { + override updateLabel(): void { if (!this.label) { return; } diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditor.contribution.ts b/src/vs/workbench/contrib/customEditor/browser/customEditor.contribution.ts index b73cc5d8..7fbc7851 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditor.contribution.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditor.contribution.ts @@ -7,21 +7,16 @@ import { Schemas } from 'vs/base/common/network'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { Registry } from 'vs/platform/registry/common/platform'; -import { EditorDescriptor, Extensions as EditorExtensions, IEditorRegistry } from 'vs/workbench/browser/editor'; -import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; -import { Extensions as EditorInputExtensions, IEditorInputFactoryRegistry } from 'vs/workbench/common/editor'; -import { customEditorInputFactory, CustomEditorInputFactory } from 'vs/workbench/contrib/customEditor/browser/customEditorInputFactory'; +import { EditorDescriptor, IEditorRegistry } from 'vs/workbench/browser/editor'; +import { EditorExtensions, IEditorInputFactoryRegistry } from 'vs/workbench/common/editor'; +import { customEditorInputFactory, CustomEditorInputSerializer } from 'vs/workbench/contrib/customEditor/browser/customEditorInputFactory'; import { ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor'; import { WebviewEditor } from 'vs/workbench/contrib/webviewPanel/browser/webviewEditor'; -import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { CustomEditorInput } from './customEditorInput'; -import { CustomEditorContribution, CustomEditorService } from './customEditors'; +import { CustomEditorService } from './customEditors'; registerSingleton(ICustomEditorService, CustomEditorService); -Registry.as(WorkbenchExtensions.Workbench) - .registerWorkbenchContribution(CustomEditorContribution, LifecyclePhase.Starting); - Registry.as(EditorExtensions.Editors) .registerEditor( EditorDescriptor.create( @@ -32,10 +27,10 @@ Registry.as(EditorExtensions.Editors) new SyncDescriptor(CustomEditorInput) ]); -Registry.as(EditorInputExtensions.EditorInputFactories) - .registerEditorInputFactory( - CustomEditorInputFactory.ID, - CustomEditorInputFactory); +Registry.as(EditorExtensions.EditorInputFactories) + .registerEditorInputSerializer( + CustomEditorInputSerializer.ID, + CustomEditorInputSerializer); -Registry.as(EditorInputExtensions.EditorInputFactories) +Registry.as(EditorExtensions.EditorInputFactories) .registerCustomEditorInputFactory(Schemas.vscodeCustomEditor, customEditorInputFactory); diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts index c7942f19..08f1b94e 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts @@ -11,20 +11,51 @@ import { basename } from 'vs/base/common/path'; import { isEqual } from 'vs/base/common/resources'; import { assertIsDefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; +import { generateUuid } from 'vs/base/common/uuid'; import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILabelService } from 'vs/platform/label/common/label'; import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; import { GroupIdentifier, IEditorInput, IRevertOptions, ISaveOptions, Verbosity } from 'vs/workbench/common/editor'; +import { defaultCustomEditor } from 'vs/workbench/contrib/customEditor/common/contributedCustomEditors'; import { ICustomEditorModel, ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor'; import { decorateFileEditorLabel } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; -import { WebviewOverlay } from 'vs/workbench/contrib/webview/browser/webview'; +import { IWebviewService, WebviewOverlay } from 'vs/workbench/contrib/webview/browser/webview'; import { IWebviewWorkbenchService, LazilyResolvedWebviewEditorInput } from 'vs/workbench/contrib/webviewPanel/browser/webviewWorkbenchService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { - public static typeId = 'workbench.editors.webviewEditor'; + static create( + instantiationService: IInstantiationService, + resource: URI, + viewType: string, + group: GroupIdentifier | undefined, + options?: { readonly customClasses?: string }, + ): IEditorInput { + return instantiationService.invokeFunction(accessor => { + if (viewType === defaultCustomEditor.id) { + return accessor.get(IEditorService).createEditorInput({ resource, forceFile: true }); + } + // If it's an untitled file we must populate the untitledDocumentData + const untitledString = accessor.get(IUntitledTextEditorService).getValue(resource); + let untitledDocumentData = untitledString ? VSBuffer.fromString(untitledString) : undefined; + const id = generateUuid(); + const webview = accessor.get(IWebviewService).createWebviewOverlay(id, { customClasses: options?.customClasses }, {}, undefined); + const input = instantiationService.createInstance(CustomEditorInput, resource, viewType, id, webview, { untitledDocumentData: untitledDocumentData }); + // If we're loading untitled file data we should ensure it's dirty + if (untitledDocumentData) { + input._defaultDirtyState = true; + } + if (typeof group !== 'undefined') { + input.updateGroup(group); + } + return input; + }); + } + + public static override readonly typeId = 'workbench.editors.webviewEditor'; private readonly _editorResource: URI; private _defaultDirtyState: boolean | undefined; @@ -33,7 +64,7 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { private readonly _untitledDocumentData: VSBuffer | undefined; - get resource() { return this._editorResource; } + override get resource() { return this._editorResource; } private _modelRef?: IReference; @@ -58,25 +89,29 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { this._untitledDocumentData = options.untitledDocumentData; } - public getTypeId(): string { + public override get typeId(): string { return CustomEditorInput.typeId; } - public supportsSplitEditor() { - return true; + public override canSplit() { + return !!this.customEditorService.getCustomEditorCapabilities(this.viewType)?.supportsMultipleEditorsPerDocument; } - getName(): string { + override getName(): string { const name = basename(this.labelService.getUriLabel(this.resource)); return this.decorateLabel(name); } - matches(other: IEditorInput): boolean { + override matches(other: IEditorInput): boolean { return this === other || (other instanceof CustomEditorInput && this.viewType === other.viewType && isEqual(this.resource, other.resource)); } + override copy(): IEditorInput { + return CustomEditorInput.create(this.instantiationService, this.resource, this.viewType, this.group, this.webview.options); + } + @memoize private get shortTitle(): string { return this.getName(); @@ -92,7 +127,7 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { return this.labelService.getUriLabel(this.resource); } - public getTitle(verbosity?: Verbosity): string { + public override getTitle(verbosity?: Verbosity): string { switch (verbosity) { case Verbosity.SHORT: return this.decorateLabel(this.shortTitle); @@ -108,7 +143,7 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { const orphaned = !!this._modelRef?.object.isOrphaned(); const readonly = this._modelRef - ? !this._modelRef.object.isEditable() || this._modelRef.object.isOnReadonlyFileSystem() + ? this._modelRef.object.isEditable() && this._modelRef.object.isOnReadonlyFileSystem() : false; return decorateFileEditorLabel(label, { @@ -117,22 +152,22 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { }); } - public isReadonly(): boolean { + public override isReadonly(): boolean { return this._modelRef ? !this._modelRef.object.isEditable() : false; } - public isUntitled(): boolean { + public override isUntitled(): boolean { return this.resource.scheme === Schemas.untitled; } - public isDirty(): boolean { + public override isDirty(): boolean { if (!this._modelRef) { return !!this._defaultDirtyState; } return this._modelRef.object.isDirty(); } - public async save(groupId: GroupIdentifier, options?: ISaveOptions): Promise { + public override async save(groupId: GroupIdentifier, options?: ISaveOptions): Promise { if (!this._modelRef) { return undefined; } @@ -143,13 +178,13 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { } if (!isEqual(target, this.resource)) { - return this.customEditorService.createInput(target, this.viewType, groupId); + return CustomEditorInput.create(this.instantiationService, target, this.viewType, groupId); } return this; } - public async saveAs(groupId: GroupIdentifier, options?: ISaveOptions): Promise { + public override async saveAs(groupId: GroupIdentifier, options?: ISaveOptions): Promise { if (!this._modelRef) { return undefined; } @@ -167,7 +202,7 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { return this.rename(groupId, target)?.editor; } - public async revert(group: GroupIdentifier, options?: IRevertOptions): Promise { + public override async revert(group: GroupIdentifier, options?: IRevertOptions): Promise { if (this._modelRef) { return this._modelRef.object.revert(options); } @@ -175,7 +210,7 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { this._onDidChangeDirty.fire(); } - public async resolve(): Promise { + public override async resolve(): Promise { await super.resolve(); if (this.isDisposed()) { @@ -195,7 +230,7 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { return null; } - rename(group: GroupIdentifier, newResource: URI): { editor: IEditorInput } | undefined { + override rename(group: GroupIdentifier, newResource: URI): { editor: IEditorInput } | undefined { // See if we can keep using the same custom editor provider const editorInfo = this.customEditorService.getCustomEditor(this.viewType); if (editorInfo?.matches(newResource)) { @@ -207,7 +242,7 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { private doMove(group: GroupIdentifier, newResource: URI): IEditorInput { if (!this._moveHandler) { - return this.customEditorService.createInput(newResource, this.viewType, group); + return CustomEditorInput.create(this.instantiationService, newResource, this.viewType, group); } this._moveHandler(newResource); @@ -239,7 +274,7 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { this._moveHandler = handler; } - protected transfer(other: CustomEditorInput): CustomEditorInput | undefined { + protected override transfer(other: CustomEditorInput): CustomEditorInput | undefined { if (!super.transfer(other)) { return; } diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts index 192cb111..23e5e9f0 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts @@ -8,11 +8,12 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { ICustomEditorInputFactory, IEditorInput } from 'vs/workbench/common/editor'; import { CustomEditorInput } from 'vs/workbench/contrib/customEditor/browser/customEditorInput'; import { IWebviewService, WebviewContentOptions, WebviewContentPurpose, WebviewExtensionDescription, WebviewOptions } from 'vs/workbench/contrib/webview/browser/webview'; -import { SerializedWebviewOptions, DeserializedWebview, reviveWebviewExtensionDescription, SerializedWebview, WebviewEditorInputFactory, restoreWebviewContentOptions, restoreWebviewOptions } from 'vs/workbench/contrib/webviewPanel/browser/webviewEditorInputFactory'; +import { SerializedWebviewOptions, DeserializedWebview, reviveWebviewExtensionDescription, SerializedWebview, WebviewEditorInputSerializer, restoreWebviewContentOptions, restoreWebviewOptions } from 'vs/workbench/contrib/webviewPanel/browser/webviewEditorInputSerializer'; import { IWebviewWorkbenchService } from 'vs/workbench/contrib/webviewPanel/browser/webviewWorkbenchService'; -import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; +import { IWorkingCopyBackupService } from 'vs/workbench/services/workingCopy/common/workingCopyBackup'; +import { IWorkingCopyBackupMeta, NO_TYPE_ID } from 'vs/workbench/services/workingCopy/common/workingCopy'; -export interface CustomDocumentBackupData { +export interface CustomDocumentBackupData extends IWorkingCopyBackupMeta { readonly viewType: string; readonly editorResource: UriComponents; backupId: string; @@ -43,9 +44,9 @@ interface DeserializedCustomEditor extends DeserializedWebview { } -export class CustomEditorInputFactory extends WebviewEditorInputFactory { +export class CustomEditorInputSerializer extends WebviewEditorInputSerializer { - public static readonly ID = CustomEditorInput.typeId; + public static override readonly ID = CustomEditorInput.typeId; public constructor( @IWebviewWorkbenchService webviewWorkbenchService: IWebviewWorkbenchService, @@ -55,7 +56,7 @@ export class CustomEditorInputFactory extends WebviewEditorInputFactory { super(webviewWorkbenchService); } - public serialize(input: CustomEditorInput): string | undefined { + public override serialize(input: CustomEditorInput): string | undefined { const dirty = input.isDirty(); const data: SerializedCustomEditor = { ...this.toJson(input), @@ -71,7 +72,7 @@ export class CustomEditorInputFactory extends WebviewEditorInputFactory { } } - protected fromJson(data: SerializedCustomEditor): DeserializedCustomEditor { + protected override fromJson(data: SerializedCustomEditor): DeserializedCustomEditor { return { ...super.fromJson(data), editorResource: URI.from(data.editorResource), @@ -79,7 +80,7 @@ export class CustomEditorInputFactory extends WebviewEditorInputFactory { }; } - public deserialize( + public override deserialize( _instantiationService: IInstantiationService, serializedEditorInput: string ): CustomEditorInput { @@ -107,9 +108,9 @@ export const customEditorInputFactory = new class implements ICustomEditorInputF public createCustomEditorInput(resource: URI, instantiationService: IInstantiationService): Promise { return instantiationService.invokeFunction(async accessor => { const webviewService = accessor.get(IWebviewService); - const backupFileService = accessor.get(IBackupFileService); + const workingCopyBackupService = accessor.get(IWorkingCopyBackupService); - const backup = await backupFileService.resolve(resource); + const backup = await workingCopyBackupService.resolve({ resource, typeId: NO_TYPE_ID }); if (!backup?.meta) { throw new Error(`No backup found for custom editor: ${resource}`); } diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts index 5e5d2470..2824537b 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts @@ -3,42 +3,35 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { coalesce, distinct, firstOrDefault } from 'vs/base/common/arrays'; -import { VSBuffer } from 'vs/base/common/buffer'; +import { coalesce } from 'vs/base/common/arrays'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { Schemas } from 'vs/base/common/network'; import { extname, isEqual } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; -import { generateUuid } from 'vs/base/common/uuid'; import { RedoCommand, UndoCommand } from 'vs/editor/browser/editorExtensions'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { EditorActivation, EditorOverride, IEditorOptions, ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { FileOperation, IFileService } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { Registry } from 'vs/platform/registry/common/platform'; import { IStorageService } from 'vs/platform/storage/common/storage'; import * as colorRegistry from 'vs/platform/theme/common/colorRegistry'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; -import { EditorsAssociations, editorsAssociationsSettingId, Extensions as EditorExtensions, IEditorAssociationsRegistry, IEditorType, IEditorTypesHandler } from 'vs/workbench/browser/editor'; -import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { EditorInput, EditorOptions, Extensions as EditorInputExtensions, GroupIdentifier, IEditorInput, IEditorInputFactoryRegistry, IEditorPane } from 'vs/workbench/common/editor'; +import { EditorInput, EditorExtensions, GroupIdentifier, IEditorInput, IEditorInputFactoryRegistry } from 'vs/workbench/common/editor'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; -import { CONTEXT_ACTIVE_CUSTOM_EDITOR_ID, CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE, CustomEditorCapabilities, CustomEditorInfo, CustomEditorInfoCollection, CustomEditorPriority, ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor'; +import { CONTEXT_ACTIVE_CUSTOM_EDITOR_ID, CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE, CustomEditorCapabilities, CustomEditorInfo, CustomEditorInfoCollection, ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor'; import { CustomEditorModelManager } from 'vs/workbench/contrib/customEditor/common/customEditorModelManager'; -import { IWebviewService } from 'vs/workbench/contrib/webview/browser/webview'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { IEditorService, IOpenEditorOverride, IOpenEditorOverrideEntry } from 'vs/workbench/services/editor/common/editorService'; -import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; +import { ContributedEditorPriority, IEditorAssociationsRegistry, IEditorOverrideService, IEditorType, IEditorTypesHandler } from 'vs/workbench/services/editor/common/editorOverrideService'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; -import { ContributedCustomEditors, defaultCustomEditor } from '../common/contributedCustomEditors'; +import { ContributedCustomEditors } from '../common/contributedCustomEditors'; import { CustomEditorInput } from './customEditorInput'; export class CustomEditorService extends Disposable implements ICustomEditorService, IEditorTypesHandler { _serviceBrand: any; private readonly _contributedEditors: ContributedCustomEditors; + private readonly _editorOverrideDisposables: IDisposable[] = []; private readonly _editorCapabilities = new Map(); private readonly _models = new CustomEditorModelManager(); @@ -49,19 +42,17 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ private readonly _onDidChangeEditorTypes = this._register(new Emitter()); public readonly onDidChangeEditorTypes: Event = this._onDidChangeEditorTypes.event; - private readonly _fileEditorInputFactory = Registry.as(EditorInputExtensions.EditorInputFactories).getFileEditorInputFactory(); + private readonly _fileEditorInputFactory = Registry.as(EditorExtensions.EditorInputFactories).getFileEditorInputFactory(); constructor( @IContextKeyService contextKeyService: IContextKeyService, @IFileService fileService: IFileService, @IStorageService storageService: IStorageService, - @IConfigurationService private readonly configurationService: IConfigurationService, @IEditorService private readonly editorService: IEditorService, @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, @IInstantiationService private readonly instantiationService: IInstantiationService, - @IWebviewService private readonly webviewService: IWebviewService, @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, - @IUntitledTextEditorService private readonly untitledTextEditorService: IUntitledTextEditorService + @IEditorOverrideService private readonly extensionContributedEditorService: IEditorOverrideService, ) { super(); @@ -69,7 +60,10 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ this._focusedCustomEditorIsEditable = CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE.bindTo(contextKeyService); this._contributedEditors = this._register(new ContributedCustomEditors(storageService)); + this.registerContributionPoints(); + this._register(this._contributedEditors.onChange(() => { + this.registerContributionPoints(); this.updateContexts(); this._onDidChangeEditorTypes.fire(); })); @@ -109,6 +103,54 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ return false; } + private registerContributionPoints(): void { + // Clear all previous contributions we know + this._editorOverrideDisposables.forEach(d => d.dispose()); + for (const contributedEditor of this._contributedEditors) { + for (const globPattern of contributedEditor.selector) { + if (!globPattern.filenamePattern) { + continue; + } + this._editorOverrideDisposables.push(this._register(this.extensionContributedEditorService.registerContributionPoint( + globPattern.filenamePattern, + { + id: contributedEditor.id, + label: contributedEditor.displayName, + detail: contributedEditor.providerDisplayName, + describes: (currentEditor) => currentEditor instanceof CustomEditorInput && currentEditor.viewType === contributedEditor.id, + priority: contributedEditor.priority, + }, + { + singlePerResource: () => !this.getCustomEditorCapabilities(contributedEditor.id)?.supportsMultipleEditorsPerDocument ?? true + }, + (resource, options, group) => { + return { editor: CustomEditorInput.create(this.instantiationService, resource, contributedEditor.id, group.id) }; + }, + (diffEditorInput, options, group) => { + return { editor: this.createDiffEditorInput(diffEditorInput, contributedEditor.id, group) }; + } + ))); + } + } + } + + private createDiffEditorInput( + editor: DiffEditorInput, + editorID: string, + group: IEditorGroup + ): DiffEditorInput { + const createEditorForSubInput = (subInput: IEditorInput, editorID: string, customClasses: string): EditorInput | undefined => { + // We check before calling this call back that both resources are defined + const input = CustomEditorInput.create(this.instantiationService, subInput.resource!, editorID, group.id, { customClasses }); + return input instanceof EditorInput ? input : undefined; + }; + + const modifiedOverride = createEditorForSubInput(editor.modifiedInput, editorID, 'modified'); + const originalOverride = createEditorForSubInput(editor.originalInput, editorID, 'original'); + + return this.instantiationService.createInstance(DiffEditorInput, editor.getName(), editor.getDescription(), originalOverride || editor.originalInput, modifiedOverride || editor.modifiedInput, true); + } + public get models() { return this._models; } public getCustomEditor(viewType: string): CustomEditorInfo | undefined { @@ -120,10 +162,9 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ } public getUserConfiguredCustomEditors(resource: URI): CustomEditorInfoCollection { - const rawAssociations = this.configurationService.getValue(editorsAssociationsSettingId) || []; + const resourceAssocations = this.extensionContributedEditorService.getAssociationsForResource(resource); return new CustomEditorInfoCollection( - coalesce(rawAssociations - .filter(association => CustomEditorInfo.selectorMatches(association, resource)) + coalesce(resourceAssocations .map(association => this._contributedEditors.get(association.viewType)))); } @@ -134,90 +175,6 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ ]); } - public async openWith( - resource: URI, - viewType: string, - options?: ITextEditorOptions, - group?: IEditorGroup, - ): Promise { - if (viewType === defaultCustomEditor.id) { - const fileEditorInput = this.editorService.createEditorInput({ resource, forceFile: true }); - return this.openEditorForResource(resource, fileEditorInput, { ...options, override: EditorOverride.DISABLED }, group); - } - - if (!this._contributedEditors.get(viewType)) { - // Prompt the user - const input = this.editorService.createEditorInput({ resource }); - return this.editorService.openEditor(input, { override: EditorOverride.PICK }); - } - - const capabilities = this.getCustomEditorCapabilities(viewType) || {}; - if (!capabilities.supportsMultipleEditorsPerDocument) { - const movedEditor = await this.tryRevealExistingEditorForResourceInGroup(resource, viewType, options, group); - if (movedEditor) { - return movedEditor; - } - } - - // If it's an untitled file we must populate the untitledDocumentData - const untitledString = this.untitledTextEditorService.getValue(resource); - let untitledDocumentData = untitledString ? VSBuffer.fromString(untitledString) : undefined; - - const input = this.createInput(resource, viewType, group?.id, { untitledDocumentData }); - return this.openEditorForResource(resource, input, options, group); - } - - public createInput( - resource: URI, - viewType: string, - group: GroupIdentifier | undefined, - options?: { readonly customClasses?: string, readonly untitledDocumentData?: VSBuffer }, - ): IEditorInput { - if (viewType === defaultCustomEditor.id) { - return this.editorService.createEditorInput({ resource, forceFile: true }); - } - - const id = generateUuid(); - const webview = this.webviewService.createWebviewOverlay(id, { customClasses: options?.customClasses }, {}, undefined); - const input = this.instantiationService.createInstance(CustomEditorInput, resource, viewType, id, webview, { untitledDocumentData: options?.untitledDocumentData }); - if (typeof group !== 'undefined') { - input.updateGroup(group); - } - return input; - } - - private async openEditorForResource( - resource: URI, - input: IEditorInput, - options?: IEditorOptions, - group?: IEditorGroup - ): Promise { - const targetGroup = group || this.editorGroupService.activeGroup; - - if (options && typeof options.activation === 'undefined') { - options = { ...options, activation: options.preserveFocus ? EditorActivation.RESTORE : undefined }; - } - - // Try to replace existing editors for resource - const existing = firstOrDefault(this.editorService.findEditors(resource, targetGroup)); - if (existing) { - if (!input.matches(existing)) { - await this.editorService.replaceEditors([{ - editor: existing, - replacement: input, - forceReplaceDirty: existing.resource?.scheme === Schemas.untitled, - options: options ? EditorOptions.create(options) : undefined, - }], targetGroup); - - if (existing instanceof CustomEditorInput) { - existing.dispose(); - } - } - } - - return this.editorService.openEditor(input, options, group); - } - public registerCustomEditorCapabilities(viewType: string, options: CustomEditorCapabilities): IDisposable { if (this._editorCapabilities.has(viewType)) { throw new Error(`Capabilities for ${viewType} already set`); @@ -228,7 +185,7 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ }); } - private getCustomEditorCapabilities(viewType: string): CustomEditorCapabilities | undefined { + public getCustomEditorCapabilities(viewType: string): CustomEditorCapabilities | undefined { return this._editorCapabilities.get(viewType); } @@ -253,7 +210,7 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ const possibleEditors = this.getAllCustomEditors(newResource); // See if we have any non-optional custom editor for this resource - if (!possibleEditors.allEditors.some(editor => editor.priority !== CustomEditorPriority.option)) { + if (!possibleEditors.allEditors.some(editor => editor.priority !== ContributedEditorPriority.option)) { return; } @@ -284,7 +241,7 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ let replacement: IEditorInput; if (possibleEditors.defaultEditor) { const viewType = possibleEditors.defaultEditor.id; - replacement = this.createInput(newResource, viewType!, group); + replacement = CustomEditorInput.create(this.instantiationService, newResource, viewType!, group); } else { replacement = this.editorService.createEditorInput({ resource: newResource }); } @@ -299,279 +256,6 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ }), group); } } - - private async tryRevealExistingEditorForResourceInGroup( - resource: URI, - viewType: string, - options?: ITextEditorOptions, - group?: IEditorGroup, - ): Promise { - const editorInfoForResource = this.findExistingEditorsForResource(resource, viewType); - if (!editorInfoForResource.length) { - return undefined; - } - - const editorToUse = editorInfoForResource[0]; - - // Replace all other editors - for (const { editor, group } of editorInfoForResource) { - if (editor !== editorToUse.editor) { - group.closeEditor(editor); - } - } - - const targetGroup = group || this.editorGroupService.activeGroup; - const newEditor = await this.openEditorForResource(resource, editorToUse.editor, { ...options, override: EditorOverride.DISABLED }, targetGroup); - if (targetGroup.id !== editorToUse.group.id) { - editorToUse.group.closeEditor(editorToUse.editor); - } - return newEditor; - } - - private findExistingEditorsForResource( - resource: URI, - viewType: string, - ): Array<{ editor: IEditorInput, group: IEditorGroup }> { - const out: Array<{ editor: IEditorInput, group: IEditorGroup }> = []; - const orderedGroups = distinct([ - this.editorGroupService.activeGroup, - ...this.editorGroupService.groups, - ]); - - for (const group of orderedGroups) { - for (const editor of group.editors) { - if (isMatchingCustomEditor(editor, viewType, resource)) { - out.push({ editor, group }); - } - } - } - return out; - } -} - -export class CustomEditorContribution extends Disposable implements IWorkbenchContribution { - - private readonly _fileEditorInputFactory = Registry.as(EditorInputExtensions.EditorInputFactories).getFileEditorInputFactory(); - - constructor( - @IEditorService private readonly editorService: IEditorService, - @ICustomEditorService private readonly customEditorService: ICustomEditorService, - @IInstantiationService private readonly instantiationService: IInstantiationService - ) { - super(); - - this._register(this.editorService.overrideOpenEditor({ - open: (editor, options, group) => { - return this.onEditorOpening(editor, options, group); - }, - getEditorOverrides: (resource: URI, options: IEditorOptions | undefined, group: IEditorGroup | undefined): IOpenEditorOverrideEntry[] => { - const currentEditor = group && firstOrDefault(this.editorService.findEditors(resource, group)); - - const toOverride = (entry: CustomEditorInfo): IOpenEditorOverrideEntry => { - return { - id: entry.id, - active: currentEditor instanceof CustomEditorInput && currentEditor.viewType === entry.id, - label: entry.displayName, - detail: entry.providerDisplayName, - }; - }; - - if (typeof options?.override === 'string') { - // A specific override was requested. Only return it. - const matchingEditor = this.customEditorService.getCustomEditor(options.override); - return matchingEditor ? [toOverride(matchingEditor)] : []; - } - - // Otherwise, return all potential overrides. - const customEditors = this.customEditorService.getAllCustomEditors(resource); - if (!customEditors.length) { - return []; - } - - return customEditors.allEditors - .filter(entry => entry.id !== defaultCustomEditor.id) - .map(toOverride); - } - })); - } - - private onEditorOpening( - editor: IEditorInput, - options: ITextEditorOptions | undefined, - group: IEditorGroup - ): IOpenEditorOverride | undefined { - const id = typeof options?.override === 'string' ? options.override : undefined; - if (editor instanceof CustomEditorInput) { - if (editor.group === group.id && (editor.viewType === id || typeof id !== 'string')) { - // No need to do anything - return undefined; - } else { - // Create a copy of the input. - // Unlike normal editor inputs, we do not want to share custom editor inputs - // between multiple editors / groups. - return { - override: this.customEditorService.openWith(editor.resource, id ?? editor.viewType, options, group) - }; - } - } - - if (editor instanceof DiffEditorInput) { - return this.onDiffEditorOpening(editor, options, group); - } - - const resource = editor.resource; - if (!resource) { - return undefined; - } - - if (id) { - return { - override: this.customEditorService.openWith(resource, id, { ...options, override: EditorOverride.DISABLED }, group) - }; - } - - return this.onResourceEditorOpening(resource, editor, options, group); - } - - private onResourceEditorOpening( - resource: URI, - editor: IEditorInput, - options: ITextEditorOptions | undefined, - group: IEditorGroup, - ): IOpenEditorOverride | undefined { - const userConfiguredEditors = this.customEditorService.getUserConfiguredCustomEditors(resource); - const contributedEditors = this.customEditorService.getContributedCustomEditors(resource); - if (!userConfiguredEditors.length && !contributedEditors.length) { - return; - } - - // Check to see if there already an editor for the resource in the group. - // If there is, we want to open that instead of creating a new editor. - // This ensures that we preserve whatever type of editor was previously being used - // when the user switches back to it. - const strictMatchEditorInput = group.editors.find(e => e === editor && !this._fileEditorInputFactory.isFileEditorInput(e)); - if (strictMatchEditorInput) { - return; - } - - const existingEditorForResource = firstOrDefault(this.editorService.findEditors(resource, group)); - if (existingEditorForResource) { - if (editor === existingEditorForResource) { - return; - } - - return { - override: this.editorService.openEditor(existingEditorForResource, { - ...options, - override: EditorOverride.DISABLED, - activation: options?.preserveFocus ? EditorActivation.RESTORE : undefined, - }, group) - }; - } - - if (userConfiguredEditors.length) { - return { - override: this.customEditorService.openWith(resource, userConfiguredEditors.allEditors[0].id, options, group), - }; - } - - if (!contributedEditors.length) { - return; - } - - const defaultEditor = contributedEditors.defaultEditor; - if (defaultEditor) { - return { - override: this.customEditorService.openWith(resource, defaultEditor.id, options, group), - }; - } - - // If we have all optional editors, then open VS Code's standard editor - if (contributedEditors.allEditors.every(editor => editor.priority === CustomEditorPriority.option)) { - return; - } - - // Open VS Code's standard editor but prompt user to see if they wish to use a custom one instead - return { - override: (async () => { - const standardEditor = await this.editorService.openEditor(editor, { ...options, override: EditorOverride.DISABLED }, group); - if (!standardEditor?.input) { - return; - } - - // Give a moment to make sure the editor is showing. - // Otherwise the focus shift can cause the prompt to be dismissed right away. - await new Promise(resolve => setTimeout(resolve, 20)); - - // Prompt the user - return this.editorService.openEditor(standardEditor.input, { override: EditorOverride.PICK }); - })() - }; - } - - private onDiffEditorOpening( - editor: DiffEditorInput, - options: ITextEditorOptions | undefined, - group: IEditorGroup - ): IOpenEditorOverride | undefined { - const getBestAvailableEditorForSubInput = (subInput: IEditorInput): CustomEditorInfo | undefined => { - if (subInput instanceof CustomEditorInput) { - return undefined; - } - const resource = subInput.resource; - if (!resource) { - return undefined; - } - - // Prefer default editors in the diff editor case but ultimately always take the first editor - const allEditors = new CustomEditorInfoCollection([ - ...this.customEditorService.getUserConfiguredCustomEditors(resource).allEditors, - ...this.customEditorService.getContributedCustomEditors(resource).allEditors.filter(x => x.priority !== CustomEditorPriority.option), - ]); - return allEditors.bestAvailableEditor; - }; - - const createEditorForSubInput = (subInput: IEditorInput, editor: CustomEditorInfo | undefined, customClasses: string): EditorInput | undefined => { - if (!editor) { - return; - } - if (!subInput.resource) { - return; - } - const input = this.customEditorService.createInput(subInput.resource, editor.id, group.id, { customClasses }); - return input instanceof EditorInput ? input : undefined; - }; - - const modifiedEditorInfo = getBestAvailableEditorForSubInput(editor.modifiedInput); - const originalEditorInfo = getBestAvailableEditorForSubInput(editor.originalInput); - - // If we are only using default editors, no need to override anything - if ( - (!modifiedEditorInfo || modifiedEditorInfo.id === defaultCustomEditor.id) && - (!originalEditorInfo || originalEditorInfo.id === defaultCustomEditor.id) - ) { - return undefined; - } - - const modifiedOverride = createEditorForSubInput(editor.modifiedInput, modifiedEditorInfo, 'modified'); - const originalOverride = createEditorForSubInput(editor.originalInput, originalEditorInfo, 'original'); - if (modifiedOverride || originalOverride) { - return { - override: (async () => { - const input = this.instantiationService.createInstance(DiffEditorInput, editor.getName(), editor.getDescription(), originalOverride || editor.originalInput, modifiedOverride || editor.modifiedInput, true); - return this.editorService.openEditor(input, { ...options, override: EditorOverride.DISABLED }, group); - })(), - }; - } - - return undefined; - } -} - -function isMatchingCustomEditor(editor: IEditorInput, viewType: string, resource: URI): boolean { - return editor instanceof CustomEditorInput - && editor.viewType === viewType - && isEqual(editor.resource, resource); } registerThemingParticipant((theme, collector) => { diff --git a/src/vs/workbench/contrib/customEditor/common/contributedCustomEditors.ts b/src/vs/workbench/contrib/customEditor/common/contributedCustomEditors.ts index 8af7b66b..3bf7d2db 100644 --- a/src/vs/workbench/contrib/customEditor/common/contributedCustomEditors.ts +++ b/src/vs/workbench/contrib/customEditor/common/contributedCustomEditors.ts @@ -10,8 +10,9 @@ import * as nls from 'vs/nls'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { Memento } from 'vs/workbench/common/memento'; -import { CustomEditorDescriptor, CustomEditorInfo, CustomEditorPriority } from 'vs/workbench/contrib/customEditor/common/customEditor'; +import { CustomEditorDescriptor, CustomEditorInfo } from 'vs/workbench/contrib/customEditor/common/customEditor'; import { customEditorsExtensionPoint, ICustomEditorsExtensionPoint } from 'vs/workbench/contrib/customEditor/common/extensionPoint'; +import { ContributedEditorPriority } from 'vs/workbench/services/editor/common/editorOverrideService'; import { IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; export const defaultCustomEditor = new CustomEditorInfo({ @@ -21,7 +22,7 @@ export const defaultCustomEditor = new CustomEditorInfo({ selector: [ { filenamePattern: '*' } ], - priority: CustomEditorPriority.default, + priority: ContributedEditorPriority.default, }); export class ContributedCustomEditors extends Disposable { @@ -99,17 +100,17 @@ export class ContributedCustomEditors extends Disposable { function getPriorityFromContribution( contribution: ICustomEditorsExtensionPoint, extension: IExtensionDescription, -): CustomEditorPriority { +): ContributedEditorPriority { switch (contribution.priority) { - case CustomEditorPriority.default: - case CustomEditorPriority.option: + case ContributedEditorPriority.default: + case ContributedEditorPriority.option: return contribution.priority; - case CustomEditorPriority.builtin: + case ContributedEditorPriority.builtin: // Builtin is only valid for builtin extensions - return extension.isBuiltin ? CustomEditorPriority.builtin : CustomEditorPriority.default; + return extension.isBuiltin ? ContributedEditorPriority.builtin : ContributedEditorPriority.default; default: - return CustomEditorPriority.default; + return ContributedEditorPriority.default; } } diff --git a/src/vs/workbench/contrib/customEditor/common/customEditor.ts b/src/vs/workbench/contrib/customEditor/common/customEditor.ts index ff89f781..c7aa1260 100644 --- a/src/vs/workbench/contrib/customEditor/common/customEditor.ts +++ b/src/vs/workbench/contrib/customEditor/common/customEditor.ts @@ -5,18 +5,13 @@ import { distinct } from 'vs/base/common/arrays'; import { Event } from 'vs/base/common/event'; -import * as glob from 'vs/base/common/glob'; import { IDisposable, IReference } from 'vs/base/common/lifecycle'; -import { Schemas } from 'vs/base/common/network'; -import { posix } from 'vs/base/common/path'; -import { basename } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { GroupIdentifier, IEditorInput, IEditorPane, IRevertOptions, ISaveOptions } from 'vs/workbench/common/editor'; -import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import * as nls from 'vs/nls'; +import { IRevertOptions, ISaveOptions } from 'vs/workbench/common/editor'; +import { ContributedEditorPriority, globMatchesResource, priorityToRank } from 'vs/workbench/services/editor/common/editorOverrideService'; export const ICustomEditorService = createDecorator('customEditorService'); @@ -41,11 +36,8 @@ export interface ICustomEditorService { getContributedCustomEditors(resource: URI): CustomEditorInfoCollection; getUserConfiguredCustomEditors(resource: URI): CustomEditorInfoCollection; - createInput(resource: URI, viewType: string, group: GroupIdentifier | undefined, options?: { readonly customClasses: string }): IEditorInput; - - openWith(resource: URI, customEditorViewType: string, options?: ITextEditorOptions, group?: IEditorGroup): Promise; - registerCustomEditorCapabilities(viewType: string, options: CustomEditorCapabilities): IDisposable; + getCustomEditorCapabilities(viewType: string): CustomEditorCapabilities | undefined } export interface ICustomEditorModelManager { @@ -92,21 +84,16 @@ export interface CustomEditorDescriptor { readonly id: string; readonly displayName: string; readonly providerDisplayName: string; - readonly priority: CustomEditorPriority; + readonly priority: ContributedEditorPriority; readonly selector: readonly CustomEditorSelector[]; } export class CustomEditorInfo implements CustomEditorDescriptor { - private static readonly excludedSchemes = new Set([ - Schemas.extension, - Schemas.webviewPanel, - ]); - public readonly id: string; public readonly displayName: string; public readonly providerDisplayName: string; - public readonly priority: CustomEditorPriority; + public readonly priority: ContributedEditorPriority; public readonly selector: readonly CustomEditorSelector[]; constructor(descriptor: CustomEditorDescriptor) { @@ -118,22 +105,7 @@ export class CustomEditorInfo implements CustomEditorDescriptor { } matches(resource: URI): boolean { - return this.selector.some(selector => CustomEditorInfo.selectorMatches(selector, resource)); - } - - static selectorMatches(selector: CustomEditorSelector, resource: URI): boolean { - if (CustomEditorInfo.excludedSchemes.has(resource.scheme)) { - return false; - } - - if (selector.filenamePattern) { - const matchOnPath = selector.filenamePattern.indexOf(posix.sep) >= 0; - const target = matchOnPath ? resource.path : basename(resource); - if (glob.match(selector.filenamePattern.toLowerCase(), target.toLowerCase())) { - return true; - } - } - return false; + return this.selector.some(selector => selector.filenamePattern && globMatchesResource(selector.filenamePattern, resource)); } } @@ -156,8 +128,8 @@ export class CustomEditorInfoCollection { public get defaultEditor(): CustomEditorInfo | undefined { return this.allEditors.find(editor => { switch (editor.priority) { - case CustomEditorPriority.default: - case CustomEditorPriority.builtin: + case ContributedEditorPriority.default: + case ContributedEditorPriority.builtin: // A default editor must have higher priority than all other contributed editors. return this.allEditors.every(otherEditor => otherEditor === editor || isLowerPriority(otherEditor, editor)); @@ -185,11 +157,3 @@ export class CustomEditorInfoCollection { function isLowerPriority(otherEditor: CustomEditorInfo, editor: CustomEditorInfo): unknown { return priorityToRank(otherEditor.priority) < priorityToRank(editor.priority); } - -function priorityToRank(priority: CustomEditorPriority): number { - switch (priority) { - case CustomEditorPriority.default: return 3; - case CustomEditorPriority.builtin: return 2; - case CustomEditorPriority.option: return 1; - } -} diff --git a/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts b/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts index cc8b3963..f075b354 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts @@ -166,7 +166,7 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi } } - show(rangeOrPos: IRange | IPosition): void { + override show(rangeOrPos: IRange | IPosition): void { const lineNum = this.input.getModel().getLineCount(); super.show(rangeOrPos, lineNum + 1); } @@ -203,7 +203,7 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi setTimeout(() => this.input.focus(), 150); } - protected _doLayout(heightInPixel: number, widthInPixel: number): void { + protected override _doLayout(heightInPixel: number, widthInPixel: number): void { this.heightInPx = heightInPixel; this.input.layout({ height: heightInPixel, width: widthInPixel - 113 }); this.centerInputVertically(); @@ -336,7 +336,7 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi this.dispose(); } - dispose(): void { + override dispose(): void { super.dispose(); this.input.dispose(); lifecycle.dispose(this.toDispose); diff --git a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts index cf61b867..e0f4fa1c 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts @@ -104,7 +104,7 @@ export class BreakpointsView extends ViewPane { this._register(this.debugService.getModel().onDidChangeBreakpoints(() => this.onBreakpointsChange())); } - renderBody(container: HTMLElement): void { + override renderBody(container: HTMLElement): void { super.renderBody(container); this.element.classList.add('debug-pane'); @@ -174,7 +174,7 @@ export class BreakpointsView extends ViewPane { })); } - focus(): void { + override focus(): void { super.focus(); if (this.list) { this.list.domFocus(); @@ -191,7 +191,7 @@ export class BreakpointsView extends ViewPane { return this._inputBoxData; } - protected layoutBody(height: number, width: number): void { + protected override layoutBody(height: number, width: number): void { if (this.ignoreLayout) { return; } @@ -326,6 +326,10 @@ interface IFunctionBreakpointTemplateData extends IBaseBreakpointWithIconTemplat condition: HTMLElement; } +interface IDataBreakpointTemplateData extends IBaseBreakpointWithIconTemplateData { + accessType: HTMLElement; +} + interface IFunctionBreakpointInputTemplateData { inputBox: InputBox; checkbox: HTMLInputElement; @@ -469,7 +473,8 @@ class ExceptionBreakpointsRenderer implements IListRenderer { +class DataBreakpointsRenderer implements IListRenderer { constructor( @IDebugService private readonly debugService: IDebugService, @@ -588,8 +593,8 @@ class DataBreakpointsRenderer implements IListRenderer 1) { result.push({ options: TOP_STACK_FRAME_INLINE_DECORATION, range: columnUntilEOLRange }); } - topStackFrameRange = columnUntilEOLRange; } else { if (isFocusedSession) { result.push({ @@ -102,7 +101,6 @@ export function createDecorationsForStackFrame(stackFrame: IStackFrame, topStack export class CallStackEditorContribution implements IEditorContribution { private toDispose: IDisposable[] = []; private decorationIds: string[] = []; - private topStackFrameRange: Range | undefined; constructor( private readonly editor: ICodeEditor, @@ -139,7 +137,7 @@ export class CallStackEditorContribution implements IEditorContribution { stackFrames.forEach(candidateStackFrame => { if (candidateStackFrame && this.uriIdentityService.extUri.isEqual(candidateStackFrame.source.uri, this.editor.getModel()?.uri)) { - decorations.push(...createDecorationsForStackFrame(candidateStackFrame, this.topStackFrameRange, isSessionFocused)); + decorations.push(...createDecorationsForStackFrame(candidateStackFrame, isSessionFocused)); } }); } diff --git a/src/vs/workbench/contrib/debug/browser/callStackView.ts b/src/vs/workbench/contrib/debug/browser/callStackView.ts index 660feecf..94c7b595 100644 --- a/src/vs/workbench/contrib/debug/browser/callStackView.ts +++ b/src/vs/workbench/contrib/debug/browser/callStackView.ts @@ -138,7 +138,7 @@ export class CallStackView extends ViewPane { @IEditorService private readonly editorService: IEditorService, @IConfigurationService configurationService: IConfigurationService, @IMenuService menuService: IMenuService, - @IContextKeyService readonly contextKeyService: IContextKeyService, + @IContextKeyService contextKeyService: IContextKeyService, @IOpenerService openerService: IOpenerService, @IThemeService themeService: IThemeService, @ITelemetryService telemetryService: ITelemetryService @@ -203,16 +203,15 @@ export class CallStackView extends ViewPane { }, 50); } - protected renderHeaderTitle(container: HTMLElement): void { - const titleContainer = dom.append(container, $('.debug-call-stack-title')); - super.renderHeaderTitle(titleContainer, this.options.title); + protected override renderHeaderTitle(container: HTMLElement): void { + super.renderHeaderTitle(container, this.options.title); - this.stateMessage = dom.append(titleContainer, $('span.state-message')); + this.stateMessage = dom.append(container, $('span.call-stack-state-message')); this.stateMessage.hidden = true; this.stateMessageLabel = dom.append(this.stateMessage, $('span.label')); } - renderBody(container: HTMLElement): void { + override renderBody(container: HTMLElement): void { super.renderBody(container); this.element.classList.add('debug-pane'); container.classList.add('debug-call-stack'); @@ -367,12 +366,12 @@ export class CallStackView extends ViewPane { })); } - layoutBody(height: number, width: number): void { + override layoutBody(height: number, width: number): void { super.layoutBody(height, width); this.tree.layout(height, width); } - focus(): void { + override focus(): void { this.tree.domFocus(); } @@ -1021,21 +1020,21 @@ registerAction2(class Collapse extends ViewAction { } }); -function registerCallStackInlineMenuItem(id: string, title: string, icon: Icon, when: ContextKeyExpression, order: number): void { +function registerCallStackInlineMenuItem(id: string, title: string, icon: Icon, when: ContextKeyExpression, order: number, precondition?: ContextKeyExpression): void { MenuRegistry.appendMenuItem(MenuId.DebugCallStackContext, { group: 'inline', order, when, - command: { id, title, icon } + command: { id, title, icon, precondition } }); } const threadOrSessionWithOneThread = ContextKeyExpr.or(CONTEXT_CALLSTACK_ITEM_TYPE.isEqualTo('thread'), ContextKeyExpr.and(CONTEXT_CALLSTACK_ITEM_TYPE.isEqualTo('session'), CONTEXT_CALLSTACK_SESSION_HAS_ONE_THREAD))!; registerCallStackInlineMenuItem(PAUSE_ID, PAUSE_LABEL, icons.debugPause, ContextKeyExpr.and(threadOrSessionWithOneThread, CONTEXT_CALLSTACK_ITEM_STOPPED.toNegated())!, 10); registerCallStackInlineMenuItem(CONTINUE_ID, CONTINUE_LABEL, icons.debugContinue, ContextKeyExpr.and(threadOrSessionWithOneThread, CONTEXT_CALLSTACK_ITEM_STOPPED)!, 10); -registerCallStackInlineMenuItem(STEP_OVER_ID, STEP_OVER_LABEL, icons.debugStepOver, threadOrSessionWithOneThread, 20); -registerCallStackInlineMenuItem(STEP_INTO_ID, STEP_INTO_LABEL, icons.debugStepInto, threadOrSessionWithOneThread, 30); -registerCallStackInlineMenuItem(STEP_OUT_ID, STEP_OUT_LABEL, icons.debugStepOut, threadOrSessionWithOneThread, 40); +registerCallStackInlineMenuItem(STEP_OVER_ID, STEP_OVER_LABEL, icons.debugStepOver, threadOrSessionWithOneThread, 20, CONTEXT_CALLSTACK_ITEM_STOPPED); +registerCallStackInlineMenuItem(STEP_INTO_ID, STEP_INTO_LABEL, icons.debugStepInto, threadOrSessionWithOneThread, 30, CONTEXT_CALLSTACK_ITEM_STOPPED); +registerCallStackInlineMenuItem(STEP_OUT_ID, STEP_OUT_LABEL, icons.debugStepOut, threadOrSessionWithOneThread, 40, CONTEXT_CALLSTACK_ITEM_STOPPED); registerCallStackInlineMenuItem(RESTART_SESSION_ID, RESTART_LABEL, icons.debugRestart, CONTEXT_CALLSTACK_ITEM_TYPE.isEqualTo('session'), 50); registerCallStackInlineMenuItem(STOP_ID, STOP_LABEL, icons.debugStop, ContextKeyExpr.and(CONTEXT_CALLSTACK_SESSION_IS_ATTACH.toNegated(), CONTEXT_CALLSTACK_ITEM_TYPE.isEqualTo('session'))!, 60); registerCallStackInlineMenuItem(DISCONNECT_ID, DISCONNECT_LABEL, icons.debugDisconnect, ContextKeyExpr.and(CONTEXT_CALLSTACK_SESSION_IS_ATTACH, CONTEXT_CALLSTACK_ITEM_TYPE.isEqualTo('session'))!, 60); diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index c8a96516..bc48d517 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_BREAK_WHEN_VALUE_IS_READ_SUPPORTED, CONTEXT_BREAK_WHEN_VALUE_IS_ACCESSED_SUPPORTED, + CONTEXT_DEBUG_STATE, VARIABLES_VIEW_ID, CALLSTACK_VIEW_ID, WATCH_VIEW_ID, BREAKPOINTS_VIEW_ID, LOADED_SCRIPTS_VIEW_ID, CONTEXT_LOADED_SCRIPTS_SUPPORTED, 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, CONTEXT_FOCUSED_SESSION_IS_ATTACH, CONTEXT_TERMINATE_DEBUGGEE_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'; @@ -97,8 +97,8 @@ registerDebugCommandPaletteItem(STEP_OVER_ID, STEP_OVER_LABEL, CONTEXT_IN_DEBUG_ registerDebugCommandPaletteItem(STEP_INTO_ID, STEP_INTO_LABEL, CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); registerDebugCommandPaletteItem(STEP_OUT_ID, STEP_OUT_LABEL, CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); registerDebugCommandPaletteItem(PAUSE_ID, PAUSE_LABEL, CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE.isEqualTo('running')); -registerDebugCommandPaletteItem(DISCONNECT_ID, DISCONNECT_LABEL, CONTEXT_IN_DEBUG_MODE, CONTEXT_FOCUSED_SESSION_IS_ATTACH); -registerDebugCommandPaletteItem(STOP_ID, STOP_LABEL, CONTEXT_IN_DEBUG_MODE, CONTEXT_FOCUSED_SESSION_IS_ATTACH.toNegated()); +registerDebugCommandPaletteItem(DISCONNECT_ID, DISCONNECT_LABEL, CONTEXT_IN_DEBUG_MODE, ContextKeyExpr.or(CONTEXT_FOCUSED_SESSION_IS_ATTACH, CONTEXT_TERMINATE_DEBUGGEE_SUPPORTED)); +registerDebugCommandPaletteItem(STOP_ID, STOP_LABEL, CONTEXT_IN_DEBUG_MODE, ContextKeyExpr.or(CONTEXT_FOCUSED_SESSION_IS_ATTACH.toNegated(), CONTEXT_TERMINATE_DEBUGGEE_SUPPORTED)); registerDebugCommandPaletteItem(CONTINUE_ID, CONTINUE_LABEL, CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); registerDebugCommandPaletteItem(FOCUS_REPL_ID, nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'debugFocusConsole' }, 'Focus on Debug Console View')); registerDebugCommandPaletteItem(JUMP_TO_CURSOR_ID, nls.localize('jumpToCursor', "Jump to Cursor"), CONTEXT_JUMP_TO_CURSOR_SUPPORTED); @@ -123,8 +123,9 @@ const registerDebugViewMenuItem = (menuId: MenuId, id: string, title: string, or } }); }; -registerDebugViewMenuItem(MenuId.DebugCallStackContext, RESTART_SESSION_ID, RESTART_LABEL, 10, CONTEXT_CALLSTACK_ITEM_TYPE.isEqualTo('session')); -registerDebugViewMenuItem(MenuId.DebugCallStackContext, STOP_ID, STOP_LABEL, 20, CONTEXT_CALLSTACK_ITEM_TYPE.isEqualTo('session')); +registerDebugViewMenuItem(MenuId.DebugCallStackContext, RESTART_SESSION_ID, RESTART_LABEL, 10, CONTEXT_CALLSTACK_ITEM_TYPE.isEqualTo('session'), undefined, '3_modification'); +registerDebugViewMenuItem(MenuId.DebugCallStackContext, DISCONNECT_ID, DISCONNECT_LABEL, 20, CONTEXT_CALLSTACK_ITEM_TYPE.isEqualTo('session'), undefined, '3_modification'); +registerDebugViewMenuItem(MenuId.DebugCallStackContext, STOP_ID, STOP_LABEL, 30, CONTEXT_CALLSTACK_ITEM_TYPE.isEqualTo('session'), undefined, '3_modification'); registerDebugViewMenuItem(MenuId.DebugCallStackContext, PAUSE_ID, PAUSE_LABEL, 10, ContextKeyExpr.and(CONTEXT_CALLSTACK_ITEM_TYPE.isEqualTo('thread'), CONTEXT_DEBUG_STATE.isEqualTo('running'))); registerDebugViewMenuItem(MenuId.DebugCallStackContext, CONTINUE_ID, CONTINUE_LABEL, 10, ContextKeyExpr.and(CONTEXT_CALLSTACK_ITEM_TYPE.isEqualTo('thread'), CONTEXT_DEBUG_STATE.isEqualTo('stopped'))); registerDebugViewMenuItem(MenuId.DebugCallStackContext, STEP_OVER_ID, STEP_OVER_LABEL, 20, CONTEXT_CALLSTACK_ITEM_TYPE.isEqualTo('thread'), CONTEXT_DEBUG_STATE.isEqualTo('stopped')); @@ -379,9 +380,15 @@ configurationRegistry.registerConfiguration({ default: false }, 'debug.inlineValues': { - type: 'boolean', + type: ['boolean', 'string'], + 'enum': [true, false, 'auto'], description: nls.localize({ comment: ['This is the description for a setting'], key: 'inlineValues' }, "Show variable values inline in editor while debugging."), - default: false + 'enumDescriptions': [ + nls.localize('inlineValues.on', 'Always show variable values inline in editor while debugging.'), + nls.localize('inlineValues.off', 'Never show variable values inline in editor while debugging.'), + nls.localize('inlineValues.focusNoScroll', 'Show variable values inline in editor while debugging when the language supports inline value locations.'), + ], + default: 'auto' }, 'debug.toolBarLocation': { enum: ['floating', 'docked', 'hidden'], @@ -407,7 +414,7 @@ configurationRegistry.registerConfiguration({ }, 'debug.openDebug': { enum: ['neverOpen', 'openOnSessionStart', 'openOnFirstSessionStart', 'openOnDebugBreak'], - default: 'openOnFirstSessionStart', + default: 'openOnDebugBreak', description: nls.localize('openDebug', "Controls when the debug view should open.") }, 'debug.showSubSessionsInToolBar': { diff --git a/src/vs/workbench/contrib/debug/browser/debugANSIHandling.ts b/src/vs/workbench/contrib/debug/browser/debugANSIHandling.ts index d3cef4bb..e5df145e 100644 --- a/src/vs/workbench/contrib/debug/browser/debugANSIHandling.ts +++ b/src/vs/workbench/contrib/debug/browser/debugANSIHandling.ts @@ -21,6 +21,8 @@ export function handleANSIOutput(text: string, linkDetector: LinkDetector, theme let styleNames: string[] = []; let customFgColor: RGBA | undefined; let customBgColor: RGBA | undefined; + let customUnderlineColor: RGBA | undefined; + let colorsInverted: boolean = false; let currentPos: number = 0; let buffer: string = ''; @@ -54,7 +56,7 @@ export function handleANSIOutput(text: string, linkDetector: LinkDetector, theme if (sequenceFound) { // Flush buffer with previous styles. - appendStylizedStringToContainer(root, buffer, styleNames, linkDetector, workspaceFolder, customFgColor, customBgColor); + appendStylizedStringToContainer(root, buffer, styleNames, linkDetector, workspaceFolder, customFgColor, customBgColor, customUnderlineColor); buffer = ''; @@ -62,17 +64,17 @@ export function handleANSIOutput(text: string, linkDetector: LinkDetector, theme * Certain ranges that are matched here do not contain real graphics rendition sequences. For * the sake of having a simpler expression, they have been included anyway. */ - if (ansiSequence.match(/^(?:[34][0-8]|9[0-7]|10[0-7]|[013]|4|[34]9)(?:;[349][0-7]|10[0-7]|[013]|[245]|[34]9)?(?:;[012]?[0-9]?[0-9])*;?m$/)) { + if (ansiSequence.match(/^(?:[34][0-8]|9[0-7]|10[0-7]|[0-9]|2[1-5,7-9]|[34]9|5[8,9]|1[0-9])(?:;[349][0-7]|10[0-7]|[013]|[245]|[34]9)?(?:;[012]?[0-9]?[0-9])*;?m$/)) { const styleCodes: number[] = ansiSequence.slice(0, -1) // Remove final 'm' character. .split(';') // Separate style codes. .filter(elem => elem !== '') // Filter empty elems as '34;m' -> ['34', '']. .map(elem => parseInt(elem, 10)); // Convert to numbers. - if (styleCodes[0] === 38 || styleCodes[0] === 48) { + if (styleCodes[0] === 38 || styleCodes[0] === 48 || styleCodes[0] === 58) { // Advanced color code - can't be combined with formatting codes like simple colors can // Ignores invalid colors and additional info beyond what is necessary - const colorType = (styleCodes[0] === 38) ? 'foreground' : 'background'; + const colorType = (styleCodes[0] === 38) ? 'foreground' : ((styleCodes[0] === 48) ? 'background' : 'underline'); if (styleCodes[1] === 5) { set8BitColor(styleCodes, colorType); @@ -100,7 +102,7 @@ export function handleANSIOutput(text: string, linkDetector: LinkDetector, theme // Flush remaining text buffer if not empty. if (buffer) { - appendStylizedStringToContainer(root, buffer, styleNames, linkDetector, workspaceFolder, customFgColor, customBgColor); + appendStylizedStringToContainer(root, buffer, styleNames, linkDetector, workspaceFolder, customFgColor, customBgColor, customUnderlineColor); } return root; @@ -109,15 +111,18 @@ export function handleANSIOutput(text: string, linkDetector: LinkDetector, theme * Change the foreground or background color by clearing the current color * and adding the new one. * @param colorType If `'foreground'`, will change the foreground color, if - * `'background'`, will change the background color. + * `'background'`, will change the background color, and if `'underline'` + * will set the underline color. * @param color Color to change to. If `undefined` or not provided, * will clear current color without adding a new one. */ - function changeColor(colorType: 'foreground' | 'background', color?: RGBA | undefined): void { + function changeColor(colorType: 'foreground' | 'background' | 'underline', color?: RGBA | undefined): void { if (colorType === 'foreground') { customFgColor = color; } else if (colorType === 'background') { customBgColor = color; + } else if (colorType === 'underline') { + customUnderlineColor = color; } styleNames = styleNames.filter(style => style !== `code-${colorType}-colored`); if (color !== undefined) { @@ -126,44 +131,165 @@ export function handleANSIOutput(text: string, linkDetector: LinkDetector, theme } /** - * Calculate and set basic ANSI formatting. Supports bold, italic, underline, - * normal foreground and background colors, and bright foreground and + * Swap foreground and background colors. Used for color inversion. Caller should check + * [] flag to make sure it is appropriate to turn ON or OFF (if it is already inverted don't call + */ + function reverseForegroundAndBackgroundColors(): void { + let oldFgColor: RGBA | undefined; + oldFgColor = customFgColor; + changeColor('foreground', customBgColor); + changeColor('background', oldFgColor); + } + + /** + * Calculate and set basic ANSI formatting. Supports ON/OFF of bold, italic, underline, + * double underline, crossed-out/strikethrough, overline, dim, blink, rapid blink, + * reverse/invert video, hidden, superscript, subscript and alternate font codes, + * clearing/resetting of foreground, background and underline colors, + * setting normal foreground and background colors, and bright foreground and * background colors. Not to be used for codes containing advanced colors. * Will ignore invalid codes. * @param styleCodes Array of ANSI basic styling numbers, which will be * applied in order. New colors and backgrounds clear old ones; new formatting * does not. - * @see {@link https://en.wikipedia.org/wiki/ANSI_escape_code } + * @see {@link https://en.wikipedia.org/wiki/ANSI_escape_code#SGR } */ function setBasicFormatters(styleCodes: number[]): void { for (let code of styleCodes) { switch (code) { - case 0: { + case 0: { // reset (everything) styleNames = []; customFgColor = undefined; customBgColor = undefined; break; } - case 1: { + case 1: { // bold + styleNames = styleNames.filter(style => style !== `code-bold`); styleNames.push('code-bold'); break; } - case 3: { + case 2: { // dim + styleNames = styleNames.filter(style => style !== `code-dim`); + styleNames.push('code-dim'); + break; + } + case 3: { // italic + styleNames = styleNames.filter(style => style !== `code-italic`); styleNames.push('code-italic'); break; } - case 4: { + case 4: { // underline + styleNames = styleNames.filter(style => (style !== `code-underline` && style !== `code-double-underline`)); styleNames.push('code-underline'); break; } - case 39: { + case 5: { // blink + styleNames = styleNames.filter(style => style !== `code-blink`); + styleNames.push('code-blink'); + break; + } + case 6: { // rapid blink + styleNames = styleNames.filter(style => style !== `code-rapid-blink`); + styleNames.push('code-rapid-blink'); + break; + } + case 7: { // invert foreground and background + if (!colorsInverted) { + colorsInverted = true; + reverseForegroundAndBackgroundColors(); + } + break; + } + case 8: { // hidden + styleNames = styleNames.filter(style => style !== `code-hidden`); + styleNames.push('code-hidden'); + break; + } + case 9: { // strike-through/crossed-out + styleNames = styleNames.filter(style => style !== `code-strike-through`); + styleNames.push('code-strike-through'); + break; + } + case 10: { // normal default font + styleNames = styleNames.filter(style => !style.startsWith('code-font')); + break; + } + case 11: case 12: case 13: case 14: case 15: case 16: case 17: case 18: case 19: case 20: { // font codes (and 20 is 'blackletter' font code) + styleNames = styleNames.filter(style => !style.startsWith('code-font')); + styleNames.push(`code-font-${code - 10}`); + break; + } + case 21: { // double underline + styleNames = styleNames.filter(style => (style !== `code-underline` && style !== `code-double-underline`)); + styleNames.push('code-double-underline'); + break; + } + case 22: { // normal intensity (bold off and dim off) + styleNames = styleNames.filter(style => (style !== `code-bold` && style !== `code-dim`)); + break; + } + case 23: { // Neither italic or blackletter (font 10) + styleNames = styleNames.filter(style => (style !== `code-italic` && style !== `code-font-10`)); + break; + } + case 24: { // not underlined (Neither singly nor doubly underlined) + styleNames = styleNames.filter(style => (style !== `code-underline` && style !== `code-double-underline`)); + break; + } + case 25: { // not blinking + styleNames = styleNames.filter(style => (style !== `code-blink` && style !== `code-rapid-blink`)); + break; + } + case 27: { // not reversed/inverted + if (colorsInverted) { + colorsInverted = false; + reverseForegroundAndBackgroundColors(); + } + break; + } + case 28: { // not hidden (reveal) + styleNames = styleNames.filter(style => style !== `code-hidden`); + break; + } + case 29: { // not crossed-out + styleNames = styleNames.filter(style => style !== `code-strike-through`); + break; + } + case 53: { // overlined + styleNames = styleNames.filter(style => style !== `code-overline`); + styleNames.push('code-overline'); + break; + } + case 55: { // not overlined + styleNames = styleNames.filter(style => style !== `code-overline`); + break; + } + case 39: { // default foreground color changeColor('foreground', undefined); break; } - case 49: { + case 49: { // default background color changeColor('background', undefined); break; } + case 59: { // default underline color + changeColor('underline', undefined); + break; + } + case 73: { // superscript + styleNames = styleNames.filter(style => (style !== `code-superscript` && style !== `code-subscript`)); + styleNames.push('code-superscript'); + break; + } + case 74: { // subscript + styleNames = styleNames.filter(style => (style !== `code-superscript` && style !== `code-subscript`)); + styleNames.push('code-subscript'); + break; + } + case 75: { // neither superscript or subscript + styleNames = styleNames.filter(style => (style !== `code-superscript` && style !== `code-subscript`)); + break; + } default: { setBasicColor(code); break; @@ -177,10 +303,11 @@ export function handleANSIOutput(text: string, linkDetector: LinkDetector, theme * @param styleCodes Full list of integer codes that make up the full ANSI * sequence, including the two defining codes and the three RGB codes. * @param colorType If `'foreground'`, will set foreground color, if - * `'background'`, will set background color. + * `'background'`, will set background color, and if it is `'underline'` + * will set the underline color. * @see {@link https://en.wikipedia.org/wiki/ANSI_escape_code#24-bit } */ - function set24BitColor(styleCodes: number[], colorType: 'foreground' | 'background'): void { + function set24BitColor(styleCodes: number[], colorType: 'foreground' | 'background' | 'underline'): void { if (styleCodes.length >= 5 && styleCodes[2] >= 0 && styleCodes[2] <= 255 && styleCodes[3] >= 0 && styleCodes[3] <= 255 && @@ -195,16 +322,27 @@ export function handleANSIOutput(text: string, linkDetector: LinkDetector, theme * @param styleCodes Full list of integer codes that make up the ANSI * sequence, including the two defining codes and the one color code. * @param colorType If `'foreground'`, will set foreground color, if - * `'background'`, will set background color. + * `'background'`, will set background color and if it is `'underline'` + * will set the underline color. * @see {@link https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit } */ - function set8BitColor(styleCodes: number[], colorType: 'foreground' | 'background'): void { + function set8BitColor(styleCodes: number[], colorType: 'foreground' | 'background' | 'underline'): void { let colorNumber = styleCodes[2]; const color = calcANSI8bitColor(colorNumber); if (color) { changeColor(colorType, color); } else if (colorNumber >= 0 && colorNumber <= 15) { + if (colorType === 'underline') { + // for underline colors we just decode the 0-15 color number to theme color, set and return + const theme = themeService.getColorTheme(); + const colorName = ansiColorIdentifiers[colorNumber]; + const color = theme.getColor(colorName); + if (color) { + changeColor(colorType, color.rgba); + } + return; + } // Need to map to one of the four basic color ranges (30-37, 90-97, 40-47, 100-107) colorNumber += 30; if (colorNumber >= 38) { @@ -261,7 +399,8 @@ export function handleANSIOutput(text: string, linkDetector: LinkDetector, theme * @param cssClasses The list of CSS styles to apply to the text content. * @param linkDetector The {@link LinkDetector} responsible for generating links from {@param stringContent}. * @param customTextColor If provided, will apply custom color with inline style. - * @param customBackgroundColor If provided, will apply custom color with inline style. + * @param customBackgroundColor If provided, will apply custom backgroundColor with inline style. + * @param customUnderlineColor If provided, will apply custom textDecorationColor with inline style. */ export function appendStylizedStringToContainer( root: HTMLElement, @@ -270,7 +409,8 @@ export function appendStylizedStringToContainer( linkDetector: LinkDetector, workspaceFolder: IWorkspaceFolder | undefined, customTextColor?: RGBA, - customBackgroundColor?: RGBA + customBackgroundColor?: RGBA, + customUnderlineColor?: RGBA ): void { if (!root || !stringContent) { return; @@ -287,7 +427,10 @@ export function appendStylizedStringToContainer( container.style.backgroundColor = Color.Format.CSS.formatRGB(new Color(customBackgroundColor)); } - + if (customUnderlineColor) { + container.style.textDecorationColor = + Color.Format.CSS.formatRGB(new Color(customUnderlineColor)); + } root.appendChild(container); } diff --git a/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts b/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts index e5197132..fb6c5847 100644 --- a/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts +++ b/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts @@ -67,7 +67,7 @@ export class StartDebugActionViewItem extends BaseActionViewItem { })); } - render(container: HTMLElement): void { + override render(container: HTMLElement): void { this.container = container; container.classList.add('start-debug-action-item'); this.start = dom.append(container, $(ThemeIcon.asCSSSelector(debugStart))); @@ -142,15 +142,15 @@ export class StartDebugActionViewItem extends BaseActionViewItem { this.updateOptions(); } - setActionContext(context: any): void { + override setActionContext(context: any): void { this.context = context; } - isEnabled(): boolean { + override isEnabled(): boolean { return true; } - focus(fromRight?: boolean): void { + override focus(fromRight?: boolean): void { if (fromRight) { this.selectBox.focus(); } else { @@ -159,13 +159,13 @@ export class StartDebugActionViewItem extends BaseActionViewItem { } } - blur(): void { + override blur(): void { this.start.tabIndex = -1; this.selectBox.blur(); this.container.blur(); } - setFocusable(focusable: boolean): void { + override setFocusable(focusable: boolean): void { if (focusable) { this.start.tabIndex = 0; } else { @@ -174,7 +174,7 @@ export class StartDebugActionViewItem extends BaseActionViewItem { } } - dispose(): void { + override dispose(): void { this.toDispose = dispose(this.toDispose); } @@ -292,7 +292,7 @@ export class FocusSessionActionViewItem extends SelectActionViewItem { this.update(selectedSession); } - protected getActionContext(_: string, index: number): any { + protected override getActionContext(_: string, index: number): any { return this.getSessions()[index]; } diff --git a/src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts b/src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts index 2e5528d9..9b114cc9 100644 --- a/src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts +++ b/src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts @@ -21,7 +21,14 @@ import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { launchSchema, debuggersExtPoint, breakpointsExtPoint } from 'vs/workbench/contrib/debug/common/debugSchemas'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { launchSchemaId } from 'vs/workbench/services/configuration/common/configuration'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; +import { IModeService } from 'vs/editor/common/services/modeService'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import Severity from 'vs/base/common/severity'; +const jsonRegistry = Registry.as(JSONExtensions.JSONContribution); export class AdapterManager implements IAdapterManager { private debuggers: Debugger[]; @@ -39,7 +46,9 @@ export class AdapterManager implements IAdapterManager { @IInstantiationService private readonly instantiationService: IInstantiationService, @ICommandService private readonly commandService: ICommandService, @IExtensionService private readonly extensionService: IExtensionService, - @IContextKeyService contextKeyService: IContextKeyService + @IContextKeyService contextKeyService: IContextKeyService, + @IModeService private readonly modeService: IModeService, + @IDialogService private readonly dialogService: IDialogService ) { this.adapterDescriptorFactories = []; this.debuggers = []; @@ -81,8 +90,10 @@ export class AdapterManager implements IAdapterManager { }); // update the schema to include all attributes, snippets and types from extensions. + const items = (launchSchema.properties!['configurations'].items); + items.oneOf = []; + items.defaultSnippets = []; this.debuggers.forEach(adapter => { - const items = (launchSchema.properties!['configurations'].items); const schemaAttributes = adapter.getSchemaAttributes(); if (schemaAttributes && items.oneOf) { items.oneOf.push(...schemaAttributes); @@ -92,6 +103,7 @@ export class AdapterManager implements IAdapterManager { items.defaultSnippets.push(...configurationSnippets); } }); + jsonRegistry.registerSchema(launchSchemaId, launchSchema); this._onDidDebuggersExtPointRead.fire(); }); @@ -211,17 +223,21 @@ export class AdapterManager implements IAdapterManager { return !!this.debuggers.find(a => language && a.languages && a.languages.indexOf(language) >= 0); } - async guessDebugger(type?: string): Promise { + async guessDebugger(gettingConfigurations: boolean, type?: string): Promise { if (type) { const adapter = this.getDebugger(type); return Promise.resolve(adapter); } const activeTextEditorControl = this.editorService.activeTextEditorControl; - let candidates: Debugger[] | undefined; + let candidates: Debugger[] = []; + let languageLabel: string | null = null; if (isCodeEditor(activeTextEditorControl)) { const model = activeTextEditorControl.getModel(); const language = model ? model.getLanguageIdentifier().language : undefined; + if (language) { + languageLabel = this.modeService.getLanguageName(language); + } const adapters = this.debuggers.filter(a => language && a.languages && a.languages.indexOf(language) >= 0); if (adapters.length === 1) { return adapters[0]; @@ -231,20 +247,38 @@ export class AdapterManager implements IAdapterManager { } } - if (!candidates) { + if (gettingConfigurations && candidates.length === 0) { await this.activateDebuggers('onDebugInitialConfigurations'); candidates = this.debuggers.filter(dbg => dbg.hasInitialConfiguration() || dbg.hasConfigurationProvider()); } candidates.sort((first, second) => first.label.localeCompare(second.label)); - const picks = candidates.map(c => ({ label: c.label, debugger: c })); - return this.quickInputService.pick<{ label: string, debugger: Debugger | undefined }>([...picks, { type: 'separator' }, { label: nls.localize('more', "More..."), debugger: undefined }], { placeHolder: nls.localize('selectDebug', "Select Environment") }) + const picks: { label: string, debugger?: Debugger, type?: string }[] = candidates.map(c => ({ label: c.label, debugger: c })); + + if (picks.length === 0 && languageLabel) { + if (languageLabel.indexOf(' ') >= 0) { + languageLabel = `'${languageLabel}'`; + } + const message = nls.localize('CouldNotFindLanguage', "You don't have an extension for debugging {0}. Should we find a {0} extension in the Marketplace?", languageLabel); + const buttonLabel = nls.localize('findExtension', "Find {0} extension", languageLabel); + const showResult = await this.dialogService.show(Severity.Warning, message, [buttonLabel, nls.localize('cancel', "Cancel")], { cancelId: 1 }); + if (showResult.choice === 0) { + await this.commandService.executeCommand('debug.installAdditionalDebuggers', languageLabel); + } + return undefined; + } + + picks.push({ type: 'separator', label: '' }); + const placeHolder = nls.localize('selectDebug', "Select environment"); + + picks.push({ label: languageLabel ? nls.localize('installLanguage', "Install an extension for {0}...", languageLabel) : nls.localize('installExt', "Install extension...") }); + return this.quickInputService.pick<{ label: string, debugger?: Debugger }>(picks, { activeItem: picks[0], placeHolder }) .then(picked => { if (picked && picked.debugger) { return picked.debugger; } if (picked) { - this.commandService.executeCommand('debug.installAdditionalDebuggers'); + this.commandService.executeCommand('debug.installAdditionalDebuggers', languageLabel); } return undefined; }); diff --git a/src/vs/workbench/contrib/debug/browser/debugCommands.ts b/src/vs/workbench/contrib/debug/browser/debugCommands.ts index 4395af94..bc274a4f 100644 --- a/src/vs/workbench/contrib/debug/browser/debugCommands.ts +++ b/src/vs/workbench/contrib/debug/browser/debugCommands.ts @@ -8,7 +8,6 @@ import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { List } from 'vs/base/browser/ui/list/listWidget'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { IListService } from 'vs/platform/list/browser/listService'; -import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IDebugService, IEnablement, CONTEXT_BREAKPOINTS_FOCUSED, CONTEXT_WATCH_EXPRESSIONS_FOCUSED, CONTEXT_VARIABLES_FOCUSED, EDITOR_CONTRIBUTION_ID, IDebugEditorContribution, CONTEXT_IN_DEBUG_MODE, CONTEXT_EXPRESSION_SELECTED, IConfig, IStackFrame, IThread, IDebugSession, CONTEXT_DEBUG_STATE, IDebugConfiguration, CONTEXT_JUMP_TO_CURSOR_SUPPORTED, REPL_VIEW_ID, CONTEXT_DEBUGGERS_AVAILABLE, State, getStateLabel, CONTEXT_BREAKPOINT_INPUT_FOCUSED, CONTEXT_FOCUSED_SESSION_IS_ATTACH } from 'vs/workbench/contrib/debug/common/debug'; import { Expression, Variable, Breakpoint, FunctionBreakpoint, DataBreakpoint } from 'vs/workbench/contrib/debug/common/debugModel'; import { IExtensionsViewPaneContainer, VIEWLET_ID as EXTENSIONS_VIEWLET_ID } from 'vs/workbench/contrib/extensions/common/extensions'; @@ -220,6 +219,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ when: CONTEXT_IN_DEBUG_MODE, handler: async (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => { const debugService = accessor.get(IDebugService); + const configurationService = accessor.get(IConfigurationService); let session: IDebugSession | undefined; if (isSessionContext(context)) { session = debugService.getModel().getSession(context.sessionId); @@ -231,6 +231,11 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ const { launch, name } = debugService.getConfigurationManager().selectedConfiguration; await debugService.startDebugging(launch, name, { noDebug: false }); } else { + const showSubSessions = configurationService.getValue('debug').showSubSessionsInToolBar; + // Stop should be sent to the root parent session + while (!showSubSessions && session && session.parentSession) { + session = session.parentSession; + } session.removeReplExpressions(); await debugService.restartSession(session); } @@ -278,7 +283,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ } }); -async function stopHandler(accessor: ServicesAccessor, _: string, context: CallStackContext | unknown): Promise { +async function stopHandler(accessor: ServicesAccessor, _: string, context: CallStackContext | unknown, disconnect: boolean): Promise { const debugService = accessor.get(IDebugService); let session: IDebugSession | undefined; if (isSessionContext(context)) { @@ -294,7 +299,7 @@ async function stopHandler(accessor: ServicesAccessor, _: string, context: CallS session = session.parentSession; } - await debugService.stopSession(session); + await debugService.stopSession(session, disconnect); } KeybindingsRegistry.registerCommandAndKeybindingRule({ @@ -302,7 +307,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.Shift | KeyCode.F5, when: ContextKeyExpr.and(CONTEXT_FOCUSED_SESSION_IS_ATTACH, CONTEXT_IN_DEBUG_MODE), - handler: stopHandler + handler: (accessor, _, context) => stopHandler(accessor, _, context, true) }); KeybindingsRegistry.registerCommandAndKeybindingRule({ @@ -310,7 +315,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.Shift | KeyCode.F5, when: ContextKeyExpr.and(CONTEXT_FOCUSED_SESSION_IS_ATTACH.toNegated(), CONTEXT_IN_DEBUG_MODE), - handler: stopHandler + handler: (accessor, _, context) => stopHandler(accessor, _, context, false) }); CommandsRegistry.registerCommand({ @@ -385,12 +390,12 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ weight: KeybindingWeight.WorkbenchContrib, primary: KeyCode.F5, when: ContextKeyExpr.and(CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_DEBUG_STATE.notEqualsTo(getStateLabel(State.Initializing))), - handler: async (accessor: ServicesAccessor, debugStartOptions: { config?: Partial; noDebug?: boolean } = {}) => { + handler: async (accessor: ServicesAccessor, debugStartOptions?: { config?: Partial; noDebug?: boolean }) => { const debugService = accessor.get(IDebugService); let { launch, name, getConfig } = debugService.getConfigurationManager().selectedConfiguration; const config = await getConfig(); - const configOrName = config ? Object.assign(deepClone(config), debugStartOptions.config) : name; - await debugService.startDebugging(launch, configOrName, { noDebug: debugStartOptions.noDebug }); + const configOrName = config ? Object.assign(deepClone(config), debugStartOptions?.config) : name; + await debugService.startDebugging(launch, configOrName, { noDebug: debugStartOptions?.noDebug }); } }); @@ -552,10 +557,14 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ weight: KeybindingWeight.WorkbenchContrib, when: undefined, primary: undefined, - handler: async (accessor) => { + handler: async (accessor, query: string) => { const viewletService = accessor.get(IViewletService); const viewlet = (await viewletService.openViewlet(EXTENSIONS_VIEWLET_ID, true))?.getViewPaneContainer() as IExtensionsViewPaneContainer; - viewlet.search('tag:debuggers @sort:installs'); + let searchFor = `@category:debuggers`; + if (typeof query === 'string') { + searchFor += ` ${query}`; + } + viewlet.search(searchFor); viewlet.focus(); } }); @@ -567,10 +576,6 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ primary: undefined, handler: async (accessor, launchUri: string) => { const manager = accessor.get(IDebugService).getConfigurationManager(); - if (accessor.get(IWorkspaceContextService).getWorkbenchState() === WorkbenchState.EMPTY) { - accessor.get(INotificationService).info(nls.localize('noFolderDebugConfig', "Please first open a folder in order to do advanced debug configuration.")); - return; - } const launch = manager.getLaunches().find(l => l.uri.toString() === launchUri) || manager.selectedConfiguration.launch; if (launch) { diff --git a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts index 73ad3d56..0fb7fab9 100644 --- a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts +++ b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts @@ -83,10 +83,11 @@ export class ConfigurationManager implements IConfigurationManager { const previousSelectedLaunch = this.launches.find(l => l.uri.toString() === previousSelectedRoot); const previousSelectedName = this.storageService.get(DEBUG_SELECTED_CONFIG_NAME_KEY, StorageScope.WORKSPACE); this.debugConfigurationTypeContext = CONTEXT_DEBUG_CONFIGURATION_TYPE.bindTo(contextKeyService); + const dynamicConfig = previousSelectedType ? { type: previousSelectedType } : undefined; if (previousSelectedLaunch && previousSelectedLaunch.getConfigurationNames().length) { - this.selectConfiguration(previousSelectedLaunch, previousSelectedName, undefined, { type: previousSelectedType }); + this.selectConfiguration(previousSelectedLaunch, previousSelectedName, undefined, dynamicConfig); } else if (this.launches.length > 0) { - this.selectConfiguration(undefined, previousSelectedName, undefined, { type: previousSelectedType }); + this.selectConfiguration(undefined, previousSelectedName, undefined, dynamicConfig); } } @@ -382,7 +383,7 @@ export class ConfigurationManager implements IConfigurationManager { const providers = (await this.getDynamicProviders()).filter(p => p.type === type); this.getSelectedConfig = async () => { const activatedProviders = await Promise.all(providers.map(p => p.getProvider())); - const provider = activatedProviders.find(p => p && p.type === type); + const provider = activatedProviders.length > 0 ? activatedProviders[0] : undefined; if (provider && launch && launch.workspace) { const token = new CancellationTokenSource(); const dynamicConfigs = await provider.provideDebugConfigurations!(launch.workspace.uri, token.token); @@ -416,7 +417,9 @@ export class ConfigurationManager implements IConfigurationManager { } this.selectedType = dynamicConfig?.type || config?.type; - this.storageService.store(DEBUG_SELECTED_TYPE, this.selectedType, StorageScope.WORKSPACE, StorageTarget.MACHINE); + // Only store the selected type if we are having a dynamic configuration. Otherwise restoring this configuration from storage might be misindentified as a dynamic configuration + this.storageService.store(DEBUG_SELECTED_TYPE, dynamicConfig ? this.selectedType : undefined, StorageScope.WORKSPACE, StorageTarget.MACHINE); + if (type) { this.debugConfigurationTypeContext.set(type); } else { @@ -513,7 +516,7 @@ abstract class AbstractLaunch { async getInitialConfigurationContent(folderUri?: uri, type?: string, token?: CancellationToken): Promise { let content = ''; - const adapter = await this.adapterManager.guessDebugger(type); + const adapter = await this.adapterManager.guessDebugger(true, type); if (adapter) { const initialConfigs = await this.configurationManager.provideDebugConfigurations(folderUri, adapter.type, token || CancellationToken.None); content = await adapter.getInitialConfigurationContent(initialConfigs); @@ -685,7 +688,7 @@ class UserLaunch extends AbstractLaunch implements ILaunch { return nls.localize('user settings', "user settings"); } - get hidden(): boolean { + override get hidden(): boolean { return true; } diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts index 849505bb..c4a86e5c 100644 --- a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts @@ -237,6 +237,7 @@ export class DebugEditorContribution implements IDebugEditorContribution { DebugEditorContribution.MEMOIZER.clear(); this.updateInlineValuesScheduler.schedule(); })); + this.toDispose.push(this.debugService.getViewModel().onWillUpdateViews(() => this.updateInlineValuesScheduler.schedule())); this.toDispose.push(this.editor.onDidChangeModel(async () => { const stackFrame = this.debugService.getViewModel().focusedStackFrame; const model = this.editor.getModel(); @@ -249,7 +250,15 @@ export class DebugEditorContribution implements IDebugEditorContribution { DebugEditorContribution.MEMOIZER.clear(); await this.updateInlineValueDecorations(stackFrame); })); - this.toDispose.push(this.editor.onDidScrollChange(() => this.hideHoverWidget)); + this.toDispose.push(this.editor.onDidScrollChange(() => { + this.hideHoverWidget(); + + // Inline value provider should get called on view port change + const model = this.editor.getModel(); + if (model && InlineValuesProviderRegistry.has(model)) { + this.updateInlineValuesScheduler.schedule(); + } + })); this.toDispose.push(this.debugService.onDidChangeState((state: State) => { if (state !== State.Stopped) { this.toggleExceptionWidget(); @@ -575,8 +584,9 @@ export class DebugEditorContribution implements IDebugEditorContribution { const separator = ', '; const model = this.editor.getModel(); - if (!this.configurationService.getValue('debug').inlineValues || - !model || !stackFrame || model.uri.toString() !== stackFrame.source.uri.toString()) { + const inlineValuesSetting = this.configurationService.getValue('debug').inlineValues; + const inlineValuesTurnedOn = inlineValuesSetting === true || (inlineValuesSetting === 'auto' && model && InlineValuesProviderRegistry.has(model)); + if (!inlineValuesTurnedOn || !model || !stackFrame || model.uri.toString() !== stackFrame.source.uri.toString()) { if (!this.removeInlineValuesScheduler.isScheduled()) { this.removeInlineValuesScheduler.schedule(); } diff --git a/src/vs/workbench/contrib/debug/browser/debugService.ts b/src/vs/workbench/contrib/debug/browser/debugService.ts index 25a28ac5..0a8d34ec 100644 --- a/src/vs/workbench/contrib/debug/browser/debugService.ts +++ b/src/vs/workbench/contrib/debug/browser/debugService.ts @@ -48,6 +48,7 @@ import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { AdapterManager } from 'vs/workbench/contrib/debug/browser/debugAdapterManager'; import { ITextModel } from 'vs/editor/common/model'; import { DEBUG_CONFIGURE_COMMAND_ID, DEBUG_CONFIGURE_LABEL } from 'vs/workbench/contrib/debug/browser/debugCommands'; +import { IWorkspaceTrustRequestService } from 'vs/platform/workspace/common/workspaceTrust'; export class DebugService implements IDebugService { declare readonly _serviceBrand: undefined; @@ -93,7 +94,8 @@ export class DebugService implements IDebugService { @IExtensionHostDebugService private readonly extensionHostDebugService: IExtensionHostDebugService, @IActivityService private readonly activityService: IActivityService, @ICommandService private readonly commandService: ICommandService, - @IQuickInputService private readonly quickInputService: IQuickInputService + @IQuickInputService private readonly quickInputService: IQuickInputService, + @IWorkspaceTrustRequestService private readonly workspaceTrustRequestService: IWorkspaceTrustRequestService ) { this.toDispose = []; @@ -127,7 +129,7 @@ export class DebugService implements IDebugService { this.taskRunner = this.instantiationService.createInstance(DebugTaskRunner); this.toDispose.push(this.fileService.onDidFilesChange(e => this.onFileChanges(e))); - this.toDispose.push(this.lifecycleService.onShutdown(this.dispose, this)); + this.toDispose.push(this.lifecycleService.onDidShutdown(this.dispose, this)); this.toDispose.push(this.extensionHostDebugService.onAttachSession(event => { const session = this.model.getSession(event.sessionId, true); @@ -273,7 +275,15 @@ export class DebugService implements IDebugService { * properly manages compounds, checks for errors and handles the initializing state. */ async startDebugging(launch: ILaunch | undefined, configOrName?: IConfig | string, options?: IDebugSessionOptions): Promise { + const message = options && options.noDebug ? nls.localize('runTrust', "Running executes build tasks and program code from your workspace.") : nls.localize('debugTrust', "Debugging executes build tasks and program code from your workspace."); + const trust = await this.workspaceTrustRequestService.requestWorkspaceTrust({ + modal: true, + message, + }); + if (!trust) { + return false; + } this.startInitializingState(); try { // make sure to save all files and that the configuration is up to date @@ -397,7 +407,7 @@ export class DebugService implements IDebugService { const unresolvedConfig = deepClone(config); if (!type) { - const guess = await this.adapterManager.guessDebugger(); + const guess = await this.adapterManager.guessDebugger(false); if (guess) { type = guess.type; } @@ -455,7 +465,7 @@ export class DebugService implements IDebugService { nls.localize({ key: 'installAdditionalDebuggers', comment: ['Placeholder is the debug type, so for example "node", "python"'] }, "Install {0} Extension", resolvedConfig.type), undefined, true, - async () => this.commandService.executeCommand('debug.installAdditionalDebuggers') + async () => this.commandService.executeCommand('debug.installAdditionalDebuggers', resolvedConfig?.type) )); await this.showError(message, actionList); @@ -501,7 +511,7 @@ export class DebugService implements IDebugService { const openDebug = this.configurationService.getValue('debug').openDebug; // Open debug viewlet based on the visibility of the side bar and openDebug setting. Do not open for 'run without debug' - if (!configuration.resolved.noDebug && (openDebug === 'openOnSessionStart' || (openDebug === 'openOnFirstSessionStart' && this.viewModel.firstSessionStart))) { + if (!configuration.resolved.noDebug && (openDebug === 'openOnSessionStart' || (openDebug !== 'neverOpen' && this.viewModel.firstSessionStart))) { await this.viewletService.openViewlet(VIEWLET_ID); } @@ -665,6 +675,39 @@ export class DebugService implements IDebugService { return; } + // Read the configuration again if a launch.json has been changed, if not just use the inmemory configuration + let needsToSubstitute = false; + let unresolved: IConfig | undefined; + const launch = session.root ? this.configurationManager.getLaunch(session.root.uri) : undefined; + if (launch) { + unresolved = launch.getConfiguration(session.configuration.name); + if (unresolved && !equals(unresolved, session.unresolvedConfiguration)) { + // Take the type from the session since the debug extension might overwrite it #21316 + unresolved.type = session.configuration.type; + unresolved.noDebug = session.configuration.noDebug; + needsToSubstitute = true; + } + } + + let resolved: IConfig | undefined | null = session.configuration; + if (launch && needsToSubstitute && unresolved) { + const initCancellationToken = new CancellationTokenSource(); + this.sessionCancellationTokens.set(session.getId(), initCancellationToken); + const resolvedByProviders = await this.configurationManager.resolveConfigurationByProviders(launch.workspace ? launch.workspace.uri : undefined, unresolved.type, unresolved, initCancellationToken.token); + if (resolvedByProviders) { + resolved = await this.substituteVariables(launch, resolvedByProviders); + if (resolved && !initCancellationToken.token.isCancellationRequested) { + resolved = await this.configurationManager.resolveDebugConfigurationWithSubstitutedVariables(launch && launch.workspace ? launch.workspace.uri : undefined, unresolved.type, resolved, initCancellationToken.token); + } + } else { + resolved = resolvedByProviders; + } + } + if (resolved) { + session.setConfiguration({ resolved, unresolved }); + } + session.configuration.__restart = restartData; + if (session.capabilities.supportsRestartRequest) { const taskResult = await runTasks(); if (taskResult === TaskRunResult.Success) { @@ -689,42 +732,10 @@ export class DebugService implements IDebugService { return; } - // Read the configuration again if a launch.json has been changed, if not just use the inmemory configuration - let needsToSubstitute = false; - let unresolved: IConfig | undefined; - const launch = session.root ? this.configurationManager.getLaunch(session.root.uri) : undefined; - if (launch) { - unresolved = launch.getConfiguration(session.configuration.name); - if (unresolved && !equals(unresolved, session.unresolvedConfiguration)) { - // Take the type from the session since the debug extension might overwrite it #21316 - unresolved.type = session.configuration.type; - unresolved.noDebug = session.configuration.noDebug; - needsToSubstitute = true; - } - } - - let resolved: IConfig | undefined | null = session.configuration; - if (launch && needsToSubstitute && unresolved) { - const initCancellationToken = new CancellationTokenSource(); - this.sessionCancellationTokens.set(session.getId(), initCancellationToken); - const resolvedByProviders = await this.configurationManager.resolveConfigurationByProviders(launch.workspace ? launch.workspace.uri : undefined, unresolved.type, unresolved, initCancellationToken.token); - if (resolvedByProviders) { - resolved = await this.substituteVariables(launch, resolvedByProviders); - if (resolved && !initCancellationToken.token.isCancellationRequested) { - resolved = await this.configurationManager.resolveDebugConfigurationWithSubstitutedVariables(launch && launch.workspace ? launch.workspace.uri : undefined, unresolved.type, resolved, initCancellationToken.token); - } - } else { - resolved = resolvedByProviders; - } - } - if (!resolved) { return c(undefined); } - session.setConfiguration({ resolved, unresolved }); - session.configuration.__restart = restartData; - try { await this.launchOrAttachToSession(session, shouldFocus); this._onDidNewSession.fire(session); @@ -736,9 +747,9 @@ export class DebugService implements IDebugService { }); } - async stopSession(session: IDebugSession | undefined): Promise { + async stopSession(session: IDebugSession | undefined, disconnect = false): Promise { if (session) { - return session.terminate(); + return disconnect ? session.disconnect() : session.terminate(); } const sessions = this.model.getSessions(); @@ -750,7 +761,7 @@ export class DebugService implements IDebugService { this.cancelTokens(undefined); } - return Promise.all(sessions.map(s => s.terminate())); + return Promise.all(sessions.map(s => disconnect ? s.disconnect() : s.terminate())); } private async substituteVariables(launch: ILaunch | undefined, config: IConfig): Promise { @@ -780,10 +791,8 @@ export class DebugService implements IDebugService { const actions = [...errorActions, configureAction]; const { choice } = await this.dialogService.show(severity.Error, message, actions.map(a => a.label).concat(nls.localize('cancel', "Cancel")), { cancelId: actions.length }); if (choice < actions.length) { - return actions[choice].run(); + await actions[choice].run(); } - - return undefined; } //---- focus management diff --git a/src/vs/workbench/contrib/debug/browser/debugSession.ts b/src/vs/workbench/contrib/debug/browser/debugSession.ts index 658d8e77..504d9c7b 100644 --- a/src/vs/workbench/contrib/debug/browser/debugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/debugSession.ts @@ -21,13 +21,11 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { RunOnceScheduler, Queue } from 'vs/base/common/async'; import { generateUuid } from 'vs/base/common/uuid'; import { IHostService } from 'vs/workbench/services/host/browser/host'; -import { IExtensionHostDebugService } from 'vs/platform/debug/common/extensionHostDebug'; import { ICustomEndpointTelemetryService, ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { normalizeDriveLetter } from 'vs/base/common/labels'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { ReplModel } from 'vs/workbench/contrib/debug/common/replModel'; -import { IOpenerService } from 'vs/platform/opener/common/opener'; import { CancellationTokenSource, CancellationToken } from 'vs/base/common/cancellation'; import { distinct } from 'vs/base/common/arrays'; import { INotificationService } from 'vs/platform/notification/common/notification'; @@ -37,6 +35,7 @@ import { canceled } from 'vs/base/common/errors'; import { filterExceptionsFromTelemetry } from 'vs/workbench/contrib/debug/common/debugUtils'; import { DebugCompoundRoot } from 'vs/workbench/contrib/debug/common/debugCompoundRoot'; import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; export class DebugSession implements IDebugSession { @@ -80,11 +79,10 @@ export class DebugSession implements IDebugSession { @IViewletService private readonly viewletService: IViewletService, @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, @IProductService private readonly productService: IProductService, - @IExtensionHostDebugService private readonly extensionHostDebugService: IExtensionHostDebugService, - @IOpenerService private readonly openerService: IOpenerService, @INotificationService private readonly notificationService: INotificationService, @ILifecycleService lifecycleService: ILifecycleService, @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, + @IInstantiationService private readonly instantiationService: IInstantiationService, @ICustomEndpointTelemetryService private readonly customEndpointTelemetryService: ICustomEndpointTelemetryService ) { this._options = options || {}; @@ -97,7 +95,7 @@ export class DebugSession implements IDebugSession { const toDispose: IDisposable[] = []; toDispose.push(this.repl.onDidChangeElements(() => this._onDidChangeREPLElements.fire())); if (lifecycleService) { - toDispose.push(lifecycleService.onShutdown(() => { + toDispose.push(lifecycleService.onDidShutdown(() => { this.shutdown(); dispose(toDispose); })); @@ -235,7 +233,7 @@ export class DebugSession implements IDebugSession { try { const debugAdapter = await dbgr.createDebugAdapter(this); - this.raw = new RawDebugSession(debugAdapter, dbgr, this.id, this.telemetryService, this.customEndpointTelemetryService, this.extensionHostDebugService, this.openerService, this.notificationService); + this.raw = this.instantiationService.createInstance(RawDebugSession, debugAdapter, dbgr, this.id); await this.raw.start(); this.registerListeners(); @@ -287,7 +285,7 @@ export class DebugSession implements IDebugSession { } /** - * end the current debug adapter session + * terminate the current debug adapter session */ async terminate(restart = false): Promise { if (!this.raw) { @@ -300,7 +298,7 @@ export class DebugSession implements IDebugSession { if (this.raw.capabilities.supportsTerminateRequest && this._configuration.resolved.request === 'launch') { await this.raw.terminate(restart); } else { - await this.raw.disconnect(restart); + await this.raw.disconnect({ restart, terminateDebuggee: true }); } } @@ -320,7 +318,7 @@ export class DebugSession implements IDebugSession { this.cancelAllRequests(); if (this.raw) { - await this.raw.disconnect(restart); + await this.raw.disconnect({ restart, terminateDebuggee: false }); } if (!restart) { @@ -337,7 +335,7 @@ export class DebugSession implements IDebugSession { } this.cancelAllRequests(); - await this.raw.restart(); + await this.raw.restart({ arguments: this.configuration }); } async sendBreakpoints(modelUri: URI, breakpointsToSend: IBreakpoint[], sourceModified: boolean): Promise { @@ -408,7 +406,15 @@ export class DebugSession implements IDebugSession { }) } : { filters: exbpts.map(exb => exb.filter) }; - await this.raw.setExceptionBreakpoints(args); + const response = await this.raw.setExceptionBreakpoints(args); + if (response && response.body && response.body.breakpoints) { + const data = new Map(); + for (let i = 0; i < exbpts.length; i++) { + data.set(exbpts[i].getId(), response.body.breakpoints[i]); + } + + this.model.setBreakpointSessionData(this.getId(), this.capabilities, data); + } } } @@ -797,7 +803,7 @@ export class DebugSession implements IDebugSession { // Disconnect the debug session on configuration done error #10596 this.notificationService.error(e); if (this.raw) { - this.raw.disconnect(); + this.raw.disconnect({}); } } } @@ -877,7 +883,7 @@ export class DebugSession implements IDebugSession { if (event.body && event.body.restart) { await this.debugService.restartSession(this, event.body.restart); } else if (this.raw) { - await this.raw.disconnect(); + await this.raw.disconnect({ terminateDebuggee: false }); } })); @@ -961,6 +967,8 @@ export class DebugSession implements IDebugSession { const id = event.body && event.body.breakpoint ? event.body.breakpoint.id : undefined; const breakpoint = this.model.getBreakpoints().find(bp => bp.getIdFromAdapter(this.getId()) === id); const functionBreakpoint = this.model.getFunctionBreakpoints().find(bp => bp.getIdFromAdapter(this.getId()) === id); + const dataBreakpoint = this.model.getDataBreakpoints().find(dbp => dbp.getIdFromAdapter(this.getId()) === id); + const exceptionBreakpoint = this.model.getExceptionBreakpoints().find(excbp => excbp.getIdFromAdapter(this.getId()) === id); if (event.body.reason === 'new' && event.body.breakpoint.source && event.body.breakpoint.line) { const source = this.getSource(event.body.breakpoint.source); @@ -982,6 +990,9 @@ export class DebugSession implements IDebugSession { if (functionBreakpoint) { this.model.removeFunctionBreakpoints(functionBreakpoint.getId()); } + if (dataBreakpoint) { + this.model.removeDataBreakpoints(dataBreakpoint.getId()); + } } if (event.body.reason === 'changed') { @@ -996,6 +1007,14 @@ export class DebugSession implements IDebugSession { const data = new Map([[functionBreakpoint.getId(), event.body.breakpoint]]); this.model.setBreakpointSessionData(this.getId(), this.capabilities, data); } + if (dataBreakpoint) { + const data = new Map([[dataBreakpoint.getId(), event.body.breakpoint]]); + this.model.setBreakpointSessionData(this.getId(), this.capabilities, data); + } + if (exceptionBreakpoint) { + const data = new Map([[exceptionBreakpoint.getId(), event.body.breakpoint]]); + this.model.setBreakpointSessionData(this.getId(), this.capabilities, data); + } } })); @@ -1047,7 +1066,7 @@ export class DebugSession implements IDebugSession { private shutdown(): void { dispose(this.rawListeners); if (this.raw) { - this.raw.disconnect(); + this.raw.disconnect({}); this.raw.dispose(); this.raw = undefined; } diff --git a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts index 1445c154..eef15c3a 100644 --- a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts +++ b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts @@ -25,7 +25,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { RunOnceScheduler } from 'vs/base/common/async'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { createActionViewItem, createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; -import { IMenu, IMenuService, MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; +import { ICommandAction, IMenu, IMenuService, MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; import { IContextKeyService, ContextKeyExpression, ContextKeyExpr, ContextKeyEqualsExpr } from 'vs/platform/contextkey/common/contextkey'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import * as icons from 'vs/workbench/contrib/debug/browser/debugIcons'; @@ -126,9 +126,7 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { } // log in telemetry - if (this.telemetryService) { - this.telemetryService.publicLog2('workbenchActionExecuted', { id: e.action.id, from: 'debugActionsWidget' }); - } + this.telemetryService.publicLog2('workbenchActionExecuted', { id: e.action.id, from: 'debugActionsWidget' }); })); this._register(dom.addDisposableListener(window, dom.EventType.RESIZE, () => this.setCoordinates())); @@ -174,7 +172,7 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { } } - protected updateStyles(): void { + protected override updateStyles(): void { super.updateStyles(); if (this.$el) { @@ -245,7 +243,7 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { dom.hide(this.$el); } - dispose(): void { + override dispose(): void { super.dispose(); if (this.$el) { @@ -259,7 +257,7 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { // Debug toolbar -const registerDebugToolBarItem = (id: string, title: string, order: number, icon?: { light?: URI, dark?: URI } | ThemeIcon, when?: ContextKeyExpression, precondition?: ContextKeyExpression) => { +const registerDebugToolBarItem = (id: string, title: string, order: number, icon?: { light?: URI, dark?: URI } | ThemeIcon, when?: ContextKeyExpression, precondition?: ContextKeyExpression, alt?: ICommandAction) => { MenuRegistry.appendMenuItem(MenuId.DebugToolBar, { group: 'navigation', when, @@ -269,7 +267,8 @@ const registerDebugToolBarItem = (id: string, title: string, order: number, icon title, icon, precondition - } + }, + alt }); // Register actions in debug viewlet when toolbar is docked @@ -288,8 +287,8 @@ const registerDebugToolBarItem = (id: string, title: string, order: number, icon registerDebugToolBarItem(CONTINUE_ID, CONTINUE_LABEL, 10, icons.debugContinue, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); registerDebugToolBarItem(PAUSE_ID, PAUSE_LABEL, 10, icons.debugPause, CONTEXT_DEBUG_STATE.notEqualsTo('stopped'), CONTEXT_DEBUG_STATE.isEqualTo('running')); -registerDebugToolBarItem(STOP_ID, STOP_LABEL, 70, icons.debugStop, CONTEXT_FOCUSED_SESSION_IS_ATTACH.toNegated()); -registerDebugToolBarItem(DISCONNECT_ID, DISCONNECT_LABEL, 70, icons.debugDisconnect, CONTEXT_FOCUSED_SESSION_IS_ATTACH); +registerDebugToolBarItem(STOP_ID, STOP_LABEL, 70, icons.debugStop, CONTEXT_FOCUSED_SESSION_IS_ATTACH.toNegated(), undefined, { id: DISCONNECT_ID, title: DISCONNECT_LABEL, icon: icons.debugDisconnect }); +registerDebugToolBarItem(DISCONNECT_ID, DISCONNECT_LABEL, 70, icons.debugDisconnect, CONTEXT_FOCUSED_SESSION_IS_ATTACH, undefined, { id: STOP_ID, title: STOP_LABEL, icon: icons.debugStop }); registerDebugToolBarItem(STEP_OVER_ID, STEP_OVER_LABEL, 20, icons.debugStepOver, undefined, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); registerDebugToolBarItem(STEP_INTO_ID, STEP_INTO_LABEL, 30, icons.debugStepInto, undefined, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); registerDebugToolBarItem(STEP_OUT_ID, STEP_OUT_LABEL, 40, icons.debugStepOut, undefined, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); diff --git a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts index d1c14273..deb151b1 100644 --- a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts +++ b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts @@ -74,12 +74,12 @@ export class DebugViewPaneContainer extends ViewPaneContainer { })); } - create(parent: HTMLElement): void { + override create(parent: HTMLElement): void { super.create(parent); parent.classList.add('debug-viewlet'); } - focus(): void { + override focus(): void { super.focus(); if (this.startDebugActionViewItem) { @@ -89,7 +89,7 @@ export class DebugViewPaneContainer extends ViewPaneContainer { } } - getActionViewItem(action: IAction): IActionViewItem | undefined { + override getActionViewItem(action: IAction): IActionViewItem | undefined { if (action.id === DEBUG_START_COMMAND_ID) { this.startDebugActionViewItem = this.instantiationService.createInstance(StartDebugActionViewItem, null, action); return this.startDebugActionViewItem; @@ -120,7 +120,7 @@ export class DebugViewPaneContainer extends ViewPaneContainer { } } - addPanes(panes: { pane: ViewPane, size: number, index?: number }[]): void { + override addPanes(panes: { pane: ViewPane, size: number, index?: number }[]): void { super.addPanes(panes); for (const { pane: pane } of panes) { @@ -134,7 +134,7 @@ export class DebugViewPaneContainer extends ViewPaneContainer { } } - removePanes(panes: ViewPane[]): void { + override removePanes(panes: ViewPane[]): void { super.removePanes(panes); for (const pane of panes) { dispose(this.paneListeners.get(pane.id)); diff --git a/src/vs/workbench/contrib/debug/browser/exceptionWidget.ts b/src/vs/workbench/contrib/debug/browser/exceptionWidget.ts index 67a12c48..430143af 100644 --- a/src/vs/workbench/contrib/debug/browser/exceptionWidget.ts +++ b/src/vs/workbench/contrib/debug/browser/exceptionWidget.ts @@ -57,7 +57,7 @@ export class ExceptionWidget extends ZoneWidget { }); // style() will trigger _applyStyles } - protected _applyStyles(): void { + protected override _applyStyles(): void { if (this.container) { this.container.style.backgroundColor = this.backgroundColor ? this.backgroundColor.toString() : ''; } @@ -105,7 +105,7 @@ export class ExceptionWidget extends ZoneWidget { container.setAttribute('aria-label', ariaLabel); } - protected _doLayout(_heightInPixel: number | undefined, _widthInPixel: number | undefined): void { + protected override _doLayout(_heightInPixel: number | undefined, _widthInPixel: number | undefined): void { // Reload the height with respect to the exception text content and relayout it to match the line count. this.container!.style.height = 'initial'; diff --git a/src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts b/src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts index 52cca682..d80eff59 100644 --- a/src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts +++ b/src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts @@ -6,13 +6,12 @@ import { ExtensionHostDebugChannelClient, ExtensionHostDebugBroadcastChannel } from 'vs/platform/debug/common/extensionHostDebugIpc'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IExtensionHostDebugService, IOpenExtensionWindowResult } from 'vs/platform/debug/common/extensionHostDebug'; +import { IExtensionHostDebugService, INullableProcessEnvironment, IOpenExtensionWindowResult } from 'vs/platform/debug/common/extensionHostDebug'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { Event } from 'vs/base/common/event'; import { URI, UriComponents } from 'vs/base/common/uri'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IWorkspaceProvider, IWorkspace } from 'vs/workbench/services/host/browser/browserHostService'; -import { IProcessEnvironment } from 'vs/base/common/platform'; import { hasWorkspaceFileExtension, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier, toWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { ILogService } from 'vs/platform/log/common/log'; import { IHostService } from 'vs/workbench/services/host/browser/host'; @@ -56,7 +55,7 @@ class BrowserExtensionHostDebugService extends ExtensionHostDebugChannelClient i if (environmentService.options && environmentService.options.workspaceProvider) { this.workspaceProvider = environmentService.options.workspaceProvider; } else { - this.workspaceProvider = { open: async () => undefined, workspace: undefined, trusted: undefined }; + this.workspaceProvider = { open: async () => true, workspace: undefined, trusted: undefined }; logService.warn('Extension Host Debugging not available due to missing workspace provider.'); } @@ -87,7 +86,7 @@ class BrowserExtensionHostDebugService extends ExtensionHostDebugChannelClient i } } - async openExtensionDevelopmentHostWindow(args: string[], env: IProcessEnvironment): Promise { + override async openExtensionDevelopmentHostWindow(args: string[], _env: INullableProcessEnvironment | undefined, _debugRenderer: boolean): Promise { // Add environment parameters required for debug to work const environment = new Map(); @@ -162,12 +161,12 @@ class BrowserExtensionHostDebugService extends ExtensionHostDebugChannelClient i } // Open debug window as new window. Pass arguments over. - await this.workspaceProvider.open(debugWorkspace, { + const success = await this.workspaceProvider.open(debugWorkspace, { reuse: false, // debugging always requires a new window payload: Array.from(environment.entries()) // mandatory properties to enable debugging }); - return {}; + return { success }; } private findArgument(key: string, args: string[]): string | undefined { diff --git a/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts b/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts index 6ebf580c..458e6f16 100644 --- a/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts +++ b/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts @@ -271,23 +271,23 @@ class SessionTreeItem extends BaseTreeItem { this._session = session; } - getInternalId(): string { + override getInternalId(): string { return this._session.getId(); } - getSession(): IDebugSession { + override getSession(): IDebugSession { return this._session; } - getHoverLabel(): string | undefined { + override getHoverLabel(): string | undefined { return undefined; } - hasChildren(): boolean { + override hasChildren(): boolean { return true; } - protected compare(a: BaseTreeItem, b: BaseTreeItem): number { + protected override compare(a: BaseTreeItem, b: BaseTreeItem): number { const acat = this.category(a); const bcat = this.category(b); if (acat !== bcat) { @@ -426,7 +426,7 @@ export class LoadedScriptsView extends ViewPane { @IViewDescriptorService viewDescriptorService: IViewDescriptorService, @IConfigurationService configurationService: IConfigurationService, @IEditorService private readonly editorService: IEditorService, - @IContextKeyService readonly contextKeyService: IContextKeyService, + @IContextKeyService contextKeyService: IContextKeyService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IDebugService private readonly debugService: IDebugService, @ILabelService private readonly labelService: ILabelService, @@ -439,7 +439,7 @@ export class LoadedScriptsView extends ViewPane { this.loadedScriptsItemType = CONTEXT_LOADED_SCRIPTS_ITEM_TYPE.bindTo(contextKeyService); } - renderBody(container: HTMLElement): void { + override renderBody(container: HTMLElement): void { super.renderBody(container); this.element.classList.add('debug-pane'); @@ -614,12 +614,12 @@ export class LoadedScriptsView extends ViewPane { this.debugService.getModel().getSessions().forEach(session => addSourcePathsToSession(session)); } - layoutBody(height: number, width: number): void { + override layoutBody(height: number, width: number): void { super.layoutBody(height, width); this.tree.layout(height, width); } - dispose(): void { + override dispose(): void { dispose(this.tree); dispose(this.treeLabels); super.dispose(); diff --git a/src/vs/workbench/contrib/debug/browser/media/debugToolBar.css b/src/vs/workbench/contrib/debug/browser/media/debugToolBar.css index 1889028b..a6711409 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debugToolBar.css +++ b/src/vs/workbench/contrib/debug/browser/media/debugToolBar.css @@ -12,7 +12,7 @@ } .monaco-workbench .debug-toolbar .monaco-action-bar .action-item { - height: 32px; + margin-right: 4px; } .monaco-workbench .debug-toolbar .monaco-action-bar .action-item.select-container { @@ -26,7 +26,6 @@ .monaco-workbench .debug-toolbar .drag-area { cursor: grab; - height: 32px; width: 16px; opacity: 0.5; display: flex; @@ -39,8 +38,6 @@ } .monaco-workbench .debug-toolbar .monaco-action-bar .action-item .action-label { - width: 32px; - height: 32px; margin-right: 0; background-size: 16px; background-position: center center; diff --git a/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css b/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css index c235447c..1e63d9c6 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css +++ b/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css @@ -28,12 +28,8 @@ .monaco-workbench .part > .title > .title-actions .start-debug-action-item { display: flex; align-items: center; - font-size: 11px; - margin-right: 0.3em; - height: 20px; line-height: 20px; flex-shrink: 1; - margin-top: 7px; } .monaco-workbench.mac .part > .title > .title-actions .start-debug-action-item { @@ -43,7 +39,6 @@ .monaco-workbench .part > .title > .title-actions .start-debug-action-item .codicon { line-height: inherit; flex-shrink: 0; - transition: transform 50ms ease; } .monaco-workbench .monaco-action-bar .start-debug-action-item .configuration .monaco-select-box { @@ -66,10 +61,6 @@ cursor: initial; } -.monaco-workbench .part > .title > .title-actions .start-debug-action-item .codicon.active { - transform: scale(1.272019649, 1.272019649); -} - /* Debug viewlet trees */ .debug-pane .line-number { @@ -93,13 +84,7 @@ /* Call stack */ -.debug-pane.expanded .debug-call-stack-title, -.debug-pane.vertical .debug-call-stack-title { - display: flex; - width: 100%; -} - -.debug-pane .debug-call-stack-title > .state-message { +.debug-pane .call-stack-state-message { flex: 1; text-align: right; text-overflow: ellipsis; @@ -108,7 +93,7 @@ margin: 0px 10px; } -.debug-pane .debug-call-stack-title > .state-message > .label { +.debug-pane .call-stack-state-message > .label { border-radius: 3px; padding: 1px 2px; font-size: 9px; @@ -155,21 +140,15 @@ display: initial; } +.debug-pane .monaco-list-row .monaco-action-bar .action-label { + padding: 2px; +} + .debug-pane .session .codicon { line-height: 22px; margin-right: 2px; } -.monaco-workbench .debug-pane .monaco-action-bar .action-item > .action-label { - width: 16px; - height: 100%; - line-height: 22px; - margin-right: 8px; - background-size: 16px; - background-position: center center; - background-repeat: no-repeat; -} - .debug-pane .debug-call-stack .stack-frame { overflow: hidden; text-overflow: ellipsis; @@ -319,6 +298,7 @@ justify-content: center; } +.debug-pane .debug-breakpoints .breakpoint > .access-type, .debug-pane .debug-breakpoints .breakpoint > .file-path, .debug-pane .debug-breakpoints .breakpoint > .condition { opacity: 0.7; diff --git a/src/vs/workbench/contrib/debug/browser/media/repl.css b/src/vs/workbench/contrib/debug/browser/media/repl.css index 9ee88bff..49e1cd82 100644 --- a/src/vs/workbench/contrib/debug/browser/media/repl.css +++ b/src/vs/workbench/contrib/debug/browser/media/repl.css @@ -19,6 +19,12 @@ .monaco-workbench .repl .repl-tree .monaco-tl-contents .expression { font-family: var(--vscode-repl-font-family); + font-size: var(--vscode-repl-font-size); + line-height: var(--vscode-repl-line-height); +} + +.monaco-workbench .repl .repl-tree .monaco-tl-twistie { + background-position-y: calc(100% - (var(--vscode-repl-font-size-for-twistie))); } .monaco-workbench .repl .repl-tree.word-wrap .monaco-tl-contents { @@ -102,7 +108,27 @@ /* ANSI Codes */ .monaco-workbench .repl .repl-tree .output.expression .code-bold { font-weight: bold; } .monaco-workbench .repl .repl-tree .output.expression .code-italic { font-style: italic; } -.monaco-workbench .repl .repl-tree .output.expression .code-underline { text-decoration: underline; } +.monaco-workbench .repl .repl-tree .output.expression .code-underline { text-decoration: underline; text-decoration-style:solid; } +.monaco-workbench .repl .repl-tree .output.expression .code-double-underline { text-decoration: underline; text-decoration-style:double; } +.monaco-workbench .repl .repl-tree .output.expression .code-strike-through { text-decoration:line-through; text-decoration-style:solid; } +.monaco-workbench .repl .repl-tree .output.expression .code-overline { text-decoration:overline; text-decoration-style:solid; } +/* because they can exist at same time we need all the possible underline(or double-underline),overline and strike-through combinations */ +.monaco-workbench .repl .repl-tree .output.expression .code-overline.code-underline.code-strike-through { text-decoration: overline underline line-through; text-decoration-style:solid; } +.monaco-workbench .repl .repl-tree .output.expression .code-overline.code-underline { text-decoration: overline underline; text-decoration-style:solid; } +.monaco-workbench .repl .repl-tree .output.expression .code-overline.code-strike-through { text-decoration: overline line-through; text-decoration-style:solid; } +.monaco-workbench .repl .repl-tree .output.expression .code-underline.code-strike-through { text-decoration: underline line-through; text-decoration-style:solid; } +.monaco-workbench .repl .repl-tree .output.expression .code-overline.code-double-underline.code-strike-through { text-decoration: overline underline line-through; text-decoration-style:double; } +.monaco-workbench .repl .repl-tree .output.expression .code-overline.code-double-underline { text-decoration: overline underline; text-decoration-style:double; } +.monaco-workbench .repl .repl-tree .output.expression .code-double-underline.code-strike-through { text-decoration: underline line-through; text-decoration-style:double; } +.monaco-workbench .repl .repl-tree .output.expression .code-dim { opacity: 0.4; } +.monaco-workbench .repl .repl-tree .output.expression .code-hidden { opacity: 0; } +.monaco-workbench .repl .repl-tree .output.expression .code-blink { animation: code-blink-key 1s cubic-bezier(1, 0, 0, 1) infinite alternate; } +.monaco-workbench .repl .repl-tree .output.expression .code-rapid-blink { animation: code-blink-key 0.3s cubic-bezier(1, 0, 0, 1) infinite alternate; } +@keyframes code-blink-key { + to { opacity: 0.4; } +} +.monaco-workbench .repl .repl-tree .output.expression .code-subscript { vertical-align: sub; font-size: smaller; line-height: normal; } +.monaco-workbench .repl .repl-tree .output.expression .code-superscript { vertical-align: super; font-size: smaller; line-height: normal; } .monaco-action-bar .action-item.repl-panel-filter-container { cursor: default; diff --git a/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts b/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts index 266edb6b..f9c3477a 100644 --- a/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts @@ -13,12 +13,11 @@ import { formatPII, isUri } from 'vs/workbench/contrib/debug/common/debugUtils'; import { IDebugAdapter, IConfig, AdapterEndEvent, IDebugger } from 'vs/workbench/contrib/debug/common/debug'; import { IExtensionHostDebugService, IOpenExtensionWindowResult } from 'vs/platform/debug/common/extensionHostDebug'; import { URI } from 'vs/base/common/uri'; -import { IProcessEnvironment } from 'vs/base/common/platform'; -import { env as processEnv } from 'vs/base/common/process'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { INotificationService } from 'vs/platform/notification/common/notification'; +import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; /** * This interface represents a single command line argument split into a "prefix" and a "path" half. @@ -82,11 +81,12 @@ export class RawDebugSession implements IDisposable { debugAdapter: IDebugAdapter, public readonly dbgr: IDebugger, private readonly sessionId: string, - private readonly telemetryService: ITelemetryService, - private readonly customTelemetryService: ICustomEndpointTelemetryService, - private readonly extensionHostDebugService: IExtensionHostDebugService, - private readonly openerService: IOpenerService, - private readonly notificationService: INotificationService + @ITelemetryService private readonly telemetryService: ITelemetryService, + @ICustomEndpointTelemetryService private readonly customTelemetryService: ICustomEndpointTelemetryService, + @IExtensionHostDebugService private readonly extensionHostDebugService: IExtensionHostDebugService, + @IOpenerService private readonly openerService: IOpenerService, + @INotificationService private readonly notificationService: INotificationService, + @IDialogService private readonly dialogSerivce: IDialogService, ) { this.debugAdapter = debugAdapter; this._capabilities = Object.create(null); @@ -275,8 +275,9 @@ export class RawDebugSession implements IDisposable { /** * Terminate the debuggee and shutdown the adapter */ - disconnect(restart = false): Promise { - return this.shutdown(undefined, restart); + disconnect(args: DebugProtocol.DisconnectArguments): Promise { + const terminateDebuggee = this.capabilities.supportTerminateDebuggee ? args.terminateDebuggee : undefined; + return this.shutdown(undefined, args.restart, terminateDebuggee); } //---- DAP requests @@ -299,14 +300,14 @@ export class RawDebugSession implements IDisposable { this.terminated = true; return this.send('terminate', { restart }, undefined, 2000); } - return this.disconnect(restart); + return this.disconnect({ terminateDebuggee: true, restart }); } return Promise.reject(new Error('terminated not supported')); } - restart(): Promise { + restart(args: DebugProtocol.RestartArguments): Promise { if (this.capabilities.supportsRestartRequest) { - return this.send('restart', null); + return this.send('restart', args); } return Promise.reject(new Error('restart not supported')); } @@ -506,12 +507,13 @@ export class RawDebugSession implements IDisposable { //---- private - private async shutdown(error?: Error, restart = false): Promise { + private async shutdown(error?: Error, restart = false, terminateDebuggee: boolean | undefined = undefined): Promise { if (!this.inShutdown) { this.inShutdown = true; if (this.debugAdapter) { try { - await this.send('disconnect', { restart }, undefined, 2000); + const args = typeof terminateDebuggee === 'boolean' ? { restart, terminateDebuggee } : { restart }; + this.send('disconnect', args, undefined, 2000); } catch (e) { // Catch the potential 'disconnect' error - no need to show it to the user since the adapter is shutting down } finally { @@ -565,17 +567,28 @@ export class RawDebugSession implements IDisposable { switch (request.command) { case 'launchVSCode': - this.launchVsCode(request.arguments).then(result => { + try { + let result = await this.launchVsCode(request.arguments); + if (!result.success) { + const showResult = await this.dialogSerivce.show(Severity.Warning, nls.localize('canNotStart', "The debugger needs to open a new tab or window for the debuggee but the browser prevented this. You must give permission to continue."), + [nls.localize('continue', "Continue"), nls.localize('cancel', "Cancel")], { cancelId: 1 }); + if (showResult.choice === 0) { + result = await this.launchVsCode(request.arguments); + } else { + response.success = false; + safeSendResponse(response); + await this.shutdown(); + } + } response.body = { rendererDebugPort: result.rendererDebugPort, - //processId: pid }; safeSendResponse(response); - }, err => { + } catch (err) { response.success = false; response.message = err.message; safeSendResponse(response); - }); + } break; case 'runInTerminal': try { @@ -620,15 +633,7 @@ export class RawDebugSession implements IDisposable { } } - let env: IProcessEnvironment = processEnv; - if (vscodeArgs.env && Object.keys(vscodeArgs.env).length > 0) { - // merge environment variables into a copy of the process.env - env = objects.mixin(objects.deepClone(processEnv), vscodeArgs.env); - // and delete some if necessary - Object.keys(env).filter(k => env[k] === null).forEach(key => delete env[key]); - } - - return this.extensionHostDebugService.openExtensionDevelopmentHostWindow(args, env, !!vscodeArgs.debugRenderer); + return this.extensionHostDebugService.openExtensionDevelopmentHostWindow(args, vscodeArgs.env, !!vscodeArgs.debugRenderer); } private send(command: string, args: any, token?: CancellationToken, timeout?: number): Promise { @@ -686,9 +691,8 @@ export class RawDebugSession implements IDisposable { if (error && url) { const label = error.urlLabel ? error.urlLabel : nls.localize('moreInfo', "More Info"); return errors.createErrorWithActions(userMessage, { - actions: [new Action('debug.moreInfo', label, undefined, true, () => { + actions: [new Action('debug.moreInfo', label, undefined, true, async () => { this.openerService.open(URI.parse(url)); - return Promise.resolve(null); })] }); } diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index 0bc759b8..4a4ded56 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -336,18 +336,6 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { // Set the font size, font family, line height and align the twistie to be centered, and input theme color this.styleElement.textContent = ` - .repl .repl-tree .expression { - font-size: ${fontSize}px; - } - - .repl .repl-tree .expression { - line-height: ${lineHeight}; - } - - .repl .repl-tree .monaco-tl-twistie { - background-position-y: calc(100% - ${fontSize * 1.4 / 2 - 8}px); - } - .repl .repl-input-wrapper .repl-input-chevron { line-height: ${replInputLineHeight}px } @@ -357,6 +345,9 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { } `; this.container.style.setProperty(`--vscode-repl-font-family`, fontFamily); + this.container.style.setProperty(`--vscode-repl-font-size`, `${fontSize}px`); + this.container.style.setProperty(`--vscode-repl-font-size-for-twistie`, `${fontSize * 1.4 / 2 - 8}px`); + this.container.style.setProperty(`--vscode-repl-line-height`, lineHeight); this.tree.rerender(); @@ -454,7 +445,7 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { return removeAnsiEscapeCodes(text); } - protected layoutBody(height: number, width: number): void { + protected override layoutBody(height: number, width: number): void { super.layoutBody(height, width); this.dimension = new dom.Dimension(width, height); const replInputHeight = Math.min(this.replInput.getContentHeight(), height); @@ -480,11 +471,11 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { return this.replInput; } - focus(): void { + override focus(): void { setTimeout(() => this.replInput.focus(), 0); } - getActionViewItem(action: IAction): IActionViewItem | undefined { + override getActionViewItem(action: IAction): IActionViewItem | undefined { if (action.id === selectReplCommandId) { const session = (this.tree ? this.tree.getInput() : undefined) ?? this.debugService.getViewModel().focusedSession; return this.instantiationService.createInstance(SelectReplActionViewItem, action, session); @@ -546,7 +537,7 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { // --- Creation - protected renderBody(parent: HTMLElement): void { + protected override renderBody(parent: HTMLElement): void { super.renderBody(parent); this.container = dom.append(parent, $('.repl')); this.treeContainer = dom.append(this.container, $(`.repl-tree.${MOUSE_CURSOR_TEXT_CSS_CLASS_NAME}`)); @@ -686,7 +677,7 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { this.replInput.setDecorations(DECORATION_KEY, decorations); } - saveState(): void { + override saveState(): void { const replHistory = this.history.getHistory(); if (replHistory.length) { this.storageService.store(HISTORY_STORAGE_KEY, JSON.stringify(replHistory), StorageScope.WORKSPACE, StorageTarget.USER); @@ -705,7 +696,7 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { super.saveState(); } - dispose(): void { + override dispose(): void { this.replInput.dispose(); if (this.replElementsChangeListener) { this.replElementsChangeListener.dispose(); @@ -790,11 +781,11 @@ registerEditorAction(FilterReplAction); class SelectReplActionViewItem extends FocusSessionActionViewItem { - protected getSessions(): ReadonlyArray { + protected override getSessions(): ReadonlyArray { return this.debugService.getModel().getSessions(true).filter(s => s.hasSeparateRepl() && !sessionsToIgnore.has(s)); } - protected mapFocusedSessionToSelected(focusedSession: IDebugSession): IDebugSession { + protected override mapFocusedSessionToSelected(focusedSession: IDebugSession): IDebugSession { while (focusedSession.parentSession && !focusedSession.hasSeparateRepl()) { focusedSession = focusedSession.parentSession; } diff --git a/src/vs/workbench/contrib/debug/browser/replFilter.ts b/src/vs/workbench/contrib/debug/browser/replFilter.ts index 15f2c7be..333230c3 100644 --- a/src/vs/workbench/contrib/debug/browser/replFilter.ts +++ b/src/vs/workbench/contrib/debug/browser/replFilter.ts @@ -146,7 +146,7 @@ export class ReplFilterActionViewItem extends BaseActionViewItem { this._register(toDisposable(() => this.delayedFilterUpdate.cancel())); } - render(container: HTMLElement): void { + override render(container: HTMLElement): void { this.container = container; this.container.classList.add('repl-panel-filter-container'); @@ -157,19 +157,19 @@ export class ReplFilterActionViewItem extends BaseActionViewItem { this.updateClass(); } - focus(): void { + override focus(): void { if (this.filterInputBox) { this.filterInputBox.focus(); } } - blur(): void { + override blur(): void { if (this.filterInputBox) { this.filterInputBox.blur(); } } - setFocusable(): void { + override setFocusable(): void { // noop input elements are focusable by default } @@ -177,7 +177,7 @@ export class ReplFilterActionViewItem extends BaseActionViewItem { return this.filterInputBox.getHistory(); } - get trapsArrowNavigation(): boolean { + override get trapsArrowNavigation(): boolean { return true; } diff --git a/src/vs/workbench/contrib/debug/browser/replViewer.ts b/src/vs/workbench/contrib/debug/browser/replViewer.ts index 937976c6..d393d97c 100644 --- a/src/vs/workbench/contrib/debug/browser/replViewer.ts +++ b/src/vs/workbench/contrib/debug/browser/replViewer.ts @@ -294,7 +294,7 @@ export class ReplDelegate extends CachedListVirtualDelegate { super(); } - getHeight(element: IReplElement): number { + override getHeight(element: IReplElement): number { const config = this.configurationService.getValue('debug'); if (!config.console.wordWrap) { diff --git a/src/vs/workbench/contrib/debug/browser/statusbarColorProvider.ts b/src/vs/workbench/contrib/debug/browser/statusbarColorProvider.ts index 7db7fd1d..48207bde 100644 --- a/src/vs/workbench/contrib/debug/browser/statusbarColorProvider.ts +++ b/src/vs/workbench/contrib/debug/browser/statusbarColorProvider.ts @@ -54,7 +54,7 @@ export class StatusBarColorProvider extends Themable implements IWorkbenchContri this._register(this.contextService.onDidChangeWorkbenchState(state => this.updateStyles())); } - protected updateStyles(): void { + protected override updateStyles(): void { super.updateStyles(); const container = assertIsDefined(this.layoutService.getContainer(Parts.STATUSBAR_PART)); diff --git a/src/vs/workbench/contrib/debug/browser/variablesView.ts b/src/vs/workbench/contrib/debug/browser/variablesView.ts index 537bf780..19ee0ed8 100644 --- a/src/vs/workbench/contrib/debug/browser/variablesView.ts +++ b/src/vs/workbench/contrib/debug/browser/variablesView.ts @@ -113,7 +113,7 @@ export class VariablesView extends ViewPane { }, 400); } - renderBody(container: HTMLElement): void { + override renderBody(container: HTMLElement): void { super.renderBody(container); this.element.classList.add('debug-pane'); @@ -182,12 +182,12 @@ export class VariablesView extends ViewPane { })); } - layoutBody(width: number, height: number): void { + override layoutBody(width: number, height: number): void { super.layoutBody(height, width); this.tree.layout(width, height); } - focus(): void { + override focus(): void { this.tree.domFocus(); } diff --git a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts index 23ee7bab..d1174015 100644 --- a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts +++ b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts @@ -75,7 +75,7 @@ export class WatchExpressionsView extends ViewPane { this.watchItemType = CONTEXT_WATCH_ITEM_TYPE.bindTo(contextKeyService); } - renderBody(container: HTMLElement): void { + override renderBody(container: HTMLElement): void { super.renderBody(container); this.element.classList.add('debug-pane'); @@ -163,12 +163,12 @@ export class WatchExpressionsView extends ViewPane { })); } - layoutBody(height: number, width: number): void { + override layoutBody(height: number, width: number): void { super.layoutBody(height, width); this.tree.layout(height, width); } - focus(): void { + override focus(): void { this.tree.domFocus(); } diff --git a/src/vs/workbench/contrib/debug/browser/welcomeView.ts b/src/vs/workbench/contrib/debug/browser/welcomeView.ts index e33671e7..e5b63d8d 100644 --- a/src/vs/workbench/contrib/debug/browser/welcomeView.ts +++ b/src/vs/workbench/contrib/debug/browser/welcomeView.ts @@ -19,7 +19,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { WorkbenchStateContext } from 'vs/workbench/browser/contextkeys'; import { OpenFolderAction, OpenFileAction, OpenFileFolderAction } from 'vs/workbench/browser/actions/workspaceActions'; -import { isMacintosh } from 'vs/base/common/platform'; +import { isMacintosh, isWeb } from 'vs/base/common/platform'; import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -100,7 +100,7 @@ export class WelcomeView extends ViewPane { debugKeybindingLabel = debugKeybinding ? ` (${debugKeybinding.getLabel()})` : ''; } - shouldShowWelcome(): boolean { + override shouldShowWelcome(): boolean { return true; } } @@ -108,7 +108,7 @@ export class WelcomeView extends ViewPane { const viewsRegistry = Registry.as(Extensions.ViewsRegistry); viewsRegistry.registerViewWelcomeContent(WelcomeView.ID, { content: localize({ key: 'openAFileWhichCanBeDebugged', comment: ['Please do not translate the word "commmand", it is part of our internal syntax which must not change'] }, - "[Open a file](command:{0}) which can be debugged or run.", isMacintosh ? OpenFileFolderAction.ID : OpenFileAction.ID), + "[Open a file](command:{0}) which can be debugged or run.", (isMacintosh && !isWeb) ? OpenFileFolderAction.ID : OpenFileAction.ID), when: ContextKeyExpr.and(CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_DEBUGGER_INTERESTED_IN_ACTIVE_EDITOR.toNegated()), group: ViewContentGroups.Open }); @@ -138,7 +138,7 @@ viewsRegistry.registerViewWelcomeContent(WelcomeView.ID, { viewsRegistry.registerViewWelcomeContent(WelcomeView.ID, { content: localize({ key: 'customizeRunAndDebugOpenFolder', comment: ['Please do not translate the word "commmand", it is part of our internal syntax which must not change'] }, - "To customize Run and Debug, [open a folder](command:{0}) and create a launch.json file.", isMacintosh ? OpenFileFolderAction.ID : OpenFolderAction.ID), + "To customize Run and Debug, [open a folder](command:{0}) and create a launch.json file.", (isMacintosh && !isWeb) ? OpenFileFolderAction.ID : OpenFolderAction.ID), when: ContextKeyExpr.and(CONTEXT_DEBUGGERS_AVAILABLE, WorkbenchStateContext.isEqualTo('empty')), group: ViewContentGroups.Debug }); diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index da477203..c005f81b 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -75,6 +75,7 @@ export const CONTEXT_SET_VARIABLE_SUPPORTED = new RawContextKey('debugS 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_TERMINATE_DEBUGGEE_SUPPORTED = new RawContextKey('terminateDebuggeeSupported', false, { type: 'boolean', description: nls.localize('terminateDebuggeeSupported', "True when the focused session supports the terminate debuggee capability.") }); 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.") }); @@ -396,6 +397,7 @@ export interface IBaseBreakpoint extends IEnablement { readonly logMessage?: string; readonly verified: boolean; readonly supported: boolean; + readonly message?: string; readonly sessionsThatVerified: string[]; getIdFromAdapter(sessionId: string): number | undefined; } @@ -406,7 +408,6 @@ export interface IBreakpoint extends IBaseBreakpoint { readonly endLineNumber?: number; readonly column?: number; readonly endColumn?: number; - readonly message?: string; readonly adapterData: any; readonly sessionAgnosticData: { lineNumber: number, column: number | undefined }; } @@ -415,10 +416,9 @@ export interface IFunctionBreakpoint extends IBaseBreakpoint { readonly name: string; } -export interface IExceptionBreakpoint extends IEnablement { +export interface IExceptionBreakpoint extends IBaseBreakpoint { readonly filter: string; readonly label: string; - readonly condition: string | undefined; readonly description: string | undefined; } @@ -501,7 +501,7 @@ export interface IDebugConfiguration { allowBreakpointsEverywhere: boolean; openDebug: 'neverOpen' | 'openOnSessionStart' | 'openOnFirstSessionStart' | 'openOnDebugBreak'; openExplorerOnEnd: boolean; - inlineValues: boolean; + inlineValues: boolean | 'auto'; toolBarLocation: 'floating' | 'docked' | 'hidden'; showInStatusBar: 'never' | 'always' | 'onFirstSessionStart'; internalConsoleOptions: 'neverOpen' | 'openOnSessionStart' | 'openOnFirstSessionStart'; @@ -931,7 +931,7 @@ export interface IDebugService { /** * Stops the session. If no session is specified then all sessions are stopped. */ - stopSession(session: IDebugSession | undefined): Promise; + stopSession(session: IDebugSession | undefined, disconnect?: boolean): Promise; /** * Makes unavailable all sources with the passed uri. Source will appear as grayed out in callstack view. diff --git a/src/vs/workbench/contrib/debug/common/debugModel.ts b/src/vs/workbench/contrib/debug/common/debugModel.ts index a72057fb..cf63563b 100644 --- a/src/vs/workbench/contrib/debug/common/debugModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugModel.ts @@ -208,7 +208,7 @@ export class Expression extends ExpressionContainer implements IExpression { this.available = await this.evaluateExpression(this.name, session, stackFrame, context); } - toString(): string { + override toString(): string { return `${this.name}\n${this.value}`; } } @@ -229,7 +229,7 @@ export class Variable extends ExpressionContainer implements IExpression { namedVariables: number | undefined, indexedVariables: number | undefined, public presentationHint: DebugProtocol.VariablePresentationHint | undefined, - public type: string | undefined = undefined, + type: string | undefined = undefined, public variableMenuContext: string | undefined = undefined, public available = true, startOfVariables = 0, @@ -237,6 +237,7 @@ export class Variable extends ExpressionContainer implements IExpression { ) { super(session, threadId, reference, `variable:${parent.getId()}:${name}:${idDuplicationIndex}`, namedVariables, indexedVariables, startOfVariables); this.value = value || ''; + this.type = type; } async setVariable(value: string): Promise { @@ -258,7 +259,7 @@ export class Variable extends ExpressionContainer implements IExpression { } } - toString(): string { + override toString(): string { return this.name ? `${this.name}: ${this.value}` : this.value; } @@ -287,7 +288,7 @@ export class Scope extends ExpressionContainer implements IScope { super(stackFrame.thread.session, stackFrame.thread.threadId, reference, `scope:${name}:${index}`, namedVariables, indexedVariables); } - toString(): string { + override toString(): string { return this.name; } @@ -310,7 +311,7 @@ export class ErrorScope extends Scope { super(stackFrame, index, message, 0, false); } - toString(): string { + override toString(): string { return this.name; } } @@ -700,7 +701,7 @@ export class Breakpoint extends BaseBreakpoint implements IBreakpoint { return this.verified && this.data && typeof this.data.line === 'number' ? this.data.line : this._lineNumber; } - get verified(): boolean { + override get verified(): boolean { if (this.data) { return this.data.verified && !this.textFileService.isDirty(this._uri); } @@ -716,7 +717,7 @@ export class Breakpoint extends BaseBreakpoint implements IBreakpoint { return this.verified && this.data && typeof this.data.column === 'number' ? this.data.column : this._column; } - get message(): string | undefined { + override get message(): string | undefined { if (this.textFileService.isDirty(this.uri)) { return nls.localize('breakpointDirtydHover', "Unverified breakpoint. File is modified, please restart debug session."); } @@ -761,14 +762,14 @@ export class Breakpoint extends BaseBreakpoint implements IBreakpoint { } - setSessionData(sessionId: string, data: IBreakpointSessionData | undefined): void { + override setSessionData(sessionId: string, data: IBreakpointSessionData | undefined): void { super.setSessionData(sessionId, data); if (!this._adapterData) { this._adapterData = this.adapterData; } } - toJSON(): any { + override toJSON(): any { const result = super.toJSON(); result.uri = this._uri; result.lineNumber = this._lineNumber; @@ -778,7 +779,7 @@ export class Breakpoint extends BaseBreakpoint implements IBreakpoint { return result; } - toString(): string { + override toString(): string { return `${resources.basenameOrAuthority(this.uri)} ${this.lineNumber}`; } @@ -814,7 +815,7 @@ export class FunctionBreakpoint extends BaseBreakpoint implements IFunctionBreak super(enabled, hitCondition, condition, logMessage, id); } - toJSON(): any { + override toJSON(): any { const result = super.toJSON(); result.name = this.name; @@ -829,7 +830,7 @@ export class FunctionBreakpoint extends BaseBreakpoint implements IFunctionBreak return this.data.supportsFunctionBreakpoints; } - toString(): string { + override toString(): string { return this.name; } } @@ -851,7 +852,7 @@ export class DataBreakpoint extends BaseBreakpoint implements IDataBreakpoint { super(enabled, hitCondition, condition, logMessage, id); } - toJSON(): any { + override toJSON(): any { const result = super.toJSON(); result.description = this.description; result.dataId = this.dataId; @@ -868,26 +869,26 @@ export class DataBreakpoint extends BaseBreakpoint implements IDataBreakpoint { return this.data.supportsDataBreakpoints; } - toString(): string { + override toString(): string { return this.description; } } -export class ExceptionBreakpoint extends Enablement implements IExceptionBreakpoint { +export class ExceptionBreakpoint extends BaseBreakpoint implements IExceptionBreakpoint { constructor( public filter: string, public label: string, enabled: boolean, public supportsCondition: boolean, - public condition: string | undefined, + condition: string | undefined, public description: string | undefined, public conditionDescription: string | undefined ) { - super(enabled, generateUuid()); + super(enabled, undefined, condition, undefined, generateUuid()); } - toJSON(): any { + override toJSON(): any { const result = Object.create(null); result.filter = this.filter; result.label = this.label; @@ -898,7 +899,11 @@ export class ExceptionBreakpoint extends Enablement implements IExceptionBreakpo return result; } - toString(): string { + get supported(): boolean { + return true; + } + + override toString(): string { return this.label; } } @@ -1182,6 +1187,16 @@ export class DebugModel implements IDebugModel { } } }); + this.exceptionBreakpoints.forEach(ebp => { + if (!data) { + ebp.setSessionData(sessionId, undefined); + } else { + const ebpData = data.get(ebp.getId()); + if (ebpData) { + ebp.setSessionData(sessionId, toBreakpointSessionData(ebpData, capabilites)); + } + } + }); this._onDidChangeBreakpoints.fire({ sessionOnly: true diff --git a/src/vs/workbench/contrib/debug/common/debugProtocol.d.ts b/src/vs/workbench/contrib/debug/common/debugProtocol.d.ts index f8aa9ce2..7c79be55 100644 --- a/src/vs/workbench/contrib/debug/common/debugProtocol.d.ts +++ b/src/vs/workbench/contrib/debug/common/debugProtocol.d.ts @@ -557,6 +557,8 @@ declare module DebugProtocol { /** Arguments for 'restart' request. */ export interface RestartArguments { + /** The latest version of the 'launch' or 'attach' configuration. */ + arguments?: LaunchRequestArguments | AttachRequestArguments; } /** Response to 'restart' request. This is just an acknowledgement, so no body field is required. */ @@ -584,6 +586,11 @@ declare module DebugProtocol { The attribute is only honored by a debug adapter if the capability 'supportTerminateDebuggee' is true. */ terminateDebuggee?: boolean; + /** Indicates whether the debuggee should stay suspended when the debugger is disconnected. + If unspecified, the debuggee should resume execution. + The attribute is only honored by a debug adapter if the capability 'supportSuspendDebuggee' is true. + */ + suspendDebuggee?: boolean; } /** Response to 'disconnect' request. This is just an acknowledgement, so no body field is required. */ @@ -710,7 +717,6 @@ declare module DebugProtocol { The request configures the debuggers response to thrown exceptions. If an exception is configured to break, a 'stopped' event is fired (with reason 'exception'). Clients should only call this request if the capability 'exceptionBreakpointFilters' returns one or more filters. - If a filter or filter option is invalid (e.g. due to an invalid 'condition'), the request should fail with an 'ErrorResponse' explaining the problem(s). */ export interface SetExceptionBreakpointsRequest extends Request { // command: 'setExceptionBreakpoints'; @@ -729,8 +735,18 @@ declare module DebugProtocol { exceptionOptions?: ExceptionOptions[]; } - /** Response to 'setExceptionBreakpoints' request. This is just an acknowledgement, so no body field is required. */ + /** Response to 'setExceptionBreakpoints' request. + The response contains an array of Breakpoint objects with information about each exception breakpoint or filter. The Breakpoint objects are in the same order as the elements of the 'filters', 'filterOptions', 'exceptionOptions' arrays given as arguments. If both 'filters' and 'filterOptions' are given, the returned array must start with 'filters' information first, followed by 'filterOptions' information. + The mandatory 'verified' property of a Breakpoint object signals whether the exception breakpoint or filter could be successfully created and whether the optional condition or hit count expressions are valid. In case of an error the 'message' property explains the problem. An optional 'id' property can be used to introduce a unique ID for the exception breakpoint or filter so that it can be updated subsequently by sending breakpoint events. + For backward compatibility both the 'breakpoints' array and the enclosing 'body' are optional. If these elements are missing a client will not be able to show problems for individual exception breakpoints or filters. + */ export interface SetExceptionBreakpointsResponse extends Response { + body?: { + /** Information about the exception breakpoints or filters. + The breakpoints returned are in the same order as the elements of the 'filters', 'filterOptions', 'exceptionOptions' arrays in the arguments. If both 'filters' and 'filterOptions' are given, the returned array must start with 'filters' information first, followed by 'filterOptions' information. + */ + breakpoints?: Breakpoint[]; + }; } /** DataBreakpointInfo request; value of command field is 'dataBreakpointInfo'. @@ -1598,6 +1614,8 @@ declare module DebugProtocol { supportsExceptionInfoRequest?: boolean; /** The debug adapter supports the 'terminateDebuggee' attribute on the 'disconnect' request. */ supportTerminateDebuggee?: boolean; + /** The debug adapter supports the 'suspendDebuggee' attribute on the 'disconnect' request. */ + supportSuspendDebuggee?: boolean; /** The debug adapter supports the delayed loading of parts of the stack, which requires that both the 'startFrame' and 'levels' arguments and an optional 'totalFrames' result of the 'StackTrace' request are supported. */ supportsDelayedStackTraceLoading?: boolean; /** The debug adapter supports the 'loadedSources' request. */ diff --git a/src/vs/workbench/contrib/debug/common/debugViewModel.ts b/src/vs/workbench/contrib/debug/common/debugViewModel.ts index a17e6118..56485139 100644 --- a/src/vs/workbench/contrib/debug/common/debugViewModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugViewModel.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Event, Emitter } from 'vs/base/common/event'; -import { CONTEXT_EXPRESSION_SELECTED, IViewModel, IStackFrame, IDebugSession, IThread, IExpression, CONTEXT_LOADED_SCRIPTS_SUPPORTED, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_FOCUSED_SESSION_IS_ATTACH, CONTEXT_RESTART_FRAME_SUPPORTED, CONTEXT_JUMP_TO_CURSOR_SUPPORTED, CONTEXT_STEP_INTO_TARGETS_SUPPORTED, CONTEXT_SET_VARIABLE_SUPPORTED, CONTEXT_MULTI_SESSION_DEBUG } from 'vs/workbench/contrib/debug/common/debug'; +import { CONTEXT_EXPRESSION_SELECTED, IViewModel, IStackFrame, IDebugSession, IThread, IExpression, CONTEXT_LOADED_SCRIPTS_SUPPORTED, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_FOCUSED_SESSION_IS_ATTACH, CONTEXT_RESTART_FRAME_SUPPORTED, CONTEXT_JUMP_TO_CURSOR_SUPPORTED, CONTEXT_STEP_INTO_TARGETS_SUPPORTED, CONTEXT_SET_VARIABLE_SUPPORTED, CONTEXT_MULTI_SESSION_DEBUG, CONTEXT_TERMINATE_DEBUGGEE_SUPPORTED } from 'vs/workbench/contrib/debug/common/debug'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { isSessionAttach } from 'vs/workbench/contrib/debug/common/debugUtils'; @@ -29,6 +29,7 @@ export class ViewModel implements IViewModel { private jumpToCursorSupported!: IContextKey; private setVariableSupported!: IContextKey; private multiSessionDebug!: IContextKey; + private terminateDebuggeeSuported!: IContextKey; constructor(private contextKeyService: IContextKeyService) { contextKeyService.bufferChangeEvents(() => { @@ -41,6 +42,7 @@ export class ViewModel implements IViewModel { this.jumpToCursorSupported = CONTEXT_JUMP_TO_CURSOR_SUPPORTED.bindTo(contextKeyService); this.setVariableSupported = CONTEXT_SET_VARIABLE_SUPPORTED.bindTo(contextKeyService); this.multiSessionDebug = CONTEXT_MULTI_SESSION_DEBUG.bindTo(contextKeyService); + this.terminateDebuggeeSuported = CONTEXT_TERMINATE_DEBUGGEE_SUPPORTED.bindTo(contextKeyService); }); } @@ -75,6 +77,7 @@ export class ViewModel implements IViewModel { this.stepIntoTargetsSupported.set(session ? !!session.capabilities.supportsStepInTargetsRequest : false); this.jumpToCursorSupported.set(session ? !!session.capabilities.supportsGotoTargetsRequest : false); this.setVariableSupported.set(session ? !!session.capabilities.supportsSetVariable : false); + this.terminateDebuggeeSuported.set(session ? !!session.capabilities.supportTerminateDebuggee : false); const attach = !!session && isSessionAttach(session); this.focusedSessionIsAttach.set(attach); }); diff --git a/src/vs/workbench/contrib/debug/common/replModel.ts b/src/vs/workbench/contrib/debug/common/replModel.ts index 61fde3f5..51730165 100644 --- a/src/vs/workbench/contrib/debug/common/replModel.ts +++ b/src/vs/workbench/contrib/debug/common/replModel.ts @@ -131,14 +131,14 @@ export class ReplEvaluationResult extends ExpressionContainer implements IReplEl super(undefined, undefined, 0, generateUuid()); } - async evaluateExpression(expression: string, session: IDebugSession | undefined, stackFrame: IStackFrame | undefined, context: string): Promise { + override async evaluateExpression(expression: string, session: IDebugSession | undefined, stackFrame: IStackFrame | undefined, context: string): Promise { const result = await super.evaluateExpression(expression, session, stackFrame, context); this._available = result; return result; } - toString(): string { + override toString(): string { return `${this.value}`; } } diff --git a/src/vs/workbench/contrib/debug/test/browser/callStack.test.ts b/src/vs/workbench/contrib/debug/test/browser/callStack.test.ts index 510cce48..66760beb 100644 --- a/src/vs/workbench/contrib/debug/test/browser/callStack.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/callStack.test.ts @@ -11,7 +11,6 @@ import { Source } from 'vs/workbench/contrib/debug/common/debugSource'; import { DebugSession } from 'vs/workbench/contrib/debug/browser/debugSession'; import { Range } from 'vs/editor/common/core/range'; import { IDebugSessionOptions, State, IDebugService } from 'vs/workbench/contrib/debug/common/debug'; -import { NullOpenerService } from 'vs/platform/opener/common/opener'; import { createDecorationsForStackFrame } from 'vs/workbench/contrib/debug/browser/callStackEditorContribution'; import { Constants } from 'vs/base/common/uint'; import { getContext, getContextForContributedActions, getSpecificSourceName } from 'vs/workbench/contrib/debug/browser/callStackView'; @@ -20,6 +19,7 @@ import { generateUuid } from 'vs/base/common/uuid'; import { debugStackframe, debugStackframeFocused } from 'vs/workbench/contrib/debug/browser/debugIcons'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; +import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; const mockWorkspaceContextService = { getWorkspace: () => { @@ -38,14 +38,14 @@ export function createMockSession(model: DebugModel, name = 'mockSession', optio } }; } - } as IDebugService, undefined!, undefined!, new TestConfigurationService({ debug: { console: { collapseIdenticalLines: true } } }), undefined!, mockWorkspaceContextService, undefined!, undefined!, NullOpenerService, undefined!, undefined!, mockUriIdentityService, undefined!); + } as IDebugService, undefined!, undefined!, new TestConfigurationService({ debug: { console: { collapseIdenticalLines: true } } }), undefined!, mockWorkspaceContextService, undefined!, undefined!, undefined!, mockUriIdentityService, new TestInstantiationService(), undefined!); } function createTwoStackFrames(session: DebugSession): { firstStackFrame: StackFrame, secondStackFrame: StackFrame } { let firstStackFrame: StackFrame; let secondStackFrame: StackFrame; const thread = new class extends Thread { - public getCallStack(): StackFrame[] { + public override getCallStack(): StackFrame[] { return [firstStackFrame, secondStackFrame]; } }(session, 'mockthread', 1); @@ -61,8 +61,8 @@ function createTwoStackFrames(session: DebugSession): { firstStackFrame: StackFr sourceReference: 11, }, 'aDebugSessionId', mockUriIdentityService); - firstStackFrame = new StackFrame(thread, 0, firstSource, 'app.js', 'normal', { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 10 }, 0, true); - secondStackFrame = new StackFrame(thread, 1, secondSource, 'app2.js', 'normal', { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 10 }, 1, true); + firstStackFrame = new StackFrame(thread, 0, firstSource, 'app.js', 'normal', { startLineNumber: 1, startColumn: 2, endLineNumber: 1, endColumn: 10 }, 0, true); + secondStackFrame = new StackFrame(thread, 1, secondSource, 'app2.js', 'normal', { startLineNumber: 1, startColumn: 2, endLineNumber: 1, endColumn: 10 }, 1, true); return { firstStackFrame, secondStackFrame }; } @@ -311,32 +311,32 @@ suite('Debug - CallStack', () => { const session = createMockSession(model); model.addSession(session); const { firstStackFrame, secondStackFrame } = createTwoStackFrames(session); - let decorations = createDecorationsForStackFrame(firstStackFrame, firstStackFrame.range, true); - assert.strictEqual(decorations.length, 2); - assert.deepStrictEqual(decorations[0].range, new Range(1, 2, 1, 1)); + let decorations = createDecorationsForStackFrame(firstStackFrame, true); + assert.strictEqual(decorations.length, 3); + assert.deepStrictEqual(decorations[0].range, new Range(1, 2, 1, 3)); assert.strictEqual(decorations[0].options.glyphMarginClassName, ThemeIcon.asClassName(debugStackframe)); - assert.deepStrictEqual(decorations[1].range, new Range(1, Constants.MAX_SAFE_SMALL_INTEGER, 1, 1)); + assert.deepStrictEqual(decorations[1].range, new Range(1, 2, 1, Constants.MAX_SAFE_SMALL_INTEGER)); assert.strictEqual(decorations[1].options.className, 'debug-top-stack-frame-line'); assert.strictEqual(decorations[1].options.isWholeLine, true); - decorations = createDecorationsForStackFrame(secondStackFrame, firstStackFrame.range, true); + decorations = createDecorationsForStackFrame(secondStackFrame, true); assert.strictEqual(decorations.length, 2); - assert.deepStrictEqual(decorations[0].range, new Range(1, 2, 1, 1)); + assert.deepStrictEqual(decorations[0].range, new Range(1, 2, 1, 3)); assert.strictEqual(decorations[0].options.glyphMarginClassName, ThemeIcon.asClassName(debugStackframeFocused)); - assert.deepStrictEqual(decorations[1].range, new Range(1, Constants.MAX_SAFE_SMALL_INTEGER, 1, 1)); + assert.deepStrictEqual(decorations[1].range, new Range(1, 2, 1, Constants.MAX_SAFE_SMALL_INTEGER)); assert.strictEqual(decorations[1].options.className, 'debug-focused-stack-frame-line'); assert.strictEqual(decorations[1].options.isWholeLine, true); - decorations = createDecorationsForStackFrame(firstStackFrame, new Range(1, 5, 1, 6), true); + decorations = createDecorationsForStackFrame(firstStackFrame, true); assert.strictEqual(decorations.length, 3); - assert.deepStrictEqual(decorations[0].range, new Range(1, 2, 1, 1)); + assert.deepStrictEqual(decorations[0].range, new Range(1, 2, 1, 3)); assert.strictEqual(decorations[0].options.glyphMarginClassName, ThemeIcon.asClassName(debugStackframe)); - assert.deepStrictEqual(decorations[1].range, new Range(1, Constants.MAX_SAFE_SMALL_INTEGER, 1, 1)); + assert.deepStrictEqual(decorations[1].range, new Range(1, 2, 1, Constants.MAX_SAFE_SMALL_INTEGER)); assert.strictEqual(decorations[1].options.className, 'debug-top-stack-frame-line'); assert.strictEqual(decorations[1].options.isWholeLine, true); // Inline decoration gets rendered in this case assert.strictEqual(decorations[2].options.beforeContentClassName, 'debug-top-stack-frame-column'); - assert.deepStrictEqual(decorations[2].range, new Range(1, Constants.MAX_SAFE_SMALL_INTEGER, 1, 1)); + assert.deepStrictEqual(decorations[2].range, new Range(1, 2, 1, Constants.MAX_SAFE_SMALL_INTEGER)); }); test('contexts', () => { @@ -375,10 +375,10 @@ suite('Debug - CallStack', () => { // Add the threads const session = new class extends DebugSession { - get state(): State { + override get state(): State { return State.Stopped; } - }(generateUuid(), { resolved: { name: 'stoppedSession', type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, undefined, undefined!, undefined!, undefined!, undefined!, undefined!, mockWorkspaceContextService, undefined!, undefined!, NullOpenerService, undefined!, undefined!, mockUriIdentityService, undefined!); + }(generateUuid(), { resolved: { name: 'stoppedSession', type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, undefined, undefined!, undefined!, undefined!, undefined!, undefined!, mockWorkspaceContextService, undefined!, undefined!, undefined!, mockUriIdentityService, new TestInstantiationService(), undefined!); const runningSession = createMockSession(model); model.addSession(runningSession); diff --git a/src/vs/workbench/contrib/debug/test/browser/debugANSIHandling.test.ts b/src/vs/workbench/contrib/debug/test/browser/debugANSIHandling.test.ts index 1932f803..9f9b3f1f 100644 --- a/src/vs/workbench/contrib/debug/test/browser/debugANSIHandling.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/debugANSIHandling.test.ts @@ -110,11 +110,15 @@ suite('Debug - ANSI Handling', () => { * @param element The HTML span element to look at. * @param colorType If `foreground`, will check the element's css `color`; * if `background`, will check the element's css `backgroundColor`. + * if `underline`, will check the elements css `textDecorationColor`. * @param color RGBA object to compare color to. If `undefined` or not provided, * will assert that no value is set. * @param message Optional custom message to pass to assertion. + * @param colorShouldMatch Optional flag (defaults TO true) which allows caller to indicate that the color SHOULD NOT MATCH + * (for testing changes to theme colors where we need color to have changed but we don't know exact color it should have + * changed to (but we do know the color it should NO LONGER BE)) */ - function assertInlineColor(element: HTMLSpanElement, colorType: 'background' | 'foreground', color?: RGBA | undefined, message?: string): void { + function assertInlineColor(element: HTMLSpanElement, colorType: 'background' | 'foreground' | 'underline', color?: RGBA | undefined, message?: string, colorShouldMatch: boolean = true): void { if (color !== undefined) { const cssColor = Color.Format.CSS.formatRGB( new Color(color) @@ -122,17 +126,23 @@ suite('Debug - ANSI Handling', () => { if (colorType === 'background') { const styleBefore = element.style.backgroundColor; element.style.backgroundColor = cssColor; - assert(styleBefore === element.style.backgroundColor, message || `Incorrect ${colorType} color style found (found color: ${styleBefore}, expected ${cssColor}).`); - } else { + assert((styleBefore === element.style.backgroundColor) === colorShouldMatch, message || `Incorrect ${colorType} color style found (found color: ${styleBefore}, expected ${cssColor}).`); + } else if (colorType === 'foreground') { const styleBefore = element.style.color; element.style.color = cssColor; - assert(styleBefore === element.style.color, message || `Incorrect ${colorType} color style found (found color: ${styleBefore}, expected ${cssColor}).`); + assert((styleBefore === element.style.color) === colorShouldMatch, message || `Incorrect ${colorType} color style found (found color: ${styleBefore}, expected ${cssColor}).`); + } else { + const styleBefore = element.style.textDecorationColor; + element.style.textDecorationColor = cssColor; + assert((styleBefore === element.style.textDecorationColor) === colorShouldMatch, message || `Incorrect ${colorType} color style found (found color: ${styleBefore}, expected ${cssColor}).`); } } else { if (colorType === 'background') { assert(!element.style.backgroundColor, message || `Defined ${colorType} color style found when it should not have been defined`); - } else { + } else if (colorType === 'foreground') { assert(!element.style.color, message || `Defined ${colorType} color style found when it should not have been defined`); + } else { + assert(!element.style.textDecorationColor, message || `Defined ${colorType} color style found when it should not have been defined`); } } @@ -185,6 +195,22 @@ suite('Debug - ANSI Handling', () => { }); } + // check all basic colors for underlines (full range is checked elsewhere, here we check cancelation) + for (let i = 0; i <= 255; i++) { + const customClassName: string = 'code-underline-colored'; + + // Underline colour class + assertSingleSequenceElement('\x1b[58;5;' + i + 'm', (child) => { + assert(child.classList.contains(customClassName), `Custom underline color class not found on element after underline color ANSI code 58;5;${i}m.`); + }); + + // Cancellation underline color code removes colour class + assertSingleSequenceElement('\x1b[58;5;' + i + 'm\x1b[59m', (child) => { + assert(child.classList.contains(customClassName) === false, 'Custom underline color class still found after underline color cancellation code 59m.'); + assertInlineColor(child, 'underline', undefined, 'Custom underline color style still found after underline color cancellation code 59m.'); + }); + } + // Different codes do not cancel each other assertSingleSequenceElement('\x1b[1;3;4;30;41m', (child) => { assert.strictEqual(5, child.classList.length, 'Incorrect number of classes found for different ANSI codes.'); @@ -196,6 +222,41 @@ suite('Debug - ANSI Handling', () => { assert(child.classList.contains('code-background-colored'), 'Different ANSI codes should not cancel each other.'); }); + // Different codes do not ACCUMULATE more than one copy of each class + assertSingleSequenceElement('\x1b[1;1;2;2;3;3;4;4;5;5;6;6;8;8;9;9;21;21;53;53;73;73;74;74m', (child) => { + assert(child.classList.contains('code-bold')); + assert(child.classList.contains('code-italic'), 'italic missing Doubles of each Different ANSI codes should not cancel each other or accumulate.'); + assert(child.classList.contains('code-underline') === false, 'underline PRESENT and double underline should have removed it- Doubles of each Different ANSI codes should not cancel each other or accumulate.'); + assert(child.classList.contains('code-dim'), 'dim missing Doubles of each Different ANSI codes should not cancel each other or accumulate.'); + assert(child.classList.contains('code-blink'), 'blink missing Doubles of each Different ANSI codes should not cancel each other or accumulate.'); + assert(child.classList.contains('code-rapid-blink'), 'rapid blink mkssing Doubles of each Different ANSI codes should not cancel each other or accumulate.'); + assert(child.classList.contains('code-double-underline'), 'double underline missing Doubles of each Different ANSI codes should not cancel each other or accumulate.'); + assert(child.classList.contains('code-hidden'), 'hidden missing Doubles of each Different ANSI codes should not cancel each other or accumulate.'); + assert(child.classList.contains('code-strike-through'), 'strike-through missing Doubles of each Different ANSI codes should not cancel each other or accumulate.'); + assert(child.classList.contains('code-overline'), 'overline missing Doubles of each Different ANSI codes should not cancel each other or accumulate.'); + assert(child.classList.contains('code-superscript') === false, 'superscript PRESENT and subscript should have removed it- Doubles of each Different ANSI codes should not cancel each other or accumulate.'); + assert(child.classList.contains('code-subscript'), 'subscript missing Doubles of each Different ANSI codes should not cancel each other or accumulate.'); + + assert.strictEqual(10, child.classList.length, 'Incorrect number of classes found for each style code sent twice ANSI codes.'); + }); + + + + // More Different codes do not cancel each other + assertSingleSequenceElement('\x1b[1;2;5;6;21;8;9m', (child) => { + assert.strictEqual(7, child.classList.length, 'Incorrect number of classes found for different ANSI codes.'); + + assert(child.classList.contains('code-bold')); + assert(child.classList.contains('code-dim'), 'Different ANSI codes should not cancel each other.'); + assert(child.classList.contains('code-blink'), 'Different ANSI codes should not cancel each other.'); + assert(child.classList.contains('code-rapid-blink'), 'Different ANSI codes should not cancel each other.'); + assert(child.classList.contains('code-double-underline'), 'Different ANSI codes should not cancel each other.'); + assert(child.classList.contains('code-hidden'), 'Different ANSI codes should not cancel each other.'); + assert(child.classList.contains('code-strike-through'), 'Different ANSI codes should not cancel each other.'); + }); + + + // New foreground codes don't remove old background codes and vice versa assertSingleSequenceElement('\x1b[40;31;42;33m', (child) => { assert.strictEqual(2, child.classList.length); @@ -253,6 +314,12 @@ suite('Debug - ANSI Handling', () => { assert(child.classList.contains('code-background-colored'), `Custom color class not found after background 8-bit color code 48;5;${i}`); assertInlineColor(child, 'background', (calcANSI8bitColor(i) as RGBA), `Incorrect or no color styling found after background 8-bit color code 48;5;${i}`); }); + + // Color underline codes should add custom class and inline style + assertSingleSequenceElement('\x1b[58;5;' + i + 'm', (child) => { + assert(child.classList.contains('code-underline-colored'), `Custom color class not found after underline 8-bit color code 58;5;${i}`); + assertInlineColor(child, 'underline', (calcANSI8bitColor(i) as RGBA), `Incorrect or no color styling found after underline 8-bit color code 58;5;${i}`); + }); } // Bad (nonexistent) color should not render @@ -285,6 +352,12 @@ suite('Debug - ANSI Handling', () => { assert(child.classList.contains('code-background-colored'), 'DOM should have "code-foreground-colored" class for advanced ANSI colors.'); assertInlineColor(child, 'background', color); }); + + // Underline color codes should add class and inline style + assertSingleSequenceElement(`\x1b[58;2;${r};${g};${b}m`, (child) => { + assert(child.classList.contains('code-underline-colored'), 'DOM should have "code-underline-colored" class for advanced ANSI colors.'); + assertInlineColor(child, 'underline', color); + }); } } } @@ -371,8 +444,474 @@ suite('Debug - ANSI Handling', () => { }, ], 5); + // Consecutive codes with ENDING/OFF codes do not LEAVE affect previous ones + assertMultipleSequenceElements('\x1b[1mbold\x1b[22m\x1b[32mgreen\x1b[4munderline\x1b[24m\x1b[3mitalic\x1b[23mjustgreen\x1b[0mnothing', [ + (bold) => { + assert.strictEqual(1, bold.classList.length); + assert(bold.classList.contains('code-bold'), 'Bold class not found after bold ANSI code.'); + }, + (green) => { + assert.strictEqual(1, green.classList.length); + assert(green.classList.contains('code-bold') === false, 'Bold class found after both bold WAS TURNED OFF with 22m'); + assert(green.classList.contains('code-foreground-colored'), 'Color class not found after color ANSI code.'); + }, + (underline) => { + assert.strictEqual(2, underline.classList.length); + assert(underline.classList.contains('code-foreground-colored'), 'Color class not found after color and underline ANSI codes.'); + assert(underline.classList.contains('code-underline'), 'Underline class not found after underline ANSI code.'); + }, + (italic) => { + assert.strictEqual(2, italic.classList.length); + assert(italic.classList.contains('code-foreground-colored'), 'Color class not found after color, underline, and italic ANSI codes.'); + assert(italic.classList.contains('code-underline') === false, 'Underline class found after underline WAS TURNED OFF with 24m'); + assert(italic.classList.contains('code-italic'), 'Italic class not found after italic ANSI code.'); + }, + (justgreen) => { + assert.strictEqual(1, justgreen.classList.length); + assert(justgreen.classList.contains('code-italic') === false, 'Italic class found after italic WAS TURNED OFF with 23m'); + assert(justgreen.classList.contains('code-foreground-colored'), 'Color class not found after color ANSI code.'); + }, + (nothing) => { + assert.strictEqual(0, nothing.classList.length, 'One or more style classes still found after reset ANSI code.'); + }, + ], 6); + + // more Consecutive codes with ENDING/OFF codes do not LEAVE affect previous ones + assertMultipleSequenceElements('\x1b[2mdim\x1b[22m\x1b[32mgreen\x1b[5mslowblink\x1b[25m\x1b[6mrapidblink\x1b[25mjustgreen\x1b[0mnothing', [ + (dim) => { + assert.strictEqual(1, dim.classList.length); + assert(dim.classList.contains('code-dim'), 'Dim class not found after dim ANSI code 2m.'); + }, + (green) => { + assert.strictEqual(1, green.classList.length); + assert(green.classList.contains('code-dim') === false, 'Dim class found after dim WAS TURNED OFF with 22m'); + assert(green.classList.contains('code-foreground-colored'), 'Color class not found after color ANSI code.'); + }, + (slowblink) => { + assert.strictEqual(2, slowblink.classList.length); + assert(slowblink.classList.contains('code-foreground-colored'), 'Color class not found after color and blink ANSI codes.'); + assert(slowblink.classList.contains('code-blink'), 'Blink class not found after underline ANSI code 5m.'); + }, + (rapidblink) => { + assert.strictEqual(2, rapidblink.classList.length); + assert(rapidblink.classList.contains('code-foreground-colored'), 'Color class not found after color, blink, and rapid blink ANSI codes.'); + assert(rapidblink.classList.contains('code-blink') === false, 'blink class found after underline WAS TURNED OFF with 25m'); + assert(rapidblink.classList.contains('code-rapid-blink'), 'Rapid blink class not found after rapid blink ANSI code 6m.'); + }, + (justgreen) => { + assert.strictEqual(1, justgreen.classList.length); + assert(justgreen.classList.contains('code-rapid-blink') === false, 'Rapid blink class found after rapid blink WAS TURNED OFF with 25m'); + assert(justgreen.classList.contains('code-foreground-colored'), 'Color class not found after color ANSI code.'); + }, + (nothing) => { + assert.strictEqual(0, nothing.classList.length, 'One or more style classes still found after reset ANSI code.'); + }, + ], 6); + + // more Consecutive codes with ENDING/OFF codes do not LEAVE affect previous ones + assertMultipleSequenceElements('\x1b[8mhidden\x1b[28m\x1b[32mgreen\x1b[9mcrossedout\x1b[29m\x1b[21mdoubleunderline\x1b[24mjustgreen\x1b[0mnothing', [ + (hidden) => { + assert.strictEqual(1, hidden.classList.length); + assert(hidden.classList.contains('code-hidden'), 'Hidden class not found after dim ANSI code 8m.'); + }, + (green) => { + assert.strictEqual(1, green.classList.length); + assert(green.classList.contains('code-hidden') === false, 'Hidden class found after Hidden WAS TURNED OFF with 28m'); + assert(green.classList.contains('code-foreground-colored'), 'Color class not found after color ANSI code.'); + }, + (crossedout) => { + assert.strictEqual(2, crossedout.classList.length); + assert(crossedout.classList.contains('code-foreground-colored'), 'Color class not found after color and hidden ANSI codes.'); + assert(crossedout.classList.contains('code-strike-through'), 'strike-through class not found after crossout/strikethrough ANSI code 9m.'); + }, + (doubleunderline) => { + assert.strictEqual(2, doubleunderline.classList.length); + assert(doubleunderline.classList.contains('code-foreground-colored'), 'Color class not found after color, hidden, and crossedout ANSI codes.'); + assert(doubleunderline.classList.contains('code-strike-through') === false, 'strike-through class found after strike-through WAS TURNED OFF with 29m'); + assert(doubleunderline.classList.contains('code-double-underline'), 'Double underline class not found after double underline ANSI code 21m.'); + }, + (justgreen) => { + assert.strictEqual(1, justgreen.classList.length); + assert(justgreen.classList.contains('code-double-underline') === false, 'Double underline class found after double underline WAS TURNED OFF with 24m'); + assert(justgreen.classList.contains('code-foreground-colored'), 'Color class not found after color ANSI code.'); + }, + (nothing) => { + assert.strictEqual(0, nothing.classList.length, 'One or more style classes still found after reset ANSI code.'); + }, + ], 6); + + // underline, double underline are mutually exclusive, test underline->double underline->off and double underline->underline->off + assertMultipleSequenceElements('\x1b[4munderline\x1b[21mdouble underline\x1b[24munderlineOff\x1b[21mdouble underline\x1b[4munderline\x1b[24munderlineOff', [ + (underline) => { + assert.strictEqual(1, underline.classList.length); + assert(underline.classList.contains('code-underline'), 'Underline class not found after underline ANSI code 4m.'); + }, + (doubleunderline) => { + assert(doubleunderline.classList.contains('code-underline') === false, 'Underline class found after double underline code 21m'); + assert(doubleunderline.classList.contains('code-double-underline'), 'Double underline class not found after double underline code 21m'); + assert.strictEqual(1, doubleunderline.classList.length, 'should have found only double underline'); + }, + (nothing) => { + assert.strictEqual(0, nothing.classList.length, 'One or more style classes still found after underline off code 4m.'); + }, + (doubleunderline) => { + assert(doubleunderline.classList.contains('code-double-underline'), 'Double underline class not found after double underline code 21m'); + assert.strictEqual(1, doubleunderline.classList.length, 'should have found only double underline'); + }, + (underline) => { + assert(underline.classList.contains('code-double-underline') === false, 'Double underline class found after underline code 4m'); + assert(underline.classList.contains('code-underline'), 'Underline class not found after underline ANSI code 4m.'); + assert.strictEqual(1, underline.classList.length, 'should have found only underline'); + }, + (nothing) => { + assert.strictEqual(0, nothing.classList.length, 'One or more style classes still found after underline off code 4m.'); + }, + ], 6); + + // underline and strike-through and overline can exist at the same time and + // in any combination + assertMultipleSequenceElements('\x1b[4munderline\x1b[9mand strikethough\x1b[53mand overline\x1b[24munderlineOff\x1b[55moverlineOff\x1b[29mstriklethoughOff', [ + (underline) => { + assert.strictEqual(1, underline.classList.length, 'should have found only underline'); + assert(underline.classList.contains('code-underline'), 'Underline class not found after underline ANSI code 4m.'); + }, + (strikethrough) => { + assert(strikethrough.classList.contains('code-underline'), 'Underline class NOT found after strikethrough code 9m'); + assert(strikethrough.classList.contains('code-strike-through'), 'Strike through class not found after strikethrough code 9m'); + assert.strictEqual(2, strikethrough.classList.length, 'should have found underline and strikethrough'); + }, + (overline) => { + assert(overline.classList.contains('code-underline'), 'Underline class NOT found after overline code 53m'); + assert(overline.classList.contains('code-strike-through'), 'Strike through class not found after overline code 53m'); + assert(overline.classList.contains('code-overline'), 'Overline class not found after overline code 53m'); + assert.strictEqual(3, overline.classList.length, 'should have found underline,strikethrough and overline'); + }, + (underlineoff) => { + assert(underlineoff.classList.contains('code-underline') === false, 'Underline class found after underline off code 24m'); + assert(underlineoff.classList.contains('code-strike-through'), 'Strike through class not found after underline off code 24m'); + assert(underlineoff.classList.contains('code-overline'), 'Overline class not found after underline off code 24m'); + assert.strictEqual(2, underlineoff.classList.length, 'should have found strikethrough and overline'); + }, + (overlineoff) => { + assert(overlineoff.classList.contains('code-underline') === false, 'Underline class found after overline off code 55m'); + assert(overlineoff.classList.contains('code-overline') === false, 'Overline class found after overline off code 55m'); + assert(overlineoff.classList.contains('code-strike-through'), 'Strike through class not found after overline off code 55m'); + assert.strictEqual(1, overlineoff.classList.length, 'should have found only strikethrough'); + }, + (nothing) => { + assert(nothing.classList.contains('code-strike-through') === false, 'Strike through class found after strikethrough off code 29m'); + assert.strictEqual(0, nothing.classList.length, 'One or more style classes still found after strikethough OFF code 29m'); + }, + ], 6); + + // double underline and strike-through and overline can exist at the same time and + // in any combination + assertMultipleSequenceElements('\x1b[21mdoubleunderline\x1b[9mand strikethough\x1b[53mand overline\x1b[29mstriklethoughOff\x1b[55moverlineOff\x1b[24munderlineOff', [ + (doubleunderline) => { + assert.strictEqual(1, doubleunderline.classList.length, 'should have found only doubleunderline'); + assert(doubleunderline.classList.contains('code-double-underline'), 'Double underline class not found after double underline ANSI code 21m.'); + }, + (strikethrough) => { + assert(strikethrough.classList.contains('code-double-underline'), 'Double nderline class NOT found after strikethrough code 9m'); + assert(strikethrough.classList.contains('code-strike-through'), 'Strike through class not found after strikethrough code 9m'); + assert.strictEqual(2, strikethrough.classList.length, 'should have found doubleunderline and strikethrough'); + }, + (overline) => { + assert(overline.classList.contains('code-double-underline'), 'Double underline class NOT found after overline code 53m'); + assert(overline.classList.contains('code-strike-through'), 'Strike through class not found after overline code 53m'); + assert(overline.classList.contains('code-overline'), 'Overline class not found after overline code 53m'); + assert.strictEqual(3, overline.classList.length, 'should have found doubleunderline,overline and strikethrough'); + }, + (strikethrougheoff) => { + assert(strikethrougheoff.classList.contains('code-double-underline'), 'Double underline class NOT found after strikethrough off code 29m'); + assert(strikethrougheoff.classList.contains('code-overline'), 'Overline class NOT found after strikethrough off code 29m'); + assert(strikethrougheoff.classList.contains('code-strike-through') === false, 'Strike through class found after strikethrough off code 29m'); + assert.strictEqual(2, strikethrougheoff.classList.length, 'should have found doubleunderline and overline'); + }, + (overlineoff) => { + assert(overlineoff.classList.contains('code-double-underline'), 'Double underline class NOT found after overline off code 55m'); + assert(overlineoff.classList.contains('code-strike-through') === false, 'Strike through class found after overline off code 55m'); + assert(overlineoff.classList.contains('code-overline') === false, 'Overline class found after overline off code 55m'); + assert.strictEqual(1, overlineoff.classList.length, 'Should have found only double underline'); + }, + (nothing) => { + assert(nothing.classList.contains('code-double-underline') === false, 'Double underline class found after underline off code 24m'); + assert.strictEqual(0, nothing.classList.length, 'One or more style classes still found after underline OFF code 24m'); + }, + ], 6); + + // superscript and subscript are mutually exclusive, test superscript->subscript->off and subscript->superscript->off + assertMultipleSequenceElements('\x1b[73msuperscript\x1b[74msubscript\x1b[75mneither\x1b[74msubscript\x1b[73msuperscript\x1b[75mneither', [ + (superscript) => { + assert.strictEqual(1, superscript.classList.length, 'should only be superscript class'); + assert(superscript.classList.contains('code-superscript'), 'Superscript class not found after superscript ANSI code 73m.'); + }, + (subscript) => { + assert(subscript.classList.contains('code-superscript') === false, 'Superscript class found after subscript code 74m'); + assert(subscript.classList.contains('code-subscript'), 'Subscript class not found after subscript code 74m'); + assert.strictEqual(1, subscript.classList.length, 'should have found only subscript class'); + }, + (nothing) => { + assert.strictEqual(0, nothing.classList.length, 'One or more style classes still found after superscript/subscript off code 75m.'); + }, + (subscript) => { + assert(subscript.classList.contains('code-subscript'), 'Subscript class not found after subscript code 74m'); + assert.strictEqual(1, subscript.classList.length, 'should have found only subscript class'); + }, + (superscript) => { + assert(superscript.classList.contains('code-subscript') === false, 'Subscript class found after superscript code 73m'); + assert(superscript.classList.contains('code-superscript'), 'Superscript class not found after superscript ANSI code 73m.'); + assert.strictEqual(1, superscript.classList.length, 'should have found only superscript class'); + }, + (nothing) => { + assert.strictEqual(0, nothing.classList.length, 'One or more style classes still found after superscipt/subscript off code 75m.'); + }, + ], 6); + + // Consecutive font codes switch to new font class and remove previous and then final switch to default font removes class + assertMultipleSequenceElements('\x1b[11mFont1\x1b[12mFont2\x1b[13mFont3\x1b[14mFont4\x1b[15mFont5\x1b[10mdefaultFont', [ + (font1) => { + assert.strictEqual(1, font1.classList.length); + assert(font1.classList.contains('code-font-1'), 'font 1 class NOT found after switch to font 1 with ANSI code 11m'); + }, + (font2) => { + assert.strictEqual(1, font2.classList.length); + assert(font2.classList.contains('code-font-1') === false, 'font 1 class found after switch to font 2 with ANSI code 12m'); + assert(font2.classList.contains('code-font-2'), 'font 2 class NOT found after switch to font 2 with ANSI code 12m'); + }, + (font3) => { + assert.strictEqual(1, font3.classList.length); + assert(font3.classList.contains('code-font-2') === false, 'font 2 class found after switch to font 3 with ANSI code 13m'); + assert(font3.classList.contains('code-font-3'), 'font 3 class NOT found after switch to font 3 with ANSI code 13m'); + }, + (font4) => { + assert.strictEqual(1, font4.classList.length); + assert(font4.classList.contains('code-font-3') === false, 'font 3 class found after switch to font 4 with ANSI code 14m'); + assert(font4.classList.contains('code-font-4'), 'font 4 class NOT found after switch to font 4 with ANSI code 14m'); + }, + (font5) => { + assert.strictEqual(1, font5.classList.length); + assert(font5.classList.contains('code-font-4') === false, 'font 4 class found after switch to font 5 with ANSI code 15m'); + assert(font5.classList.contains('code-font-5'), 'font 5 class NOT found after switch to font 5 with ANSI code 15m'); + }, + (defaultfont) => { + assert.strictEqual(0, defaultfont.classList.length, 'One or more font style classes still found after reset to default font with ANSI code 10m.'); + }, + ], 6); + + // More Consecutive font codes switch to new font class and remove previous and then final switch to default font removes class + assertMultipleSequenceElements('\x1b[16mFont6\x1b[17mFont7\x1b[18mFont8\x1b[19mFont9\x1b[20mFont10\x1b[10mdefaultFont', [ + (font6) => { + assert.strictEqual(1, font6.classList.length); + assert(font6.classList.contains('code-font-6'), 'font 6 class NOT found after switch to font 6 with ANSI code 16m'); + }, + (font7) => { + assert.strictEqual(1, font7.classList.length); + assert(font7.classList.contains('code-font-6') === false, 'font 6 class found after switch to font 7 with ANSI code 17m'); + assert(font7.classList.contains('code-font-7'), 'font 7 class NOT found after switch to font 7 with ANSI code 17m'); + }, + (font8) => { + assert.strictEqual(1, font8.classList.length); + assert(font8.classList.contains('code-font-7') === false, 'font 7 class found after switch to font 8 with ANSI code 18m'); + assert(font8.classList.contains('code-font-8'), 'font 8 class NOT found after switch to font 8 with ANSI code 18m'); + }, + (font9) => { + assert.strictEqual(1, font9.classList.length); + assert(font9.classList.contains('code-font-8') === false, 'font 8 class found after switch to font 9 with ANSI code 19m'); + assert(font9.classList.contains('code-font-9'), 'font 9 class NOT found after switch to font 9 with ANSI code 19m'); + }, + (font10) => { + assert.strictEqual(1, font10.classList.length); + assert(font10.classList.contains('code-font-9') === false, 'font 9 class found after switch to font 10 with ANSI code 20m'); + assert(font10.classList.contains('code-font-10'), `font 10 class NOT found after switch to font 10 with ANSI code 20m (${font10.classList})`); + }, + (defaultfont) => { + assert.strictEqual(0, defaultfont.classList.length, 'One or more font style classes (2nd series) still found after reset to default font with ANSI code 10m.'); + }, + ], 6); + + // Blackletter font codes can be turned off with other font codes or 23m + assertMultipleSequenceElements('\x1b[3mitalic\x1b[20mfont10blacklatter\x1b[23mitalicAndBlackletterOff\x1b[20mFont10Again\x1b[11mFont1\x1b[10mdefaultFont', [ + (italic) => { + assert.strictEqual(1, italic.classList.length); + assert(italic.classList.contains('code-italic'), 'italic class NOT found after italic code ANSI code 3m'); + }, + (font10) => { + assert.strictEqual(2, font10.classList.length); + assert(font10.classList.contains('code-italic'), 'no itatic class found after switch to font 10 (blackletter) with ANSI code 20m'); + assert(font10.classList.contains('code-font-10'), 'font 10 class NOT found after switch to font 10 with ANSI code 20m'); + }, + (italicAndBlackletterOff) => { + assert.strictEqual(0, italicAndBlackletterOff.classList.length, 'italic or blackletter (font10) class found after both switched off with ANSI code 23m'); + }, + (font10) => { + assert.strictEqual(1, font10.classList.length); + assert(font10.classList.contains('code-font-10'), 'font 10 class NOT found after switch to font 10 with ANSI code 20m'); + }, + (font1) => { + assert.strictEqual(1, font1.classList.length); + assert(font1.classList.contains('code-font-10') === false, 'font 10 class found after switch to font 1 with ANSI code 11m'); + assert(font1.classList.contains('code-font-1'), 'font 1 class NOT found after switch to font 1 with ANSI code 11m'); + }, + (defaultfont) => { + assert.strictEqual(0, defaultfont.classList.length, 'One or more font style classes (2nd series) still found after reset to default font with ANSI code 10m.'); + }, + ], 6); + + // italic can be turned on/off with affecting font codes 1-9 (italic off will clear 'blackletter'(font 23) as per spec) + assertMultipleSequenceElements('\x1b[3mitalic\x1b[12mfont2\x1b[23mitalicOff\x1b[3mitalicFont2\x1b[10mjustitalic\x1b[23mnothing', [ + (italic) => { + assert.strictEqual(1, italic.classList.length); + assert(italic.classList.contains('code-italic'), 'italic class NOT found after italic code ANSI code 3m'); + }, + (font10) => { + assert.strictEqual(2, font10.classList.length); + assert(font10.classList.contains('code-italic'), 'no itatic class found after switch to font 2 with ANSI code 12m'); + assert(font10.classList.contains('code-font-2'), 'font 2 class NOT found after switch to font 2 with ANSI code 12m'); + }, + (italicOff) => { + assert.strictEqual(1, italicOff.classList.length, 'italic class found after both switched off with ANSI code 23m'); + assert(italicOff.classList.contains('code-italic') === false, 'itatic class found after switching it OFF with ANSI code 23m'); + assert(italicOff.classList.contains('code-font-2'), 'font 2 class NOT found after switching italic off with ANSI code 23m'); + }, + (italicFont2) => { + assert.strictEqual(2, italicFont2.classList.length); + assert(italicFont2.classList.contains('code-italic'), 'no itatic class found after italic ANSI code 3m'); + assert(italicFont2.classList.contains('code-font-2'), 'font 2 class NOT found after italic ANSI code 3m'); + }, + (justitalic) => { + assert.strictEqual(1, justitalic.classList.length); + assert(justitalic.classList.contains('code-font-2') === false, 'font 2 class found after switch to default font with ANSI code 10m'); + assert(justitalic.classList.contains('code-italic'), 'italic class NOT found after switch to default font with ANSI code 10m'); + }, + (nothing) => { + assert.strictEqual(0, nothing.classList.length, 'One or more classes still found after final italic removal with ANSI code 23m.'); + }, + ], 6); + + // Reverse video reverses Foreground/Background colors WITH both SET and can called in sequence + assertMultipleSequenceElements('\x1b[38;2;10;20;30mfg10,20,30\x1b[48;2;167;168;169mbg167,168,169\x1b[7m8ReverseVideo\x1b[7mDuplicateReverseVideo\x1b[27mReverseOff\x1b[27mDupReverseOff', [ + (fg10_20_30) => { + assert.strictEqual(1, fg10_20_30.classList.length, 'Foreground ANSI color code should add one class.'); + assert(fg10_20_30.classList.contains('code-foreground-colored'), 'Foreground ANSI color codes should add custom foreground color class.'); + assertInlineColor(fg10_20_30, 'foreground', new RGBA(10, 20, 30), '24-bit RGBA ANSI color code (10,20,30) should add matching color inline style.'); + }, + (bg167_168_169) => { + assert.strictEqual(2, bg167_168_169.classList.length, 'background ANSI color codes should only add a single class.'); + assert(bg167_168_169.classList.contains('code-background-colored'), 'Background ANSI color codes should add custom background color class.'); + assertInlineColor(bg167_168_169, 'background', new RGBA(167, 168, 169), '24-bit RGBA ANSI background color code (167,168,169) should add matching color inline style.'); + assert(bg167_168_169.classList.contains('code-foreground-colored'), 'Still Foreground ANSI color codes should add custom foreground color class.'); + assertInlineColor(bg167_168_169, 'foreground', new RGBA(10, 20, 30), 'Still 24-bit RGBA ANSI color code (10,20,30) should add matching color inline style.'); + }, + (reverseVideo) => { + assert.strictEqual(2, reverseVideo.classList.length, 'background ANSI color codes should only add a single class.'); + assert(reverseVideo.classList.contains('code-background-colored'), 'Background ANSI color codes should add custom background color class.'); + assertInlineColor(reverseVideo, 'foreground', new RGBA(167, 168, 169), 'Reversed 24-bit RGBA ANSI foreground color code (167,168,169) should add matching former background color inline style.'); + assert(reverseVideo.classList.contains('code-foreground-colored'), 'Still Foreground ANSI color codes should add custom foreground color class.'); + assertInlineColor(reverseVideo, 'background', new RGBA(10, 20, 30), 'Reversed 24-bit RGBA ANSI background color code (10,20,30) should add matching former foreground color inline style.'); + }, + (dupReverseVideo) => { + assert.strictEqual(2, dupReverseVideo.classList.length, 'After second Reverse Video - background ANSI color codes should only add a single class.'); + assert(dupReverseVideo.classList.contains('code-background-colored'), 'After second Reverse Video - Background ANSI color codes should add custom background color class.'); + assertInlineColor(dupReverseVideo, 'foreground', new RGBA(167, 168, 169), 'After second Reverse Video - Reversed 24-bit RGBA ANSI foreground color code (167,168,169) should add matching former background color inline style.'); + assert(dupReverseVideo.classList.contains('code-foreground-colored'), 'After second Reverse Video - Still Foreground ANSI color codes should add custom foreground color class.'); + assertInlineColor(dupReverseVideo, 'background', new RGBA(10, 20, 30), 'After second Reverse Video - Reversed 24-bit RGBA ANSI background color code (10,20,30) should add matching former foreground color inline style.'); + }, + (reversedBack) => { + assert.strictEqual(2, reversedBack.classList.length, 'Reversed Back - background ANSI color codes should only add a single class.'); + assert(reversedBack.classList.contains('code-background-colored'), 'Reversed Back - Background ANSI color codes should add custom background color class.'); + assertInlineColor(reversedBack, 'background', new RGBA(167, 168, 169), 'Reversed Back - 24-bit RGBA ANSI background color code (167,168,169) should add matching color inline style.'); + assert(reversedBack.classList.contains('code-foreground-colored'), 'Reversed Back - Foreground ANSI color codes should add custom foreground color class.'); + assertInlineColor(reversedBack, 'foreground', new RGBA(10, 20, 30), 'Reversed Back - 24-bit RGBA ANSI color code (10,20,30) should add matching color inline style.'); + }, + (dupReversedBack) => { + assert.strictEqual(2, dupReversedBack.classList.length, '2nd Reversed Back - background ANSI color codes should only add a single class.'); + assert(dupReversedBack.classList.contains('code-background-colored'), '2nd Reversed Back - Background ANSI color codes should add custom background color class.'); + assertInlineColor(dupReversedBack, 'background', new RGBA(167, 168, 169), '2nd Reversed Back - 24-bit RGBA ANSI background color code (167,168,169) should add matching color inline style.'); + assert(dupReversedBack.classList.contains('code-foreground-colored'), '2nd Reversed Back - Foreground ANSI color codes should add custom foreground color class.'); + assertInlineColor(dupReversedBack, 'foreground', new RGBA(10, 20, 30), '2nd Reversed Back - 24-bit RGBA ANSI color code (10,20,30) should add matching color inline style.'); + }, + ], 6); + + // Reverse video reverses Foreground/Background colors WITH ONLY foreground color SET + assertMultipleSequenceElements('\x1b[38;2;10;20;30mfg10,20,30\x1b[7m8ReverseVideo\x1b[27mReverseOff', [ + (fg10_20_30) => { + assert.strictEqual(1, fg10_20_30.classList.length, 'Foreground ANSI color code should add one class.'); + assert(fg10_20_30.classList.contains('code-foreground-colored'), 'Foreground ANSI color codes should add custom foreground color class.'); + assertInlineColor(fg10_20_30, 'foreground', new RGBA(10, 20, 30), '24-bit RGBA ANSI color code (10,20,30) should add matching color inline style.'); + }, + (reverseVideo) => { + assert.strictEqual(1, reverseVideo.classList.length, 'Background ANSI color codes should only add a single class.'); + assert(reverseVideo.classList.contains('code-background-colored'), 'Background ANSI color codes should add custom background color class.'); + assert(reverseVideo.classList.contains('code-foreground-colored') === false, 'After Reverse with NO background the Foreground ANSI color codes should NOT BE SET.'); + assertInlineColor(reverseVideo, 'background', new RGBA(10, 20, 30), 'Reversed 24-bit RGBA ANSI background color code (10,20,30) should add matching former foreground color inline style.'); + }, + (reversedBack) => { + assert.strictEqual(1, reversedBack.classList.length, 'Reversed Back - background ANSI color codes should only add a single class.'); + assert(reversedBack.classList.contains('code-background-colored') === false, 'AFTER Reversed Back - Background ANSI color should NOT BE SET.'); + assert(reversedBack.classList.contains('code-foreground-colored'), 'Reversed Back - Foreground ANSI color codes should add custom foreground color class.'); + assertInlineColor(reversedBack, 'foreground', new RGBA(10, 20, 30), 'Reversed Back - 24-bit RGBA ANSI color code (10,20,30) should add matching color inline style.'); + }, + ], 3); + + // Reverse video reverses Foreground/Background colors WITH ONLY background color SET + assertMultipleSequenceElements('\x1b[48;2;167;168;169mbg167,168,169\x1b[7m8ReverseVideo\x1b[27mReverseOff', [ + (bg167_168_169) => { + assert.strictEqual(1, bg167_168_169.classList.length, 'Background ANSI color code should add one class.'); + assert(bg167_168_169.classList.contains('code-background-colored'), 'Background ANSI color codes should add custom foreground color class.'); + assertInlineColor(bg167_168_169, 'background', new RGBA(167, 168, 169), '24-bit RGBA ANSI color code (167, 168, 169) should add matching background color inline style.'); + }, + (reverseVideo) => { + assert.strictEqual(1, reverseVideo.classList.length, 'After ReverseVideo Foreground ANSI color codes should only add a single class.'); + assert(reverseVideo.classList.contains('code-foreground-colored'), 'After ReverseVideo Foreground ANSI color codes should add custom background color class.'); + assert(reverseVideo.classList.contains('code-background-colored') === false, 'After Reverse with NO foreground color the background ANSI color codes should BE SET.'); + assertInlineColor(reverseVideo, 'foreground', new RGBA(167, 168, 169), 'Reversed 24-bit RGBA ANSI background color code (10,20,30) should add matching former background color inline style.'); + }, + (reversedBack) => { + assert.strictEqual(1, reversedBack.classList.length, 'Reversed Back - background ANSI color codes should only add a single class.'); + assert(reversedBack.classList.contains('code-foreground-colored') === false, 'AFTER Reversed Back - Foreground ANSI color should NOT BE SET.'); + assert(reversedBack.classList.contains('code-background-colored'), 'Reversed Back - Background ANSI color codes should add custom background color class.'); + assertInlineColor(reversedBack, 'background', new RGBA(167, 168, 169), 'Reversed Back - 24-bit RGBA ANSI color code (10,20,30) should add matching background color inline style.'); + }, + ], 3); + + // Underline color Different types of color codes still cancel each other + assertMultipleSequenceElements('\x1b[58;2;101;102;103m24bitUnderline101,102,103\x1b[58;5;3m8bitsimpleUnderline\x1b[58;2;104;105;106m24bitUnderline104,105,106\x1b[58;5;101m8bitadvanced\x1b[58;2;200;200;200munderline200,200,200\x1b[59mUnderlineColorResetToDefault', [ + (adv24Bit) => { + assert.strictEqual(1, adv24Bit.classList.length, 'Underline ANSI color codes should only add a single class (1).'); + assert(adv24Bit.classList.contains('code-underline-colored'), 'Underline ANSI color codes should add custom underline color class.'); + assertInlineColor(adv24Bit, 'underline', new RGBA(101, 102, 103), '24-bit RGBA ANSI color code (101,102,103) should add matching color inline style.'); + }, + (adv8BitSimple) => { + assert.strictEqual(1, adv8BitSimple.classList.length, 'Multiple underline ANSI color codes should only add a single class (2).'); + assert(adv8BitSimple.classList.contains('code-underline-colored'), 'Underline ANSI color codes should add custom underline color class.'); + // changed to simple theme color, don't know exactly what it should be, but it should NO LONGER BE 101,102,103 + assertInlineColor(adv8BitSimple, 'underline', new RGBA(101, 102, 103), 'Change to theme color SHOULD NOT STILL BE 24-bit RGBA ANSI color code (101,102,103) should add matching color inline style.', false); + }, + (adv24BitAgain) => { + assert.strictEqual(1, adv24BitAgain.classList.length, 'Multiple underline ANSI color codes should only add a single class (3).'); + assert(adv24BitAgain.classList.contains('code-underline-colored'), 'Underline ANSI color codes should add custom underline color class.'); + assertInlineColor(adv24BitAgain, 'underline', new RGBA(104, 105, 106), '24-bit RGBA ANSI color code (100,100,100) should add matching color inline style.'); + }, + (adv8BitAdvanced) => { + assert.strictEqual(1, adv8BitAdvanced.classList.length, 'Multiple underline ANSI color codes should only add a single class (4).'); + assert(adv8BitAdvanced.classList.contains('code-underline-colored'), 'Underline ANSI color codes should add custom underline color class.'); + // changed to 8bit advanced color, don't know exactly what it should be, but it should NO LONGER BE 104,105,106 + assertInlineColor(adv8BitAdvanced, 'underline', new RGBA(104, 105, 106), 'Change to theme color SHOULD NOT BE 24-bit RGBA ANSI color code (104,105,106) should add matching color inline style.', false); + }, + (adv24BitUnderlin200) => { + assert.strictEqual(1, adv24BitUnderlin200.classList.length, 'Multiple underline ANSI color codes should only add a single class 4.'); + assert(adv24BitUnderlin200.classList.contains('code-underline-colored'), 'Underline ANSI color codes should add custom underline color class.'); + assertInlineColor(adv24BitUnderlin200, 'underline', new RGBA(200, 200, 200), 'after change underline color SHOULD BE 24-bit RGBA ANSI color code (200,200,200) should add matching color inline style.'); + }, + (underlineColorResetToDefault) => { + assert.strictEqual(0, underlineColorResetToDefault.classList.length, 'After Underline Color reset to default NO underline color class should be set.'); + assertInlineColor(underlineColorResetToDefault, 'underline', undefined, 'after RESET TO DEFAULT underline color SHOULD NOT BE SET (no color inline style.)'); + }, + ], 6); + // Different types of color codes still cancel each other - assertMultipleSequenceElements('\x1b[34msimple\x1b[38;2;100;100;100m24bit\x1b[38;5;3m8bitsimple\x1b[38;5;101m8bitadvanced', [ + assertMultipleSequenceElements('\x1b[34msimple\x1b[38;2;101;102;103m24bit\x1b[38;5;3m8bitsimple\x1b[38;2;104;105;106m24bitAgain\x1b[38;5;101m8bitadvanced', [ (simple) => { assert.strictEqual(1, simple.classList.length, 'Foreground ANSI color code should add one class.'); assert(simple.classList.contains('code-foreground-colored'), 'Foreground ANSI color codes should add custom foreground color class.'); @@ -380,18 +919,26 @@ suite('Debug - ANSI Handling', () => { (adv24Bit) => { assert.strictEqual(1, adv24Bit.classList.length, 'Multiple foreground ANSI color codes should only add a single class.'); assert(adv24Bit.classList.contains('code-foreground-colored'), 'Foreground ANSI color codes should add custom foreground color class.'); - assertInlineColor(adv24Bit, 'foreground', new RGBA(100, 100, 100), '24-bit RGBA ANSI color code (100,100,100) should add matching color inline style.'); + assertInlineColor(adv24Bit, 'foreground', new RGBA(101, 102, 103), '24-bit RGBA ANSI color code (101,102,103) should add matching color inline style.'); }, (adv8BitSimple) => { assert.strictEqual(1, adv8BitSimple.classList.length, 'Multiple foreground ANSI color codes should only add a single class.'); assert(adv8BitSimple.classList.contains('code-foreground-colored'), 'Foreground ANSI color codes should add custom foreground color class.'); - // Won't assert color because it's theme based + //color is theme based, so we can't check what it should be but we know it should NOT BE 101,102,103 anymore + assertInlineColor(adv8BitSimple, 'foreground', new RGBA(101, 102, 103), 'SHOULD NOT LONGER BE 24-bit RGBA ANSI color code (101,102,103) after simple color change.', false); + }, + (adv24BitAgain) => { + assert.strictEqual(1, adv24BitAgain.classList.length, 'Multiple foreground ANSI color codes should only add a single class.'); + assert(adv24BitAgain.classList.contains('code-foreground-colored'), 'Foreground ANSI color codes should add custom foreground color class.'); + assertInlineColor(adv24BitAgain, 'foreground', new RGBA(104, 105, 106), '24-bit RGBA ANSI color code (104,105,106) should add matching color inline style.'); }, (adv8BitAdvanced) => { assert.strictEqual(1, adv8BitAdvanced.classList.length, 'Multiple foreground ANSI color codes should only add a single class.'); assert(adv8BitAdvanced.classList.contains('code-foreground-colored'), 'Foreground ANSI color codes should add custom foreground color class.'); + // color should NO LONGER BE 104,105,106 + assertInlineColor(adv8BitAdvanced, 'foreground', new RGBA(104, 105, 106), 'SHOULD NOT LONGER BE 24-bit RGBA ANSI color code (104,105,106) after advanced color change.', false); } - ], 4); + ], 5); }); diff --git a/src/vs/workbench/contrib/debug/test/browser/debugHover.test.ts b/src/vs/workbench/contrib/debug/test/browser/debugHover.test.ts index 2d9eaeb1..19b009ef 100644 --- a/src/vs/workbench/contrib/debug/test/browser/debugHover.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/debugHover.test.ts @@ -18,7 +18,7 @@ suite('Debug - Hover', () => { let stackFrame: StackFrame; const thread = new class extends Thread { - public getCallStack(): StackFrame[] { + public override getCallStack(): StackFrame[] { return [stackFrame]; } }(session, 'mockthread', 1); @@ -31,7 +31,7 @@ suite('Debug - Hover', () => { let scope: Scope; stackFrame = new class extends StackFrame { - getScopes(): Promise { + override getScopes(): Promise { return Promise.resolve([scope]); } }(thread, 1, firstSource, 'app.js', 'normal', { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 10 }, 1, true); @@ -40,13 +40,13 @@ suite('Debug - Hover', () => { let variableA: Variable; let variableB: Variable; scope = new class extends Scope { - getChildren(): Promise { + override getChildren(): Promise { return Promise.resolve([variableA]); } }(stackFrame, 1, 'local', 1, false, 10, 10); variableA = new class extends Variable { - getChildren(): Promise { + override getChildren(): Promise { return Promise.resolve([variableB]); } }(session, 1, scope, 2, 'A', 'A', undefined!, 0, 0, {}, 'string'); diff --git a/src/vs/workbench/contrib/debug/test/browser/mockDebug.ts b/src/vs/workbench/contrib/debug/test/browser/mockDebug.ts index 8983f96e..4e0173b4 100644 --- a/src/vs/workbench/contrib/debug/test/browser/mockDebug.ts +++ b/src/vs/workbench/contrib/debug/test/browser/mockDebug.ts @@ -593,32 +593,32 @@ class MockDebugStorage extends DebugStorage { super(undefined as any, undefined as any, undefined as any); } - loadBreakpoints(): Breakpoint[] { + override loadBreakpoints(): Breakpoint[] { return []; } - loadFunctionBreakpoints(): FunctionBreakpoint[] { + override loadFunctionBreakpoints(): FunctionBreakpoint[] { return []; } - loadExceptionBreakpoints(): ExceptionBreakpoint[] { + override loadExceptionBreakpoints(): ExceptionBreakpoint[] { return []; } - loadDataBreakpoints(): DataBreakpoint[] { + override loadDataBreakpoints(): DataBreakpoint[] { return []; } - loadWatchExpressions(): Expression[] { + override loadWatchExpressions(): Expression[] { return []; } - storeWatchExpressions(_watchExpressions: (IExpression & IEvaluate)[]): void { } + override storeWatchExpressions(_watchExpressions: (IExpression & IEvaluate)[]): void { } - storeBreakpoints(_debugModel: IDebugModel): void { } + override storeBreakpoints(_debugModel: IDebugModel): void { } } export function createMockDebugModel(): DebugModel { diff --git a/src/vs/workbench/contrib/debug/test/browser/repl.test.ts b/src/vs/workbench/contrib/debug/test/browser/repl.test.ts index 30441041..50567b6a 100644 --- a/src/vs/workbench/contrib/debug/test/browser/repl.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/repl.test.ts @@ -176,7 +176,7 @@ suite('Debug - REPL', () => { model.addSession(session); const adapter = new MockDebugAdapter(); - const raw = new RawDebugSession(adapter, undefined!, '', undefined!, undefined!, undefined!, undefined!, undefined!); + const raw = new RawDebugSession(adapter, undefined!, '', undefined!, undefined!, undefined!, undefined!, undefined!, undefined!); session.initializeForTest(raw); await session.addReplExpression(undefined, 'before.1'); diff --git a/src/vs/workbench/contrib/emmet/test/browser/emmetAction.test.ts b/src/vs/workbench/contrib/emmet/test/browser/emmetAction.test.ts index c56afa2d..50833760 100644 --- a/src/vs/workbench/contrib/emmet/test/browser/emmetAction.test.ts +++ b/src/vs/workbench/contrib/emmet/test/browser/emmetAction.test.ts @@ -44,10 +44,11 @@ suite('Emmet', () => { withTestCodeEditor([], {}, (editor) => { function testIsEnabled(mode: string, scopeName: string, expectedLanguage?: string, expectedParentLanguage?: string) { - const languageIdentifier = new LanguageIdentifier(mode, 73); + const customLanguageId: LanguageId = 73; + const languageIdentifier = new LanguageIdentifier(mode, customLanguageId); const languageIdentifierResolver: ILanguageIdentifierResolver = { getLanguageIdentifier: (languageId: LanguageId) => { - if (languageId === 73) { + if (languageId === customLanguageId) { return languageIdentifier; } throw new Error('Unexpected'); @@ -64,8 +65,8 @@ suite('Emmet', () => { assert.fail('langOutput not found'); } - assert.equal(langOutput.language, expectedLanguage); - assert.equal(langOutput.parentMode, expectedParentLanguage); + assert.strictEqual(langOutput.language, expectedLanguage); + assert.strictEqual(langOutput.parentMode, expectedParentLanguage); } // syntaxes mapped using the scope name of the grammar diff --git a/src/vs/workbench/contrib/experiments/browser/experiments.contribution.ts b/src/vs/workbench/contrib/experiments/browser/experiments.contribution.ts index b13961ea..825e120e 100644 --- a/src/vs/workbench/contrib/experiments/browser/experiments.contribution.ts +++ b/src/vs/workbench/contrib/experiments/browser/experiments.contribution.ts @@ -10,7 +10,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { ExperimentalPrompts } from 'vs/workbench/contrib/experiments/browser/experimentalPrompt'; -import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; +import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration'; registerSingleton(IExperimentService, ExperimentService, true); @@ -27,6 +27,8 @@ registry.registerConfiguration({ 'type': 'boolean', 'description': localize('workbench.enableExperiments', "Fetches experiments to run from a Microsoft online service."), 'default': true, + 'scope': ConfigurationScope.APPLICATION, + 'restricted': true, 'tags': ['usesOnlineServices'] } } diff --git a/src/vs/workbench/contrib/experiments/test/electron-browser/experimentService.test.ts b/src/vs/workbench/contrib/experiments/test/electron-browser/experimentService.test.ts index 7bdf51ae..d4183d0e 100644 --- a/src/vs/workbench/contrib/experiments/test/electron-browser/experimentService.test.ts +++ b/src/vs/workbench/contrib/experiments/test/electron-browser/experimentService.test.ts @@ -31,8 +31,8 @@ import { IWillActivateEvent, IExtensionService } from 'vs/workbench/services/ext import { timeout } from 'vs/base/common/async'; import { TestExtensionService } from 'vs/workbench/test/common/workbenchTestServices'; import { OS } from 'vs/base/common/platform'; -import { IWorkspaceTrustService } from 'vs/platform/workspace/common/workspaceTrust'; -import { TestWorkspaceTrustService } from 'vs/workbench/services/workspaces/test/common/testWorkspaceTrustService'; +import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust'; +import { TestWorkspaceTrustManagementService } from 'vs/workbench/services/workspaces/test/common/testWorkspaceTrustService'; interface ExperimentSettings { enabled?: boolean; @@ -58,7 +58,7 @@ function aLocalExtension(name: string = 'someext', manifest: any = {}, propertie } export class TestExperimentService extends ExperimentService { - public getExperiments(): Promise { + public override getExperiments(): Promise { return Promise.resolve(experimentData.experiments); } } @@ -96,7 +96,7 @@ suite('Experiment Service', () => { instantiationService.stub(IConfigurationService, testConfigurationService); instantiationService.stub(ILifecycleService, new TestLifecycleService()); instantiationService.stub(IStorageService, >{ get: (a: string, b: StorageScope, c?: string) => c, getBoolean: (a: string, b: StorageScope, c?: boolean) => c, store: () => { }, remove: () => { } }); - instantiationService.stub(IWorkspaceTrustService, new TestWorkspaceTrustService()); + instantiationService.stub(IWorkspaceTrustManagementService, new TestWorkspaceTrustManagementService()); setup(() => { instantiationService.stub(IProductService, {}); @@ -150,25 +150,25 @@ suite('Experiment Service', () => { tests.push(testObject.getExperimentById('experiment5')); return Promise.all(tests).then(results => { - assert.equal(results[0].id, 'experiment1'); - assert.equal(results[0].enabled, false); - assert.equal(results[0].state, ExperimentState.NoRun); + assert.strictEqual(results[0].id, 'experiment1'); + assert.strictEqual(results[0].enabled, false); + assert.strictEqual(results[0].state, ExperimentState.NoRun); - assert.equal(results[1].id, 'experiment2'); - assert.equal(results[1].enabled, false); - assert.equal(results[1].state, ExperimentState.NoRun); + assert.strictEqual(results[1].id, 'experiment2'); + assert.strictEqual(results[1].enabled, false); + assert.strictEqual(results[1].state, ExperimentState.NoRun); - assert.equal(results[2].id, 'experiment3'); - assert.equal(results[2].enabled, true); - assert.equal(results[2].state, ExperimentState.Run); + assert.strictEqual(results[2].id, 'experiment3'); + assert.strictEqual(results[2].enabled, true); + assert.strictEqual(results[2].state, ExperimentState.Run); - assert.equal(results[3].id, 'experiment4'); - assert.equal(results[3].enabled, true); - assert.equal(results[3].state, ExperimentState.Run); + assert.strictEqual(results[3].id, 'experiment4'); + assert.strictEqual(results[3].enabled, true); + assert.strictEqual(results[3].state, ExperimentState.Run); - assert.equal(results[4].id, 'experiment5'); - assert.equal(results[4].enabled, true); - assert.equal(results[4].state, ExperimentState.Run); + assert.strictEqual(results[4].id, 'experiment5'); + assert.strictEqual(results[4].enabled, true); + assert.strictEqual(results[4].state, ExperimentState.Run); }); }); @@ -197,9 +197,9 @@ suite('Experiment Service', () => { testObject.getExperimentById('experiment3'), ]); - assert.equal(actual[0]?.id, 'experiment1'); - assert.equal(actual[1]?.id, 'experiment2'); - assert.equal(actual[2], undefined); + assert.strictEqual(actual[0]?.id, 'experiment1'); + assert.strictEqual(actual[1]?.id, 'experiment2'); + assert.strictEqual(actual[2], undefined); }); test('Insiders only experiment shouldnt be enabled in stable', () => { @@ -218,8 +218,8 @@ suite('Experiment Service', () => { instantiationService.stub(IProductService, { quality: 'stable' }); testObject = instantiationService.createInstance(TestExperimentService); return testObject.getExperimentById('experiment1').then(result => { - assert.equal(result.enabled, true); - assert.equal(result.state, ExperimentState.NoRun); + assert.strictEqual(result.enabled, true); + assert.strictEqual(result.state, ExperimentState.NoRun); }); }); @@ -244,8 +244,8 @@ suite('Experiment Service', () => { }); testObject = instantiationService.createInstance(TestExperimentService); return testObject.getExperimentById('experiment1').then(result => { - assert.equal(result.enabled, true); - assert.equal(result.state, ExperimentState.NoRun); + assert.strictEqual(result.enabled, true); + assert.strictEqual(result.state, ExperimentState.NoRun); }); }); @@ -264,8 +264,8 @@ suite('Experiment Service', () => { testObject = instantiationService.createInstance(TestExperimentService); return testObject.getExperimentById('experiment1').then(result => { - assert.equal(result.enabled, true); - assert.equal(result.state, ExperimentState.NoRun); + assert.strictEqual(result.enabled, true); + assert.strictEqual(result.state, ExperimentState.NoRun); }); }); @@ -288,8 +288,8 @@ suite('Experiment Service', () => { }); testObject = instantiationService.createInstance(TestExperimentService); return testObject.getExperimentById('experiment1').then(result => { - assert.equal(result.enabled, true); - assert.equal(result.state, ExperimentState.Run); + assert.strictEqual(result.enabled, true); + assert.strictEqual(result.state, ExperimentState.Run); }); }); @@ -306,8 +306,8 @@ suite('Experiment Service', () => { testObject = instantiationService.createInstance(TestExperimentService); return testObject.getExperimentById('experiment1').then(result => { - assert.equal(result.enabled, true); - assert.equal(result.state, ExperimentState.Run); + assert.strictEqual(result.enabled, true); + assert.strictEqual(result.state, ExperimentState.Run); }); }); @@ -326,7 +326,7 @@ suite('Experiment Service', () => { testObject = instantiationService.createInstance(TestExperimentService); return testObject.getExperimentById('experiment1').then(result => { - assert.equal(result.state, ExperimentState.Run); + assert.strictEqual(result.state, ExperimentState.Run); }); }); @@ -345,7 +345,7 @@ suite('Experiment Service', () => { testObject = instantiationService.createInstance(TestExperimentService); return testObject.getExperimentById('experiment1').then(result => { - assert.equal(result.state, ExperimentState.NoRun); + assert.strictEqual(result.state, ExperimentState.NoRun); }); }); @@ -373,8 +373,8 @@ suite('Experiment Service', () => { testObject = instantiationService.createInstance(TestExperimentService); return testObject.getExperimentById('experiment1').then(result => { - assert.equal(result.enabled, true); - assert.equal(result.state, ExperimentState.Evaluating); + assert.strictEqual(result.enabled, true); + assert.strictEqual(result.state, ExperimentState.Evaluating); }); }); @@ -402,8 +402,8 @@ suite('Experiment Service', () => { testObject = instantiationService.createInstance(TestExperimentService); return testObject.getExperimentById('experiment1').then(result => { - assert.equal(result.enabled, true); - assert.equal(result.state, ExperimentState.Run); + assert.strictEqual(result.enabled, true); + assert.strictEqual(result.state, ExperimentState.Run); }); }); @@ -431,8 +431,8 @@ suite('Experiment Service', () => { testObject = instantiationService.createInstance(TestExperimentService); return testObject.getExperimentById('experiment1').then(result => { - assert.equal(result.enabled, true); - assert.equal(result.state, ExperimentState.Evaluating); + assert.strictEqual(result.enabled, true); + assert.strictEqual(result.state, ExperimentState.Evaluating); }); }); @@ -444,7 +444,7 @@ suite('Experiment Service', () => { let rec = getCurrentActivationRecord(); // good default: - assert.deepEqual(rec, { + assert.deepStrictEqual(rec, { count: [0, 0, 0, 0, 0, 0, 0], mostRecentBucket: Date.now(), }); @@ -454,7 +454,7 @@ suite('Experiment Service', () => { rec = getCurrentActivationRecord(rec); // does not advance unnecessarily - assert.deepEqual(getCurrentActivationRecord(rec), { + assert.deepStrictEqual(getCurrentActivationRecord(rec), { count: [1, 0, 0, 0, 0, 0, 0], mostRecentBucket: Date.now() - 1, }); @@ -462,7 +462,7 @@ suite('Experiment Service', () => { // advances time timers.tick(oneDay * 3); rec = getCurrentActivationRecord(rec); - assert.deepEqual(getCurrentActivationRecord(rec), { + assert.deepStrictEqual(getCurrentActivationRecord(rec), { count: [0, 0, 0, 1, 0, 0, 0], mostRecentBucket: Date.now() - 1, }); @@ -471,7 +471,7 @@ suite('Experiment Service', () => { timers.tick(oneDay * 4); rec.count[0] = 2; rec = getCurrentActivationRecord(rec); - assert.deepEqual(getCurrentActivationRecord(rec), { + assert.deepStrictEqual(getCurrentActivationRecord(rec), { count: [0, 0, 0, 0, 2, 0, 0], mostRecentBucket: Date.now() - 1, }); @@ -503,9 +503,9 @@ suite('Experiment Service', () => { instantiationService.stub(IStorageService, 'store', (key: string, value: string, scope: StorageScope) => { if (key.includes('experimentEventRecord')) { didGetCall = true; - assert.equal(key, 'experimentEventRecord-my-event'); - assert.deepEqual(JSON.parse(value).count, [1, 0, 10, 0, 0, 0, 0]); - assert.equal(scope, StorageScope.GLOBAL); + assert.strictEqual(key, 'experimentEventRecord-my-event'); + assert.deepStrictEqual(JSON.parse(value).count, [1, 0, 10, 0, 0, 0, 0]); + assert.strictEqual(scope, StorageScope.GLOBAL); } }); @@ -543,14 +543,14 @@ suite('Experiment Service', () => { testObject = instantiationService.createInstance(TestExperimentService); testObject.onExperimentEnabled(enabledListener); - assert.equal((await testObject.getExperimentById('experiment1')).state, ExperimentState.Evaluating); - assert.equal((await testObject.getExperimentById('experiment1')).state, ExperimentState.Evaluating); - assert.equal(enabledListener.callCount, 0); + assert.strictEqual((await testObject.getExperimentById('experiment1')).state, ExperimentState.Evaluating); + assert.strictEqual((await testObject.getExperimentById('experiment1')).state, ExperimentState.Evaluating); + assert.strictEqual(enabledListener.callCount, 0); activationEvent.fire({ event: 'my:event', activation: Promise.resolve() }); await timeout(1); - assert.equal(enabledListener.callCount, 1); - assert.equal((await testObject.getExperimentById('experiment1')).state, ExperimentState.Run); + assert.strictEqual(enabledListener.callCount, 1); + assert.strictEqual((await testObject.getExperimentById('experiment1')).state, ExperimentState.Run); }); test('Experiment not matching user setting should be disabled', () => { @@ -570,8 +570,8 @@ suite('Experiment Service', () => { (key: string) => key === 'neat' ? false : undefined); testObject = instantiationService.createInstance(TestExperimentService); return testObject.getExperimentById('experiment1').then(result => { - assert.equal(result.enabled, true); - assert.equal(result.state, ExperimentState.NoRun); + assert.strictEqual(result.enabled, true); + assert.strictEqual(result.state, ExperimentState.NoRun); }); }); @@ -592,8 +592,8 @@ suite('Experiment Service', () => { (key: string) => key === 'neat' ? true : undefined); testObject = instantiationService.createInstance(TestExperimentService); return testObject.getExperimentById('experiment1').then(result => { - assert.equal(result.enabled, true); - assert.equal(result.state, ExperimentState.Run); + assert.strictEqual(result.enabled, true); + assert.strictEqual(result.state, ExperimentState.Run); }); }); @@ -612,8 +612,8 @@ suite('Experiment Service', () => { testObject = instantiationService.createInstance(TestExperimentService); return testObject.getExperimentById('experiment1').then(result => { - assert.equal(result.enabled, true); - assert.equal(result.state, ExperimentState.NoRun); + assert.strictEqual(result.enabled, true); + assert.strictEqual(result.state, ExperimentState.NoRun); }); }); @@ -634,8 +634,8 @@ suite('Experiment Service', () => { testObject = instantiationService.createInstance(TestExperimentService); return testObject.getExperimentById('experiment1').then(result => { - assert.equal(result.enabled, true); - assert.equal(result.state, ExperimentState.Run); + assert.strictEqual(result.enabled, true); + assert.strictEqual(result.state, ExperimentState.Run); }); }); @@ -656,8 +656,8 @@ suite('Experiment Service', () => { testObject = instantiationService.createInstance(TestExperimentService); return testObject.getExperimentById('experiment1').then(result => { - assert.equal(result.enabled, true); - assert.equal(result.state, ExperimentState.NoRun); + assert.strictEqual(result.enabled, true); + assert.strictEqual(result.state, ExperimentState.NoRun); }); }); @@ -678,8 +678,8 @@ suite('Experiment Service', () => { testObject = instantiationService.createInstance(TestExperimentService); return testObject.getExperimentById('experiment1').then(result => { - assert.equal(result.enabled, true); - assert.equal(result.state, ExperimentState.NoRun); + assert.strictEqual(result.enabled, true); + assert.strictEqual(result.state, ExperimentState.NoRun); }); }); @@ -705,8 +705,8 @@ suite('Experiment Service', () => { testObject = instantiationService.createInstance(TestExperimentService); return testObject.getExperimentById('experiment1').then(result => { - assert.equal(result.enabled, true); - assert.equal(result.state, ExperimentState.Complete); + assert.strictEqual(result.enabled, true); + assert.strictEqual(result.state, ExperimentState.Complete); }); }); @@ -732,8 +732,8 @@ suite('Experiment Service', () => { }); testObject = instantiationService.createInstance(TestExperimentService); return testObject.getExperimentById('experiment1').then(result => { - assert.equal(result.enabled, true); - assert.equal(result.state, ExperimentState.Run); + assert.strictEqual(result.enabled, true); + assert.strictEqual(result.state, ExperimentState.Run); }); }); @@ -769,10 +769,10 @@ suite('Experiment Service', () => { testObject = instantiationService.createInstance(TestExperimentService); return testObject.getExperimentById('experiment1').then(result => { - assert.equal(result.enabled, true); - assert.equal(result.state, ExperimentState.Run); + assert.strictEqual(result.enabled, true); + assert.strictEqual(result.state, ExperimentState.Run); return testObject.getCuratedExtensionsList(curatedExtensionsKey).then(curatedList => { - assert.equal(curatedList, curatedExtensionsList); + assert.strictEqual(curatedList, curatedExtensionsList); }); }); }); @@ -809,11 +809,11 @@ suite('Experiment Service', () => { testObject = instantiationService.createInstance(TestExperimentService); return testObject.getExperimentById('experiment1').then(result => { - assert.equal(result.enabled, false); - assert.equal(result.action?.type, 'Prompt'); - assert.equal(result.state, ExperimentState.NoRun); + assert.strictEqual(result.enabled, false); + assert.strictEqual(result.action?.type, 'Prompt'); + assert.strictEqual(result.state, ExperimentState.NoRun); return testObject.getCuratedExtensionsList(curatedExtensionsKey).then(curatedList => { - assert.equal(curatedList.length, 0); + assert.strictEqual(curatedList.length, 0); }); }); }); @@ -837,7 +837,7 @@ suite('Experiment Service', () => { testObject = instantiationService.createInstance(TestExperimentService); return testObject.getExperimentById('experiment1').then(result => { - assert.equal(result.action?.type, 'Prompt'); + assert.strictEqual(result.action?.type, 'Prompt'); }); }); @@ -906,16 +906,16 @@ suite('Experiment Service', () => { testObject = instantiationService.createInstance(TestExperimentService); const disabledExperiment = testObject.getExperimentById('experiment1').then(result => { - assert.equal(result.enabled, false); - assert.equal(!!storageDataExperiment1, false); + assert.strictEqual(result.enabled, false); + assert.strictEqual(!!storageDataExperiment1, false); }); const deletedExperiment = testObject.getExperimentById('experiment2').then(result => { - assert.equal(!!result, false); - assert.equal(!!storageDataExperiment2, false); + assert.strictEqual(!!result, false); + assert.strictEqual(!!storageDataExperiment2, false); }); return Promise.all([disabledExperiment, deletedExperiment]).then(() => { - assert.equal(storageDataAllExperiments!.length, 1); - assert.equal(storageDataAllExperiments![0], 'experiment3'); + assert.strictEqual(storageDataAllExperiments!.length, 1); + assert.strictEqual(storageDataAllExperiments![0], 'experiment3'); }); }); @@ -1001,21 +1001,21 @@ suite('Experiment Service', () => { tests.push(testObject.getExperimentById('experiment4')); return Promise.all(tests).then(results => { - assert.equal(results[0].id, 'experiment1'); - assert.equal(results[0].enabled, true); - assert.equal(results[0].state, ExperimentState.Run); + assert.strictEqual(results[0].id, 'experiment1'); + assert.strictEqual(results[0].enabled, true); + assert.strictEqual(results[0].state, ExperimentState.Run); - assert.equal(results[1].id, 'experiment2'); - assert.equal(results[1].enabled, true); - assert.equal(results[1].state, ExperimentState.NoRun); + assert.strictEqual(results[1].id, 'experiment2'); + assert.strictEqual(results[1].enabled, true); + assert.strictEqual(results[1].state, ExperimentState.NoRun); - assert.equal(results[2].id, 'experiment3'); - assert.equal(results[2].enabled, true); - assert.equal(results[2].state, ExperimentState.Evaluating); + assert.strictEqual(results[2].id, 'experiment3'); + assert.strictEqual(results[2].enabled, true); + assert.strictEqual(results[2].state, ExperimentState.Evaluating); - assert.equal(results[3].id, 'experiment4'); - assert.equal(results[3].enabled, true); - assert.equal(results[3].state, ExperimentState.Complete); + assert.strictEqual(results[3].id, 'experiment4'); + assert.strictEqual(results[3].enabled, true); + assert.strictEqual(results[3].state, ExperimentState.Complete); }); }); @@ -1075,17 +1075,17 @@ suite('Experiment Service', () => { testObject = instantiationService.createInstance(TestExperimentService); const custom = testObject.getExperimentsByType(ExperimentActionType.Custom).then(result => { - assert.equal(result.length, 3); - assert.equal(result[0].id, 'simple-experiment'); - assert.equal(result[1].id, 'custom-experiment'); - assert.equal(result[1].action!.properties, customProperties); - assert.equal(result[2].id, 'custom-experiment-no-properties'); - assert.equal(!!result[2].action!.properties, true); + assert.strictEqual(result.length, 3); + assert.strictEqual(result[0].id, 'simple-experiment'); + assert.strictEqual(result[1].id, 'custom-experiment'); + assert.strictEqual(result[1].action!.properties, customProperties); + assert.strictEqual(result[2].id, 'custom-experiment-no-properties'); + assert.strictEqual(!!result[2].action!.properties, true); }); const prompt = testObject.getExperimentsByType(ExperimentActionType.Prompt).then(result => { - assert.equal(result.length, 2); - assert.equal(result[0].id, 'prompt-with-no-commands'); - assert.equal(result[1].id, 'prompt-with-commands'); + assert.strictEqual(result.length, 2); + assert.strictEqual(result[0].id, 'prompt-with-no-commands'); + assert.strictEqual(result[1].id, 'prompt-with-commands'); }); return Promise.all([custom, prompt]); }); @@ -1144,13 +1144,13 @@ suite('Experiment Service', () => { testObject = instantiationService.createInstance(TestExperimentService); return testObject.getExperimentsByType(ExperimentActionType.Custom).then(result => { - assert.equal(result.length, 2); - assert.equal(result[0].id, 'experiment3'); - assert.equal(result[0].state, ExperimentState.NoRun); - assert.equal(result[1].id, 'experiment4'); - assert.equal(result[1].state, ExperimentState.Run); - assert.equal(storageDataExperiment3.state, ExperimentState.NoRun); - assert.equal(storageDataExperiment4.state, ExperimentState.Run); + assert.strictEqual(result.length, 2); + assert.strictEqual(result[0].id, 'experiment3'); + assert.strictEqual(result[0].state, ExperimentState.NoRun); + assert.strictEqual(result[1].id, 'experiment4'); + assert.strictEqual(result[1].state, ExperimentState.Run); + assert.strictEqual(storageDataExperiment3.state, ExperimentState.NoRun); + assert.strictEqual(storageDataExperiment4.state, ExperimentState.Run); return Promise.resolve(null); }); }); diff --git a/src/vs/workbench/contrib/experiments/test/electron-browser/experimentalPrompts.test.ts b/src/vs/workbench/contrib/experiments/test/electron-browser/experimentalPrompts.test.ts index 584f71b2..93490e6e 100644 --- a/src/vs/workbench/contrib/experiments/test/electron-browser/experimentalPrompts.test.ts +++ b/src/vs/workbench/contrib/experiments/test/electron-browser/experimentalPrompts.test.ts @@ -96,8 +96,8 @@ suite('Experimental Prompts', () => { instantiationService.stub(INotificationService, { prompt: (a: Severity, b: string, c: IPromptChoice[]) => { - assert.equal(b, promptText); - assert.equal(c.length, 2); + assert.strictEqual(b, promptText); + assert.strictEqual(c.length, 2); c[1].run(); return undefined!; } @@ -107,7 +107,7 @@ suite('Experimental Prompts', () => { onExperimentEnabledEvent.fire(experiment); return Promise.resolve(null).then(result => { - assert.equal(storageData['state'], ExperimentState.Complete); + assert.strictEqual(storageData['state'], ExperimentState.Complete); }); }); @@ -146,7 +146,7 @@ suite('Experimental Prompts', () => { return Promise.resolve(null).then(result => { assert.deepStrictEqual(stub.args[0], ['greet', 'world']); - assert.equal(storageData['state'], ExperimentState.Complete); + assert.strictEqual(storageData['state'], ExperimentState.Complete); }); }); @@ -160,8 +160,8 @@ suite('Experimental Prompts', () => { instantiationService.stub(INotificationService, { prompt: (a: Severity, b: string, c: IPromptChoice[], options: IPromptOptions) => { - assert.equal(b, promptText); - assert.equal(c.length, 2); + assert.strictEqual(b, promptText); + assert.strictEqual(c.length, 2); options.onCancel!(); return undefined!; } @@ -171,7 +171,7 @@ suite('Experimental Prompts', () => { onExperimentEnabledEvent.fire(experiment); return Promise.resolve(null).then(result => { - assert.equal(storageData['state'], ExperimentState.Complete); + assert.strictEqual(storageData['state'], ExperimentState.Complete); }); }); @@ -203,14 +203,14 @@ suite('Experimental Prompts', () => { commands: [] }; - assert.equal(ExperimentalPrompts.getLocalizedText(simpleTextCase.promptText, 'any-language'), simpleTextCase.promptText); + assert.strictEqual(ExperimentalPrompts.getLocalizedText(simpleTextCase.promptText, 'any-language'), simpleTextCase.promptText); const multipleLocalePromptText = multipleLocaleCase.promptText as LocalizedPromptText; - assert.equal(ExperimentalPrompts.getLocalizedText(multipleLocaleCase.promptText, 'en'), multipleLocalePromptText['en']); - assert.equal(ExperimentalPrompts.getLocalizedText(multipleLocaleCase.promptText, 'de'), multipleLocalePromptText['de']); - assert.equal(ExperimentalPrompts.getLocalizedText(multipleLocaleCase.promptText, 'en-au'), multipleLocalePromptText['en-au']); - assert.equal(ExperimentalPrompts.getLocalizedText(multipleLocaleCase.promptText, 'en-gb'), multipleLocalePromptText['en']); - assert.equal(ExperimentalPrompts.getLocalizedText(multipleLocaleCase.promptText, 'fr'), multipleLocalePromptText['en']); - assert.equal(ExperimentalPrompts.getLocalizedText(englishUSTextCase.promptText, 'fr'), (englishUSTextCase.promptText as LocalizedPromptText)['en-us']); - assert.equal(!!ExperimentalPrompts.getLocalizedText(noEnglishTextCase.promptText, 'fr'), false); + assert.strictEqual(ExperimentalPrompts.getLocalizedText(multipleLocaleCase.promptText, 'en'), multipleLocalePromptText['en']); + assert.strictEqual(ExperimentalPrompts.getLocalizedText(multipleLocaleCase.promptText, 'de'), multipleLocalePromptText['de']); + assert.strictEqual(ExperimentalPrompts.getLocalizedText(multipleLocaleCase.promptText, 'en-au'), multipleLocalePromptText['en-au']); + assert.strictEqual(ExperimentalPrompts.getLocalizedText(multipleLocaleCase.promptText, 'en-gb'), multipleLocalePromptText['en']); + assert.strictEqual(ExperimentalPrompts.getLocalizedText(multipleLocaleCase.promptText, 'fr'), multipleLocalePromptText['en']); + assert.strictEqual(ExperimentalPrompts.getLocalizedText(englishUSTextCase.promptText, 'fr'), (englishUSTextCase.promptText as LocalizedPromptText)['en-us']); + assert.strictEqual(!!ExperimentalPrompts.getLocalizedText(noEnglishTextCase.promptText, 'fr'), false); }); }); diff --git a/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts b/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts index feae8000..9fb0cfc9 100644 --- a/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts @@ -137,7 +137,7 @@ export abstract class AbstractRuntimeExtensionsEditor extends EditorPane { for (let i = 0, len = extensionsDescriptions.length; i < len; i++) { const extensionDescription = extensionsDescriptions[i]; - let profileInfo: IExtensionProfileInformation | null = null; + let extProfileInfo: IExtensionProfileInformation | null = null; if (profileInfo) { let extensionSegments = segments[ExtensionIdentifier.toKey(extensionDescription.identifier)] || []; let extensionTotalTime = 0; @@ -146,7 +146,7 @@ export abstract class AbstractRuntimeExtensionsEditor extends EditorPane { const endTime = extensionSegments[2 * j + 1]; extensionTotalTime += (endTime - startTime); } - profileInfo = { + extProfileInfo = { segments: extensionSegments, totalTime: extensionTotalTime }; @@ -157,7 +157,7 @@ export abstract class AbstractRuntimeExtensionsEditor extends EditorPane { description: extensionDescription, marketplaceInfo: marketplaceMap[ExtensionIdentifier.toKey(extensionDescription.identifier)], status: statusMap[extensionDescription.identifier.value], - profileInfo: profileInfo || undefined, + profileInfo: extProfileInfo || undefined, unresponsiveProfile: this._getUnresponsiveProfile(extensionDescription.identifier) }; } @@ -477,7 +477,7 @@ export class ShowRuntimeExtensionsAction extends Action { super(id, label); } - public async run(e?: any): Promise { + public override async run(e?: any): Promise { await this._editorService.openEditor(RuntimeExtensionsInput.instance, { revealIfOpened: true, pinned: true }); } } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index 0f560a7a..3f99c17d 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -44,7 +44,7 @@ import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegis import { Color } from 'vs/base/common/color'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; -import { ExtensionsTree, ExtensionData, ExtensionsGridView, getExtensions, IExtensionsGridViewVirtualDelegate } from 'vs/workbench/contrib/extensions/browser/extensionsViewer'; +import { ExtensionsTree, ExtensionData, ExtensionsGridView, getExtensions } from 'vs/workbench/contrib/extensions/browser/extensionsViewer'; import { ShowCurrentReleaseNotesActionId } from 'vs/workbench/contrib/update/common/update'; import { KeybindingParser } from 'vs/base/common/keybindingParser'; import { IStorageService } from 'vs/platform/storage/common/storage'; @@ -58,37 +58,17 @@ import { generateUuid } from 'vs/base/common/uuid'; import { platform } from 'vs/base/common/process'; import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; -import { renderMarkdownDocument } from 'vs/workbench/contrib/markdown/common/markdownDocumentRenderer'; +import { DEFAULT_MARKDOWN_STYLES, renderMarkdownDocument } from 'vs/workbench/contrib/markdown/common/markdownDocumentRenderer'; import { IModeService } from 'vs/editor/common/services/modeService'; import { TokenizationRegistry } from 'vs/editor/common/modes'; import { generateTokensCSSForColorMap } from 'vs/editor/common/modes/supports/tokenization'; import { editorBackground } from 'vs/platform/theme/common/colorRegistry'; 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'; import { renderMarkdown } from 'vs/base/browser/markdownRenderer'; - -function removeEmbeddedSVGs(documentContent: string): string { - return insane(documentContent, { - allowedTags: [ - 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'h7', 'h8', 'br', 'b', 'i', 'strong', 'em', 'a', 'pre', 'code', 'img', 'tt', - 'div', 'ins', 'del', 'sup', 'sub', 'p', 'ol', 'ul', 'table', 'thead', 'tbody', 'tfoot', 'blockquote', 'dl', 'dt', - 'dd', 'kbd', 'q', 'samp', 'var', 'hr', 'ruby', 'rt', 'rp', 'li', 'tr', 'td', 'th', 's', 'strike', 'summary', 'details', - 'caption', 'figure', 'figcaption', 'abbr', 'bdo', 'cite', 'dfn', 'mark', 'small', 'span', 'time', 'wbr' - ], - allowedAttributes: { - '*': [ - 'align', - ], - img: ['src', 'alt', 'title', 'aria-label', 'width', 'height'], - }, - filter(token: { tag: string, attrs: { readonly [key: string]: string } }): boolean { - return token.tag !== 'svg'; - } - }); -} +import { attachKeybindingLabelStyler } from 'vs/platform/theme/common/styler'; class NavBar extends Disposable { @@ -201,6 +181,7 @@ export class ExtensionEditor extends EditorPane { private layoutParticipants: ILayoutParticipant[] = []; private readonly contentDisposables = this._register(new DisposableStore()); private readonly transientDisposables = this._register(new DisposableStore()); + private readonly keybindingLabelStylers = this.contentDisposables.add(new DisposableStore()); private activeElement: IActiveElement | null = null; private editorLoadComplete: boolean = false; @@ -209,7 +190,7 @@ export class ExtensionEditor extends EditorPane { @IInstantiationService private readonly instantiationService: IInstantiationService, @IViewletService private readonly viewletService: IViewletService, @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, - @IThemeService protected themeService: IThemeService, + @IThemeService themeService: IThemeService, @IKeybindingService private readonly keybindingService: IKeybindingService, @INotificationService private readonly notificationService: INotificationService, @IOpenerService private readonly openerService: IOpenerService, @@ -282,7 +263,8 @@ export class ExtensionEditor extends EditorPane { return new ExtensionActionWithDropdownActionViewItem(action, { icon: true, label: true, menuActionsOrProvider: { getActions: () => action.menuActions }, menuActionClassNames: (action.class || '').split(' ') }, this.contextMenuService); } return undefined; - } + }, + focusOnlyEnabledItems: true })); const subtextContainer = append(details, $('.subtext-container')); @@ -343,7 +325,7 @@ export class ExtensionEditor extends EditorPane { return disposables; } - async setInput(input: ExtensionsInput, options: EditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise { + override async setInput(input: ExtensionsInput, options: EditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise { await super.setInput(input, options, context, token); if (this.template) { await this.updateTemplate(input, this.template, !!options?.preserveFocus); @@ -464,6 +446,7 @@ export class ExtensionEditor extends EditorPane { template.extensionActionBar.clear(); template.extensionActionBar.push(actions, { icon: true, label: true }); + template.extensionActionBar.setFocusable(true); for (const disposable of [...actions, ...widgets, extensionContainers]) { this.transientDisposables.add(disposable); } @@ -522,14 +505,14 @@ export class ExtensionEditor extends EditorPane { this.transientDisposables.add(this.extensionRecommendationsService.onDidChangeRecommendations(() => updateRecommendationFn())); } - clearInput(): void { + override clearInput(): void { this.contentDisposables.clear(); this.transientDisposables.clear(); super.clearInput(); } - focus(): void { + override focus(): void { this.activeElement?.focus(); } @@ -639,11 +622,12 @@ export class ExtensionEditor extends EditorPane { return; } // Only allow links with specific schemes - if (matchesScheme(link, Schemas.http) || matchesScheme(link, Schemas.https) || matchesScheme(link, Schemas.mailto) - || (matchesScheme(link, Schemas.command) && URI.parse(link).path === ShowCurrentReleaseNotesActionId) - ) { + if (matchesScheme(link, Schemas.http) || matchesScheme(link, Schemas.https) || matchesScheme(link, Schemas.mailto)) { this.openerService.open(link); } + if (matchesScheme(link, Schemas.command) && URI.parse(link).path === ShowCurrentReleaseNotesActionId) { + this.openerService.open(link, { allowCommands: true }); // TODO@sandy081 use commands service + } }, null, this.contentDisposables)); return webview; @@ -657,8 +641,7 @@ export class ExtensionEditor extends EditorPane { private async renderMarkdown(cacheResult: CacheResult, template: IExtensionEditorTemplate) { const contents = await this.loadContents(() => cacheResult, template); const content = await renderMarkdownDocument(contents, this.extensionService, this.modeService); - const sanitizedContent = removeEmbeddedSVGs(content); - return await this.renderBody(sanitizedContent); + return this.renderBody(content); } private async renderBody(body: string): Promise { @@ -671,100 +654,7 @@ export class ExtensionEditor extends EditorPane { @@ -1045,9 +897,7 @@ export class ExtensionEditor extends EditorPane { const scrollableContent = new DomScrollableElement(content, { useShadows: false }); append(parent, scrollableContent.getDomNode()); - const extensionsGridView = this.instantiationService.createInstance(ExtensionsGridView, content, new class ExtensionsGridViewVirtualDelegate extends Delegate implements IExtensionsGridViewVirtualDelegate { - getWidth() { return 275; } - }); + const extensionsGridView = this.instantiationService.createInstance(ExtensionsGridView, content, new Delegate()); const extensions: IExtension[] = await getExtensions(manifest.extensionPack!, this.extensionsWorkbenchService); extensionsGridView.setExtensions(extensions); scrollableContent.scanDomNode(); @@ -1427,9 +1277,12 @@ export class ExtensionEditor extends EditorPane { return false; } + this.keybindingLabelStylers.clear(); const renderKeybinding = (keybinding: ResolvedKeybinding): HTMLElement => { const element = $(''); - new KeybindingLabel(element, OS).set(keybinding); + const kbl = new KeybindingLabel(element, OS); + kbl.set(keybinding); + this.keybindingLabelStylers.add(attachKeybindingLabelStyler(kbl, this.themeService)); return element; }; diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEnablementByWorkspaceTrustRequirement.ts b/src/vs/workbench/contrib/extensions/browser/extensionEnablementByWorkspaceTrustRequirement.ts new file mode 100644 index 00000000..9cd55e30 --- /dev/null +++ b/src/vs/workbench/contrib/extensions/browser/extensionEnablementByWorkspaceTrustRequirement.ts @@ -0,0 +1,36 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable } from 'vs/base/common/lifecycle'; +import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust'; +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { IWorkbenchExtensionEnablementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; + + +export class ExtensionEnablementByWorkspaceTrustRequirement extends Disposable implements IWorkbenchContribution { + + constructor( + @IWorkspaceTrustManagementService workspaceTrustManagementService: IWorkspaceTrustManagementService, + @IExtensionService private readonly extensionService: IExtensionService, + @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService, + ) { + super(); + + this._register(workspaceTrustManagementService.onDidChangeTrust(trusted => this.onDidChangeTrustState(trusted))); + } + + private async onDidChangeTrustState(trusted: boolean): Promise { + if (trusted) { + // Untrusted -> Trusted + await this.extensionEnablementService.updateEnablementByWorkspaceTrustRequirement(); + } else { + // Trusted -> Untrusted + this.extensionService.stopExtensionHosts(); + await this.extensionEnablementService.updateEnablementByWorkspaceTrustRequirement(); + this.extensionService.startExtensionHosts(); + } + } +} diff --git a/src/vs/workbench/contrib/extensions/browser/extensionRecommendationsService.ts b/src/vs/workbench/contrib/extensions/browser/extensionRecommendationsService.ts index 60fa348e..5477d214 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionRecommendationsService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionRecommendationsService.ts @@ -105,7 +105,7 @@ export class ExtensionRecommendationsService extends Disposable implements IExte } private isEnabled(): boolean { - return this.galleryService.isEnabled() && !this.environmentService.extensionDevelopmentLocationURI; + return this.galleryService.isEnabled() && !this.environmentService.isExtensionDevelopment; } private async activateProactiveRecommendations(): Promise { diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index 49010ad5..41e39af9 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -26,7 +26,7 @@ import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/c import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeymapExtensions } from 'vs/workbench/contrib/extensions/common/extensionsUtils'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; -import { EditorDescriptor, IEditorRegistry, Extensions as EditorExtensions } from 'vs/workbench/browser/editor'; +import { EditorDescriptor, IEditorRegistry } from 'vs/workbench/browser/editor'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { URI, UriComponents } from 'vs/base/common/uri'; import { ExtensionActivationProgress } from 'vs/workbench/contrib/extensions/browser/extensionsActivationProgress'; @@ -36,7 +36,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { IViewContainersRegistry, ViewContainerLocation, Extensions as ViewContainerExtensions, IViewsService } from 'vs/workbench/common/views'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; -import { ContextKeyAndExpr, ContextKeyDefinedExpr, ContextKeyEqualsExpr, ContextKeyExpr, ContextKeyOrExpr, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyAndExpr, ContextKeyDefinedExpr, ContextKeyEqualsExpr, ContextKeyExpr, ContextKeyNotEqualsExpr, ContextKeyOrExpr, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IQuickAccessRegistry, Extensions } from 'vs/platform/quickinput/common/quickAccess'; import { InstallExtensionQuickAccessProvider, ManageExtensionsQuickAccessProvider } from 'vs/workbench/contrib/extensions/browser/extensionsQuickAccess'; @@ -60,6 +60,7 @@ import { IAction } from 'vs/base/common/actions'; import { IWorkpsaceExtensionsConfigService } from 'vs/workbench/services/extensionRecommendations/common/workspaceExtensionsConfig'; import { Schemas } from 'vs/base/common/network'; import { ShowRuntimeExtensionsAction } from 'vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor'; +import { ExtensionEnablementByWorkspaceTrustRequirement } from 'vs/workbench/contrib/extensions/browser/extensionEnablementByWorkspaceTrustRequirement'; import { clearSearchResultsIcon, configureRecommendedIcon, extensionsViewIcon, filterIcon, installWorkspaceRecommendedIcon, refreshIcon } from 'vs/workbench/contrib/extensions/browser/extensionsIcons'; import { EXTENSION_CATEGORIES } from 'vs/platform/extensions/common/extensions'; import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; @@ -69,6 +70,8 @@ import { IDialogService, IFileDialogService } from 'vs/platform/dialogs/common/d import { mnemonicButtonLabel } from 'vs/base/common/labels'; import { Query } from 'vs/workbench/contrib/extensions/common/extensionQuery'; import { Promises } from 'vs/base/common/async'; +import { EditorExtensions } from 'vs/workbench/common/editor'; +import { WORKSPACE_TRUST_EXTENSION_SUPPORT } from 'vs/workbench/services/workspaces/common/workspaceTrust'; // Singletons registerSingleton(IExtensionsWorkbenchService, ExtensionsWorkbenchService); @@ -124,8 +127,18 @@ Registry.as(ConfigurationExtensions.Configuration) type: 'object', properties: { 'extensions.autoUpdate': { - type: 'boolean', - description: localize('extensionsAutoUpdate', "When enabled, automatically installs updates for extensions. The updates are fetched from a Microsoft online service."), + enum: [true, 'onlyEnabledExtensions', false,], + enumItemLabels: [ + localize('all', "All Extensions"), + localize('enabled', "Only Enabled Extensions"), + localize('none', "None"), + ], + enumDescriptions: [ + localize('extensions.autoUpdate.true', 'Download and install updates automatically for all extensions.'), + localize('extensions.autoUpdate.enabled', 'Download and install updates automatically only for enabled extensions. Disabled extensions will not be updated automatically.'), + localize('extensions.autoUpdate.false', 'Extensions are not automatically updated.'), + ], + description: localize('extensions.autoUpdate', "Controls the automatic update behavior of extensions. The updates are fetched from a Microsoft online service."), default: true, scope: ConfigurationScope.APPLICATION, tags: ['usesOnlineServices'] @@ -163,6 +176,45 @@ Registry.as(ConfigurationExtensions.Configuration) type: 'boolean', description: localize('extensionsWebWorker', "Enable web worker extension host."), default: false + }, + 'extensions.supportVirtualWorkspaces': { + type: 'object', + markdownDescription: localize('extensions.supportVirtualWorkspaces', "Override the virtual workspaces support of an extension."), + patternProperties: { + '([a-z0-9A-Z][a-z0-9\-A-Z]*)\\.([a-z0-9A-Z][a-z0-9\-A-Z]*)$': { + type: 'boolean', + default: false + } + }, + default: { + 'pub.name': false + } + }, + [WORKSPACE_TRUST_EXTENSION_SUPPORT]: { + type: 'object', + scope: ConfigurationScope.APPLICATION, + markdownDescription: localize('extensions.supportUntrustedWorkspaces', "Override the untrusted workpace support of an extension. Extensions using `true` will always be enabled. Extensions using `limited` will always be enabled, and the extension will hide functionality that requires trust. Extensions using `false` will only be enabled only when the workspace is trusted."), + patternProperties: { + '([a-z0-9A-Z][a-z0-9\-A-Z]*)\\.([a-z0-9A-Z][a-z0-9\-A-Z]*)$': { + type: 'object', + properties: { + 'supported': { + type: ['boolean', 'string'], + enum: [true, false, 'limited'], + enumDescriptions: [ + localize('extensions.supportUntrustedWorkspaces.true', "Extension will always be enabled."), + localize('extensions.supportUntrustedWorkspaces.false', "Extension will only be enabled only when the workspace is trusted."), + localize('extensions.supportUntrustedWorkspaces.limited', "Extension will always be enabled, and the extension will hide functionality requiring trust."), + ], + description: localize('extensions.supportUntrustedWorkspaces.supported', "Defines the untrusted workspace support setting for the extension."), + }, + 'version': { + type: 'string', + description: localize('extensions.supportUntrustedWorkspaces.version', "Defines the version of the extension for which the override should be applied. If not specified, the override will be applied independent of the extension version."), + } + } + } + } } } }); @@ -475,17 +527,44 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi } }); + const autoUpdateExtensionsSubMenu = new MenuId('autoUpdateExtensionsSubMenu'); + MenuRegistry.appendMenuItem(MenuId.ViewContainerTitle, { + submenu: autoUpdateExtensionsSubMenu, + title: localize('configure auto updating extensions', "Auto Update Extensions"), + when: ContextKeyEqualsExpr.create('viewContainer', VIEWLET_ID), + group: '1_updates', + order: 5, + }); + this.registerExtensionAction({ - id: 'workbench.extensions.action.disableAutoUpdate', - title: { value: localize('disableAutoUpdate', "Disable Auto Updating Extensions"), original: 'Disable Auto Updating Extensions' }, - category: ExtensionsLocalizedLabel, + id: 'configureExtensionsAutoUpdate.all', + title: localize('configureExtensionsAutoUpdate.all', "All Extensions"), + toggled: ContextKeyAndExpr.create([ContextKeyDefinedExpr.create(`config.${AutoUpdateConfigurationKey}`), ContextKeyNotEqualsExpr.create(`config.${AutoUpdateConfigurationKey}`, 'onlyEnabledExtensions')]), menu: [{ - id: MenuId.CommandPalette, - }, { - id: MenuId.ViewContainerTitle, - when: ContextKeyAndExpr.create([ContextKeyEqualsExpr.create('viewContainer', VIEWLET_ID), ContextKeyDefinedExpr.create(`config.${AutoUpdateConfigurationKey}`)]), - group: '1_updates', - order: 2 + id: autoUpdateExtensionsSubMenu, + order: 1, + }], + run: (accessor: ServicesAccessor) => accessor.get(IConfigurationService).updateValue(AutoUpdateConfigurationKey, true) + }); + + this.registerExtensionAction({ + id: 'configureExtensionsAutoUpdate.enabled', + title: localize('configureExtensionsAutoUpdate.enabled', "Only Enabled Extensions"), + toggled: ContextKeyEqualsExpr.create(`config.${AutoUpdateConfigurationKey}`, 'onlyEnabledExtensions'), + menu: [{ + id: autoUpdateExtensionsSubMenu, + order: 2, + }], + run: (accessor: ServicesAccessor) => accessor.get(IConfigurationService).updateValue(AutoUpdateConfigurationKey, 'onlyEnabledExtensions') + }); + + this.registerExtensionAction({ + id: 'configureExtensionsAutoUpdate.none', + title: localize('configureExtensionsAutoUpdate.none', "None"), + toggled: ContextKeyEqualsExpr.create(`config.${AutoUpdateConfigurationKey}`, false), + menu: [{ + id: autoUpdateExtensionsSubMenu, + order: 3, }], run: (accessor: ServicesAccessor) => accessor.get(IConfigurationService).updateValue(AutoUpdateConfigurationKey, false) }); @@ -500,7 +579,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi when: ContextKeyAndExpr.create([CONTEXT_HAS_GALLERY, ContextKeyOrExpr.create([CONTEXT_HAS_LOCAL_SERVER, CONTEXT_HAS_REMOTE_SERVER, CONTEXT_HAS_WEB_SERVER])]) }, { id: MenuId.ViewContainerTitle, - when: ContextKeyAndExpr.create([ContextKeyEqualsExpr.create('viewContainer', VIEWLET_ID), ContextKeyDefinedExpr.create(`config.${AutoUpdateConfigurationKey}`).negate()]), + when: ContextKeyAndExpr.create([ContextKeyEqualsExpr.create('viewContainer', VIEWLET_ID), ContextKeyOrExpr.create([ContextKeyDefinedExpr.create(`config.${AutoUpdateConfigurationKey}`).negate(), ContextKeyEqualsExpr.create(`config.${AutoUpdateConfigurationKey}`, 'onlyEnabledExtensions')])]), group: '1_updates', order: 2 }], @@ -516,17 +595,18 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi }); this.registerExtensionAction({ - id: 'workbench.extensions.action.enableAutoUpdate', - title: { value: localize('enableAutoUpdate', "Enable Auto Updating Extensions"), original: 'Enable Auto Updating Extensions' }, + id: 'workbench.extensions.action.disableAutoUpdate', + title: { value: localize('disableAutoUpdate', "Disable Auto Update for all extensions"), original: 'Disable Auto Update for all extensions' }, category: ExtensionsLocalizedLabel, - menu: [{ - id: MenuId.CommandPalette, - }, { - id: MenuId.ViewContainerTitle, - when: ContextKeyAndExpr.create([ContextKeyEqualsExpr.create('viewContainer', VIEWLET_ID), ContextKeyDefinedExpr.create(`config.${AutoUpdateConfigurationKey}`).negate()]), - group: '1_updates', - order: 3 - }], + f1: true, + run: (accessor: ServicesAccessor) => accessor.get(IConfigurationService).updateValue(AutoUpdateConfigurationKey, false) + }); + + this.registerExtensionAction({ + id: 'workbench.extensions.action.enableAutoUpdate', + title: { value: localize('enableAutoUpdate', "Enable Auto Update for all extensions"), original: 'Enable Auto Update for all extensions' }, + category: ExtensionsLocalizedLabel, + f1: true, run: (accessor: ServicesAccessor) => accessor.get(IConfigurationService).updateValue(AutoUpdateConfigurationKey, true) }); @@ -795,6 +875,13 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi run: () => runAction(this.instantiationService.createInstance(SearchExtensionsAction, '@builtin ')) }); + this.registerExtensionAction({ + id: 'workbench.extensions.action.listTrustRequiredExtensions', + title: { value: localize('showTrustRequiredExtensions', "Show Extensions Requiring Trust"), original: 'Show Extensions Requiring Trust' }, + category: ExtensionsLocalizedLabel, + run: () => runAction(this.instantiationService.createInstance(SearchExtensionsAction, '@trustRequired')) + }); + this.registerExtensionAction({ id: 'workbench.extensions.action.showInstalledExtensions', title: { value: localize('showInstalledExtensions', "Show Installed Extensions"), original: 'Show Installed Extensions' }, @@ -1238,6 +1325,7 @@ workbenchRegistry.registerWorkbenchContribution(KeymapExtensions, LifecyclePhase workbenchRegistry.registerWorkbenchContribution(ExtensionsViewletViewsContribution, LifecyclePhase.Starting); workbenchRegistry.registerWorkbenchContribution(ExtensionActivationProgress, LifecyclePhase.Eventually); workbenchRegistry.registerWorkbenchContribution(ExtensionDependencyChecker, LifecyclePhase.Eventually); +workbenchRegistry.registerWorkbenchContribution(ExtensionEnablementByWorkspaceTrustRequirement, LifecyclePhase.Restored); // Running Extensions const actionRegistry = Registry.as(WorkbenchActionExtensions.WorkbenchActions); diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.web.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.web.contribution.ts index f5d4587b..fb4812b5 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.web.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.web.contribution.ts @@ -6,9 +6,10 @@ import { localize } from 'vs/nls'; import { Registry } from 'vs/platform/registry/common/platform'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; -import { EditorDescriptor, IEditorRegistry, Extensions as EditorExtensions } from 'vs/workbench/browser/editor'; +import { EditorDescriptor, IEditorRegistry } from 'vs/workbench/browser/editor'; import { RuntimeExtensionsEditor } from 'vs/workbench/contrib/extensions/browser/browserRuntimeExtensionsEditor'; import { RuntimeExtensionsInput } from 'vs/workbench/contrib/extensions/common/runtimeExtensionsInput'; +import { EditorExtensions } from 'vs/workbench/common/editor'; // Running Extensions Registry.as(EditorExtensions.Editors).registerEditor( diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index 0a03f1f7..458801de 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -45,7 +45,6 @@ import { alert } from 'vs/base/browser/ui/aria/aria'; import { coalesce } from 'vs/base/common/arrays'; import { IWorkbenchThemeService, IWorkbenchTheme, IWorkbenchColorTheme, IWorkbenchFileIconTheme, IWorkbenchProductIconTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { ILabelService } from 'vs/platform/label/common/label'; -import { ExtensionKindController } from 'vs/workbench/services/extensions/common/extensionsUtil'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IProductService } from 'vs/platform/product/common/productService'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; @@ -59,8 +58,9 @@ import { IContextMenuProvider } from 'vs/base/browser/contextmenu'; import { ILogService } from 'vs/platform/log/common/log'; import * as Constants from 'vs/workbench/contrib/logs/common/logConstants'; import { infoIcon, manageExtensionIcon, syncEnabledIcon, syncIgnoredIcon, trustIcon, warningIcon } from 'vs/workbench/contrib/extensions/browser/extensionsIcons'; -import { IWorkspaceTrustService } from 'vs/platform/workspace/common/workspaceTrust'; import { isWeb } from 'vs/base/common/platform'; +import { isWorkspaceTrustEnabled } from 'vs/workbench/services/workspaces/common/workspaceTrust'; +import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService'; function getRelativeDateLabel(date: Date): string { const delta = new Date().getTime() - date.getTime(); @@ -113,7 +113,7 @@ export class PromptExtensionInstallFailureAction extends Action { super('extension.promptExtensionInstallFailure'); } - async run(): Promise { + override async run(): Promise { if (isPromiseCanceledError(this.error)) { return; } @@ -178,11 +178,11 @@ export class ActionWithDropDownAction extends ExtensionAction { private _menuActions: IAction[] = []; get menuActions(): IAction[] { return [...this._menuActions]; } - get extension(): IExtension | null { + override get extension(): IExtension | null { return super.extension; } - set extension(extension: IExtension | null) { + override set extension(extension: IExtension | null) { this.actions.forEach(a => a.extension = extension); super.extension = extension; } @@ -220,7 +220,7 @@ export class ActionWithDropDownAction extends ExtensionAction { this.class = clazz; } - run(): Promise { + override run(): Promise { const enabledActions = this.actions.filter(a => a.enabled); return enabledActions[0].run(); } @@ -259,7 +259,7 @@ export abstract class AbstractInstallAction extends ExtensionAction { } } - async run(): Promise { + override async run(): Promise { if (!this.extension) { return; } @@ -393,7 +393,7 @@ export class InstallAndSyncAction extends AbstractInstallAction { } - update(): void { + override update(): void { super.update(); if (this.enabled) { this.enabled = this.userDataAutoSyncEnablementService.isEnabled() && this.userDataSyncResourceEnablementService.isResourceEnabled(SyncResource.Extensions); @@ -448,8 +448,6 @@ export abstract class InstallInOtherServerAction extends ExtensionAction { private static readonly Class = `${ExtensionAction.LABEL_ACTION_CLASS} prominent install`; private static readonly InstallingClass = `${ExtensionAction.LABEL_ACTION_CLASS} install installing`; - private _extensionKindController: ExtensionKindController; - updateWhenCounterExtensionChanges: boolean = true; constructor( @@ -460,11 +458,10 @@ export abstract class InstallInOtherServerAction extends ExtensionAction { @IExtensionManagementServerService protected readonly extensionManagementServerService: IExtensionManagementServerService, @IProductService productService: IProductService, @IConfigurationService configurationService: IConfigurationService, + @IExtensionManifestPropertiesService private readonly extensionManifestPropertiesService: IExtensionManifestPropertiesService, ) { super(id, InstallInOtherServerAction.INSTALL_LABEL, InstallInOtherServerAction.Class, false); this.update(); - - this._extensionKindController = new ExtensionKindController(productService, configurationService); } update(): void { @@ -506,28 +503,28 @@ export abstract class InstallInOtherServerAction extends ExtensionAction { } // Prefers to run on UI - if (this.server === this.extensionManagementServerService.localExtensionManagementServer && this._extensionKindController.prefersExecuteOnUI(this.extension.local.manifest)) { + if (this.server === this.extensionManagementServerService.localExtensionManagementServer && this.extensionManifestPropertiesService.prefersExecuteOnUI(this.extension.local.manifest)) { return true; } // Prefers to run on Workspace - if (this.server === this.extensionManagementServerService.remoteExtensionManagementServer && this._extensionKindController.prefersExecuteOnWorkspace(this.extension.local.manifest)) { + if (this.server === this.extensionManagementServerService.remoteExtensionManagementServer && this.extensionManifestPropertiesService.prefersExecuteOnWorkspace(this.extension.local.manifest)) { return true; } // Prefers to run on Web - if (this.server === this.extensionManagementServerService.webExtensionManagementServer && this._extensionKindController.prefersExecuteOnWeb(this.extension.local.manifest)) { + if (this.server === this.extensionManagementServerService.webExtensionManagementServer && this.extensionManifestPropertiesService.prefersExecuteOnWeb(this.extension.local.manifest)) { return true; } if (this.canInstallAnyWhere) { // Can run on UI - if (this.server === this.extensionManagementServerService.localExtensionManagementServer && this._extensionKindController.canExecuteOnUI(this.extension.local.manifest)) { + if (this.server === this.extensionManagementServerService.localExtensionManagementServer && this.extensionManifestPropertiesService.canExecuteOnUI(this.extension.local.manifest)) { return true; } // Can run on Workspace - if (this.server === this.extensionManagementServerService.remoteExtensionManagementServer && this._extensionKindController.canExecuteOnWorkspace(this.extension.local.manifest)) { + if (this.server === this.extensionManagementServerService.remoteExtensionManagementServer && this.extensionManifestPropertiesService.canExecuteOnWorkspace(this.extension.local.manifest)) { return true; } } @@ -535,7 +532,7 @@ export abstract class InstallInOtherServerAction extends ExtensionAction { return false; } - async run(): Promise { + override async run(): Promise { if (!this.extension) { return; } @@ -562,8 +559,9 @@ export class RemoteInstallAction extends InstallInOtherServerAction { @IExtensionManagementServerService extensionManagementServerService: IExtensionManagementServerService, @IProductService productService: IProductService, @IConfigurationService configurationService: IConfigurationService, + @IExtensionManifestPropertiesService extensionManifestPropertiesService: IExtensionManifestPropertiesService, ) { - super(`extensions.remoteinstall`, extensionManagementServerService.remoteExtensionManagementServer, canInstallAnyWhere, extensionsWorkbenchService, extensionManagementServerService, productService, configurationService); + super(`extensions.remoteinstall`, extensionManagementServerService.remoteExtensionManagementServer, canInstallAnyWhere, extensionsWorkbenchService, extensionManagementServerService, productService, configurationService, extensionManifestPropertiesService); } protected getInstallLabel(): string { @@ -581,8 +579,9 @@ export class LocalInstallAction extends InstallInOtherServerAction { @IExtensionManagementServerService extensionManagementServerService: IExtensionManagementServerService, @IProductService productService: IProductService, @IConfigurationService configurationService: IConfigurationService, + @IExtensionManifestPropertiesService extensionManifestPropertiesService: IExtensionManifestPropertiesService, ) { - super(`extensions.localinstall`, extensionManagementServerService.localExtensionManagementServer, false, extensionsWorkbenchService, extensionManagementServerService, productService, configurationService); + super(`extensions.localinstall`, extensionManagementServerService.localExtensionManagementServer, false, extensionsWorkbenchService, extensionManagementServerService, productService, configurationService, extensionManifestPropertiesService); } protected getInstallLabel(): string { @@ -599,15 +598,16 @@ export class WebInstallAction extends InstallInOtherServerAction { @IProductService productService: IProductService, @IConfigurationService configurationService: IConfigurationService, @IWebExtensionsScannerService private readonly webExtensionsScannerService: IWebExtensionsScannerService, + @IExtensionManifestPropertiesService extensionManifestPropertiesService: IExtensionManifestPropertiesService, ) { - super(`extensions.webInstall`, extensionManagementServerService.webExtensionManagementServer, false, extensionsWorkbenchService, extensionManagementServerService, productService, configurationService); + super(`extensions.webInstall`, extensionManagementServerService.webExtensionManagementServer, false, extensionsWorkbenchService, extensionManagementServerService, productService, configurationService, extensionManifestPropertiesService); } protected getInstallLabel(): string { return localize('install browser', "Install in Browser"); } - protected canInstall(): boolean { + protected override canInstall(): boolean { if (super.canInstall()) { return !!this.extension?.gallery && this.webExtensionsScannerService.canAddExtension(this.extension.gallery); } @@ -663,7 +663,7 @@ export class UninstallAction extends ExtensionAction { this.enabled = true; } - async run(): Promise { + override async run(): Promise { if (!this.extension) { return; } @@ -711,7 +711,7 @@ export class UpdateAction extends ExtensionAction { this.label = this.extension.outdated ? this.getUpdateLabel(this.extension.latestVersion) : this.getUpdateLabel(); } - async run(): Promise { + override async run(): Promise { if (!this.extension) { return; } @@ -747,12 +747,12 @@ export class ExtensionActionWithDropdownActionViewItem extends ActionWithDropdow super(null, action, options, contextMenuProvider); } - render(container: HTMLElement): void { + override render(container: HTMLElement): void { super.render(container); this.updateClass(); } - updateClass(): void { + override updateClass(): void { super.updateClass(); if (this.element && this.dropdownMenuActionViewItem && this.dropdownMenuActionViewItem.element) { this.element.classList.toggle('empty', (this._action).menuActions.length === 0); @@ -780,7 +780,7 @@ export abstract class ExtensionDropDownAction extends ExtensionAction { return this._actionViewItem; } - public run({ actionGroups, disposeActionsOnHide }: { actionGroups: IAction[][], disposeActionsOnHide: boolean }): Promise { + public override run({ actionGroups, disposeActionsOnHide }: { actionGroups: IAction[][], disposeActionsOnHide: boolean }): Promise { if (this._actionViewItem) { this._actionViewItem.showMenu(actionGroups, disposeActionsOnHide); } @@ -917,7 +917,7 @@ export class ManageExtensionAction extends ExtensionDropDownAction { return groups; } - async run(): Promise { + override async run(): Promise { const runtimeExtensions = await this.extensionService.getExtensions(); return super.run({ actionGroups: await this.getActionGroups(runtimeExtensions), disposeActionsOnHide: true }); } @@ -945,7 +945,7 @@ export class ExtensionEditorManageExtensionAction extends ExtensionDropDownActio update(): void { } - run(): Promise { + override run(): Promise { const actionGroups: IAction[][] = []; getContextMenuActions(this.extension, true, this.instantiationService).forEach(actions => actionGroups.push(actions)); actionGroups.forEach(group => group.forEach(extensionAction => { @@ -976,9 +976,9 @@ export class MenuItemExtensionAction extends ExtensionAction { } } - async run(): Promise { + override async run(): Promise { if (this.extension) { - return this.action.run(this.extension.identifier.id); + await this.action.run(this.extension.identifier.id); } } } @@ -1002,7 +1002,7 @@ export class InstallAnotherVersionAction extends ExtensionAction { this.enabled = !!this.extension && !this.extension.isBuiltin && !!this.extension.gallery && this.extension.state === ExtensionState.Installed; } - run(): Promise { + override run(): Promise { if (!this.enabled) { return Promise.resolve(); } @@ -1055,7 +1055,7 @@ export class EnableForWorkspaceAction extends ExtensionAction { } } - async run(): Promise { + override async run(): Promise { if (!this.extension) { return; } @@ -1086,7 +1086,7 @@ export class EnableGloballyAction extends ExtensionAction { } } - async run(): Promise { + override async run(): Promise { if (!this.extension) { return; } @@ -1123,7 +1123,7 @@ export class DisableForWorkspaceAction extends ExtensionAction { } } - async run(): Promise { + override async run(): Promise { if (!this.extension) { return; } @@ -1160,7 +1160,7 @@ export class DisableGloballyAction extends ExtensionAction { } } - async run(): Promise { + override async run(): Promise { if (!this.extension) { return; } @@ -1210,22 +1210,19 @@ export class ReloadAction extends ExtensionAction { updateWhenCounterExtensionChanges: boolean = true; private _runningExtensions: IExtensionDescription[] | null = null; - private _extensionKindController: ExtensionKindController; - constructor( @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, @IHostService private readonly hostService: IHostService, @IExtensionService private readonly extensionService: IExtensionService, @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService, @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, + @IExtensionManifestPropertiesService private readonly extensionManifestPropertiesService: IExtensionManifestPropertiesService, @IProductService productService: IProductService, @IConfigurationService configurationService: IConfigurationService, ) { super('extensions.reload', localize('reloadAction', "Reload"), ReloadAction.DisabledClass, false); this._register(this.extensionService.onDidChangeExtensions(this.updateRunningExtensions, this)); this.updateRunningExtensions(); - - this._extensionKindController = new ExtensionKindController(productService, configurationService); } private updateRunningExtensions(): void { @@ -1293,7 +1290,7 @@ export class ReloadAction extends ExtensionAction { const extensionInOtherServer = this.extensionsWorkbenchService.installed.filter(e => areSameExtensions(e.identifier, this.extension!.identifier) && e.server !== this.extension!.server)[0]; if (extensionInOtherServer) { // This extension prefers to run on UI/Local side but is running in remote - if (runningExtensionServer === this.extensionManagementServerService.remoteExtensionManagementServer && this._extensionKindController.prefersExecuteOnUI(this.extension.local!.manifest)) { + if (runningExtensionServer === this.extensionManagementServerService.remoteExtensionManagementServer && this.extensionManifestPropertiesService.prefersExecuteOnUI(this.extension.local!.manifest)) { this.enabled = true; this.label = localize('reloadRequired', "Reload Required"); this.tooltip = localize('enable locally', "Please reload Visual Studio Code to enable this extension locally."); @@ -1301,7 +1298,7 @@ export class ReloadAction extends ExtensionAction { } // This extension prefers to run on Workspace/Remote side but is running in local - if (runningExtensionServer === this.extensionManagementServerService.localExtensionManagementServer && this._extensionKindController.prefersExecuteOnWorkspace(this.extension.local!.manifest)) { + if (runningExtensionServer === this.extensionManagementServerService.localExtensionManagementServer && this.extensionManifestPropertiesService.prefersExecuteOnWorkspace(this.extension.local!.manifest)) { this.enabled = true; this.label = localize('reloadRequired', "Reload Required"); this.tooltip = localize('enable remote', "Please reload Visual Studio Code to enable this extension in {0}.", this.extensionManagementServerService.remoteExtensionManagementServer?.label); @@ -1313,7 +1310,7 @@ export class ReloadAction extends ExtensionAction { if (this.extension.server === this.extensionManagementServerService.localExtensionManagementServer && runningExtensionServer === this.extensionManagementServerService.remoteExtensionManagementServer) { // This extension prefers to run on UI/Local side but is running in remote - if (this._extensionKindController.prefersExecuteOnUI(this.extension.local!.manifest)) { + if (this.extensionManifestPropertiesService.prefersExecuteOnUI(this.extension.local!.manifest)) { this.enabled = true; this.label = localize('reloadRequired', "Reload Required"); this.tooltip = localize('postEnableTooltip', "Please reload Visual Studio Code to enable this extension."); @@ -1321,7 +1318,7 @@ export class ReloadAction extends ExtensionAction { } if (this.extension.server === this.extensionManagementServerService.remoteExtensionManagementServer && runningExtensionServer === this.extensionManagementServerService.localExtensionManagementServer) { // This extension prefers to run on Workspace/Remote side but is running in local - if (this._extensionKindController.prefersExecuteOnWorkspace(this.extension.local!.manifest)) { + if (this.extensionManifestPropertiesService.prefersExecuteOnWorkspace(this.extension.local!.manifest)) { this.enabled = true; this.label = localize('reloadRequired', "Reload Required"); this.tooltip = localize('postEnableTooltip', "Please reload Visual Studio Code to enable this extension."); @@ -1364,7 +1361,7 @@ export class ReloadAction extends ExtensionAction { } } - run(): Promise { + override run(): Promise { return Promise.resolve(this.hostService.reload()); } } @@ -1419,7 +1416,7 @@ export class SetColorThemeAction extends ExtensionAction { this.class = this.enabled ? SetColorThemeAction.EnabledClass : SetColorThemeAction.DisabledClass; } - async run({ showCurrentTheme, ignoreFocusLost }: { showCurrentTheme: boolean, ignoreFocusLost: boolean } = { showCurrentTheme: false, ignoreFocusLost: false }): Promise { + override async run({ showCurrentTheme, ignoreFocusLost }: { showCurrentTheme: boolean, ignoreFocusLost: boolean } = { showCurrentTheme: false, ignoreFocusLost: false }): Promise { this.colorThemes = await this.workbenchThemeService.getColorThemes(); this.update(); @@ -1472,7 +1469,7 @@ export class SetFileIconThemeAction extends ExtensionAction { this.class = this.enabled ? SetFileIconThemeAction.EnabledClass : SetFileIconThemeAction.DisabledClass; } - async run({ showCurrentTheme, ignoreFocusLost }: { showCurrentTheme: boolean, ignoreFocusLost: boolean } = { showCurrentTheme: false, ignoreFocusLost: false }): Promise { + override async run({ showCurrentTheme, ignoreFocusLost }: { showCurrentTheme: boolean, ignoreFocusLost: boolean } = { showCurrentTheme: false, ignoreFocusLost: false }): Promise { this.fileIconThemes = await this.workbenchThemeService.getFileIconThemes(); this.update(); if (!this.enabled) { @@ -1526,7 +1523,7 @@ export class SetProductIconThemeAction extends ExtensionAction { this.class = this.enabled ? SetProductIconThemeAction.EnabledClass : SetProductIconThemeAction.DisabledClass; } - async run({ showCurrentTheme, ignoreFocusLost }: { showCurrentTheme: boolean, ignoreFocusLost: boolean } = { showCurrentTheme: false, ignoreFocusLost: false }): Promise { + override async run({ showCurrentTheme, ignoreFocusLost }: { showCurrentTheme: boolean, ignoreFocusLost: boolean } = { showCurrentTheme: false, ignoreFocusLost: false }): Promise { this.productIconThemes = await this.workbenchThemeService.getProductIconThemes(); this.update(); if (!this.enabled) { @@ -1564,7 +1561,7 @@ export class ShowRecommendedExtensionAction extends Action { this.extensionId = extensionId; } - run(): Promise { + override run(): Promise { return this.viewletService.openViewlet(VIEWLET_ID, true) .then(viewlet => viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer) .then(viewlet => { @@ -1599,7 +1596,7 @@ export class InstallRecommendedExtensionAction extends Action { this.extensionId = extensionId; } - async run(): Promise { + override async run(): Promise { const viewlet = await this.viewletService.openViewlet(VIEWLET_ID, true); const viewPaneContainer = viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer; viewPaneContainer.search(`@id:${this.extensionId}`); @@ -1634,7 +1631,7 @@ export class IgnoreExtensionRecommendationAction extends Action { this.enabled = true; } - public run(): Promise { + public override run(): Promise { this.extensionRecommendationsManagementService.toggleGlobalIgnoredRecommendation(this.extension.identifier.id, true); return Promise.resolve(); } @@ -1657,7 +1654,7 @@ export class UndoIgnoreExtensionRecommendationAction extends Action { this.enabled = true; } - public run(): Promise { + public override run(): Promise { this.extensionRecommendationsManagementService.toggleGlobalIgnoredRecommendation(this.extension.identifier.id, false); return Promise.resolve(); } @@ -1672,7 +1669,7 @@ export class SearchExtensionsAction extends Action { super('extensions.searchExtensions', localize('search recommendations', "Search Extensions"), undefined, true); } - async run(): Promise { + override async run(): Promise { const viewPaneContainer = (await this.viewletService.openViewlet(VIEWLET_ID, true))?.getViewPaneContainer() as IExtensionsViewPaneContainer; viewPaneContainer.search(this.searchValue); viewPaneContainer.focus(); @@ -1789,7 +1786,7 @@ export class ConfigureWorkspaceRecommendedExtensionsAction extends AbstractConfi this.enabled = this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY; } - public run(): Promise { + public override run(): Promise { switch (this.contextService.getWorkbenchState()) { case WorkbenchState.FOLDER: return this.openExtensionsFile(this.contextService.getWorkspace().folders[0].toResource(EXTENSIONS_CONFIG)); @@ -1819,7 +1816,7 @@ export class ConfigureWorkspaceFolderRecommendedExtensionsAction extends Abstrac super(id, label, contextService, fileService, textFileService, editorService, jsonEditingService, textModelResolverService); } - public run(): Promise { + public override run(): Promise { const folderCount = this.contextService.getWorkspace().folders.length; const pickFolderPromise = folderCount === 1 ? Promise.resolve(this.contextService.getWorkspace().folders[0]) : this.commandService.executeCommand(PICK_WORKSPACE_FOLDER_COMMAND_ID); return Promise.resolve(pickFolderPromise) @@ -1928,7 +1925,7 @@ export class StatusLabelAction extends Action implements IExtensionContainer { return null; } - run(): Promise { + override run(): Promise { return Promise.resolve(); } @@ -1953,7 +1950,7 @@ export class MaliciousStatusLabelAction extends ExtensionAction { } } - run(): Promise { + override run(): Promise { return Promise.resolve(); } } @@ -1984,7 +1981,7 @@ export class ToggleSyncExtensionAction extends ExtensionDropDownAction { } } - async run(): Promise { + override async run(): Promise { return super.run({ actionGroups: [ [ @@ -2070,7 +2067,7 @@ export class ExtensionToolTipAction extends ExtensionAction { return ''; } - run(): Promise { + override run(): Promise { return Promise.resolve(null); } } @@ -2085,24 +2082,19 @@ export class SystemDisabledWarningAction extends ExtensionAction { updateWhenCounterExtensionChanges: boolean = true; private _runningExtensions: IExtensionDescription[] | null = null; - private _extensionKindController: ExtensionKindController; - constructor( @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, @ILabelService private readonly labelService: ILabelService, @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, @IExtensionService private readonly extensionService: IExtensionService, - @IProductService productService: IProductService, - @IConfigurationService configurationService: IConfigurationService, - @IWorkspaceTrustService private readonly workspaceTrustService: IWorkspaceTrustService + @IConfigurationService private readonly configurationService: IConfigurationService, + @IExtensionManifestPropertiesService private readonly extensionManifestPropertiesService: IExtensionManifestPropertiesService, ) { super('extensions.install', '', `${SystemDisabledWarningAction.CLASS} hide`, false); this._register(this.labelService.onDidChangeFormatters(() => this.update(), this)); this._register(this.extensionService.onDidChangeExtensions(this.updateRunningExtensions, this)); this.updateRunningExtensions(); this.update(); - - this._extensionKindController = new ExtensionKindController(productService, configurationService); } private updateRunningExtensions(): void { @@ -2121,6 +2113,11 @@ export class SystemDisabledWarningAction extends ExtensionAction { ) { return; } + if (this.extension.enablementState === EnablementState.DisabledByVirtualWorkspace) { + this.class = `${SystemDisabledWarningAction.INFO_CLASS}`; + this.tooltip = localize('disabled because of virtual workspace', "This extension has been disabled because it does not support virtual workspaces."); + return; + } if (this.extensionManagementServerService.localExtensionManagementServer && this.extensionManagementServerService.remoteExtensionManagementServer) { if (isLanguagePackExtension(this.extension.local.manifest)) { if (!this.extensionsWorkbenchService.installed.some(e => areSameExtensions(e.identifier, this.extension!.identifier) && e.server !== this.extension!.server)) { @@ -2148,28 +2145,28 @@ export class SystemDisabledWarningAction extends ExtensionAction { const runningExtension = this._runningExtensions.filter(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension!.identifier))[0]; const runningExtensionServer = runningExtension ? this.extensionManagementServerService.getExtensionManagementServer(toExtension(runningExtension)) : null; if (this.extension.server === this.extensionManagementServerService.localExtensionManagementServer && runningExtensionServer === this.extensionManagementServerService.remoteExtensionManagementServer) { - if (this._extensionKindController.prefersExecuteOnWorkspace(this.extension.local!.manifest)) { + if (this.extensionManifestPropertiesService.prefersExecuteOnWorkspace(this.extension.local!.manifest)) { this.class = `${SystemDisabledWarningAction.INFO_CLASS}`; this.tooltip = localize('disabled locally', "Extension is enabled on '{0}' and disabled locally.", this.extensionManagementServerService.remoteExtensionManagementServer.label); } return; } if (this.extension.server === this.extensionManagementServerService.remoteExtensionManagementServer && runningExtensionServer === this.extensionManagementServerService.localExtensionManagementServer) { - if (this._extensionKindController.prefersExecuteOnUI(this.extension.local!.manifest)) { + if (this.extensionManifestPropertiesService.prefersExecuteOnUI(this.extension.local!.manifest)) { this.class = `${SystemDisabledWarningAction.INFO_CLASS}`; this.tooltip = localize('disabled remotely', "Extension is enabled locally and disabled on '{0}'.", this.extensionManagementServerService.remoteExtensionManagementServer.label); } return; } } - if (this.workspaceTrustService.isWorkspaceTrustEnabled() && this.extension.enablementState === EnablementState.DisabledByTrustRequirement) { + if (isWorkspaceTrustEnabled(this.configurationService) && this.extension.enablementState === EnablementState.DisabledByTrustRequirement) { this.class = `${SystemDisabledWarningAction.TRUST_CLASS}`; this.tooltip = localize('extension disabled because of trust requirement', "This extension has been disabled because the current workspace is not trusted"); return; } } - run(): Promise { + override run(): Promise { return Promise.resolve(null); } } @@ -2191,11 +2188,11 @@ export class ReinstallAction extends Action { super(id, label); } - get enabled(): boolean { + override get enabled(): boolean { return this.extensionsWorkbenchService.local.filter(l => !l.isBuiltin && l.local).length > 0; } - run(): Promise { + override run(): Promise { return this.quickInputService.pick(this.getEntries(), { placeHolder: localize('selectExtensionToReinstall', "Select Extension to Reinstall") }) .then(pick => pick && this.reinstallExtension(pick.extension)); } @@ -2259,11 +2256,11 @@ export class InstallSpecificVersionOfExtensionAction extends Action { super(id, label); } - get enabled(): boolean { + override get enabled(): boolean { return this.extensionsWorkbenchService.local.some(l => this.isEnabled(l)); } - async run(): Promise { + override async run(): Promise { const extensionPick = await this.quickInputService.pick(this.getExtensionEntries(), { placeHolder: localize('selectExtension', "Select Extension"), matchOnDetail: true }); if (extensionPick && extensionPick.extension) { const versionPick = await this.quickInputService.pick(extensionPick.versions.map(v => ({ id: v.version, label: v.version, description: `${getRelativeDateLabel(new Date(Date.parse(v.date)))}${v.version === extensionPick.extension.version ? ` (${localize('current', "Current")})` : ''}` })), { placeHolder: localize('selectVersion', "Select Version to Install"), matchOnDetail: true }); @@ -2361,7 +2358,7 @@ export abstract class AbstractInstallExtensionsInServerAction extends Action { this.tooltip = this.label; } - async run(): Promise { + override async run(): Promise { return this.selectAndInstallExtensions(); } @@ -2432,7 +2429,7 @@ export class InstallLocalExtensionsInRemoteAction extends AbstractInstallExtensi super('workbench.extensions.actions.installLocalExtensionsInRemote', extensionsWorkbenchService, quickInputService, notificationService, progressService); } - get label(): string { + override get label(): string { if (this.extensionManagementServerService && this.extensionManagementServerService.remoteExtensionManagementServer) { return localize('select and install local extensions', "Install Local Extensions in '{0}'...", this.extensionManagementServerService.remoteExtensionManagementServer.label); } @@ -2485,7 +2482,7 @@ export class InstallRemoteExtensionsInLocalAction extends AbstractInstallExtensi super(id, extensionsWorkbenchService, quickInputService, notificationService, progressService); } - get label(): string { + override get label(): string { return localize('select and install remote extensions', "Install Remote Extensions Locally..."); } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsList.ts b/src/vs/workbench/contrib/extensions/browser/extensionsList.ts index 9b83bc61..d9aadc6c 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsList.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsList.ts @@ -25,6 +25,8 @@ import { registerThemingParticipant, IColorTheme, ICssStyleCollector } from 'vs/ import { foreground, listActiveSelectionForeground, listActiveSelectionBackground, listInactiveSelectionForeground, listInactiveSelectionBackground, listFocusForeground, listFocusBackground, listHoverForeground, listHoverBackground } from 'vs/platform/theme/common/colorRegistry'; import { WORKBENCH_BACKGROUND } from 'vs/workbench/common/theme'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { localize } from 'vs/nls'; +import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService'; export const EXTENSION_LIST_ELEMENT_HEIGHT = 62; @@ -42,6 +44,7 @@ export interface ITemplateData { ratings: HTMLElement; author: HTMLElement; description: HTMLElement; + workspaceTrustDescription: HTMLElement; extension: IExtension | null; disposables: IDisposable[]; extensionDisposables: IDisposable[]; @@ -64,6 +67,7 @@ export class Renderer implements IPagedRenderer { @IExtensionService private readonly extensionService: IExtensionService, @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, + @IExtensionManifestPropertiesService private readonly extensionManifestPropertiesService: IExtensionManifestPropertiesService, @IContextMenuService private readonly contextMenuService: IContextMenuService, ) { } @@ -86,6 +90,7 @@ export class Renderer implements IPagedRenderer { const syncIgnore = append(header, $('span.sync-ignored')); const headerRemoteBadgeWidget = this.instantiationService.createInstance(RemoteBadgeWidget, header, false); const description = append(details, $('.description.ellipsis')); + const workspaceTrustDescription = append(details, $('.workspace-trust-description.ellipsis')); const footer = append(details, $('.footer')); const author = append(footer, $('.author.ellipsis')); const actionbar = new ActionBar(footer, { @@ -138,7 +143,7 @@ export class Renderer implements IPagedRenderer { const disposable = combinedDisposable(...actions, ...widgets, actionbar, extensionContainers, extensionTooltipAction); return { - root, element, icon, name, installCount, ratings, author, description, disposables: [disposable], actionbar, + root, element, icon, name, installCount, ratings, author, description, workspaceTrustDescription, disposables: [disposable], actionbar, extensionDisposables: [], set extension(extension: IExtension) { extensionContainers.extension = extension; @@ -199,6 +204,19 @@ export class Renderer implements IPagedRenderer { data.name.textContent = extension.displayName; data.author.textContent = extension.publisherDisplayName; data.description.textContent = extension.description; + + if (extension.local?.manifest.capabilities?.untrustedWorkspaces?.supported) { + const untrustedWorkspaceCapability = extension.local.manifest.capabilities.untrustedWorkspaces; + const untrustedWorkspaceSupported = this.extensionManifestPropertiesService.getExtensionUntrustedWorkspaceSupportType(extension.local.manifest); + if (untrustedWorkspaceSupported !== true && untrustedWorkspaceCapability.supported !== true) { + data.workspaceTrustDescription.textContent = untrustedWorkspaceCapability.description; + } else if (untrustedWorkspaceSupported === false) { + data.workspaceTrustDescription.textContent = localize('onStartDefaultText', "A trusted workspace is required to enable this extension."); + } else if (untrustedWorkspaceSupported === 'limited') { + data.workspaceTrustDescription.textContent = localize('onDemandDefaultText', "Some features require a trusted workspace."); + } + } + data.installCount.style.display = ''; data.ratings.style.display = ''; data.extension = extension; diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts index 4b4b3ab4..4185fb85 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts @@ -22,27 +22,23 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { CancellationToken } from 'vs/base/common/cancellation'; import { isNonEmptyArray } from 'vs/base/common/arrays'; import { IColorMapping } from 'vs/platform/theme/common/styler'; -import { Renderer } from 'vs/workbench/contrib/extensions/browser/extensionsList'; +import { Delegate, Renderer } from 'vs/workbench/contrib/extensions/browser/extensionsList'; import { listFocusForeground, listFocusBackground } from 'vs/platform/theme/common/colorRegistry'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; -export interface IExtensionsGridViewVirtualDelegate extends IListVirtualDelegate { - getWidth?(element: IExtension): number; -} - export class ExtensionsGridView extends Disposable { readonly element: HTMLElement; private readonly renderer: Renderer; - private readonly delegate: IExtensionsGridViewVirtualDelegate; + private readonly delegate: Delegate; private readonly disposableStore: DisposableStore; constructor( parent: HTMLElement, - delegate: IExtensionsGridViewVirtualDelegate, + delegate: Delegate, @IInstantiationService private readonly instantiationService: IInstantiationService ) { super(); @@ -59,10 +55,7 @@ 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(extension)}px`; - if (this.delegate.getWidth) { - extensionContainer.style.width = `${this.delegate.getWidth(extension)}px`; - } + extensionContainer.style.height = `${this.delegate.getHeight()}px`; extensionContainer.setAttribute('tabindex', '0'); const template = this.renderer.renderTemplate(extensionContainer); @@ -233,7 +226,7 @@ class OpenExtensionAction extends Action { this._extension = extension; } - run(sideByside: boolean): Promise { + override run(sideByside: boolean): Promise { if (this._extension) { return this.extensionsWorkdbenchService.open(this._extension, { sideByside }); } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts index 4c1c975a..8646d37a 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts @@ -22,7 +22,7 @@ import { InstallLocalExtensionsInRemoteAction, InstallRemoteExtensionsInLocalAct import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IWorkbenchExtensionEnablementService, IExtensionManagementServerService, IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { ExtensionsInput } from 'vs/workbench/contrib/extensions/common/extensionsInput'; -import { ExtensionsListView, EnabledExtensionsView, DisabledExtensionsView, RecommendedExtensionsView, WorkspaceRecommendedExtensionsView, BuiltInFeatureExtensionsView, BuiltInThemesExtensionsView, BuiltInProgrammingLanguageExtensionsView, ServerInstalledExtensionsView, DefaultRecommendedExtensionsView } from 'vs/workbench/contrib/extensions/browser/extensionsViews'; +import { ExtensionsListView, EnabledExtensionsView, DisabledExtensionsView, RecommendedExtensionsView, WorkspaceRecommendedExtensionsView, BuiltInFeatureExtensionsView, BuiltInThemesExtensionsView, BuiltInProgrammingLanguageExtensionsView, ServerInstalledExtensionsView, DefaultRecommendedExtensionsView, TrustRequiredOnStartExtensionsView, TrustRequiredOnDemandExtensionsView } from 'vs/workbench/contrib/extensions/browser/extensionsViews'; import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import Severity from 'vs/base/common/severity'; @@ -69,6 +69,8 @@ const HasInstalledExtensionsContext = new RawContextKey('hasInstalledEx const HasInstalledWebExtensionsContext = new RawContextKey('hasInstalledWebExtensions', false); const BuiltInExtensionsContext = new RawContextKey('builtInExtensions', false); const SearchBuiltInExtensionsContext = new RawContextKey('searchBuiltInExtensions', false); +const TrustRequiredExtensionsContext = new RawContextKey('trustRequiredExtensions', false); +const SearchTrustRequiredExtensionsContext = new RawContextKey('searchTrustRequiredExtensions', false); const RecommendedExtensionsContext = new RawContextKey('recommendedExtensions', false); export class ExtensionsViewletViewsContribution implements IWorkbenchContribution { @@ -100,6 +102,9 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio /* Built-in extensions views */ viewDescriptors.push(...this.createBuiltinExtensionsViewDescriptors()); + /* Trust Required extensions views */ + viewDescriptors.push(...this.createTrustRequiredExtensionsViewDescriptors()); + Registry.as(Extensions.ViewsRegistry).registerViews(viewDescriptors, this.container); } @@ -337,6 +342,16 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio when: ContextKeyExpr.and(ContextKeyExpr.has('searchBuiltInExtensions')), }); + /* + * View used for searching trustRequired extensions + */ + viewDescriptors.push({ + id: 'workbench.views.extensions.searchTrustRequired', + name: localize('trustRequired', "Trust Required"), + ctorDescriptor: new SyncDescriptor(ExtensionsListView, [{}]), + when: ContextKeyExpr.and(ContextKeyExpr.has('searchTrustRequiredExtensions')), + }); + return viewDescriptors; } @@ -389,6 +404,26 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio return viewDescriptors; } + private createTrustRequiredExtensionsViewDescriptors(): IViewDescriptor[] { + const viewDescriptors: IViewDescriptor[] = []; + + viewDescriptors.push({ + id: 'workbench.views.extensions.trustRequiredOnStartExtensions', + name: localize('trustRequiredOnStartExtensions', "Trust Required To Enable"), + ctorDescriptor: new SyncDescriptor(TrustRequiredOnStartExtensionsView, [{}]), + when: ContextKeyExpr.has('trustRequiredExtensions'), + }); + + viewDescriptors.push({ + id: 'workbench.views.extensions.trustRequiredOnDemandExtensions', + name: localize('trustRequiredOnDemandExtensions', "Trust Required For Features"), + ctorDescriptor: new SyncDescriptor(TrustRequiredOnDemandExtensionsView, [{}]), + when: ContextKeyExpr.has('trustRequiredExtensions'), + }); + + return viewDescriptors; + } + } export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IExtensionsViewPaneContainer { @@ -404,6 +439,8 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE private hasInstalledWebExtensionsContextKey: IContextKey; private builtInExtensionsContextKey: IContextKey; private searchBuiltInExtensionsContextKey: IContextKey; + private trustRequiredExtensionsContextKey: IContextKey; + private searchTrustRequiredExtensionsContextKey: IContextKey; private recommendedExtensionsContextKey: IContextKey; private searchDelayer: Delayer; @@ -439,6 +476,8 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE this.sortByContextKey = ExtensionsSortByContext.bindTo(contextKeyService); this.searchMarketplaceExtensionsContextKey = SearchMarketplaceExtensionsContext.bindTo(contextKeyService); this.searchInstalledExtensionsContextKey = SearchIntalledExtensionsContext.bindTo(contextKeyService); + this.trustRequiredExtensionsContextKey = TrustRequiredExtensionsContext.bindTo(contextKeyService); + this.searchTrustRequiredExtensionsContextKey = SearchTrustRequiredExtensionsContext.bindTo(contextKeyService); this.searchOutdatedExtensionsContextKey = SearchOutdatedExtensionsContext.bindTo(contextKeyService); this.searchEnabledExtensionsContextKey = SearchEnabledExtensionsContext.bindTo(contextKeyService); this.searchDisabledExtensionsContextKey = SearchDisabledExtensionsContext.bindTo(contextKeyService); @@ -465,7 +504,7 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE return this.searchBox?.getValue(); } - create(parent: HTMLElement): void { + override create(parent: HTMLElement): void { parent.classList.add('extensions-viewlet'); this.root = parent; @@ -555,13 +594,13 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE super.create(append(this.root, $('.extensions'))); } - focus(): void { + override focus(): void { if (this.searchBox) { this.searchBox.focus(); } } - layout(dimension: Dimension): void { + override layout(dimension: Dimension): void { if (this.root) { this.root.classList.toggle('narrow', dimension.width <= 300); } @@ -571,7 +610,7 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE super.layout(new Dimension(dimension.width, dimension.height - 41)); } - getOptimalWidth(): number { + override getOptimalWidth(): number { return 400; } @@ -612,7 +651,7 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE : ''; } - saveState(): void { + override saveState(): void { const value = this.searchBox ? this.searchBox.getValue() : ''; if (ExtensionsListView.isLocalExtensionsQuery(value)) { this.searchViewletState['query.value'] = value; @@ -630,6 +669,8 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE this.searchEnabledExtensionsContextKey.set(ExtensionsListView.isEnabledExtensionsQuery(value)); this.searchDisabledExtensionsContextKey.set(ExtensionsListView.isDisabledExtensionsQuery(value)); this.searchBuiltInExtensionsContextKey.set(ExtensionsListView.isSearchBuiltInExtensionsQuery(value)); + this.trustRequiredExtensionsContextKey.set(ExtensionsListView.isTrustRequiredExtensionsQuery(value)); + this.searchTrustRequiredExtensionsContextKey.set(ExtensionsListView.isSearchTrustRequiredExtensionsQuery(value)); this.builtInExtensionsContextKey.set(ExtensionsListView.isBuiltInExtensionsQuery(value)); this.recommendedExtensionsContextKey.set(isRecommendedExtensionsQuery); this.searchMarketplaceExtensionsContextKey.set(!!value && !ExtensionsListView.isLocalExtensionsQuery(value) && !isRecommendedExtensionsQuery); @@ -642,7 +683,7 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE ))).then(() => undefined); } - protected onDidAddViewDescriptors(added: IAddedViewDescriptorRef[]): ViewPane[] { + protected override onDidAddViewDescriptors(added: IAddedViewDescriptorRef[]): ViewPane[] { const addedViews = super.onDidAddViewDescriptors(added); this.progress(Promise.all(addedViews.map(addedView => (addedView).show(this.normalizedQuery()) diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts index f9a525c8..67e0af11 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts @@ -48,6 +48,7 @@ import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; +import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService'; // Extensions that are automatically classified as Programming Language extensions, but should be Feature extensions const FORCE_FEATURE_EXTENSIONS = ['vscode.git', 'vscode.search-result']; @@ -108,7 +109,7 @@ export class ExtensionsListView extends ViewPane { @INotificationService protected notificationService: INotificationService, @IKeybindingService keybindingService: IKeybindingService, @IContextMenuService contextMenuService: IContextMenuService, - @IInstantiationService protected instantiationService: IInstantiationService, + @IInstantiationService instantiationService: IInstantiationService, @IThemeService themeService: IThemeService, @IExtensionService private readonly extensionService: IExtensionService, @IExtensionsWorkbenchService protected extensionsWorkbenchService: IExtensionsWorkbenchService, @@ -118,6 +119,7 @@ export class ExtensionsListView extends ViewPane { @IWorkspaceContextService protected contextService: IWorkspaceContextService, @IExperimentService private readonly experimentService: IExperimentService, @IExtensionManagementServerService protected readonly extensionManagementServerService: IExtensionManagementServerService, + @IExtensionManifestPropertiesService private readonly extensionManifestPropertiesService: IExtensionManifestPropertiesService, @IWorkbenchExtensionManagementService protected readonly extensionManagementService: IWorkbenchExtensionManagementService, @IProductService protected readonly productService: IProductService, @IContextKeyService contextKeyService: IContextKeyService, @@ -141,7 +143,7 @@ export class ExtensionsListView extends ViewPane { protected registerActions(): void { } - protected renderHeader(container: HTMLElement): void { + protected override renderHeader(container: HTMLElement): void { container.classList.add('extension-view-header'); super.renderHeader(container); @@ -149,7 +151,7 @@ export class ExtensionsListView extends ViewPane { this._register(attachBadgeStyler(this.badge, this.themeService)); } - renderBody(container: HTMLElement): void { + override renderBody(container: HTMLElement): void { super.renderBody(container); const extensionsList = append(container, $('.extensions-list')); @@ -193,7 +195,7 @@ export class ExtensionsListView extends ViewPane { }; } - protected layoutBody(height: number, width: number): void { + protected override layoutBody(height: number, width: number): void { super.layoutBody(height, width); if (this.bodyTemplate) { this.bodyTemplate.extensionsList.style.height = height + 'px'; @@ -392,6 +394,10 @@ export class ExtensionsListView extends ViewPane { extensions = this.filterEnabledExtensions(local, runningExtensions, query, options); } + else if (/@trustRequired/i.test(value)) { + extensions = this.filterTrustRequiredExtensions(local, query, options); + } + return { extensions, canIncludeInstalledExtensions }; } @@ -542,6 +548,35 @@ export class ExtensionsListView extends ViewPane { return this.sortExtensions(result, options); } + private filterTrustRequiredExtensions(local: IExtension[], query: Query, options: IQueryOptions): IExtension[] { + let value = query.value; + const onStartOnly = /@trustRequired:onStart/i.test(value); + if (onStartOnly) { + value = value.replace(/@trustRequired:onStart/g, ''); + } + const onDemandOnly = /@trustRequired:onDemand/i.test(value); + if (onDemandOnly) { + value = value.replace(/@trustRequired:onDemand/g, ''); + } + + value = value.replace(/@trustRequired/g, '').replace(/@sort:(\w+)(-\w*)?/g, '').trim().toLowerCase(); + + const result = local.filter(extension => extension.local && this.extensionManifestPropertiesService.getExtensionUntrustedWorkspaceSupportType(extension.local.manifest) !== true && (extension.name.toLowerCase().indexOf(value) > -1 || extension.displayName.toLowerCase().indexOf(value) > -1)); + + if (onStartOnly) { + const onStartExtensions = result.filter(extension => extension.local && this.extensionManifestPropertiesService.getExtensionUntrustedWorkspaceSupportType(extension.local.manifest) === false); + return this.sortExtensions(onStartExtensions, options); + } + + if (onDemandOnly) { + const onDemandExtensions = result.filter(extension => extension.local && this.extensionManifestPropertiesService.getExtensionUntrustedWorkspaceSupportType(extension.local.manifest) === 'limited'); + return this.sortExtensions(onDemandExtensions, options); + } + + return this.sortExtensions(result, options); + } + + private mergeAddedExtensions(extensions: IExtension[], newExtensions: IExtension[]): IExtension[] | undefined { const oldExtensions = [...extensions]; const findPreviousExtensionIndex = (from: number): number => { @@ -908,7 +943,7 @@ export class ExtensionsListView extends ViewPane { return new PagedModel(pager); } - dispose(): void { + override dispose(): void { super.dispose(); if (this.queryRequest) { this.queryRequest.request.cancel(); @@ -928,7 +963,10 @@ export class ExtensionsListView extends ViewPane { || this.isDisabledExtensionsQuery(query) || this.isBuiltInExtensionsQuery(query) || this.isSearchBuiltInExtensionsQuery(query) - || this.isBuiltInGroupExtensionsQuery(query); + || this.isBuiltInGroupExtensionsQuery(query) + || this.isSearchTrustRequiredExtensionsQuery(query) + || this.isTrustRequiredExtensionsQuery(query) + || this.isTrustRequiredGroupExtensionsQuery(query); } static isSearchBuiltInExtensionsQuery(query: string): boolean { @@ -943,6 +981,18 @@ export class ExtensionsListView extends ViewPane { return /^\s*@builtin:.+$/i.test(query.trim()); } + static isSearchTrustRequiredExtensionsQuery(query: string): boolean { + return /@trustRequired\s.+/i.test(query); + } + + static isTrustRequiredExtensionsQuery(query: string): boolean { + return /^\s*@trustRequired$/i.test(query.trim()); + } + + static isTrustRequiredGroupExtensionsQuery(query: string): boolean { + return /^\s*@trustRequired:.+$/i.test(query.trim()); + } + static isInstalledExtensionsQuery(query: string): boolean { return /@installed/i.test(query); } @@ -979,7 +1029,7 @@ export class ExtensionsListView extends ViewPane { return /@recommended:keymaps/i.test(query); } - focus(): void { + override focus(): void { super.focus(); if (!this.list) { return; @@ -994,7 +1044,7 @@ export class ExtensionsListView extends ViewPane { export class ServerInstalledExtensionsView extends ExtensionsListView { - async show(query: string): Promise> { + override async show(query: string): Promise> { query = query ? query : '@installed'; if (!ExtensionsListView.isLocalExtensionsQuery(query)) { query = query += ' @installed'; @@ -1006,7 +1056,7 @@ export class ServerInstalledExtensionsView extends ExtensionsListView { export class EnabledExtensionsView extends ExtensionsListView { - async show(query: string): Promise> { + override async show(query: string): Promise> { query = query || '@enabled'; return ExtensionsListView.isEnabledExtensionsQuery(query) ? super.show(query) : this.showEmptyModel(); } @@ -1014,34 +1064,46 @@ export class EnabledExtensionsView extends ExtensionsListView { export class DisabledExtensionsView extends ExtensionsListView { - async show(query: string): Promise> { + override async show(query: string): Promise> { query = query || '@disabled'; return ExtensionsListView.isDisabledExtensionsQuery(query) ? super.show(query) : this.showEmptyModel(); } } export class BuiltInFeatureExtensionsView extends ExtensionsListView { - async show(query: string): Promise> { + override async show(query: string): Promise> { return (query && query.trim() !== '@builtin') ? this.showEmptyModel() : super.show('@builtin:features'); } } export class BuiltInThemesExtensionsView extends ExtensionsListView { - async show(query: string): Promise> { + override async show(query: string): Promise> { return (query && query.trim() !== '@builtin') ? this.showEmptyModel() : super.show('@builtin:themes'); } } export class BuiltInProgrammingLanguageExtensionsView extends ExtensionsListView { - async show(query: string): Promise> { + override async show(query: string): Promise> { return (query && query.trim() !== '@builtin') ? this.showEmptyModel() : super.show('@builtin:basics'); } } +export class TrustRequiredOnStartExtensionsView extends ExtensionsListView { + override async show(query: string): Promise> { + return (query && query.trim() !== '@trustRequired') ? this.showEmptyModel() : super.show('@trustRequired:onStart'); + } +} + +export class TrustRequiredOnDemandExtensionsView extends ExtensionsListView { + override async show(query: string): Promise> { + return (query && query.trim() !== '@trustRequired') ? this.showEmptyModel() : super.show('@trustRequired:onDemand'); + } +} + export class DefaultRecommendedExtensionsView extends ExtensionsListView { private readonly recommendedExtensionsQuery = '@recommended:all'; - renderBody(container: HTMLElement): void { + override renderBody(container: HTMLElement): void { super.renderBody(container); this._register(this.extensionRecommendationsService.onDidChangeRecommendations(() => { @@ -1049,7 +1111,7 @@ export class DefaultRecommendedExtensionsView extends ExtensionsListView { })); } - async show(query: string): Promise> { + override async show(query: string): Promise> { if (query && query.trim() !== this.recommendedExtensionsQuery) { return this.showEmptyModel(); } @@ -1066,7 +1128,7 @@ export class DefaultRecommendedExtensionsView extends ExtensionsListView { export class RecommendedExtensionsView extends ExtensionsListView { private readonly recommendedExtensionsQuery = '@recommended'; - renderBody(container: HTMLElement): void { + override renderBody(container: HTMLElement): void { super.renderBody(container); this._register(this.extensionRecommendationsService.onDidChangeRecommendations(() => { @@ -1074,7 +1136,7 @@ export class RecommendedExtensionsView extends ExtensionsListView { })); } - async show(query: string): Promise> { + override async show(query: string): Promise> { return (query && query.trim() !== this.recommendedExtensionsQuery) ? this.showEmptyModel() : super.show(this.recommendedExtensionsQuery); } } @@ -1082,14 +1144,14 @@ export class RecommendedExtensionsView extends ExtensionsListView { export class WorkspaceRecommendedExtensionsView extends ExtensionsListView implements IWorkspaceRecommendedExtensionsView { private readonly recommendedExtensionsQuery = '@recommended:workspace'; - renderBody(container: HTMLElement): void { + override renderBody(container: HTMLElement): void { super.renderBody(container); this._register(this.extensionRecommendationsService.onDidChangeRecommendations(() => this.show(this.recommendedExtensionsQuery))); this._register(this.contextService.onDidChangeWorkbenchState(() => this.show(this.recommendedExtensionsQuery))); } - async show(query: string): Promise> { + override async show(query: string): Promise> { let shouldShowEmptyView = query && query.trim() !== '@recommended' && query.trim() !== '@recommended:workspace'; let model = await (shouldShowEmptyView ? this.showEmptyModel() : super.show(this.recommendedExtensionsQuery)); this.setExpanded(model.length > 0); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index 56d5dfc6..02ec9138 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -36,11 +36,12 @@ import { IFileService } from 'vs/platform/files/common/files'; import { IExtensionManifest, ExtensionType, IExtension as IPlatformExtension } from 'vs/platform/extensions/common/extensions'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IProductService } from 'vs/platform/product/common/productService'; -import { ExtensionKindController } from 'vs/workbench/services/extensions/common/extensionsUtil'; import { FileAccess } from 'vs/base/common/network'; import { IIgnoredExtensionsManagementService } from 'vs/platform/userDataSync/common/ignoredExtensions'; import { IUserDataAutoSyncService } from 'vs/platform/userDataSync/common/userDataSync'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { isBoolean } from 'vs/base/common/types'; +import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService'; interface IExtensionStateProvider { (extension: Extension): T; @@ -517,8 +518,6 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension private installing: IExtension[] = []; - private readonly extensionKindController: ExtensionKindController; - constructor( @IInstantiationService private readonly instantiationService: IInstantiationService, @IEditorService private readonly editorService: IEditorService, @@ -537,7 +536,8 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension @IIgnoredExtensionsManagementService private readonly extensionsSyncManagementService: IIgnoredExtensionsManagementService, @IUserDataAutoSyncService private readonly userDataAutoSyncService: IUserDataAutoSyncService, @IProductService private readonly productService: IProductService, - @IContextKeyService contextKeyService: IContextKeyService + @IContextKeyService contextKeyService: IContextKeyService, + @IExtensionManifestPropertiesService private readonly extensionManifestPropertiesService: IExtensionManifestPropertiesService, ) { super(); this.hasOutdatedExtensionsContextKey = HasOutdatedExtensionsContext.bindTo(contextKeyService); @@ -572,6 +572,12 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension } }, this)); + this._register(extensionEnablementService.onEnablementChanged(platformExtensions => { + if (this.getAutoUpdateValue() === 'onlyEnabledExtensions' && platformExtensions.some(e => this.extensionEnablementService.isEnabled(e))) { + this.checkForUpdates(); + } + }, this)); + this.queryLocal().then(() => { this.resetIgnoreAutoUpdateExtensions(); this.eventuallySyncWithGallery(true); @@ -581,8 +587,6 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension this.updateContexts(); this.updateActivity(); })); - - this.extensionKindController = new ExtensionKindController(productService, configurationService); } get local(): IExtension[] { @@ -709,7 +713,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension return extensionsToChoose[0]; } - const extensionKinds = this.extensionKindController.getExtensionKind(manifest); + const extensionKinds = this.extensionManifestPropertiesService.getExtensionKind(manifest); let extension = extensionsToChoose.find(extension => { for (const extensionKind of extensionKinds) { @@ -847,8 +851,13 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension return Promise.resolve(this.syncDelayer.trigger(() => this.syncWithGallery(), 0)); } + private getAutoUpdateValue(): boolean | 'onlyEnabledExtensions' { + const autoUpdate = this.configurationService.getValue(AutoUpdateConfigurationKey); + return isBoolean(autoUpdate) || autoUpdate === 'onlyEnabledExtensions' ? autoUpdate : true; + } + private isAutoUpdateEnabled(): boolean { - return this.configurationService.getValue(AutoUpdateConfigurationKey); + return this.getAutoUpdateValue() !== false; } private isAutoCheckUpdatesEnabled(): boolean { @@ -897,7 +906,11 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension return Promise.resolve(); } - const toUpdate = this.outdated.filter(e => !this.isAutoUpdateIgnored(new ExtensionIdentifierWithVersion(e.identifier, e.version))); + const toUpdate = this.outdated.filter(e => + !this.isAutoUpdateIgnored(new ExtensionIdentifierWithVersion(e.identifier, e.version)) && + (this.getAutoUpdateValue() === true || (e.local && this.extensionEnablementService.isEnabled(e.local))) + ); + return Promises.settled(toUpdate.map(e => this.install(e))); } @@ -1317,7 +1330,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension this.ignoredAutoUpdateExtensions = this.ignoredAutoUpdateExtensions.filter(extensionId => this.local.some(local => !!local.local && new ExtensionIdentifierWithVersion(local.identifier, local.version).key() === extensionId)); } - dispose(): void { + override dispose(): void { super.dispose(); this.syncDelayer.cancel(); } diff --git a/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts b/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts index cab0ed9a..984defd9 100644 --- a/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts +++ b/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts @@ -12,7 +12,8 @@ import { IExtensionsViewPaneContainer, IExtensionsWorkbenchService, IExtension } import { CancellationToken } from 'vs/base/common/cancellation'; import { localize } from 'vs/nls'; import { StorageScope, IStorageService, StorageTarget } from 'vs/platform/storage/common/storage'; -import { ImportantExtensionTip, IProductService } from 'vs/platform/product/common/productService'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { ImportantExtensionTip } from 'vs/base/common/product'; import { forEach, IStringDictionary } from 'vs/base/common/collections'; import { ITextModel } from 'vs/editor/common/model'; import { Schemas } from 'vs/base/common/network'; diff --git a/src/vs/workbench/contrib/extensions/browser/media/extension.css b/src/vs/workbench/contrib/extensions/browser/media/extension.css index 6c7ae23f..5518b286 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extension.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extension.css @@ -152,6 +152,11 @@ padding-right: 11px; } +.extension-list-item > .details > .workspace-trust-description { + display: none; + padding-right: 11px; +} + .extension-list-item > .details > .footer { display: flex; justify-content: flex-end; @@ -176,6 +181,11 @@ flex-wrap: wrap-reverse; } + +.extension-list-item > .details > .footer > .monaco-action-bar > .actions-container .action-label:not(.icon) { + border-radius: 0; +} + .extension-list-item > .details > .footer > .monaco-action-bar > .actions-container .extension-action.label { max-width: 150px; } @@ -183,3 +193,7 @@ .extension-list-item .footer .monaco-action-bar .action-item.action-dropdown-item.empty > .action-label { margin-right: 4px; } + +.extension-list-item .monaco-action-bar > .actions-container > .action-item.disabled { + min-width: 0; +} diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensionActions.css b/src/vs/workbench/contrib/extensions/browser/media/extensionActions.css index bb1107d0..3a7d6537 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extensionActions.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extensionActions.css @@ -15,6 +15,10 @@ padding: 0 5px; } +.monaco-action-bar .action-dropdown-item > .monaco-dropdown .action-label { + padding: 0; +} + .monaco-action-bar .action-item .action-label.extension-action.label { outline-offset: 1px; } @@ -22,13 +26,16 @@ .monaco-action-bar .action-item .action-label.extension-action.text, .monaco-action-bar .action-item .action-label.extension-action.label, .monaco-action-bar .action-dropdown-item .action-label.extension-action.label { + width: auto; + height: auto; line-height: 14px; - margin-top: 2px; } -.monaco-action-bar .action-item .action-label.extension-action.icon { - padding: 0 2px; - width: 16px; +.monaco-action-bar .action-item:not(.disabled) .action-label.extension-action.text, +.monaco-action-bar .action-item:not(.disabled) .action-label.extension-action.label, +.monaco-action-bar .action-item .action-label.extension-action.icon, +.monaco-action-bar .action-dropdown-item .action-label.extension-action.label { + margin-top: 2px; /* margin for outline */ } .monaco-action-bar .action-item .action-label.extension-action.multiserver.install:after, diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css b/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css index dff118c3..9b74196c 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css @@ -148,12 +148,32 @@ text-align: initial; } -.extension-editor > .header > .details > .actions > .monaco-action-bar > .actions-container { - justify-content: flex-start; +.extension-editor > .header > .details > .actions > .monaco-action-bar > .actions-container > .action-item { + margin-right: 0; + overflow: hidden; + flex-shrink: 0; +} + +.extension-editor > .header > .details > .actions > .monaco-action-bar > .actions-container > .action-item.disabled { + min-width: 0; } .extension-editor > .header > .details > .actions > .monaco-action-bar > .actions-container > .action-item .extension-action { - margin-top: 0px; /* overrides from extension actions */ + margin-bottom: 2px; /* margin for outline */ +} + +.extension-editor > .header > .details > .actions > .monaco-action-bar > .actions-container > .action-item > .extension-action:not(.icon) { + margin-left: 2px; /* margin for outline */ +} + +.extension-editor > .header > .details > .actions > .monaco-action-bar > .actions-container > .action-item.action-dropdown-item > .monaco-dropdown .extension-action.action-dropdown { + margin-right: 2px; /* margin for outline */ +} + +.extension-editor > .header > .details > .actions > .monaco-action-bar > .actions-container > .action-item .extension-action:not(.icon) { + border-radius: 0; + padding-top: 0; + padding-bottom: 0; } .extension-editor > .header > .details > .actions > .monaco-action-bar > .actions-container > .action-item > .extension-action, @@ -163,7 +183,7 @@ .extension-editor > .header > .details > .actions > .monaco-action-bar > .actions-container > .action-item.action-dropdown-item, .extension-editor > .header > .details > .actions > .monaco-action-bar > .actions-container > .action-item:not(.action-dropdown-item) > .extension-action { - margin-right: 8px; + margin-right: 6px; } .extension-editor > .header > .details > .actions > .monaco-action-bar > .actions-container > .action-item > .extension-action.label, @@ -239,11 +259,12 @@ .extension-editor > .body > .navbar > .monaco-action-bar > .actions-container > .action-item > .action-label { margin-right: 16px; + font-size: inherit; + opacity: 0.7; } .extension-editor > .body > .navbar > .monaco-action-bar > .actions-container > .action-item > .action-label { - font-size: inherit; - opacity: 0.7; + background: none !important; } .extension-editor > .body > .navbar > .monaco-action-bar > .actions-container > .action-item > .action-label.checked { @@ -501,7 +522,7 @@ } .extension-editor .extensions-grid-view > .extension-container { - height: 275px; + width: 275px; margin: 0 10px 20px 0; } diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensionsViewlet.css b/src/vs/workbench/contrib/extensions/browser/media/extensionsViewlet.css index 5110de46..99d2c924 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extensionsViewlet.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extensionsViewlet.css @@ -39,10 +39,15 @@ } .extensions-viewlet > .extensions .extension-view-header .count-badge-wrapper, +.extensions-viewlet > .extensions .extension-list-item .monaco-action-bar, .extensions-viewlet > .extensions .extension-view-header .monaco-action-bar { margin-right: 4px; } +.extensions-viewlet > .extensions .extension-list-item .monaco-action-bar .action-label.icon { + padding: 1px 2px; +} + .extensions-viewlet > .extensions .extension-view-header .monaco-action-bar .action-item > .action-label.icon.codicon { vertical-align: middle; line-height: 22px; @@ -52,6 +57,11 @@ display: none; } +.extensions-viewlet > .extensions .extension-list-item .monaco-action-bar > .actions-container > .action-item.action-dropdown-item, +.extensions-viewlet > .extensions .extension-list-item .monaco-action-bar > .actions-container > .action-item:not(.action-dropdown-item) > .extension-action { + margin-left: 6px; +} + .extensions-viewlet > .extensions .extensions-list.hidden, .extensions-viewlet > .extensions .message-container.hidden { display: none; diff --git a/src/vs/workbench/contrib/extensions/common/extensionQuery.ts b/src/vs/workbench/contrib/extensions/common/extensionQuery.ts index e28e8348..e1cac15c 100644 --- a/src/vs/workbench/contrib/extensions/common/extensionQuery.ts +++ b/src/vs/workbench/contrib/extensions/common/extensionQuery.ts @@ -13,7 +13,7 @@ export class Query { } static suggestions(query: string): string[] { - const commands = ['installed', 'outdated', 'enabled', 'disabled', 'builtin', 'recommended', 'sort', 'category', 'tag', 'ext', 'id'] as const; + const commands = ['installed', 'outdated', 'enabled', 'disabled', 'builtin', 'recommended', 'trustRequired', 'sort', 'category', 'tag', 'ext', 'id'] as const; const subcommands = { 'sort': ['installs', 'rating', 'name'], 'category': EXTENSION_CATEGORIES.map(c => `"${c.toLowerCase()}"`), diff --git a/src/vs/workbench/contrib/extensions/common/extensionsInput.ts b/src/vs/workbench/contrib/extensions/common/extensionsInput.ts index eef81202..ad1f9feb 100644 --- a/src/vs/workbench/contrib/extensions/common/extensionsInput.ts +++ b/src/vs/workbench/contrib/extensions/common/extensionsInput.ts @@ -15,7 +15,11 @@ export class ExtensionsInput extends EditorInput { static readonly ID = 'workbench.extensions.input2'; - get resource() { + override get typeId(): string { + return ExtensionsInput.ID; + } + + override get resource() { return URI.from({ scheme: Schemas.extension, path: join(this.extension.identifier.id, 'extension') @@ -28,19 +32,15 @@ export class ExtensionsInput extends EditorInput { super(); } - getTypeId(): string { - return ExtensionsInput.ID; - } - - getName(): string { + override getName(): string { return localize('extensionsInputName', "Extension: {0}", this.extension.displayName); } - supportsSplitEditor(): boolean { + override canSplit(): boolean { return false; } - matches(other: unknown): boolean { + override matches(other: unknown): boolean { if (super.matches(other)) { return true; } diff --git a/src/vs/workbench/contrib/extensions/common/extensionsUtils.ts b/src/vs/workbench/contrib/extensions/common/extensionsUtils.ts index 7887fc06..6c4be2b7 100644 --- a/src/vs/workbench/contrib/extensions/common/extensionsUtils.ts +++ b/src/vs/workbench/contrib/extensions/common/extensionsUtils.ts @@ -34,7 +34,7 @@ export class KeymapExtensions extends Disposable implements IWorkbenchContributi @ITelemetryService private readonly telemetryService: ITelemetryService, ) { super(); - this._register(lifecycleService.onShutdown(() => this.dispose())); + this._register(lifecycleService.onDidShutdown(() => this.dispose())); this._register(instantiationService.invokeFunction(onExtensionChanged)((identifiers => { Promise.all(identifiers.map(identifier => this.checkForOtherKeymaps(identifier))) .then(undefined, onUnexpectedError); diff --git a/src/vs/workbench/contrib/extensions/common/runtimeExtensionsInput.ts b/src/vs/workbench/contrib/extensions/common/runtimeExtensionsInput.ts index 8fa384f0..e5ff9281 100644 --- a/src/vs/workbench/contrib/extensions/common/runtimeExtensionsInput.ts +++ b/src/vs/workbench/contrib/extensions/common/runtimeExtensionsInput.ts @@ -11,6 +11,10 @@ export class RuntimeExtensionsInput extends EditorInput { static readonly ID = 'workbench.runtimeExtensions.input'; + override get typeId(): string { + return RuntimeExtensionsInput.ID; + } + static _instance: RuntimeExtensionsInput; static get instance() { if (!RuntimeExtensionsInput._instance || RuntimeExtensionsInput._instance.isDisposed()) { @@ -25,19 +29,15 @@ export class RuntimeExtensionsInput extends EditorInput { path: 'default' }); - getTypeId(): string { - return RuntimeExtensionsInput.ID; - } - - getName(): string { + override getName(): string { return nls.localize('extensionsInputName', "Running Extensions"); } - supportsSplitEditor(): boolean { + override canSplit(): boolean { return false; } - matches(other: unknown): boolean { + override matches(other: unknown): boolean { return other instanceof RuntimeExtensionsInput; } } diff --git a/src/vs/workbench/contrib/extensions/electron-browser/debugExtensionHostAction.ts b/src/vs/workbench/contrib/extensions/electron-browser/debugExtensionHostAction.ts index bd3ea488..45eb957a 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/debugExtensionHostAction.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/debugExtensionHostAction.ts @@ -27,7 +27,7 @@ export class DebugExtensionHostAction extends Action { super(DebugExtensionHostAction.ID, DebugExtensionHostAction.LABEL, DebugExtensionHostAction.CSS_CLASS); } - async run(): Promise { + override async run(): Promise { const inspectPort = await this._extensionService.getInspectPort(false); if (!inspectPort) { diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts index 45fc3d5b..2c581094 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts @@ -12,11 +12,11 @@ import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWo import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { EditorDescriptor, IEditorRegistry, Extensions as EditorExtensions } from 'vs/workbench/browser/editor'; +import { EditorDescriptor, IEditorRegistry } from 'vs/workbench/browser/editor'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { RuntimeExtensionsEditor, IExtensionHostProfileService, StartExtensionHostProfileAction, StopExtensionHostProfileAction, CONTEXT_PROFILE_SESSION_STATE, CONTEXT_EXTENSION_HOST_PROFILE_RECORDED, SaveExtensionHostProfileAction } from 'vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor'; import { DebugExtensionHostAction } from 'vs/workbench/contrib/extensions/electron-browser/debugExtensionHostAction'; -import { EditorInput, IEditorInputFactory, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions, ActiveEditorContext } from 'vs/workbench/common/editor'; +import { EditorInput, IEditorInputSerializer, IEditorInputFactoryRegistry, ActiveEditorContext, EditorExtensions } from 'vs/workbench/common/editor'; import { ExtensionHostProfileService } from 'vs/workbench/contrib/extensions/electron-browser/extensionProfileService'; import { RuntimeExtensionsInput } from 'vs/workbench/contrib/extensions/common/runtimeExtensionsInput'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; @@ -40,7 +40,7 @@ Registry.as(EditorExtensions.Editors).registerEditor( [new SyncDescriptor(RuntimeExtensionsInput)] ); -class RuntimeExtensionsInputFactory implements IEditorInputFactory { +class RuntimeExtensionsInputSerializer implements IEditorInputSerializer { canSerialize(editorInput: EditorInput): boolean { return true; } @@ -52,7 +52,7 @@ class RuntimeExtensionsInputFactory implements IEditorInputFactory { } } -Registry.as(EditorInputExtensions.EditorInputFactories).registerEditorInputFactory(RuntimeExtensionsInput.ID, RuntimeExtensionsInputFactory); +Registry.as(EditorExtensions.EditorInputFactories).registerEditorInputSerializer(RuntimeExtensionsInput.ID, RuntimeExtensionsInputSerializer); // Global actions diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionsSlowActions.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionsSlowActions.ts index d025c2e1..82b6883f 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionsSlowActions.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionsSlowActions.ts @@ -74,7 +74,7 @@ export class SlowExtensionAction extends Action { this.enabled = Boolean(RepoInfo.fromExtension(extension)); } - async run(): Promise { + override async run(): Promise { const action = await this._instantiationService.invokeFunction(createSlowExtensionAction, this.extension, this.profile); if (action) { await action.run(); @@ -127,7 +127,7 @@ class ReportExtensionSlowAction extends Action { super('report.slow', localize('cmd.report', "Report Issue")); } - async run(): Promise { + override async run(): Promise { // rewrite pii (paths) and store on disk const profiler = await import('v8-inspect-profiler'); @@ -171,7 +171,7 @@ class ShowExtensionSlowAction extends Action { super('show.slow', localize('cmd.show', "Show Issues")); } - async run(): Promise { + override async run(): Promise { // rewrite pii (paths) and store on disk const profiler = await import('v8-inspect-profiler'); diff --git a/src/vs/workbench/contrib/extensions/electron-browser/reportExtensionIssueAction.ts b/src/vs/workbench/contrib/extensions/electron-browser/reportExtensionIssueAction.ts index bd4331a3..f1755db8 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/reportExtensionIssueAction.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/reportExtensionIssueAction.ts @@ -40,7 +40,7 @@ export class ReportExtensionIssueAction extends Action { this.enabled = extension.description.isBuiltin || (!!extension.description.repository && !!extension.description.repository.url); } - async run(): Promise { + override async run(): Promise { if (!this._url) { this._url = await this._generateNewIssueUrl(this.extension); } diff --git a/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts b/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts index 8aba9dd1..a5c303f8 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts @@ -134,7 +134,7 @@ export class StartExtensionHostProfileAction extends Action { super(id, label); } - run(): Promise { + override run(): Promise { this._extensionHostProfileService.startProfiling(); return Promise.resolve(); } @@ -151,7 +151,7 @@ export class StopExtensionHostProfileAction extends Action { super(id, label); } - run(): Promise { + override run(): Promise { this._extensionHostProfileService.stopProfiling(); return Promise.resolve(); } @@ -175,7 +175,7 @@ export class SaveExtensionHostProfileAction extends Action { }); } - run(): Promise { + override run(): Promise { return Promise.resolve(this._asyncRun()); } diff --git a/src/vs/workbench/contrib/extensions/electron-sandbox/extensionsActions.ts b/src/vs/workbench/contrib/extensions/electron-sandbox/extensionsActions.ts index 99cd8d05..0ff09d63 100644 --- a/src/vs/workbench/contrib/extensions/electron-sandbox/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/electron-sandbox/extensionsActions.ts @@ -26,7 +26,7 @@ export class OpenExtensionsFolderAction extends Action { super(id, label, undefined, true); } - async run(): Promise { + override async run(): Promise { const extensionsHome = URI.file(this.environmentService.extensionsPath); const file = await this.fileService.resolve(extensionsHome); diff --git a/src/vs/workbench/contrib/extensions/test/common/extensionQuery.test.ts b/src/vs/workbench/contrib/extensions/test/common/extensionQuery.test.ts index c998dc99..d296d20c 100644 --- a/src/vs/workbench/contrib/extensions/test/common/extensionQuery.test.ts +++ b/src/vs/workbench/contrib/extensions/test/common/extensionQuery.test.ts @@ -9,90 +9,90 @@ import { Query } from 'vs/workbench/contrib/extensions/common/extensionQuery'; suite('Extension query', () => { test('parse', () => { let query = Query.parse(''); - assert.equal(query.value, ''); - assert.equal(query.sortBy, ''); + assert.strictEqual(query.value, ''); + assert.strictEqual(query.sortBy, ''); query = Query.parse('hello'); - assert.equal(query.value, 'hello'); - assert.equal(query.sortBy, ''); + assert.strictEqual(query.value, 'hello'); + assert.strictEqual(query.sortBy, ''); query = Query.parse(' hello world '); - assert.equal(query.value, 'hello world'); - assert.equal(query.sortBy, ''); + assert.strictEqual(query.value, 'hello world'); + assert.strictEqual(query.sortBy, ''); query = Query.parse('@sort'); - assert.equal(query.value, '@sort'); - assert.equal(query.sortBy, ''); + assert.strictEqual(query.value, '@sort'); + assert.strictEqual(query.sortBy, ''); query = Query.parse('@sort:'); - assert.equal(query.value, '@sort:'); - assert.equal(query.sortBy, ''); + assert.strictEqual(query.value, '@sort:'); + assert.strictEqual(query.sortBy, ''); query = Query.parse(' @sort: '); - assert.equal(query.value, '@sort:'); - assert.equal(query.sortBy, ''); + assert.strictEqual(query.value, '@sort:'); + assert.strictEqual(query.sortBy, ''); query = Query.parse('@sort:installs'); - assert.equal(query.value, ''); - assert.equal(query.sortBy, 'installs'); + assert.strictEqual(query.value, ''); + assert.strictEqual(query.sortBy, 'installs'); query = Query.parse(' @sort:installs '); - assert.equal(query.value, ''); - assert.equal(query.sortBy, 'installs'); + assert.strictEqual(query.value, ''); + assert.strictEqual(query.sortBy, 'installs'); query = Query.parse('@sort:installs-'); - assert.equal(query.value, ''); - assert.equal(query.sortBy, 'installs'); + assert.strictEqual(query.value, ''); + assert.strictEqual(query.sortBy, 'installs'); query = Query.parse('@sort:installs-foo'); - assert.equal(query.value, ''); - assert.equal(query.sortBy, 'installs'); + assert.strictEqual(query.value, ''); + assert.strictEqual(query.sortBy, 'installs'); query = Query.parse('@sort:installs'); - assert.equal(query.value, ''); - assert.equal(query.sortBy, 'installs'); + assert.strictEqual(query.value, ''); + assert.strictEqual(query.sortBy, 'installs'); query = Query.parse('@sort:installs'); - assert.equal(query.value, ''); - assert.equal(query.sortBy, 'installs'); + assert.strictEqual(query.value, ''); + assert.strictEqual(query.sortBy, 'installs'); query = Query.parse('vs @sort:installs'); - assert.equal(query.value, 'vs'); - assert.equal(query.sortBy, 'installs'); + assert.strictEqual(query.value, 'vs'); + assert.strictEqual(query.sortBy, 'installs'); query = Query.parse('vs @sort:installs code'); - assert.equal(query.value, 'vs code'); - assert.equal(query.sortBy, 'installs'); + assert.strictEqual(query.value, 'vs code'); + assert.strictEqual(query.sortBy, 'installs'); query = Query.parse('@sort:installs @sort:ratings'); - assert.equal(query.value, ''); - assert.equal(query.sortBy, 'ratings'); + assert.strictEqual(query.value, ''); + assert.strictEqual(query.sortBy, 'ratings'); }); test('toString', () => { let query = new Query('hello', '', ''); - assert.equal(query.toString(), 'hello'); + assert.strictEqual(query.toString(), 'hello'); query = new Query('hello world', '', ''); - assert.equal(query.toString(), 'hello world'); + assert.strictEqual(query.toString(), 'hello world'); query = new Query(' hello ', '', ''); - assert.equal(query.toString(), 'hello'); + assert.strictEqual(query.toString(), 'hello'); query = new Query('', 'installs', ''); - assert.equal(query.toString(), '@sort:installs'); + assert.strictEqual(query.toString(), '@sort:installs'); query = new Query('', 'installs', ''); - assert.equal(query.toString(), '@sort:installs'); + assert.strictEqual(query.toString(), '@sort:installs'); query = new Query('', 'installs', ''); - assert.equal(query.toString(), '@sort:installs'); + assert.strictEqual(query.toString(), '@sort:installs'); query = new Query('hello', 'installs', ''); - assert.equal(query.toString(), 'hello @sort:installs'); + assert.strictEqual(query.toString(), 'hello @sort:installs'); query = new Query(' hello ', 'installs', ''); - assert.equal(query.toString(), 'hello @sort:installs'); + assert.strictEqual(query.toString(), 'hello @sort:installs'); }); test('isValid', () => { @@ -146,4 +146,4 @@ suite('Extension query', () => { Query.suggestions('@category:blah').some(x => x === '@category:"extension packs" '); Query.suggestions('@category:"extension packs"').every(x => x !== '@category:formatters '); }); -}); \ No newline at end of file +}); diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionRecommendationsService.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionRecommendationsService.test.ts index 6230d2b2..efedc1c2 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionRecommendationsService.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionRecommendationsService.test.ts @@ -261,7 +261,7 @@ suite('ExtensionRecommendationsService Test', () => { prompted = false; class TestNotificationService2 extends TestNotificationService { - public prompt(severity: Severity, message: string, choices: IPromptChoice[], options?: IPromptOptions) { + public override prompt(severity: Severity, message: string, choices: IPromptChoice[], options?: IPromptOptions) { prompted = true; promptedEmitter.fire(); return super.prompt(severity, message, choices, options); @@ -313,7 +313,7 @@ suite('ExtensionRecommendationsService Test', () => { return setUpFolderWorkspace('myFolder', recommendations).then(() => { testObject = instantiationService.createInstance(ExtensionRecommendationsService); return testObject.activationPromise.then(() => { - assert.equal(Object.keys(testObject.getAllRecommendationsWithReason()).length, recommendations.length); + assert.strictEqual(Object.keys(testObject.getAllRecommendationsWithReason()).length, recommendations.length); assert.ok(!prompted); }); }); @@ -325,7 +325,7 @@ suite('ExtensionRecommendationsService Test', () => { assert.ok(!prompted); return testObject.getWorkspaceRecommendations().then(() => { - assert.equal(Object.keys(testObject.getAllRecommendationsWithReason()).length, 0); + assert.strictEqual(Object.keys(testObject.getAllRecommendationsWithReason()).length, 0); assert.ok(!prompted); }); }); @@ -340,7 +340,7 @@ suite('ExtensionRecommendationsService Test', () => { }); test('ExtensionRecommendationsService: No Prompt for valid workspace recommendations during extension development', () => { - instantiationService.stub(IEnvironmentService, { extensionDevelopmentLocationURI: [URI.file('/folder/file')] }); + instantiationService.stub(IEnvironmentService, { extensionDevelopmentLocationURI: [URI.file('/folder/file')], isExtensionDevelopment: true }); return testNoPromptOrRecommendationsForValidRecommendations(mockTestData.validRecommendedExtensions); }); @@ -354,9 +354,9 @@ suite('ExtensionRecommendationsService Test', () => { await Event.toPromise(promptedEmitter.event); const recommendations = Object.keys(testObject.getAllRecommendationsWithReason()); - assert.equal(recommendations.length, mockTestData.validRecommendedExtensions.length); + assert.strictEqual(recommendations.length, mockTestData.validRecommendedExtensions.length); mockTestData.validRecommendedExtensions.forEach(x => { - assert.equal(recommendations.indexOf(x.toLowerCase()) > -1, true); + assert.strictEqual(recommendations.indexOf(x.toLowerCase()) > -1, true); }); }); @@ -517,7 +517,7 @@ suite('ExtensionRecommendationsService Test', () => { testObject = instantiationService.createInstance(ExtensionRecommendationsService); return testObject.activationPromise.then(() => { const recommendations = testObject.getFileBasedRecommendations(); - assert.equal(recommendations.length, 2); + assert.strictEqual(recommendations.length, 2); assert.ok(recommendations.some(extensionId => extensionId === 'ms-dotnettools.csharp')); // stored recommendation that exists in product.extensionTips assert.ok(recommendations.some(extensionId => extensionId === 'ms-python.python')); // stored recommendation that exists in product.extensionImportantTips assert.ok(recommendations.every(extensionId => extensionId !== 'ms-vscode.vscode-typescript-tslint-plugin')); // stored recommendation that is no longer in neither product.extensionTips nor product.extensionImportantTips @@ -536,7 +536,7 @@ suite('ExtensionRecommendationsService Test', () => { testObject = instantiationService.createInstance(ExtensionRecommendationsService); return testObject.activationPromise.then(() => { const recommendations = testObject.getFileBasedRecommendations(); - assert.equal(recommendations.length, 2); + assert.strictEqual(recommendations.length, 2); assert.ok(recommendations.some(extensionId => extensionId === 'ms-dotnettools.csharp')); // stored recommendation that exists in product.extensionTips assert.ok(recommendations.some(extensionId => extensionId === 'ms-python.python')); // stored recommendation that exists in product.extensionImportantTips assert.ok(recommendations.every(extensionId => extensionId !== 'ms-vscode.vscode-typescript-tslint-plugin')); // stored recommendation that is no longer in neither product.extensionTips nor product.extensionImportantTips diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts index d83e4d42..329cb0ab 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts @@ -55,8 +55,8 @@ import { IUserDataAutoSyncEnablementService, IUserDataSyncResourceEnablementServ import { UserDataSyncResourceEnablementService } from 'vs/platform/userDataSync/common/userDataSyncResourceEnablementService'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; -import { IWorkspaceTrustService } from 'vs/platform/workspace/common/workspaceTrust'; -import { TestWorkspaceTrustService } from 'vs/workbench/services/workspaces/test/common/testWorkspaceTrustService'; +import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust'; +import { TestWorkspaceTrustManagementService } from 'vs/workbench/services/workspaces/test/common/testWorkspaceTrustService'; import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/common/environment'; let instantiationService: TestInstantiationService; @@ -140,7 +140,7 @@ async function setupTest() { instantiationService.stub(IUserDataSyncResourceEnablementService, instantiationService.createInstance(UserDataSyncResourceEnablementService)); instantiationService.set(IExtensionsWorkbenchService, disposables.add(instantiationService.createInstance(ExtensionsWorkbenchService))); - instantiationService.stub(IWorkspaceTrustService, new TestWorkspaceTrustService()); + instantiationService.stub(IWorkspaceTrustManagementService, new TestWorkspaceTrustManagementService()); } @@ -168,8 +168,8 @@ suite('ExtensionsActions', () => { .then((paged) => { testObject.extension = paged.firstPage[0]; assert.ok(!testObject.enabled); - assert.equal('Install', testObject.label); - assert.equal('extension-action label prominent install', testObject.class); + assert.strictEqual('Install', testObject.label); + assert.strictEqual('extension-action label prominent install', testObject.class); }); }); }); @@ -186,8 +186,8 @@ suite('ExtensionsActions', () => { installEvent.fire({ identifier: gallery.identifier, gallery }); assert.ok(!testObject.enabled); - assert.equal('Installing', testObject.label); - assert.equal('extension-action label install installing', testObject.class); + assert.strictEqual('Installing', testObject.label); + assert.strictEqual('extension-action label install installing', testObject.class); }); }); @@ -201,7 +201,7 @@ suite('ExtensionsActions', () => { .then((paged) => { testObject.extension = paged.firstPage[0]; assert.ok(testObject.enabled); - assert.equal('Install', testObject.label); + assert.strictEqual('Install', testObject.label); }); }); @@ -253,8 +253,8 @@ suite('ExtensionsActions', () => { testObject.extension = extensions[0]; uninstallEvent.fire(local.identifier); assert.ok(!testObject.enabled); - assert.equal('Uninstalling', testObject.label); - assert.equal('extension-action label uninstall uninstalling', testObject.class); + assert.strictEqual('Uninstalling', testObject.label); + assert.strictEqual('extension-action label uninstall uninstalling', testObject.class); }); }); @@ -268,8 +268,8 @@ suite('ExtensionsActions', () => { .then(extensions => { testObject.extension = extensions[0]; assert.ok(testObject.enabled); - assert.equal('Uninstall', testObject.label); - assert.equal('extension-action label uninstall', testObject.class); + assert.strictEqual('Uninstall', testObject.label); + assert.strictEqual('extension-action label uninstall', testObject.class); }); }); @@ -283,8 +283,8 @@ suite('ExtensionsActions', () => { .then(extensions => { testObject.extension = extensions[0]; assert.ok(!testObject.enabled); - assert.equal('Uninstall', testObject.label); - assert.equal('extension-action label uninstall', testObject.class); + assert.strictEqual('Uninstall', testObject.label); + assert.strictEqual('extension-action label uninstall', testObject.class); }); }); @@ -319,8 +319,8 @@ suite('ExtensionsActions', () => { didInstallEvent.fire({ identifier: gallery.identifier, gallery, operation: InstallOperation.Install, local: aLocalExtension('a', gallery, gallery) }); assert.ok(testObject.enabled); - assert.equal('Uninstall', testObject.label); - assert.equal('extension-action label uninstall', testObject.class); + assert.strictEqual('Uninstall', testObject.label); + assert.strictEqual('extension-action label uninstall', testObject.class); }); }); @@ -432,8 +432,8 @@ suite('ExtensionsActions', () => { .then(extensions => { testObject.extension = extensions[0]; assert.ok(testObject.enabled); - assert.equal('extension-action icon manage codicon codicon-extensions-manage', testObject.class); - assert.equal('', testObject.tooltip); + assert.strictEqual('extension-action icon manage codicon codicon-extensions-manage', testObject.class); + assert.strictEqual('', testObject.tooltip); }); }); @@ -447,8 +447,8 @@ suite('ExtensionsActions', () => { .then(page => { testObject.extension = page.firstPage[0]; assert.ok(!testObject.enabled); - assert.equal('extension-action icon manage codicon codicon-extensions-manage hide', testObject.class); - assert.equal('', testObject.tooltip); + assert.strictEqual('extension-action icon manage codicon codicon-extensions-manage hide', testObject.class); + assert.strictEqual('', testObject.tooltip); }); }); @@ -464,8 +464,8 @@ suite('ExtensionsActions', () => { installEvent.fire({ identifier: gallery.identifier, gallery }); assert.ok(!testObject.enabled); - assert.equal('extension-action icon manage codicon codicon-extensions-manage hide', testObject.class); - assert.equal('', testObject.tooltip); + assert.strictEqual('extension-action icon manage codicon codicon-extensions-manage hide', testObject.class); + assert.strictEqual('', testObject.tooltip); }); }); @@ -482,8 +482,8 @@ suite('ExtensionsActions', () => { didInstallEvent.fire({ identifier: gallery.identifier, gallery, operation: InstallOperation.Install, local: aLocalExtension('a', gallery, gallery) }); assert.ok(testObject.enabled); - assert.equal('extension-action icon manage codicon codicon-extensions-manage', testObject.class); - assert.equal('', testObject.tooltip); + assert.strictEqual('extension-action icon manage codicon codicon-extensions-manage', testObject.class); + assert.strictEqual('', testObject.tooltip); }); }); @@ -497,8 +497,8 @@ suite('ExtensionsActions', () => { .then(extensions => { testObject.extension = extensions[0]; assert.ok(testObject.enabled); - assert.equal('extension-action icon manage codicon codicon-extensions-manage', testObject.class); - assert.equal('', testObject.tooltip); + assert.strictEqual('extension-action icon manage codicon codicon-extensions-manage', testObject.class); + assert.strictEqual('', testObject.tooltip); }); }); @@ -514,8 +514,8 @@ suite('ExtensionsActions', () => { uninstallEvent.fire(local.identifier); assert.ok(!testObject.enabled); - assert.equal('extension-action icon manage codicon codicon-extensions-manage', testObject.class); - assert.equal('Uninstalling', testObject.tooltip); + assert.strictEqual('extension-action icon manage codicon codicon-extensions-manage', testObject.class); + assert.strictEqual('Uninstalling', testObject.tooltip); }); }); @@ -957,7 +957,7 @@ suite('ReloadAction', () => { installEvent.fire({ identifier: gallery.identifier, gallery }); didInstallEvent.fire({ identifier: gallery.identifier, gallery, operation: InstallOperation.Install, local: aLocalExtension('a', gallery, gallery) }); assert.ok(testObject.enabled); - assert.equal(testObject.tooltip, 'Please reload Visual Studio Code to enable this extension.'); + assert.strictEqual(testObject.tooltip, 'Please reload Visual Studio Code to enable this extension.'); }); test('Test ReloadAction when extension is newly installed and reload is not required', async () => { @@ -1012,7 +1012,7 @@ suite('ReloadAction', () => { uninstallEvent.fire(local.identifier); didUninstallEvent.fire({ identifier: local.identifier }); assert.ok(testObject.enabled); - assert.equal(testObject.tooltip, 'Please reload Visual Studio Code to complete the uninstallation of this extension.'); + assert.strictEqual(testObject.tooltip, 'Please reload Visual Studio Code to complete the uninstallation of this extension.'); }); test('Test ReloadAction when extension is uninstalled and can be removed', async () => { @@ -1107,7 +1107,7 @@ suite('ReloadAction', () => { await testObject.update(); assert.ok(testObject.enabled); - assert.equal('Please reload Visual Studio Code to disable this extension.', testObject.tooltip); + assert.strictEqual('Please reload Visual Studio Code to disable this extension.', testObject.tooltip); }); test('Test ReloadAction when extension enablement is toggled when running', async () => { @@ -1137,7 +1137,7 @@ suite('ReloadAction', () => { await workbenchService.setEnablement(extensions[0], EnablementState.EnabledGlobally); await testObject.update(); assert.ok(testObject.enabled); - assert.equal('Please reload Visual Studio Code to enable this extension.', testObject.tooltip); + assert.strictEqual('Please reload Visual Studio Code to enable this extension.', testObject.tooltip); }); test('Test ReloadAction when extension enablement is toggled when not running', async () => { @@ -1172,7 +1172,7 @@ suite('ReloadAction', () => { await workbenchService.setEnablement(extensions[0], EnablementState.EnabledGlobally); await testObject.update(); assert.ok(testObject.enabled); - assert.equal('Please reload Visual Studio Code to enable this extension.', testObject.tooltip); + assert.strictEqual('Please reload Visual Studio Code to enable this extension.', testObject.tooltip); }); test('Test ReloadAction when a localization extension is newly installed', async () => { @@ -1308,7 +1308,7 @@ suite('ReloadAction', () => { onDidInstallEvent.fire({ identifier: remoteExtension.identifier, local: remoteExtension, operation: InstallOperation.Install }); assert.ok(testObject.enabled); - assert.equal(testObject.tooltip, 'Please reload Visual Studio Code to enable this extension.'); + assert.strictEqual(testObject.tooltip, 'Please reload Visual Studio Code to enable this extension.'); }); test('Test ReloadAction when ui extension is disabled on remote server and installed in local server', async () => { @@ -1344,7 +1344,7 @@ suite('ReloadAction', () => { onDidInstallEvent.fire({ identifier: localExtension.identifier, local: localExtension, operation: InstallOperation.Install }); assert.ok(testObject.enabled); - assert.equal(testObject.tooltip, 'Please reload Visual Studio Code to enable this extension.'); + assert.strictEqual(testObject.tooltip, 'Please reload Visual Studio Code to enable this extension.'); }); test('Test ReloadAction for remote ui extension is disabled when it is installed and enabled in local server', async () => { @@ -1525,8 +1525,8 @@ suite('RemoteInstallAction', () => { await workbenchService.queryGallery(CancellationToken.None); testObject.extension = extensions[0]; assert.ok(testObject.enabled); - assert.equal('Install in remote', testObject.label); - assert.equal('extension-action label prominent install', testObject.class); + assert.strictEqual('Install in remote', testObject.label); + assert.strictEqual('extension-action label prominent install', testObject.class); }); test('Test remote install action when installing local workspace extension', async () => { @@ -1551,13 +1551,13 @@ suite('RemoteInstallAction', () => { await workbenchService.queryGallery(CancellationToken.None); testObject.extension = extensions[0]; assert.ok(testObject.enabled); - assert.equal('Install in remote', testObject.label); - assert.equal('extension-action label prominent install', testObject.class); + assert.strictEqual('Install in remote', testObject.label); + assert.strictEqual('extension-action label prominent install', testObject.class); onInstallExtension.fire({ identifier: localWorkspaceExtension.identifier, gallery }); assert.ok(testObject.enabled); - assert.equal('Installing', testObject.label); - assert.equal('extension-action label install installing', testObject.class); + assert.strictEqual('Installing', testObject.label); + assert.strictEqual('extension-action label install installing', testObject.class); }); test('Test remote install action when installing local workspace extension is finished', async () => { @@ -1584,13 +1584,13 @@ suite('RemoteInstallAction', () => { await workbenchService.queryGallery(CancellationToken.None); testObject.extension = extensions[0]; assert.ok(testObject.enabled); - assert.equal('Install in remote', testObject.label); - assert.equal('extension-action label prominent install', testObject.class); + assert.strictEqual('Install in remote', testObject.label); + assert.strictEqual('extension-action label prominent install', testObject.class); onInstallExtension.fire({ identifier: localWorkspaceExtension.identifier, gallery }); assert.ok(testObject.enabled); - assert.equal('Installing', testObject.label); - assert.equal('extension-action label install installing', testObject.class); + assert.strictEqual('Installing', testObject.label); + assert.strictEqual('extension-action label install installing', testObject.class); const installedExtension = aLocalExtension('a', { extensionKind: ['workspace'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); onDidInstallEvent.fire({ identifier: installedExtension.identifier, local: installedExtension, operation: InstallOperation.Install }); @@ -1615,8 +1615,8 @@ suite('RemoteInstallAction', () => { await workbenchService.queryGallery(CancellationToken.None); testObject.extension = extensions[0]; assert.ok(testObject.enabled); - assert.equal('Install in remote', testObject.label); - assert.equal('extension-action label prominent install', testObject.class); + assert.strictEqual('Install in remote', testObject.label); + assert.strictEqual('extension-action label prominent install', testObject.class); }); test('Test remote install action is enabled local workspace+ui extension', async () => { @@ -1637,8 +1637,8 @@ suite('RemoteInstallAction', () => { await workbenchService.queryGallery(CancellationToken.None); testObject.extension = extensions[0]; assert.ok(testObject.enabled); - assert.equal('Install in remote', testObject.label); - assert.equal('extension-action label prominent install', testObject.class); + assert.strictEqual('Install in remote', testObject.label); + assert.strictEqual('extension-action label prominent install', testObject.class); }); test('Test remote install action is enabled for local ui+workapace extension if can install is true', async () => { @@ -1659,8 +1659,8 @@ suite('RemoteInstallAction', () => { await workbenchService.queryGallery(CancellationToken.None); testObject.extension = extensions[0]; assert.ok(testObject.enabled); - assert.equal('Install in remote', testObject.label); - assert.equal('extension-action label prominent install', testObject.class); + assert.strictEqual('Install in remote', testObject.label); + assert.strictEqual('extension-action label prominent install', testObject.class); }); test('Test remote install action is disabled for local ui+workapace extension if can install is false', async () => { @@ -1779,7 +1779,7 @@ suite('RemoteInstallAction', () => { await workbenchService.queryGallery(CancellationToken.None); testObject.extension = extensions[0]; assert.ok(testObject.enabled); - assert.equal('Install in remote', testObject.label); + assert.strictEqual('Install in remote', testObject.label); uninstallEvent.fire(localWorkspaceExtension.identifier); assert.ok(!testObject.enabled); @@ -1900,8 +1900,8 @@ suite('RemoteInstallAction', () => { await workbenchService.queryGallery(CancellationToken.None); testObject.extension = extensions[0]; assert.ok(testObject.enabled); - assert.equal('Install in remote', testObject.label); - assert.equal('extension-action label prominent install', testObject.class); + assert.strictEqual('Install in remote', testObject.label); + assert.strictEqual('extension-action label prominent install', testObject.class); }); test('Test remote install action is disabled if local language pack extension is uninstalled', async () => { @@ -1923,7 +1923,7 @@ suite('RemoteInstallAction', () => { await workbenchService.queryGallery(CancellationToken.None); testObject.extension = extensions[0]; assert.ok(testObject.enabled); - assert.equal('Install in remote', testObject.label); + assert.strictEqual('Install in remote', testObject.label); uninstallEvent.fire(languagePackExtension.identifier); assert.ok(!testObject.enabled); @@ -1952,8 +1952,8 @@ suite('LocalInstallAction', () => { await workbenchService.queryGallery(CancellationToken.None); testObject.extension = extensions[0]; assert.ok(testObject.enabled); - assert.equal('Install Locally', testObject.label); - assert.equal('extension-action label prominent install', testObject.class); + assert.strictEqual('Install Locally', testObject.label); + assert.strictEqual('extension-action label prominent install', testObject.class); }); test('Test local install action is enabled for remote ui+workspace extension', async () => { @@ -1973,8 +1973,8 @@ suite('LocalInstallAction', () => { await workbenchService.queryGallery(CancellationToken.None); testObject.extension = extensions[0]; assert.ok(testObject.enabled); - assert.equal('Install Locally', testObject.label); - assert.equal('extension-action label prominent install', testObject.class); + assert.strictEqual('Install Locally', testObject.label); + assert.strictEqual('extension-action label prominent install', testObject.class); }); test('Test local install action when installing remote ui extension', async () => { @@ -1999,13 +1999,13 @@ suite('LocalInstallAction', () => { await workbenchService.queryGallery(CancellationToken.None); testObject.extension = extensions[0]; assert.ok(testObject.enabled); - assert.equal('Install Locally', testObject.label); - assert.equal('extension-action label prominent install', testObject.class); + assert.strictEqual('Install Locally', testObject.label); + assert.strictEqual('extension-action label prominent install', testObject.class); onInstallExtension.fire({ identifier: remoteUIExtension.identifier, gallery }); assert.ok(testObject.enabled); - assert.equal('Installing', testObject.label); - assert.equal('extension-action label install installing', testObject.class); + assert.strictEqual('Installing', testObject.label); + assert.strictEqual('extension-action label install installing', testObject.class); }); test('Test local install action when installing remote ui extension is finished', async () => { @@ -2032,13 +2032,13 @@ suite('LocalInstallAction', () => { await workbenchService.queryGallery(CancellationToken.None); testObject.extension = extensions[0]; assert.ok(testObject.enabled); - assert.equal('Install Locally', testObject.label); - assert.equal('extension-action label prominent install', testObject.class); + assert.strictEqual('Install Locally', testObject.label); + assert.strictEqual('extension-action label prominent install', testObject.class); onInstallExtension.fire({ identifier: remoteUIExtension.identifier, gallery }); assert.ok(testObject.enabled); - assert.equal('Installing', testObject.label); - assert.equal('extension-action label install installing', testObject.class); + assert.strictEqual('Installing', testObject.label); + assert.strictEqual('extension-action label install installing', testObject.class); const installedExtension = aLocalExtension('a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`) }); onDidInstallEvent.fire({ identifier: installedExtension.identifier, local: installedExtension, operation: InstallOperation.Install }); @@ -2063,8 +2063,8 @@ suite('LocalInstallAction', () => { await workbenchService.queryGallery(CancellationToken.None); testObject.extension = extensions[0]; assert.ok(testObject.enabled); - assert.equal('Install Locally', testObject.label); - assert.equal('extension-action label prominent install', testObject.class); + assert.strictEqual('Install Locally', testObject.label); + assert.strictEqual('extension-action label prominent install', testObject.class); }); test('Test local install action is disabled when extension is not set', async () => { @@ -2187,7 +2187,7 @@ suite('LocalInstallAction', () => { await workbenchService.queryGallery(CancellationToken.None); testObject.extension = extensions[0]; assert.ok(testObject.enabled); - assert.equal('Install Locally', testObject.label); + assert.strictEqual('Install Locally', testObject.label); uninstallEvent.fire(remoteUIExtension.identifier); assert.ok(!testObject.enabled); @@ -2287,8 +2287,8 @@ suite('LocalInstallAction', () => { await workbenchService.queryGallery(CancellationToken.None); testObject.extension = extensions[0]; assert.ok(testObject.enabled); - assert.equal('Install Locally', testObject.label); - assert.equal('extension-action label prominent install', testObject.class); + assert.strictEqual('Install Locally', testObject.label); + assert.strictEqual('extension-action label prominent install', testObject.class); }); test('Test local install action is disabled if remote language pack extension is uninstalled', async () => { @@ -2310,7 +2310,7 @@ suite('LocalInstallAction', () => { await workbenchService.queryGallery(CancellationToken.None); testObject.extension = extensions[0]; assert.ok(testObject.enabled); - assert.equal('Install Locally', testObject.label); + assert.strictEqual('Install Locally', testObject.label); uninstallEvent.fire(languagePackExtension.identifier); assert.ok(!testObject.enabled); diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts index 8a64229d..fbe191d5 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts @@ -194,15 +194,15 @@ suite('ExtensionsListView Tests', () => { }); test('Test query types', () => { - assert.equal(ExtensionsListView.isBuiltInExtensionsQuery('@builtin'), true); - assert.equal(ExtensionsListView.isLocalExtensionsQuery('@installed'), true); - assert.equal(ExtensionsListView.isLocalExtensionsQuery('@enabled'), true); - assert.equal(ExtensionsListView.isLocalExtensionsQuery('@disabled'), true); - assert.equal(ExtensionsListView.isLocalExtensionsQuery('@outdated'), true); - assert.equal(ExtensionsListView.isLocalExtensionsQuery('@installed searchText'), true); - assert.equal(ExtensionsListView.isLocalExtensionsQuery('@enabled searchText'), true); - assert.equal(ExtensionsListView.isLocalExtensionsQuery('@disabled searchText'), true); - assert.equal(ExtensionsListView.isLocalExtensionsQuery('@outdated searchText'), true); + assert.strictEqual(ExtensionsListView.isBuiltInExtensionsQuery('@builtin'), true); + assert.strictEqual(ExtensionsListView.isLocalExtensionsQuery('@installed'), true); + assert.strictEqual(ExtensionsListView.isLocalExtensionsQuery('@enabled'), true); + assert.strictEqual(ExtensionsListView.isLocalExtensionsQuery('@disabled'), true); + assert.strictEqual(ExtensionsListView.isLocalExtensionsQuery('@outdated'), true); + assert.strictEqual(ExtensionsListView.isLocalExtensionsQuery('@installed searchText'), true); + assert.strictEqual(ExtensionsListView.isLocalExtensionsQuery('@enabled searchText'), true); + assert.strictEqual(ExtensionsListView.isLocalExtensionsQuery('@disabled searchText'), true); + assert.strictEqual(ExtensionsListView.isLocalExtensionsQuery('@outdated searchText'), true); }); test('Test empty query equates to sort by install count', () => { @@ -210,7 +210,7 @@ suite('ExtensionsListView Tests', () => { return testableView.show('').then(() => { assert.ok(target.calledOnce); const options: IQueryOptions = target.args[0][0]; - assert.equal(options.sortBy, SortBy.InstallCount); + assert.strictEqual(options.sortBy, SortBy.InstallCount); }); }); @@ -219,7 +219,7 @@ suite('ExtensionsListView Tests', () => { return testableView.show('some extension').then(() => { assert.ok(target.calledOnce); const options: IQueryOptions = target.args[0][0]; - assert.equal(options.sortBy, undefined); + assert.strictEqual(options.sortBy, undefined); }); }); @@ -228,115 +228,115 @@ suite('ExtensionsListView Tests', () => { return testableView.show('some extension @sort:rating').then(() => { assert.ok(target.calledOnce); const options: IQueryOptions = target.args[0][0]; - assert.equal(options.sortBy, SortBy.WeightedRating); + assert.strictEqual(options.sortBy, SortBy.WeightedRating); }); }); test('Test installed query results', async () => { await testableView.show('@installed').then(result => { - assert.equal(result.length, 5, 'Unexpected number of results for @installed query'); + assert.strictEqual(result.length, 5, 'Unexpected number of results for @installed query'); const actual = [result.get(0).name, result.get(1).name, result.get(2).name, result.get(3).name, result.get(4).name].sort(); const expected = [localDisabledTheme.manifest.name, localEnabledTheme.manifest.name, localRandom.manifest.name, localDisabledLanguage.manifest.name, localEnabledLanguage.manifest.name]; for (let i = 0; i < result.length; i++) { - assert.equal(actual[i], expected[i], 'Unexpected extension for @installed query.'); + assert.strictEqual(actual[i], expected[i], 'Unexpected extension for @installed query.'); } }); await testableView.show('@installed first').then(result => { - assert.equal(result.length, 2, 'Unexpected number of results for @installed query'); - assert.equal(result.get(0).name, localEnabledTheme.manifest.name, 'Unexpected extension for @installed query with search text.'); - assert.equal(result.get(1).name, localDisabledTheme.manifest.name, 'Unexpected extension for @installed query with search text.'); + assert.strictEqual(result.length, 2, 'Unexpected number of results for @installed query'); + assert.strictEqual(result.get(0).name, localEnabledTheme.manifest.name, 'Unexpected extension for @installed query with search text.'); + assert.strictEqual(result.get(1).name, localDisabledTheme.manifest.name, 'Unexpected extension for @installed query with search text.'); }); await testableView.show('@disabled').then(result => { - assert.equal(result.length, 2, 'Unexpected number of results for @disabled query'); - assert.equal(result.get(0).name, localDisabledTheme.manifest.name, 'Unexpected extension for @disabled query.'); - assert.equal(result.get(1).name, localDisabledLanguage.manifest.name, 'Unexpected extension for @disabled query.'); + assert.strictEqual(result.length, 2, 'Unexpected number of results for @disabled query'); + assert.strictEqual(result.get(0).name, localDisabledTheme.manifest.name, 'Unexpected extension for @disabled query.'); + assert.strictEqual(result.get(1).name, localDisabledLanguage.manifest.name, 'Unexpected extension for @disabled query.'); }); await testableView.show('@enabled').then(result => { - assert.equal(result.length, 3, 'Unexpected number of results for @enabled query'); - assert.equal(result.get(0).name, localEnabledTheme.manifest.name, 'Unexpected extension for @enabled query.'); - assert.equal(result.get(1).name, localRandom.manifest.name, 'Unexpected extension for @enabled query.'); - assert.equal(result.get(2).name, localEnabledLanguage.manifest.name, 'Unexpected extension for @enabled query.'); + assert.strictEqual(result.length, 3, 'Unexpected number of results for @enabled query'); + assert.strictEqual(result.get(0).name, localEnabledTheme.manifest.name, 'Unexpected extension for @enabled query.'); + assert.strictEqual(result.get(1).name, localRandom.manifest.name, 'Unexpected extension for @enabled query.'); + assert.strictEqual(result.get(2).name, localEnabledLanguage.manifest.name, 'Unexpected extension for @enabled query.'); }); await testableView.show('@builtin:themes').then(result => { - assert.equal(result.length, 1, 'Unexpected number of results for @builtin:themes query'); - assert.equal(result.get(0).name, builtInTheme.manifest.name, 'Unexpected extension for @builtin:themes query.'); + assert.strictEqual(result.length, 1, 'Unexpected number of results for @builtin:themes query'); + assert.strictEqual(result.get(0).name, builtInTheme.manifest.name, 'Unexpected extension for @builtin:themes query.'); }); await testableView.show('@builtin:basics').then(result => { - assert.equal(result.length, 1, 'Unexpected number of results for @builtin:basics query'); - assert.equal(result.get(0).name, builtInBasic.manifest.name, 'Unexpected extension for @builtin:basics query.'); + assert.strictEqual(result.length, 1, 'Unexpected number of results for @builtin:basics query'); + assert.strictEqual(result.get(0).name, builtInBasic.manifest.name, 'Unexpected extension for @builtin:basics query.'); }); await testableView.show('@builtin').then(result => { - assert.equal(result.length, 2, 'Unexpected number of results for @builtin query'); - assert.equal(result.get(0).name, builtInBasic.manifest.name, 'Unexpected extension for @builtin query.'); - assert.equal(result.get(1).name, builtInTheme.manifest.name, 'Unexpected extension for @builtin query.'); + assert.strictEqual(result.length, 2, 'Unexpected number of results for @builtin query'); + assert.strictEqual(result.get(0).name, builtInBasic.manifest.name, 'Unexpected extension for @builtin query.'); + assert.strictEqual(result.get(1).name, builtInTheme.manifest.name, 'Unexpected extension for @builtin query.'); }); await testableView.show('@builtin my-theme').then(result => { - assert.equal(result.length, 1, 'Unexpected number of results for @builtin query'); - assert.equal(result.get(0).name, builtInTheme.manifest.name, 'Unexpected extension for @builtin query.'); + assert.strictEqual(result.length, 1, 'Unexpected number of results for @builtin query'); + assert.strictEqual(result.get(0).name, builtInTheme.manifest.name, 'Unexpected extension for @builtin query.'); }); }); test('Test installed query with category', async () => { await testableView.show('@installed category:themes').then(result => { - assert.equal(result.length, 2, 'Unexpected number of results for @installed query with category'); - assert.equal(result.get(0).name, localEnabledTheme.manifest.name, 'Unexpected extension for @installed query with category.'); - assert.equal(result.get(1).name, localDisabledTheme.manifest.name, 'Unexpected extension for @installed query with category.'); + assert.strictEqual(result.length, 2, 'Unexpected number of results for @installed query with category'); + assert.strictEqual(result.get(0).name, localEnabledTheme.manifest.name, 'Unexpected extension for @installed query with category.'); + assert.strictEqual(result.get(1).name, localDisabledTheme.manifest.name, 'Unexpected extension for @installed query with category.'); }); await testableView.show('@installed category:"themes"').then(result => { - assert.equal(result.length, 2, 'Unexpected number of results for @installed query with quoted category'); - assert.equal(result.get(0).name, localEnabledTheme.manifest.name, 'Unexpected extension for @installed query with quoted category.'); - assert.equal(result.get(1).name, localDisabledTheme.manifest.name, 'Unexpected extension for @installed query with quoted category.'); + assert.strictEqual(result.length, 2, 'Unexpected number of results for @installed query with quoted category'); + assert.strictEqual(result.get(0).name, localEnabledTheme.manifest.name, 'Unexpected extension for @installed query with quoted category.'); + assert.strictEqual(result.get(1).name, localDisabledTheme.manifest.name, 'Unexpected extension for @installed query with quoted category.'); }); await testableView.show('@installed category:"programming languages"').then(result => { - assert.equal(result.length, 2, 'Unexpected number of results for @installed query with quoted category including space'); - assert.equal(result.get(0).name, localEnabledLanguage.manifest.name, 'Unexpected extension for @installed query with quoted category including space.'); - assert.equal(result.get(1).name, localDisabledLanguage.manifest.name, 'Unexpected extension for @installed query with quoted category inlcuding space.'); + assert.strictEqual(result.length, 2, 'Unexpected number of results for @installed query with quoted category including space'); + assert.strictEqual(result.get(0).name, localEnabledLanguage.manifest.name, 'Unexpected extension for @installed query with quoted category including space.'); + assert.strictEqual(result.get(1).name, localDisabledLanguage.manifest.name, 'Unexpected extension for @installed query with quoted category inlcuding space.'); }); await testableView.show('@installed category:themes category:random').then(result => { - assert.equal(result.length, 3, 'Unexpected number of results for @installed query with multiple category'); - assert.equal(result.get(0).name, localEnabledTheme.manifest.name, 'Unexpected extension for @installed query with multiple category.'); - assert.equal(result.get(1).name, localRandom.manifest.name, 'Unexpected extension for @installed query with multiple category.'); - assert.equal(result.get(2).name, localDisabledTheme.manifest.name, 'Unexpected extension for @installed query with multiple category.'); + assert.strictEqual(result.length, 3, 'Unexpected number of results for @installed query with multiple category'); + assert.strictEqual(result.get(0).name, localEnabledTheme.manifest.name, 'Unexpected extension for @installed query with multiple category.'); + assert.strictEqual(result.get(1).name, localRandom.manifest.name, 'Unexpected extension for @installed query with multiple category.'); + assert.strictEqual(result.get(2).name, localDisabledTheme.manifest.name, 'Unexpected extension for @installed query with multiple category.'); }); await testableView.show('@enabled category:themes').then(result => { - assert.equal(result.length, 1, 'Unexpected number of results for @enabled query with category'); - assert.equal(result.get(0).name, localEnabledTheme.manifest.name, 'Unexpected extension for @enabled query with category.'); + assert.strictEqual(result.length, 1, 'Unexpected number of results for @enabled query with category'); + assert.strictEqual(result.get(0).name, localEnabledTheme.manifest.name, 'Unexpected extension for @enabled query with category.'); }); await testableView.show('@enabled category:"themes"').then(result => { - assert.equal(result.length, 1, 'Unexpected number of results for @enabled query with quoted category'); - assert.equal(result.get(0).name, localEnabledTheme.manifest.name, 'Unexpected extension for @enabled query with quoted category.'); + assert.strictEqual(result.length, 1, 'Unexpected number of results for @enabled query with quoted category'); + assert.strictEqual(result.get(0).name, localEnabledTheme.manifest.name, 'Unexpected extension for @enabled query with quoted category.'); }); await testableView.show('@enabled category:"programming languages"').then(result => { - assert.equal(result.length, 1, 'Unexpected number of results for @enabled query with quoted category inlcuding space'); - assert.equal(result.get(0).name, localEnabledLanguage.manifest.name, 'Unexpected extension for @enabled query with quoted category including space.'); + assert.strictEqual(result.length, 1, 'Unexpected number of results for @enabled query with quoted category inlcuding space'); + assert.strictEqual(result.get(0).name, localEnabledLanguage.manifest.name, 'Unexpected extension for @enabled query with quoted category including space.'); }); await testableView.show('@disabled category:themes').then(result => { - assert.equal(result.length, 1, 'Unexpected number of results for @disabled query with category'); - assert.equal(result.get(0).name, localDisabledTheme.manifest.name, 'Unexpected extension for @disabled query with category.'); + assert.strictEqual(result.length, 1, 'Unexpected number of results for @disabled query with category'); + assert.strictEqual(result.get(0).name, localDisabledTheme.manifest.name, 'Unexpected extension for @disabled query with category.'); }); await testableView.show('@disabled category:"themes"').then(result => { - assert.equal(result.length, 1, 'Unexpected number of results for @disabled query with quoted category'); - assert.equal(result.get(0).name, localDisabledTheme.manifest.name, 'Unexpected extension for @disabled query with quoted category.'); + assert.strictEqual(result.length, 1, 'Unexpected number of results for @disabled query with quoted category'); + assert.strictEqual(result.get(0).name, localDisabledTheme.manifest.name, 'Unexpected extension for @disabled query with quoted category.'); }); await testableView.show('@disabled category:"programming languages"').then(result => { - assert.equal(result.length, 1, 'Unexpected number of results for @disabled query with quoted category inlcuding space'); - assert.equal(result.get(0).name, localDisabledLanguage.manifest.name, 'Unexpected extension for @disabled query with quoted category including space.'); + assert.strictEqual(result.length, 1, 'Unexpected number of results for @disabled query with quoted category inlcuding space'); + assert.strictEqual(result.get(0).name, localDisabledLanguage.manifest.name, 'Unexpected extension for @disabled query with quoted category including space.'); }); }); @@ -351,11 +351,11 @@ suite('ExtensionsListView Tests', () => { return testableView.show('@recommended:workspace').then(result => { assert.ok(target.calledOnce); const options: IQueryOptions = target.args[0][0]; - assert.equal(options.names!.length, workspaceRecommendedExtensions.length); - assert.equal(result.length, workspaceRecommendedExtensions.length); + assert.strictEqual(options.names!.length, workspaceRecommendedExtensions.length); + assert.strictEqual(result.length, workspaceRecommendedExtensions.length); for (let i = 0; i < workspaceRecommendedExtensions.length; i++) { - assert.equal(options.names![i], workspaceRecommendedExtensions[i].identifier.id); - assert.equal(result.get(i).identifier.id, workspaceRecommendedExtensions[i].identifier.id); + assert.strictEqual(options.names![i], workspaceRecommendedExtensions[i].identifier.id); + assert.strictEqual(result.get(i).identifier.id, workspaceRecommendedExtensions[i].identifier.id); } }); }); @@ -373,11 +373,11 @@ suite('ExtensionsListView Tests', () => { const options: IQueryOptions = target.args[0][0]; assert.ok(target.calledOnce); - assert.equal(options.names!.length, allRecommendedExtensions.length); - assert.equal(result.length, allRecommendedExtensions.length); + assert.strictEqual(options.names!.length, allRecommendedExtensions.length); + assert.strictEqual(result.length, allRecommendedExtensions.length); for (let i = 0; i < allRecommendedExtensions.length; i++) { - assert.equal(options.names![i], allRecommendedExtensions[i].identifier.id); - assert.equal(result.get(i).identifier.id, allRecommendedExtensions[i].identifier.id); + assert.strictEqual(options.names![i], allRecommendedExtensions[i].identifier.id); + assert.strictEqual(result.get(i).identifier.id, allRecommendedExtensions[i].identifier.id); } }); }); @@ -399,11 +399,11 @@ suite('ExtensionsListView Tests', () => { const options: IQueryOptions = target.args[0][0]; assert.ok(target.calledOnce); - assert.equal(options.names!.length, allRecommendedExtensions.length); - assert.equal(result.length, allRecommendedExtensions.length); + assert.strictEqual(options.names!.length, allRecommendedExtensions.length); + assert.strictEqual(result.length, allRecommendedExtensions.length); for (let i = 0; i < allRecommendedExtensions.length; i++) { - assert.equal(options.names![i], allRecommendedExtensions[i].identifier.id); - assert.equal(result.get(i).identifier.id, allRecommendedExtensions[i].identifier.id); + assert.strictEqual(options.names![i], allRecommendedExtensions[i].identifier.id); + assert.strictEqual(result.get(i).identifier.id, allRecommendedExtensions[i].identifier.id); } }); }); @@ -422,13 +422,13 @@ suite('ExtensionsListView Tests', () => { assert.ok(experimentTarget.calledOnce); assert.ok(queryTarget.calledOnce); - assert.equal(options.names!.length, curatedList.length); - assert.equal(result.length, curatedList.length); + assert.strictEqual(options.names!.length, curatedList.length); + assert.strictEqual(result.length, curatedList.length); for (let i = 0; i < curatedList.length; i++) { - assert.equal(options.names![i], curatedList[i].identifier.id); - assert.equal(result.get(i).identifier.id, curatedList[i].identifier.id); + assert.strictEqual(options.names![i], curatedList[i].identifier.id); + assert.strictEqual(result.get(i).identifier.id, curatedList[i].identifier.id); } - assert.equal(curatedKey, 'mykey'); + assert.strictEqual(curatedKey, 'mykey'); }); }); @@ -445,10 +445,10 @@ suite('ExtensionsListView Tests', () => { const options: IQueryOptions = queryTarget.args[0][0]; assert.ok(queryTarget.calledOnce); - assert.equal(options.text, searchText); - assert.equal(result.length, results.length); + assert.strictEqual(options.text, searchText); + assert.strictEqual(result.length, results.length); for (let i = 0; i < results.length; i++) { - assert.equal(result.get(i).identifier.id, results[i].identifier.id); + assert.strictEqual(result.get(i).identifier.id, results[i].identifier.id); } }); }); @@ -494,10 +494,10 @@ suite('ExtensionsListView Tests', () => { assert.ok(experimentTarget.calledOnce); assert.ok(queryTarget.calledOnce); - assert.equal(options.text, searchText); - assert.equal(result.length, expected.length); + assert.strictEqual(options.text, searchText); + assert.strictEqual(result.length, expected.length); for (let i = 0; i < expected.length; i++) { - assert.equal(result.get(i).identifier.id, expected[i].identifier.id); + assert.strictEqual(result.get(i).identifier.id, expected[i].identifier.id); } }); }); @@ -520,10 +520,10 @@ suite('ExtensionsListView Tests', () => { const options: IQueryOptions = queryTarget.args[0][0]; assert.ok(queryTarget.calledOnce); - assert.equal(options.text, searchText); - assert.equal(result.length, realResults.length); + assert.strictEqual(options.text, searchText); + assert.strictEqual(result.length, realResults.length); for (let i = 0; i < realResults.length; i++) { - assert.equal(result.get(i).identifier.id, realResults[i].identifier.id); + assert.strictEqual(result.get(i).identifier.id, realResults[i].identifier.id); } }); }); diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts index c8741601..36890e2a 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts @@ -156,26 +156,26 @@ suite('ExtensionsWorkbenchServiceTest', () => { instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(expected)); return testObject.queryGallery(CancellationToken.None).then(pagedResponse => { - assert.equal(1, pagedResponse.firstPage.length); + assert.strictEqual(1, pagedResponse.firstPage.length); const actual = pagedResponse.firstPage[0]; - assert.equal(ExtensionType.User, actual.type); - assert.equal('expectedName', actual.name); - assert.equal('expectedDisplayName', actual.displayName); - assert.equal('expectedpublisher.expectedname', actual.identifier.id); - assert.equal('expectedPublisher', actual.publisher); - assert.equal('expectedPublisherDisplayName', actual.publisherDisplayName); - assert.equal('1.5.0', actual.version); - assert.equal('1.5.0', actual.latestVersion); - assert.equal('expectedDescription', actual.description); - assert.equal('uri:icon', actual.iconUrl); - assert.equal('fallback:icon', actual.iconUrlFallback); - assert.equal('uri:license', actual.licenseUrl); - assert.equal(ExtensionState.Uninstalled, actual.state); - assert.equal(1000, actual.installCount); - assert.equal(4, actual.rating); - assert.equal(100, actual.ratingCount); - assert.equal(false, actual.outdated); + assert.strictEqual(ExtensionType.User, actual.type); + assert.strictEqual('expectedName', actual.name); + assert.strictEqual('expectedDisplayName', actual.displayName); + assert.strictEqual('expectedpublisher.expectedname', actual.identifier.id); + assert.strictEqual('expectedPublisher', actual.publisher); + assert.strictEqual('expectedPublisherDisplayName', actual.publisherDisplayName); + assert.strictEqual('1.5.0', actual.version); + assert.strictEqual('1.5.0', actual.latestVersion); + assert.strictEqual('expectedDescription', actual.description); + assert.strictEqual('uri:icon', actual.iconUrl); + assert.strictEqual('fallback:icon', actual.iconUrlFallback); + assert.strictEqual('uri:license', actual.licenseUrl); + assert.strictEqual(ExtensionState.Uninstalled, actual.state); + assert.strictEqual(1000, actual.installCount); + assert.strictEqual(4, actual.rating); + assert.strictEqual(100, actual.ratingCount); + assert.strictEqual(false, actual.outdated); assert.deepEqual(['pub.1', 'pub.2'], actual.dependencies); }); }); @@ -214,43 +214,43 @@ suite('ExtensionsWorkbenchServiceTest', () => { testObject = await aWorkbenchService(); const actuals = testObject.local; - assert.equal(2, actuals.length); + assert.strictEqual(2, actuals.length); let actual = actuals[0]; - assert.equal(ExtensionType.User, actual.type); - assert.equal('local1', actual.name); - assert.equal('localDisplayName1', actual.displayName); - assert.equal('localpublisher1.local1', actual.identifier.id); - assert.equal('localPublisher1', actual.publisher); - assert.equal('1.1.0', actual.version); - assert.equal('1.1.0', actual.latestVersion); - assert.equal('localDescription1', actual.description); + assert.strictEqual(ExtensionType.User, actual.type); + assert.strictEqual('local1', actual.name); + assert.strictEqual('localDisplayName1', actual.displayName); + assert.strictEqual('localpublisher1.local1', actual.identifier.id); + assert.strictEqual('localPublisher1', actual.publisher); + assert.strictEqual('1.1.0', actual.version); + assert.strictEqual('1.1.0', actual.latestVersion); + assert.strictEqual('localDescription1', actual.description); assert.ok(actual.iconUrl === 'file:///localPath1/localIcon1' || actual.iconUrl === 'vscode-file://vscode-app/localPath1/localIcon1'); assert.ok(actual.iconUrlFallback === 'file:///localPath1/localIcon1' || actual.iconUrlFallback === 'vscode-file://vscode-app/localPath1/localIcon1'); - assert.equal(null, actual.licenseUrl); - assert.equal(ExtensionState.Installed, actual.state); - assert.equal(null, actual.installCount); - assert.equal(null, actual.rating); - assert.equal(null, actual.ratingCount); - assert.equal(false, actual.outdated); + assert.strictEqual(undefined, actual.licenseUrl); + assert.strictEqual(ExtensionState.Installed, actual.state); + assert.strictEqual(undefined, actual.installCount); + assert.strictEqual(undefined, actual.rating); + assert.strictEqual(undefined, actual.ratingCount); + assert.strictEqual(false, actual.outdated); assert.deepEqual(['pub.1', 'pub.2'], actual.dependencies); actual = actuals[1]; - assert.equal(ExtensionType.System, actual.type); - assert.equal('local2', actual.name); - assert.equal('localDisplayName2', actual.displayName); - assert.equal('localpublisher2.local2', actual.identifier.id); - assert.equal('localPublisher2', actual.publisher); - assert.equal('1.2.0', actual.version); - assert.equal('1.2.0', actual.latestVersion); - assert.equal('localDescription2', actual.description); + assert.strictEqual(ExtensionType.System, actual.type); + assert.strictEqual('local2', actual.name); + assert.strictEqual('localDisplayName2', actual.displayName); + assert.strictEqual('localpublisher2.local2', actual.identifier.id); + assert.strictEqual('localPublisher2', actual.publisher); + assert.strictEqual('1.2.0', actual.version); + assert.strictEqual('1.2.0', actual.latestVersion); + assert.strictEqual('localDescription2', actual.description); assert.ok(fs.existsSync(URI.parse(actual.iconUrl).fsPath)); - assert.equal(null, actual.licenseUrl); - assert.equal(ExtensionState.Installed, actual.state); - assert.equal(null, actual.installCount); - assert.equal(null, actual.rating); - assert.equal(null, actual.ratingCount); - assert.equal(false, actual.outdated); + assert.strictEqual(undefined, actual.licenseUrl); + assert.strictEqual(ExtensionState.Installed, actual.state); + assert.strictEqual(undefined, actual.installCount); + assert.strictEqual(undefined, actual.rating); + assert.strictEqual(undefined, actual.ratingCount); + assert.strictEqual(false, actual.outdated); assert.deepEqual([], actual.dependencies); }); @@ -308,43 +308,43 @@ suite('ExtensionsWorkbenchServiceTest', () => { return eventToPromise(testObject.onChange).then(() => { const actuals = testObject.local; - assert.equal(2, actuals.length); + assert.strictEqual(2, actuals.length); let actual = actuals[0]; - assert.equal(ExtensionType.User, actual.type); - assert.equal('local1', actual.name); - assert.equal('expectedDisplayName', actual.displayName); - assert.equal('localpublisher1.local1', actual.identifier.id); - assert.equal('localPublisher1', actual.publisher); - assert.equal('1.1.0', actual.version); - assert.equal('1.5.0', actual.latestVersion); - assert.equal('expectedDescription', actual.description); - assert.equal('uri:icon', actual.iconUrl); - assert.equal('fallback:icon', actual.iconUrlFallback); - assert.equal(ExtensionState.Installed, actual.state); - assert.equal('uri:license', actual.licenseUrl); - assert.equal(1000, actual.installCount); - assert.equal(4, actual.rating); - assert.equal(100, actual.ratingCount); - assert.equal(true, actual.outdated); + assert.strictEqual(ExtensionType.User, actual.type); + assert.strictEqual('local1', actual.name); + assert.strictEqual('expectedDisplayName', actual.displayName); + assert.strictEqual('localpublisher1.local1', actual.identifier.id); + assert.strictEqual('localPublisher1', actual.publisher); + assert.strictEqual('1.1.0', actual.version); + assert.strictEqual('1.5.0', actual.latestVersion); + assert.strictEqual('expectedDescription', actual.description); + assert.strictEqual('uri:icon', actual.iconUrl); + assert.strictEqual('fallback:icon', actual.iconUrlFallback); + assert.strictEqual(ExtensionState.Installed, actual.state); + assert.strictEqual('uri:license', actual.licenseUrl); + assert.strictEqual(1000, actual.installCount); + assert.strictEqual(4, actual.rating); + assert.strictEqual(100, actual.ratingCount); + assert.strictEqual(true, actual.outdated); assert.deepEqual(['pub.1'], actual.dependencies); actual = actuals[1]; - assert.equal(ExtensionType.System, actual.type); - assert.equal('local2', actual.name); - assert.equal('localDisplayName2', actual.displayName); - assert.equal('localpublisher2.local2', actual.identifier.id); - assert.equal('localPublisher2', actual.publisher); - assert.equal('1.2.0', actual.version); - assert.equal('1.2.0', actual.latestVersion); - assert.equal('localDescription2', actual.description); + assert.strictEqual(ExtensionType.System, actual.type); + assert.strictEqual('local2', actual.name); + assert.strictEqual('localDisplayName2', actual.displayName); + assert.strictEqual('localpublisher2.local2', actual.identifier.id); + assert.strictEqual('localPublisher2', actual.publisher); + assert.strictEqual('1.2.0', actual.version); + assert.strictEqual('1.2.0', actual.latestVersion); + assert.strictEqual('localDescription2', actual.description); assert.ok(fs.existsSync(URI.parse(actual.iconUrl).fsPath)); - assert.equal(null, actual.licenseUrl); - assert.equal(ExtensionState.Installed, actual.state); - assert.equal(null, actual.installCount); - assert.equal(null, actual.rating); - assert.equal(null, actual.ratingCount); - assert.equal(false, actual.outdated); + assert.strictEqual(undefined, actual.licenseUrl); + assert.strictEqual(ExtensionState.Installed, actual.state); + assert.strictEqual(undefined, actual.installCount); + assert.strictEqual(undefined, actual.rating); + assert.strictEqual(undefined, actual.ratingCount); + assert.strictEqual(false, actual.outdated); assert.deepEqual([], actual.dependencies); }); }); @@ -356,7 +356,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { return testObject.queryGallery(CancellationToken.None).then(page => { const extension = page.firstPage[0]; - assert.equal(ExtensionState.Uninstalled, extension.state); + assert.strictEqual(ExtensionState.Uninstalled, extension.state); testObject.install(extension); const identifier = gallery.identifier; @@ -364,27 +364,27 @@ suite('ExtensionsWorkbenchServiceTest', () => { // Installing installEvent.fire({ identifier, gallery }); let local = testObject.local; - assert.equal(1, local.length); + assert.strictEqual(1, local.length); const actual = local[0]; - assert.equal(`${gallery.publisher}.${gallery.name}`, actual.identifier.id); - assert.equal(ExtensionState.Installing, actual.state); + assert.strictEqual(`${gallery.publisher}.${gallery.name}`, actual.identifier.id); + assert.strictEqual(ExtensionState.Installing, actual.state); // Installed didInstallEvent.fire({ identifier, gallery, operation: InstallOperation.Install, local: aLocalExtension(gallery.name, gallery, { identifier }) }); - assert.equal(ExtensionState.Installed, actual.state); - assert.equal(1, testObject.local.length); + assert.strictEqual(ExtensionState.Installed, actual.state); + assert.strictEqual(1, testObject.local.length); testObject.uninstall(actual); // Uninstalling uninstallEvent.fire(identifier); - assert.equal(ExtensionState.Uninstalling, actual.state); + assert.strictEqual(ExtensionState.Uninstalling, actual.state); // Uninstalled didUninstallEvent.fire({ identifier }); - assert.equal(ExtensionState.Uninstalled, actual.state); + assert.strictEqual(ExtensionState.Uninstalled, actual.state); - assert.equal(0, testObject.local.length); + assert.strictEqual(0, testObject.local.length); }); }); @@ -440,7 +440,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { return testObject.queryGallery(CancellationToken.None).then(page => { const extension = page.firstPage[0]; - assert.equal(ExtensionState.Uninstalled, extension.state); + assert.strictEqual(ExtensionState.Uninstalled, extension.state); testObject.install(extension); installEvent.fire({ identifier: gallery.identifier, gallery }); @@ -461,7 +461,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { return testObject.queryGallery(CancellationToken.None).then(page => { const extension = page.firstPage[0]; - assert.equal(ExtensionState.Uninstalled, extension.state); + assert.strictEqual(ExtensionState.Uninstalled, extension.state); testObject.install(extension); testObject.onChange(target); @@ -508,7 +508,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a'))); return testObject.queryGallery(CancellationToken.None).then(pagedResponse => { const actual = pagedResponse.firstPage[0]; - assert.equal(actual.enablementState, EnablementState.EnabledGlobally); + assert.strictEqual(actual.enablementState, EnablementState.EnabledGlobally); }); }); }); @@ -522,7 +522,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { const actual = testObject.local[0]; - assert.equal(actual.enablementState, EnablementState.EnabledGlobally); + assert.strictEqual(actual.enablementState, EnablementState.EnabledGlobally); }); }); @@ -538,7 +538,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { const actual = testObject.local[0]; - assert.equal(actual.enablementState, EnablementState.DisabledWorkspace); + assert.strictEqual(actual.enablementState, EnablementState.DisabledWorkspace); }); }); @@ -553,7 +553,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { const actual = testObject.local[0]; - assert.equal(actual.enablementState, EnablementState.DisabledGlobally); + assert.strictEqual(actual.enablementState, EnablementState.DisabledGlobally); }); }); @@ -566,7 +566,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { return testObject.setEnablement(testObject.local[0], EnablementState.DisabledWorkspace) .then(() => { const actual = testObject.local[0]; - assert.equal(actual.enablementState, EnablementState.DisabledWorkspace); + assert.strictEqual(actual.enablementState, EnablementState.DisabledWorkspace); }); }); }); @@ -580,7 +580,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { return testObject.setEnablement(testObject.local[0], EnablementState.EnabledGlobally) .then(() => { const actual = testObject.local[0]; - assert.equal(actual.enablementState, EnablementState.EnabledGlobally); + assert.strictEqual(actual.enablementState, EnablementState.EnabledGlobally); }); }); }); @@ -592,7 +592,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { return testObject.setEnablement(testObject.local[0], EnablementState.DisabledGlobally) .then(() => { const actual = testObject.local[0]; - assert.equal(actual.enablementState, EnablementState.DisabledGlobally); + assert.strictEqual(actual.enablementState, EnablementState.DisabledGlobally); }); }); @@ -603,7 +603,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { return testObject.setEnablement(testObject.local[0], EnablementState.DisabledGlobally) .then(() => { const actual = testObject.local[0]; - assert.equal(actual.enablementState, EnablementState.DisabledGlobally); + assert.strictEqual(actual.enablementState, EnablementState.DisabledGlobally); }); }); @@ -618,7 +618,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { return instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([localExtension], EnablementState.DisabledGlobally) .then(() => { const actual = testObject.local[0]; - assert.equal(actual.enablementState, EnablementState.DisabledGlobally); + assert.strictEqual(actual.enablementState, EnablementState.DisabledGlobally); }); }); }); @@ -637,8 +637,8 @@ suite('ExtensionsWorkbenchServiceTest', () => { return testObject.setEnablement(testObject.local[0], EnablementState.DisabledGlobally) .then(() => { - assert.equal(testObject.local[0].enablementState, EnablementState.DisabledGlobally); - assert.equal(testObject.local[1].enablementState, EnablementState.EnabledGlobally); + assert.strictEqual(testObject.local[0].enablementState, EnablementState.DisabledGlobally); + assert.strictEqual(testObject.local[1].enablementState, EnablementState.EnabledGlobally); }); }); }); @@ -657,8 +657,8 @@ suite('ExtensionsWorkbenchServiceTest', () => { return testObject.setEnablement(testObject.local[0], EnablementState.DisabledGlobally) .then(() => { - assert.equal(testObject.local[0].enablementState, EnablementState.DisabledGlobally); - assert.equal(testObject.local[1].enablementState, EnablementState.DisabledGlobally); + assert.strictEqual(testObject.local[0].enablementState, EnablementState.DisabledGlobally); + assert.strictEqual(testObject.local[1].enablementState, EnablementState.DisabledGlobally); }); }); }); @@ -677,8 +677,8 @@ suite('ExtensionsWorkbenchServiceTest', () => { return testObject.setEnablement(testObject.local[0], EnablementState.DisabledGlobally) .then(() => { - assert.equal(testObject.local[0].enablementState, EnablementState.DisabledGlobally); - assert.equal(testObject.local[1].enablementState, EnablementState.DisabledGlobally); + assert.strictEqual(testObject.local[0].enablementState, EnablementState.DisabledGlobally); + assert.strictEqual(testObject.local[1].enablementState, EnablementState.DisabledGlobally); }); }); }); @@ -720,8 +720,8 @@ suite('ExtensionsWorkbenchServiceTest', () => { instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [extensionA, extensionB, extensionC]); testObject = await aWorkbenchService(); await testObject.setEnablement(testObject.local[1], EnablementState.DisabledGlobally); - assert.equal(testObject.local[0].enablementState, EnablementState.DisabledGlobally); - assert.equal(testObject.local[1].enablementState, EnablementState.DisabledGlobally); + assert.strictEqual(testObject.local[0].enablementState, EnablementState.DisabledGlobally); + assert.strictEqual(testObject.local[1].enablementState, EnablementState.DisabledGlobally); }); }); @@ -738,7 +738,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { testObject = await aWorkbenchService(); return testObject.setEnablement(testObject.local[1], EnablementState.DisabledGlobally) .then(() => { - assert.equal(testObject.local[1].enablementState, EnablementState.DisabledGlobally); + assert.strictEqual(testObject.local[1].enablementState, EnablementState.DisabledGlobally); }); }); }); @@ -759,8 +759,8 @@ suite('ExtensionsWorkbenchServiceTest', () => { return testObject.setEnablement([testObject.local[1], testObject.local[0]], EnablementState.DisabledGlobally) .then(() => { assert.ok(!target.called); - assert.equal(testObject.local[0].enablementState, EnablementState.DisabledGlobally); - assert.equal(testObject.local[1].enablementState, EnablementState.DisabledGlobally); + assert.strictEqual(testObject.local[0].enablementState, EnablementState.DisabledGlobally); + assert.strictEqual(testObject.local[1].enablementState, EnablementState.DisabledGlobally); }); }); }); @@ -781,8 +781,8 @@ suite('ExtensionsWorkbenchServiceTest', () => { return testObject.setEnablement([testObject.local[1], testObject.local[0]], EnablementState.EnabledGlobally) .then(() => { assert.ok(!target.called); - assert.equal(testObject.local[0].enablementState, EnablementState.EnabledGlobally); - assert.equal(testObject.local[1].enablementState, EnablementState.EnabledGlobally); + assert.strictEqual(testObject.local[0].enablementState, EnablementState.EnabledGlobally); + assert.strictEqual(testObject.local[1].enablementState, EnablementState.EnabledGlobally); }); }); }); @@ -801,7 +801,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { return testObject.setEnablement(testObject.local[0], EnablementState.DisabledGlobally) .then(() => { - assert.equal(testObject.local[0].enablementState, EnablementState.DisabledGlobally); + assert.strictEqual(testObject.local[0].enablementState, EnablementState.DisabledGlobally); }); }); }); @@ -820,7 +820,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { return testObject.setEnablement(testObject.local[0], EnablementState.DisabledGlobally) .then(() => { - assert.equal(testObject.local[0].enablementState, EnablementState.DisabledGlobally); + assert.strictEqual(testObject.local[0].enablementState, EnablementState.DisabledGlobally); }); }); }); @@ -861,7 +861,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { testObject = await aWorkbenchService(); return testObject.setEnablement(testObject.local[0], EnablementState.DisabledGlobally) - .then(() => assert.equal(testObject.local[0].enablementState, EnablementState.DisabledGlobally)); + .then(() => assert.strictEqual(testObject.local[0].enablementState, EnablementState.DisabledGlobally)); }); }); @@ -900,8 +900,8 @@ suite('ExtensionsWorkbenchServiceTest', () => { return testObject.setEnablement(testObject.local[0], EnablementState.EnabledGlobally) .then(() => { - assert.equal(testObject.local[0].enablementState, EnablementState.EnabledGlobally); - assert.equal(testObject.local[1].enablementState, EnablementState.EnabledGlobally); + assert.strictEqual(testObject.local[0].enablementState, EnablementState.EnabledGlobally); + assert.strictEqual(testObject.local[1].enablementState, EnablementState.EnabledGlobally); }); }); }); @@ -922,7 +922,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { return testObject.setEnablement(testObject.local[0], EnablementState.EnabledGlobally) .then(() => { assert.ok(!target.called); - assert.equal(testObject.local[0].enablementState, EnablementState.EnabledGlobally); + assert.strictEqual(testObject.local[0].enablementState, EnablementState.EnabledGlobally); }); }); }); @@ -943,8 +943,8 @@ suite('ExtensionsWorkbenchServiceTest', () => { return testObject.setEnablement([testObject.local[1], testObject.local[0]], EnablementState.EnabledGlobally) .then(() => { assert.ok(!target.called); - assert.equal(testObject.local[0].enablementState, EnablementState.EnabledGlobally); - assert.equal(testObject.local[1].enablementState, EnablementState.EnabledGlobally); + assert.strictEqual(testObject.local[0].enablementState, EnablementState.EnabledGlobally); + assert.strictEqual(testObject.local[1].enablementState, EnablementState.EnabledGlobally); }); }); }); @@ -964,9 +964,9 @@ suite('ExtensionsWorkbenchServiceTest', () => { return testObject.setEnablement(testObject.local[0], EnablementState.EnabledGlobally) .then(() => { - assert.equal(testObject.local[0].enablementState, EnablementState.EnabledGlobally); - assert.equal(testObject.local[1].enablementState, EnablementState.EnabledGlobally); - assert.equal(testObject.local[2].enablementState, EnablementState.EnabledGlobally); + assert.strictEqual(testObject.local[0].enablementState, EnablementState.EnabledGlobally); + assert.strictEqual(testObject.local[1].enablementState, EnablementState.EnabledGlobally); + assert.strictEqual(testObject.local[2].enablementState, EnablementState.EnabledGlobally); }); }); }); @@ -1007,7 +1007,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { didInstallEvent.fire({ local, identifier: local.identifier, operation: InstallOperation.Update }); instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); const actual = await testObject.queryLocal(); - assert.equal(actual[0].enablementState, EnablementState.DisabledGlobally); + assert.strictEqual(actual[0].enablementState, EnablementState.DisabledGlobally); }); test('test updating an extension does not re-eanbles it when workspace disabled', async () => { @@ -1017,7 +1017,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { didInstallEvent.fire({ local, identifier: local.identifier, operation: InstallOperation.Update }); instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); const actual = await testObject.queryLocal(); - assert.equal(actual[0].enablementState, EnablementState.DisabledWorkspace); + assert.strictEqual(actual[0].enablementState, EnablementState.DisabledWorkspace); }); test('test user extension is preferred when the same extension exists as system and user extension', async () => { @@ -1028,8 +1028,8 @@ suite('ExtensionsWorkbenchServiceTest', () => { const actual = await testObject.queryLocal(); - assert.equal(actual.length, 1); - assert.equal(actual[0].local, userExtension); + assert.strictEqual(actual.length, 1); + assert.strictEqual(actual[0].local, userExtension); }); test('test user extension is disabled when the same extension exists as system and user extension and system extension is disabled', async () => { @@ -1041,9 +1041,9 @@ suite('ExtensionsWorkbenchServiceTest', () => { const actual = await testObject.queryLocal(); - assert.equal(actual.length, 1); - assert.equal(actual[0].local, userExtension); - assert.equal(actual[0].enablementState, EnablementState.DisabledGlobally); + assert.strictEqual(actual.length, 1); + assert.strictEqual(actual[0].local, userExtension); + assert.strictEqual(actual[0].enablementState, EnablementState.DisabledGlobally); }); test('Test local ui extension is chosen if it exists only in local server', async () => { @@ -1058,8 +1058,8 @@ suite('ExtensionsWorkbenchServiceTest', () => { const actual = await testObject.queryLocal(); - assert.equal(actual.length, 1); - assert.equal(actual[0].local, localExtension); + assert.strictEqual(actual.length, 1); + assert.strictEqual(actual[0].local, localExtension); }); test('Test local workspace extension is chosen if it exists only in local server', async () => { @@ -1074,8 +1074,8 @@ suite('ExtensionsWorkbenchServiceTest', () => { const actual = await testObject.queryLocal(); - assert.equal(actual.length, 1); - assert.equal(actual[0].local, localExtension); + assert.strictEqual(actual.length, 1); + assert.strictEqual(actual[0].local, localExtension); }); test('Test local web extension is chosen if it exists only in local server', async () => { @@ -1090,8 +1090,8 @@ suite('ExtensionsWorkbenchServiceTest', () => { const actual = await testObject.queryLocal(); - assert.equal(actual.length, 1); - assert.equal(actual[0].local, localExtension); + assert.strictEqual(actual.length, 1); + assert.strictEqual(actual[0].local, localExtension); }); test('Test local ui,workspace extension is chosen if it exists only in local server', async () => { @@ -1106,8 +1106,8 @@ suite('ExtensionsWorkbenchServiceTest', () => { const actual = await testObject.queryLocal(); - assert.equal(actual.length, 1); - assert.equal(actual[0].local, localExtension); + assert.strictEqual(actual.length, 1); + assert.strictEqual(actual[0].local, localExtension); }); test('Test local workspace,ui extension is chosen if it exists only in local server', async () => { @@ -1122,8 +1122,8 @@ suite('ExtensionsWorkbenchServiceTest', () => { const actual = await testObject.queryLocal(); - assert.equal(actual.length, 1); - assert.equal(actual[0].local, localExtension); + assert.strictEqual(actual.length, 1); + assert.strictEqual(actual[0].local, localExtension); }); test('Test local ui,workspace,web extension is chosen if it exists only in local server', async () => { @@ -1138,8 +1138,8 @@ suite('ExtensionsWorkbenchServiceTest', () => { const actual = await testObject.queryLocal(); - assert.equal(actual.length, 1); - assert.equal(actual[0].local, localExtension); + assert.strictEqual(actual.length, 1); + assert.strictEqual(actual[0].local, localExtension); }); test('Test local ui,web,workspace extension is chosen if it exists only in local server', async () => { @@ -1154,8 +1154,8 @@ suite('ExtensionsWorkbenchServiceTest', () => { const actual = await testObject.queryLocal(); - assert.equal(actual.length, 1); - assert.equal(actual[0].local, localExtension); + assert.strictEqual(actual.length, 1); + assert.strictEqual(actual[0].local, localExtension); }); test('Test local web,ui,workspace extension is chosen if it exists only in local server', async () => { @@ -1170,8 +1170,8 @@ suite('ExtensionsWorkbenchServiceTest', () => { const actual = await testObject.queryLocal(); - assert.equal(actual.length, 1); - assert.equal(actual[0].local, localExtension); + assert.strictEqual(actual.length, 1); + assert.strictEqual(actual[0].local, localExtension); }); test('Test local web,workspace,ui extension is chosen if it exists only in local server', async () => { @@ -1186,8 +1186,8 @@ suite('ExtensionsWorkbenchServiceTest', () => { const actual = await testObject.queryLocal(); - assert.equal(actual.length, 1); - assert.equal(actual[0].local, localExtension); + assert.strictEqual(actual.length, 1); + assert.strictEqual(actual[0].local, localExtension); }); test('Test local workspace,web,ui extension is chosen if it exists only in local server', async () => { @@ -1202,8 +1202,8 @@ suite('ExtensionsWorkbenchServiceTest', () => { const actual = await testObject.queryLocal(); - assert.equal(actual.length, 1); - assert.equal(actual[0].local, localExtension); + assert.strictEqual(actual.length, 1); + assert.strictEqual(actual[0].local, localExtension); }); test('Test local workspace,ui,web extension is chosen if it exists only in local server', async () => { @@ -1218,8 +1218,8 @@ suite('ExtensionsWorkbenchServiceTest', () => { const actual = await testObject.queryLocal(); - assert.equal(actual.length, 1); - assert.equal(actual[0].local, localExtension); + assert.strictEqual(actual.length, 1); + assert.strictEqual(actual[0].local, localExtension); }); test('Test local UI extension is chosen if it exists in both servers', async () => { @@ -1235,8 +1235,8 @@ suite('ExtensionsWorkbenchServiceTest', () => { const actual = await testObject.queryLocal(); - assert.equal(actual.length, 1); - assert.equal(actual[0].local, localExtension); + assert.strictEqual(actual.length, 1); + assert.strictEqual(actual[0].local, localExtension); }); test('Test local ui,workspace extension is chosen if it exists in both servers', async () => { @@ -1252,8 +1252,8 @@ suite('ExtensionsWorkbenchServiceTest', () => { const actual = await testObject.queryLocal(); - assert.equal(actual.length, 1); - assert.equal(actual[0].local, localExtension); + assert.strictEqual(actual.length, 1); + assert.strictEqual(actual[0].local, localExtension); }); test('Test remote workspace extension is chosen if it exists in remote server', async () => { @@ -1268,8 +1268,8 @@ suite('ExtensionsWorkbenchServiceTest', () => { const actual = await testObject.queryLocal(); - assert.equal(actual.length, 1); - assert.equal(actual[0].local, remoteExtension); + assert.strictEqual(actual.length, 1); + assert.strictEqual(actual[0].local, remoteExtension); }); test('Test remote workspace extension is chosen if it exists in both servers', async () => { @@ -1285,8 +1285,8 @@ suite('ExtensionsWorkbenchServiceTest', () => { const actual = await testObject.queryLocal(); - assert.equal(actual.length, 1); - assert.equal(actual[0].local, remoteExtension); + assert.strictEqual(actual.length, 1); + assert.strictEqual(actual[0].local, remoteExtension); }); test('Test remote workspace extension is chosen if it exists in both servers and local is disabled', async () => { @@ -1303,9 +1303,9 @@ suite('ExtensionsWorkbenchServiceTest', () => { const actual = await testObject.queryLocal(); - assert.equal(actual.length, 1); - assert.equal(actual[0].local, remoteExtension); - assert.equal(actual[0].enablementState, EnablementState.DisabledGlobally); + assert.strictEqual(actual.length, 1); + assert.strictEqual(actual[0].local, remoteExtension); + assert.strictEqual(actual[0].enablementState, EnablementState.DisabledGlobally); }); test('Test remote workspace extension is chosen if it exists in both servers and remote is disabled in workspace', async () => { @@ -1322,9 +1322,9 @@ suite('ExtensionsWorkbenchServiceTest', () => { const actual = await testObject.queryLocal(); - assert.equal(actual.length, 1); - assert.equal(actual[0].local, remoteExtension); - assert.equal(actual[0].enablementState, EnablementState.DisabledWorkspace); + assert.strictEqual(actual.length, 1); + assert.strictEqual(actual[0].local, remoteExtension); + assert.strictEqual(actual[0].enablementState, EnablementState.DisabledWorkspace); }); test('Test local ui, workspace extension is chosen if it exists in both servers and local is disabled', async () => { @@ -1341,9 +1341,9 @@ suite('ExtensionsWorkbenchServiceTest', () => { const actual = await testObject.queryLocal(); - assert.equal(actual.length, 1); - assert.equal(actual[0].local, localExtension); - assert.equal(actual[0].enablementState, EnablementState.DisabledGlobally); + assert.strictEqual(actual.length, 1); + assert.strictEqual(actual[0].local, localExtension); + assert.strictEqual(actual[0].enablementState, EnablementState.DisabledGlobally); }); test('Test local ui, workspace extension is chosen if it exists in both servers and local is disabled in workspace', async () => { @@ -1360,9 +1360,9 @@ suite('ExtensionsWorkbenchServiceTest', () => { const actual = await testObject.queryLocal(); - assert.equal(actual.length, 1); - assert.equal(actual[0].local, localExtension); - assert.equal(actual[0].enablementState, EnablementState.DisabledWorkspace); + assert.strictEqual(actual.length, 1); + assert.strictEqual(actual[0].local, localExtension); + assert.strictEqual(actual[0].enablementState, EnablementState.DisabledWorkspace); }); test('Test local web extension is chosen if it exists in both servers', async () => { @@ -1378,8 +1378,8 @@ suite('ExtensionsWorkbenchServiceTest', () => { const actual = await testObject.queryLocal(); - assert.equal(actual.length, 1); - assert.equal(actual[0].local, localExtension); + assert.strictEqual(actual.length, 1); + assert.strictEqual(actual[0].local, localExtension); }); test('Test remote web extension is chosen if it exists only in remote', async () => { @@ -1394,8 +1394,8 @@ suite('ExtensionsWorkbenchServiceTest', () => { const actual = await testObject.queryLocal(); - assert.equal(actual.length, 1); - assert.equal(actual[0].local, remoteExtension); + assert.strictEqual(actual.length, 1); + assert.strictEqual(actual[0].local, remoteExtension); }); async function aWorkbenchService(): Promise { diff --git a/src/vs/workbench/contrib/externalTerminal/common/externalTerminal.ts b/src/vs/workbench/contrib/externalTerminal/common/externalTerminal.ts index 4e46df2b..3aab081a 100644 --- a/src/vs/workbench/contrib/externalTerminal/common/externalTerminal.ts +++ b/src/vs/workbench/contrib/externalTerminal/common/externalTerminal.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { ITerminalEnvironment } from 'vs/platform/terminal/common/terminal'; export const IExternalTerminalService = createDecorator('nativeTerminalService'); @@ -16,7 +17,7 @@ export interface IExternalTerminalSettings { export interface IExternalTerminalService { readonly _serviceBrand: undefined; openTerminal(path: string): void; - runInTerminal(title: string, cwd: string, args: string[], env: { [key: string]: string | null; }, settings: IExternalTerminalSettings): Promise; + runInTerminal(title: string, cwd: string, args: string[], env: ITerminalEnvironment, settings: IExternalTerminalSettings): Promise; } export interface IExternalTerminalConfiguration { diff --git a/src/vs/workbench/contrib/externalTerminal/node/externalTerminalService.ts b/src/vs/workbench/contrib/externalTerminal/node/externalTerminalService.ts index 1806bf96..eb3b37ab 100644 --- a/src/vs/workbench/contrib/externalTerminal/node/externalTerminalService.ts +++ b/src/vs/workbench/contrib/externalTerminal/node/externalTerminalService.ts @@ -14,6 +14,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { optional } from 'vs/platform/instantiation/common/instantiation'; import { DEFAULT_TERMINAL_OSX } from 'vs/workbench/contrib/externalTerminal/node/externalTerminal'; import { FileAccess } from 'vs/base/common/network'; +import { ITerminalEnvironment } from 'vs/platform/terminal/common/terminal'; const TERMINAL_TITLE = nls.localize('console.title', "VS Code Console"); @@ -37,7 +38,7 @@ export class WindowsExternalTerminalService implements IExternalTerminalService } } - public runInTerminal(title: string, dir: string, args: string[], envVars: env.IProcessEnvironment, settings: IExternalTerminalSettings): Promise { + public runInTerminal(title: string, dir: string, args: string[], envVars: ITerminalEnvironment, settings: IExternalTerminalSettings): Promise { const exec = settings.windowsExec || WindowsExternalTerminalService.getDefaultTerminalWindows(); @@ -139,7 +140,7 @@ export class MacExternalTerminalService implements IExternalTerminalService { } } - public runInTerminal(title: string, dir: string, args: string[], envVars: env.IProcessEnvironment, settings: IExternalTerminalSettings): Promise { + public runInTerminal(title: string, dir: string, args: string[], envVars: ITerminalEnvironment, settings: IExternalTerminalSettings): Promise { const terminalApp = settings.osxExec || DEFAULT_TERMINAL_OSX; @@ -239,7 +240,7 @@ export class LinuxExternalTerminalService implements IExternalTerminalService { } } - public runInTerminal(title: string, dir: string, args: string[], envVars: env.IProcessEnvironment, settings: IExternalTerminalSettings): Promise { + public runInTerminal(title: string, dir: string, args: string[], envVars: ITerminalEnvironment, settings: IExternalTerminalSettings): Promise { const execPromise = settings.linuxExec ? Promise.resolve(settings.linuxExec) : LinuxExternalTerminalService.getDefaultTerminalLinuxReady(); @@ -341,7 +342,7 @@ export class LinuxExternalTerminalService implements IExternalTerminalService { /** * tries to turn OS errors into more meaningful error messages */ -function improveError(err: Error): Error { +function improveError(err: Error & { errno?: string, path?: string }): Error { if ('errno' in err && err['errno'] === 'ENOENT' && 'path' in err && typeof err['path'] === 'string') { return new Error(nls.localize('ext.term.app.not.found', "can't find terminal application '{0}'", err['path'])); } diff --git a/src/vs/workbench/contrib/externalUriOpener/common/externalUriOpenerService.ts b/src/vs/workbench/contrib/externalUriOpener/common/externalUriOpenerService.ts index ad371f4f..6ba9cfa6 100644 --- a/src/vs/workbench/contrib/externalUriOpener/common/externalUriOpenerService.ts +++ b/src/vs/workbench/contrib/externalUriOpener/common/externalUriOpenerService.ts @@ -108,7 +108,7 @@ export class ExternalUriOpenerService extends Disposable implements IExternalUri await Promise.all(Array.from(allOpeners.values()).map(async opener => { let priority: modes.ExternalUriOpenerPriority; try { - priority = await opener.canOpen(targetUri, token); + priority = await opener.canOpen(ctx.sourceUri, token); } catch (e) { this.logService.error(e); return; diff --git a/src/vs/workbench/contrib/feedback/browser/feedback.ts b/src/vs/workbench/contrib/feedback/browser/feedback.ts index 223c6bb7..9bd1c3ba 100644 --- a/src/vs/workbench/contrib/feedback/browser/feedback.ts +++ b/src/vs/workbench/contrib/feedback/browser/feedback.ts @@ -92,7 +92,7 @@ export class FeedbackWidget extends Dropdown { this.element.title = nls.localize('sendFeedback', "Tweet Feedback"); } - protected getAnchor(): HTMLElement | IAnchor { + protected override getAnchor(): HTMLElement | IAnchor { const position = dom.getDomNodePagePosition(this.element); return { @@ -103,7 +103,7 @@ export class FeedbackWidget extends Dropdown { }; } - protected renderContents(container: HTMLElement): IDisposable { + protected override renderContents(container: HTMLElement): IDisposable { const disposables = new DisposableStore(); container.classList.add('monaco-menu-container'); @@ -379,7 +379,7 @@ export class FeedbackWidget extends Dropdown { return element; } - show(): void { + override show(): void { super.show(); if (this.options.onFeedbackVisibilityChange) { @@ -389,13 +389,13 @@ export class FeedbackWidget extends Dropdown { this.updateCharCountText(); } - protected onHide(): void { + protected override onHide(): void { if (this.options.onFeedbackVisibilityChange) { this.options.onFeedbackVisibilityChange(false); } } - hide(): void { + override hide(): void { if (this.feedbackDescriptionInput) { this.feedback = this.feedbackDescriptionInput.value; } @@ -412,7 +412,7 @@ export class FeedbackWidget extends Dropdown { super.hide(); } - onEvent(e: Event, activeElement: HTMLElement): void { + override onEvent(e: Event, activeElement: HTMLElement): void { if (e instanceof KeyboardEvent) { const keyboardEvent = e; if (keyboardEvent.keyCode === 27) { // Escape diff --git a/src/vs/workbench/contrib/files/browser/editors/binaryFileEditor.ts b/src/vs/workbench/contrib/files/browser/editors/binaryFileEditor.ts index 59334b13..7eed77c0 100644 --- a/src/vs/workbench/contrib/files/browser/editors/binaryFileEditor.ts +++ b/src/vs/workbench/contrib/files/browser/editors/binaryFileEditor.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; +import { localize } from 'vs/nls'; import { BaseBinaryResourceEditor } from 'vs/workbench/browser/parts/editor/binaryEditor'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; @@ -11,10 +11,9 @@ import { EditorInput, EditorOptions } from 'vs/workbench/common/editor'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; import { BINARY_FILE_EDITOR_ID } from 'vs/workbench/contrib/files/common/files'; import { IStorageService } from 'vs/platform/storage/common/storage'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { EditorOverride } from 'vs/platform/editor/common/editor'; +import { IEditorOverrideService } from 'vs/workbench/services/editor/common/editorOverrideService'; /** * An implementation of editor for binary files that cannot be displayed. @@ -26,20 +25,17 @@ export class BinaryFileEditor extends BaseBinaryResourceEditor { constructor( @ITelemetryService telemetryService: ITelemetryService, @IThemeService themeService: IThemeService, - @IOpenerService private readonly openerService: IOpenerService, @IEditorService private readonly editorService: IEditorService, - @IStorageService storageService: IStorageService, - @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, + @IEditorOverrideService private readonly editorOverrideService: IEditorOverrideService, + @IStorageService storageService: IStorageService ) { super( BinaryFileEditor.ID, { - openInternal: (input, options) => this.openInternal(input, options), - openExternal: resource => this.openerService.open(resource, { openExternal: true }) + openInternal: (input, options) => this.openInternal(input, options) }, telemetryService, themeService, - environmentService, storageService ); } @@ -50,12 +46,21 @@ export class BinaryFileEditor extends BaseBinaryResourceEditor { // Enforce to open the input as text to enable our text based viewer input.setForceOpenAsText(); - // If more editors are installed that can handle this input, show a picker - await this.editorService.openEditor(input, { ...options, override: EditorOverride.PICK, }, this.group); + // Try to let the user pick an override if there is one availabe + const overridenInput = await this.editorOverrideService.resolveEditorOverride(input, { ...options, override: EditorOverride.PICK, }, this.group); + + let newOptions = overridenInput?.options ?? options; + newOptions = { ...newOptions, override: EditorOverride.DISABLED }; + // Replace the overrriden input, with the text based input + await this.editorService.replaceEditors([{ + editor: input, + replacement: overridenInput?.editor ?? input, + options: newOptions, + }], overridenInput?.group ?? this.group); } } - getTitle(): string { - return this.input ? this.input.getName() : nls.localize('binaryFileEditor', "Binary File Viewer"); + override getTitle(): string { + return this.input ? this.input.getName() : localize('binaryFileEditor', "Binary File Viewer"); } } diff --git a/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts b/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts index 6bbeff0f..0d0bac84 100644 --- a/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts +++ b/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts @@ -28,7 +28,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { CancellationToken } from 'vs/base/common/cancellation'; import { createErrorWithActions } from 'vs/base/common/errors'; -import { EditorActivation, IEditorOptions } from 'vs/platform/editor/common/editor'; +import { EditorActivation, EditorOverride, IEditorOptions } from 'vs/platform/editor/common/editor'; import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; import { IExplorerService } from 'vs/workbench/contrib/files/browser/files'; @@ -88,7 +88,7 @@ export class TextFileEditor extends BaseTextEditor { } } - protected onWillCloseEditorInGroup(editor: IEditorInput): void { + protected override onWillCloseEditorInGroup(editor: IEditorInput): void { // React to editors closing to preserve or clear view state. This needs to happen // in the onWillCloseEditor because at that time the editor has not yet @@ -96,15 +96,15 @@ export class TextFileEditor extends BaseTextEditor { this.doSaveOrClearTextEditorViewState(editor); } - getTitle(): string { + override getTitle(): string { return this.input ? this.input.getName() : localize('textFileEditor', "Text File Editor"); } - get input(): FileEditorInput | undefined { + override get input(): FileEditorInput | undefined { return this._input as FileEditorInput; } - async setInput(input: FileEditorInput, options: EditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise { + override async setInput(input: FileEditorInput, options: EditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise { // Update/clear view settings if input changes this.doSaveOrClearTextEditorViewState(this.input); @@ -203,7 +203,7 @@ export class TextFileEditor extends BaseTextEditor { // because we are triggering another openEditor() call // and do not control the initial intent that resulted // in us now opening as binary. - const preservingOptions: IEditorOptions = { activation: EditorActivation.PRESERVE }; + const preservingOptions: IEditorOptions = { activation: EditorActivation.PRESERVE, override: EditorOverride.DISABLED }; if (options) { options.overwrite(preservingOptions); } else { @@ -229,7 +229,7 @@ export class TextFileEditor extends BaseTextEditor { } } - clearInput(): void { + override clearInput(): void { // Update/clear editor view state in settings this.doSaveOrClearTextEditorViewState(this.input); @@ -244,7 +244,7 @@ export class TextFileEditor extends BaseTextEditor { super.clearInput(); } - protected saveState(): void { + protected override saveState(): void { // Update/clear editor view State this.doSaveOrClearTextEditorViewState(this.input); @@ -259,7 +259,7 @@ export class TextFileEditor extends BaseTextEditor { // If the user configured to not restore view state, we clear the view // state unless the editor is still opened in the group. - if (!this.shouldRestoreTextEditorViewState(input) && (!this.group || !this.group.isOpened(input))) { + if (!this.shouldRestoreTextEditorViewState(input) && (!this.group || !this.group.contains(input))) { this.clearTextEditorViewState([input.resource], this.group); } diff --git a/src/vs/workbench/contrib/files/browser/editors/textFileEditorTracker.ts b/src/vs/workbench/contrib/files/browser/editors/textFileEditorTracker.ts index 460ba3e2..20ed5ecf 100644 --- a/src/vs/workbench/contrib/files/browser/editors/textFileEditorTracker.ts +++ b/src/vs/workbench/contrib/files/browser/editors/textFileEditorTracker.ts @@ -14,6 +14,9 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { RunOnceWorker } from 'vs/base/common/async'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { IFilesConfigurationService, AutoSaveMode } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; +import { FILE_EDITOR_INPUT_ID } from 'vs/workbench/contrib/files/common/files'; +import { Schemas } from 'vs/base/common/network'; +import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; export class TextFileEditorTracker extends Disposable implements IWorkbenchContribution { @@ -41,7 +44,7 @@ export class TextFileEditorTracker extends Disposable implements IWorkbenchContr this._register(this.hostService.onDidChangeFocus(hasFocus => hasFocus ? this.reloadVisibleTextFileEditors() : undefined)); // Lifecycle - this.lifecycleService.onShutdown(() => this.dispose()); + this.lifecycleService.onDidShutdown(() => this.dispose()); } //#region Text File: Ensure every dirty text and untitled file is opened in an editor @@ -65,7 +68,7 @@ export class TextFileEditorTracker extends Disposable implements IWorkbenchContr return false; } - if (this.editorService.isOpen({ resource })) { + if (this.editorService.isOpened({ resource, typeId: resource.scheme === Schemas.untitled ? UntitledTextEditorInput.ID : FILE_EDITOR_INPUT_ID })) { return false; // model must not be opened already as file } diff --git a/src/vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler.ts b/src/vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler.ts index 1ef4caee..c9c2651d 100644 --- a/src/vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler.ts +++ b/src/vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler.ts @@ -30,7 +30,7 @@ import { isWindows } from 'vs/base/common/platform'; import { Schemas } from 'vs/base/common/network'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { IEditorIdentifier, SaveReason } from 'vs/workbench/common/editor'; -import { GroupsOrder, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { hash } from 'vs/base/common/hash'; export const CONFLICT_RESOLUTION_CONTEXT = 'saveConflictResolutionContext'; export const CONFLICT_RESOLUTION_SCHEME = 'conflictResolution'; @@ -136,7 +136,7 @@ export class TextFileSaveErrorHandler extends Disposable implements ISaveErrorHa const isWriteLocked = fileOperationError.fileOperationResult === FileOperationResult.FILE_WRITE_LOCKED; const triedToUnlock = isWriteLocked && fileOperationError.options?.unlock; const isPermissionDenied = fileOperationError.fileOperationResult === FileOperationResult.FILE_PERMISSION_DENIED; - const canSaveElevated = resource.scheme === Schemas.file; // https://github.com/microsoft/vscode/issues/48659 TODO + const canSaveElevated = resource.scheme === Schemas.file; // currently only supported for local schemes (https://github.com/microsoft/vscode/issues/48659) // Save Elevated if (canSaveElevated && (isPermissionDenied || triedToUnlock)) { @@ -175,12 +175,17 @@ export class TextFileSaveErrorHandler extends Disposable implements ISaveErrorHa // Show message and keep function to hide in case the file gets saved/reverted const actions: INotificationActions = { primary: primaryActions, secondary: secondaryActions }; - const handle = this.notificationService.notify({ severity: Severity.Error, message, actions }); + const handle = this.notificationService.notify({ + id: `${hash(model.resource.toString())}`, // unique per model (https://github.com/microsoft/vscode/issues/121539) + severity: Severity.Error, + message, + actions + }); Event.once(handle.onDidClose)(() => { dispose(primaryActions); dispose(secondaryActions); }); this.messages.set(model.resource, handle); } - dispose(): void { + override dispose(): void { super.dispose(); this.messages.clear(); @@ -205,7 +210,7 @@ class ResolveConflictLearnMoreAction extends Action { super('workbench.files.action.resolveConflictLearnMore', localize('learnMore', "Learn More")); } - async run(): Promise { + override async run(): Promise { await this.openerService.open(URI.parse('https://go.microsoft.com/fwlink/?linkid=868264')); } } @@ -218,7 +223,7 @@ class DoNotShowResolveConflictLearnMoreAction extends Action { super('workbench.files.action.resolveConflictLearnMoreDoNotShowAgain', localize('dontShowAgain', "Don't Show Again")); } - async run(notification: IDisposable): Promise { + override async run(notification: IDisposable): Promise { this.storageService.store(LEARN_MORE_DIRTY_WRITE_IGNORE_KEY, true, StorageScope.GLOBAL, StorageTarget.USER); // Hide notification @@ -238,7 +243,7 @@ class ResolveSaveConflictAction extends Action { super('workbench.files.action.resolveConflict', localize('compareChanges', "Compare")); } - async run(): Promise { + override async run(): Promise { if (!this.model.isDisposed()) { const resource = this.model.resource; const name = basename(resource); @@ -249,6 +254,7 @@ class ResolveSaveConflictAction extends Action { // Show additional help how to resolve the save conflict const actions = { primary: [this.instantiationService.createInstance(ResolveConflictLearnMoreAction)] }; const handle = this.notificationService.notify({ + id: `${hash(resource.toString())}`, // unique per model severity: Severity.Info, message: conflictEditorHelp, actions, @@ -269,7 +275,7 @@ class SaveModelElevatedAction extends Action { super('workbench.files.action.saveModelElevated', triedToUnlock ? isWindows ? localize('overwriteElevated', "Overwrite as Admin...") : localize('overwriteElevatedSudo', "Overwrite as Sudo...") : isWindows ? localize('saveElevated', "Retry as Admin...") : localize('saveElevatedSudo', "Retry as Sudo...")); } - async run(): Promise { + override async run(): Promise { if (!this.model.isDisposed()) { await this.model.save({ writeElevated: true, @@ -288,7 +294,7 @@ class RetrySaveModelAction extends Action { super('workbench.files.action.saveModel', localize('retry', "Retry")); } - async run(): Promise { + override async run(): Promise { if (!this.model.isDisposed()) { await this.model.save({ reason: SaveReason.EXPLICIT }); } @@ -303,7 +309,7 @@ class DiscardModelAction extends Action { super('workbench.files.action.discardModel', localize('discard', "Discard")); } - async run(): Promise { + override async run(): Promise { if (!this.model.isDisposed()) { await this.model.revert(); } @@ -314,13 +320,12 @@ class SaveModelAsAction extends Action { constructor( private model: ITextFileEditorModel, - @IEditorService private editorService: IEditorService, - @IEditorGroupsService private editorGroupService: IEditorGroupsService + @IEditorService private editorService: IEditorService ) { super('workbench.files.action.saveModelAs', SAVE_FILE_AS_LABEL); } - async run(): Promise { + override async run(): Promise { if (!this.model.isDisposed()) { const editor = this.findEditor(); if (editor) { @@ -331,25 +336,22 @@ class SaveModelAsAction extends Action { private findEditor(): IEditorIdentifier | undefined { 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) { - // 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 }); + const editors = this.editorService.findEditors(this.model.resource); + for (const identifier of editors) { + if (identifier.editor instanceof FileEditorInput) { + // 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 = identifier; + break; + } else if (!preferredMatchingEditor) { + preferredMatchingEditor = identifier; } } - return preferredMatchingEditor || otherMatchingEditors[0]; + return preferredMatchingEditor; } } @@ -361,7 +363,7 @@ class UnlockModelAction extends Action { super('workbench.files.action.unlock', localize('overwrite', "Overwrite")); } - async run(): Promise { + override async run(): Promise { if (!this.model.isDisposed()) { await this.model.save({ writeUnlock: true, reason: SaveReason.EXPLICIT }); } @@ -376,7 +378,7 @@ class SaveModelIgnoreModifiedSinceAction extends Action { super('workbench.files.action.saveIgnoreModifiedSince', localize('overwrite', "Overwrite")); } - async run(): Promise { + override async run(): Promise { if (!this.model.isDisposed()) { await this.model.save({ ignoreModifiedSince: true, reason: SaveReason.EXPLICIT }); } @@ -391,7 +393,7 @@ class ConfigureSaveConflictAction extends Action { super('workbench.files.action.configureSaveConflict', localize('configure', "Configure")); } - async run(): Promise { + override async run(): Promise { this.preferencesService.openSettings(undefined, 'files.saveConflictResolution'); } } diff --git a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts index dbe99c77..ebdde76f 100644 --- a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts +++ b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts @@ -173,11 +173,11 @@ export class ExplorerViewPaneContainer extends ViewPaneContainer { constructor( @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @ITelemetryService telemetryService: ITelemetryService, - @IWorkspaceContextService protected contextService: IWorkspaceContextService, - @IStorageService protected storageService: IStorageService, + @IWorkspaceContextService contextService: IWorkspaceContextService, + @IStorageService storageService: IStorageService, @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, @IConfigurationService configurationService: IConfigurationService, - @IInstantiationService protected instantiationService: IInstantiationService, + @IInstantiationService instantiationService: IInstantiationService, @IContextKeyService contextKeyService: IContextKeyService, @IThemeService themeService: IThemeService, @IContextMenuService contextMenuService: IContextMenuService, @@ -192,12 +192,12 @@ export class ExplorerViewPaneContainer extends ViewPaneContainer { this._register(this.contextService.onDidChangeWorkspaceName(e => this.updateTitleArea())); } - create(parent: HTMLElement): void { + override create(parent: HTMLElement): void { super.create(parent); parent.classList.add('explorer-viewlet'); } - protected createView(viewDescriptor: IViewDescriptor, options: IViewletViewOptions): ViewPane { + protected override createView(viewDescriptor: IViewDescriptor, options: IViewletViewOptions): ViewPane { if (viewDescriptor.id === VIEW_ID) { // Create a delegating editor service for the explorer to be able to delay the refresh in the opened // editors view above. This is a workaround for being able to double click on a file to make it pinned @@ -245,12 +245,12 @@ export class ExplorerViewPaneContainer extends ViewPaneContainer { return this.getView(OpenEditorsView.ID); } - public setVisible(visible: boolean): void { + public override setVisible(visible: boolean): void { this.viewletVisibleContextKey.set(visible); super.setVisible(visible); } - focus(): void { + override focus(): void { const explorerView = this.getView(VIEW_ID); if (explorerView && this.panes.every(p => !p.isExpanded())) { explorerView.setExpanded(true); diff --git a/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts b/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts index e19fa398..ab99f725 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts @@ -14,7 +14,7 @@ import { openWindowCommand, COPY_PATH_COMMAND_ID, REVEAL_IN_EXPLORER_COMMAND_ID, import { CommandsRegistry, ICommandHandler } from 'vs/platform/commands/common/commands'; import { ContextKeyExpr, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { isMacintosh } from 'vs/base/common/platform'; +import { isMacintosh, isWeb } from 'vs/base/common/platform'; import { FilesExplorerFocusCondition, ExplorerRootContext, ExplorerFolderContext, ExplorerResourceNotReadonlyContext, ExplorerResourceCut, ExplorerResourceMoveableToTrash, ExplorerViewletVisibleContext, ExplorerResourceAvailableEditorIdsContext } from 'vs/workbench/contrib/files/common/files'; import { ADD_ROOT_FOLDER_COMMAND_ID, ADD_ROOT_FOLDER_LABEL } from 'vs/workbench/browser/actions/workspaceCommands'; import { CLOSE_SAVED_EDITORS_COMMAND_ID, CLOSE_EDITORS_IN_GROUP_COMMAND_ID, CLOSE_EDITOR_COMMAND_ID, CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; @@ -36,7 +36,7 @@ import { Codicon } from 'vs/base/common/codicons'; const category = { value: nls.localize('filesCategory', "File"), original: 'File' }; const registry = Registry.as(ActionExtensions.WorkbenchActions); -registry.registerWorkbenchAction(SyncActionDescriptor.from(GlobalCompareResourcesAction), 'File: Compare Active File With...', category.value); +registry.registerWorkbenchAction(SyncActionDescriptor.from(GlobalCompareResourcesAction), 'File: Compare Active File With...', category.value, ActiveEditorContext); registry.registerWorkbenchAction(SyncActionDescriptor.from(FocusFilesExplorer), 'File: Focus on Files Explorer', category.value); registry.registerWorkbenchAction(SyncActionDescriptor.from(ShowActiveFileInExplorer), 'File: Reveal Active File in Side Bar', category.value); registry.registerWorkbenchAction(SyncActionDescriptor.from(CompareWithClipboardAction, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_C) }), 'File: Compare Active File with Clipboard', category.value); @@ -47,7 +47,7 @@ const workspacesCategory = nls.localize('workspaces', "Workspaces"); registry.registerWorkbenchAction(SyncActionDescriptor.from(OpenWorkspaceAction), 'Workspaces: Open Workspace...', workspacesCategory); const fileCategory = nls.localize('file', "File"); -if (isMacintosh) { +if (isMacintosh && !isWeb) { registry.registerWorkbenchAction(SyncActionDescriptor.from(OpenFileFolderAction, { primary: KeyMod.CtrlCmd | KeyCode.KEY_O }), 'File: Open...', fileCategory); } else { registry.registerWorkbenchAction(SyncActionDescriptor.from(OpenFileAction, { primary: KeyMod.CtrlCmd | KeyCode.KEY_O }), 'File: Open File...', fileCategory); @@ -618,7 +618,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { order: 3 }); -if (isMacintosh) { +if (isMacintosh && !isWeb) { MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { group: '2_open', command: { diff --git a/src/vs/workbench/contrib/files/browser/fileActions.ts b/src/vs/workbench/contrib/files/browser/fileActions.ts index 02ebcee8..1a9407c2 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.ts @@ -40,7 +40,8 @@ import { getErrorMessage } from 'vs/base/common/errors'; import { WebFileSystemAccess, triggerDownload } from 'vs/base/browser/dom'; import { mnemonicButtonLabel } from 'vs/base/common/labels'; import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; -import { IWorkingCopyService, IWorkingCopy } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { IWorkingCopy } from 'vs/workbench/services/workingCopy/common/workingCopy'; import { RunOnceWorker, sequence, timeout } from 'vs/base/common/async'; import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; import { once } from 'vs/base/common/functional'; @@ -55,6 +56,7 @@ import { ResourceFileEdit } from 'vs/editor/browser/services/bulkEditService'; import { IExplorerService } from 'vs/workbench/contrib/files/browser/files'; import { listenStream } from 'vs/base/common/stream'; import { EditorOverride } from 'vs/platform/editor/common/editor'; +import { ContributedEditorPriority, IEditorOverrideService } from 'vs/workbench/services/editor/common/editorOverrideService'; export const NEW_FILE_COMMAND_ID = 'explorer.newFile'; export const NEW_FILE_LABEL = nls.localize('newFile', "New File"); @@ -442,56 +444,59 @@ export class GlobalCompareResourcesAction extends Action { label: string, @IQuickInputService private readonly quickInputService: IQuickInputService, @IEditorService private readonly editorService: IEditorService, - @INotificationService private readonly notificationService: INotificationService, - @ITextModelService private readonly textModelService: ITextModelService + @ITextModelService private readonly textModelService: ITextModelService, + @IEditorOverrideService private readonly editorOverrideService: IEditorOverrideService ) { super(id, label); } - async run(): Promise { + override async run(): Promise { const activeInput = this.editorService.activeEditor; const activeResource = EditorResourceAccessor.getOriginalUri(activeInput); if (activeResource && this.textModelService.canHandleResource(activeResource)) { - // Compare with next editor that opens - const toDispose = this.editorService.overrideOpenEditor({ - open: editor => { + // Define a one-time override that has highest priority + // and matches every resource to be able to create a + // diff editor to show the comparison. + const editorOverrideDisposable = this.editorOverrideService.registerContributionPoint('*', { + id: GlobalCompareResourcesAction.ID, + label: GlobalCompareResourcesAction.LABEL, + priority: ContributedEditorPriority.exclusive, + detail: '', + describes: () => false + }, {}, resource => { - // Only once! - toDispose.dispose(); + // Only once! + editorOverrideDisposable.dispose(); - // Open editor as diff - const resource = EditorResourceAccessor.getOriginalUri(editor); - if (resource && this.textModelService.canHandleResource(resource)) { - return { - override: this.editorService.openEditor({ - leftResource: activeResource, - rightResource: resource, - options: { override: EditorOverride.DISABLED, pinned: true } - }) - }; - } - - // Otherwise stay on current resource - this.notificationService.info(nls.localize('fileToCompareNoFile', "Please select a file to compare with.")); + // Open editor as diff if the selected editor resource + // can be handled by the text model service + if (this.textModelService.canHandleResource(resource)) { return { - override: this.editorService.openEditor({ - resource: activeResource, + editor: this.editorService.createEditorInput({ + leftResource: activeResource, + rightResource: resource, options: { override: EditorOverride.DISABLED, pinned: true } }) }; } + + // Otherwise stay on current resource + return { + editor: this.editorService.createEditorInput({ + resource: activeResource, + options: { override: EditorOverride.DISABLED, pinned: true } + }) + }; }); once(this.quickInputService.onHide)((async () => { await timeout(0); // prevent race condition with editor - toDispose.dispose(); + editorOverrideDisposable.dispose(); })); // Bring up quick access this.quickInputService.quickAccess.show('', { itemActivation: ItemActivation.SECOND }); - } else { - this.notificationService.info(nls.localize('openFileToCompare', "Open a file first to compare it with another file.")); } } } @@ -508,7 +513,7 @@ export class ToggleAutoSaveAction extends Action { super(id, label); } - run(): Promise { + override run(): Promise { return this.filesConfigurationService.toggleAutoSave(); } } @@ -547,7 +552,7 @@ export abstract class BaseSaveAllAction extends Action { } } - async run(context?: unknown): Promise { + override async run(context?: unknown): Promise { try { await this.doRun(context); } catch (error) { @@ -561,7 +566,7 @@ export class SaveAllInGroupAction extends BaseSaveAllAction { static readonly ID = 'workbench.files.action.saveAllInGroup'; static readonly LABEL = nls.localize('saveAllInGroup', "Save All in Group"); - get class(): string { + override get class(): string { return 'explorer-action ' + Codicon.saveAll.classNames; } @@ -579,7 +584,7 @@ export class CloseGroupAction extends Action { super(id, label, Codicon.closeAll.classNames); } - run(context?: unknown): Promise { + override run(context?: unknown): Promise { return this.commandService.executeCommand(CLOSE_EDITORS_AND_GROUP_COMMAND_ID, {}, context); } } @@ -597,7 +602,7 @@ export class FocusFilesExplorer extends Action { super(id, label); } - async run(): Promise { + override async run(): Promise { await this.viewletService.openViewlet(VIEWLET_ID, true); } } @@ -611,18 +616,15 @@ export class ShowActiveFileInExplorer extends Action { id: string, label: string, @IEditorService private readonly editorService: IEditorService, - @INotificationService private readonly notificationService: INotificationService, @ICommandService private readonly commandService: ICommandService ) { super(id, label); } - async run(): Promise { + override async run(): Promise { const resource = EditorResourceAccessor.getOriginalUri(this.editorService.activeEditor, { supportSideBySide: SideBySideEditor.PRIMARY }); if (resource) { this.commandService.executeCommand(REVEAL_IN_EXPLORER_COMMAND_ID, resource); - } else { - this.notificationService.info(nls.localize('openFileToShow', "Open a file first to show it in the explorer")); } } } @@ -643,7 +645,7 @@ export class ShowOpenedFileInNewWindow extends Action { super(id, label); } - async run(): Promise { + override async run(): Promise { const fileResource = EditorResourceAccessor.getOriginalUri(this.editorService.activeEditor, { supportSideBySide: SideBySideEditor.PRIMARY }); if (fileResource) { if (this.fileService.canHandleResource(fileResource)) { @@ -651,8 +653,6 @@ export class ShowOpenedFileInNewWindow extends Action { } else { this.notificationService.info(nls.localize('openFileToShowInNewWindow.unsupportedschema', "The active editor must contain an openable resource.")); } - } else { - this.notificationService.info(nls.localize('openFileToShowInNewWindow.nofile', "Open a file first to open in new window")); } } } @@ -755,7 +755,7 @@ export class CompareWithClipboardAction extends Action { this.enabled = true; } - async run(): Promise { + override async run(): Promise { const resource = EditorResourceAccessor.getOriginalUri(this.editorService.activeEditor, { supportSideBySide: SideBySideEditor.PRIMARY }); const scheme = `clipboardCompare${CompareWithClipboardAction.SCHEME_COUNTER++}`; if (resource && (this.fileService.canHandleResource(resource) || resource.scheme === Schemas.untitled)) { @@ -778,7 +778,7 @@ export class CompareWithClipboardAction extends Action { } } - dispose(): void { + override dispose(): void { super.dispose(); dispose(this.registrationDisposal); @@ -1015,7 +1015,7 @@ const downloadFileHandler = async (accessor: ServicesAccessor) => { fileBytesDownloaded: number; } - async function downloadFileBuffered(resource: URI, target: WebFileSystemAccess.FileSystemWritableFileStream, operation: IDownloadOperation): Promise { + async function downloadFileBuffered(resource: URI, target: FileSystemWritableFileStream, operation: IDownloadOperation): Promise { const contents = await fileService.readFileStream(resource); if (cts.token.isCancellationRequested) { target.close(); @@ -1055,7 +1055,7 @@ const downloadFileHandler = async (accessor: ServicesAccessor) => { }); } - async function downloadFileUnbuffered(resource: URI, target: WebFileSystemAccess.FileSystemWritableFileStream, operation: IDownloadOperation): Promise { + async function downloadFileUnbuffered(resource: URI, target: FileSystemWritableFileStream, operation: IDownloadOperation): Promise { const contents = await fileService.readFile(resource); if (!cts.token.isCancellationRequested) { target.write(contents.value.buffer); @@ -1065,7 +1065,7 @@ const downloadFileHandler = async (accessor: ServicesAccessor) => { target.close(); } - async function downloadFile(targetFolder: WebFileSystemAccess.FileSystemDirectoryHandle, file: IFileStatWithMetadata, operation: IDownloadOperation): Promise { + async function downloadFile(targetFolder: FileSystemDirectoryHandle, file: IFileStatWithMetadata, operation: IDownloadOperation): Promise { // Report progress operation.filesDownloaded++; @@ -1085,7 +1085,7 @@ const downloadFileHandler = async (accessor: ServicesAccessor) => { return downloadFileUnbuffered(file.resource, targetFileWriter, operation); } - async function downloadFolder(folder: IFileStatWithMetadata, targetFolder: WebFileSystemAccess.FileSystemDirectoryHandle, operation: IDownloadOperation): Promise { + async function downloadFolder(folder: IFileStatWithMetadata, targetFolder: FileSystemDirectoryHandle, operation: IDownloadOperation): Promise { if (folder.children) { operation.filesTotal += (folder.children.map(child => child.isFile)).length; @@ -1132,7 +1132,7 @@ const downloadFileHandler = async (accessor: ServicesAccessor) => { } try { - const parentFolder: WebFileSystemAccess.FileSystemDirectoryHandle = await window.showDirectoryPicker(); + const parentFolder: FileSystemDirectoryHandle = await window.showDirectoryPicker(); const operation: IDownloadOperation = { startTime: Date.now(), progressScheduler: new RunOnceWorker(steps => { progress.report(steps[steps.length - 1]); }, 1000), @@ -1211,6 +1211,7 @@ export const pasteFileHandler = async (accessor: ServicesAccessor) => { const explorerService = accessor.get(IExplorerService); const fileService = accessor.get(IFileService); const notificationService = accessor.get(INotificationService); + const editorService = accessor.get(IEditorService); const configurationService = accessor.get(IConfigurationService); const uriIdentityService = accessor.get(IUriIdentityService); @@ -1265,6 +1266,12 @@ export const pasteFileHandler = async (accessor: ServicesAccessor) => { const pair = sourceTargetPairs[0]; await explorerService.select(pair.target); + if (sourceTargetPairs.length === 1) { + const item = explorerService.findClosest(pair.target); + if (item && !item.isDirectory) { + await editorService.openEditor({ resource: item.resource, options: { pinned: true, preserveFocus: true } }); + } + } } } catch (e) { onError(notificationService, new Error(nls.localize('fileDeleted', "The file(s) to paste have been deleted or moved since you copied them. {0}", getErrorMessage(e)))); diff --git a/src/vs/workbench/contrib/files/browser/fileCommands.ts b/src/vs/workbench/contrib/files/browser/fileCommands.ts index 322b53e6..f2894496 100644 --- a/src/vs/workbench/contrib/files/browser/fileCommands.ts +++ b/src/vs/workbench/contrib/files/browser/fileCommands.ts @@ -44,6 +44,7 @@ import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/ur import { isPromiseCanceledError } from 'vs/base/common/errors'; import { toAction } from 'vs/base/common/actions'; import { EditorOverride } from 'vs/platform/editor/common/editor'; +import { hash } from 'vs/base/common/hash'; // Commands @@ -276,8 +277,6 @@ async function resourcesToClipboard(resources: URI[], relative: boolean, clipboa const text = resources.map(resource => labelService.getUriLabel(resource, { relative, noPrefix: true })) .join(lineDelimiter); await clipboardService.writeText(text); - } else { - notificationService.info(nls.localize('openFileToCopy', "Open a file first to copy its path")); } } @@ -357,9 +356,7 @@ CommandsRegistry.registerCommand({ const uri = getResourceForCommand(resource, accessor.get(IListService), accessor.get(IEditorService)); if (uri) { - const input = editorService.createEditorInput({ resource: uri }); - - return editorService.openEditor(input, { override: EditorOverride.PICK }); + return editorService.openEditor({ resource: uri, options: { override: EditorOverride.PICK } }); } return undefined; @@ -424,7 +421,7 @@ async function saveSelectedEditors(accessor: ServicesAccessor, options?: ISaveEd } } -function saveDirtyEditorsOfGroups(accessor: ServicesAccessor, groups: ReadonlyArray, options?: ISaveEditorsOptions): Promise { +function saveDirtyEditorsOfGroups(accessor: ServicesAccessor, groups: readonly IEditorGroup[], options?: ISaveEditorsOptions): Promise { const dirtyEditors: IEditorIdentifier[] = []; for (const group of groups) { for (const editor of group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE)) { @@ -440,17 +437,19 @@ function saveDirtyEditorsOfGroups(accessor: ServicesAccessor, groups: ReadonlyAr async function doSaveEditors(accessor: ServicesAccessor, editors: IEditorIdentifier[], options?: ISaveEditorsOptions): Promise { const editorService = accessor.get(IEditorService); const notificationService = accessor.get(INotificationService); + const instantiationService = accessor.get(IInstantiationService); try { await editorService.save(editors, options); } catch (error) { if (!isPromiseCanceledError(error)) { notificationService.notify({ + id: editors.map(({ editor }) => hash(editor.resource?.toString())).join(), // ensure unique notification ID per set of editor severity: Severity.Error, message: nls.localize({ key: 'genericSaveError', comment: ['{0} is the resource that failed to save and {1} the error message'] }, "Failed to save '{0}': {1}", editors.map(({ editor }) => editor.getName()).join(', '), toErrorMessage(error, false)), actions: { primary: [ - toAction({ id: 'workbench.action.files.saveEditors', label: nls.localize('retry', "Retry"), run: () => editorService.save(editors, options) }), + toAction({ id: 'workbench.action.files.saveEditors', label: nls.localize('retry', "Retry"), run: () => instantiationService.invokeFunction(accessor => doSaveEditors(accessor, editors, options)) }), toAction({ id: 'workbench.action.files.revertEditors', label: nls.localize('discard', "Discard"), run: () => editorService.revert(editors) }) ] } @@ -509,7 +508,7 @@ CommandsRegistry.registerCommand({ const contexts = getMultiSelectedEditorContexts(editorContext, accessor.get(IListService), accessor.get(IEditorGroupsService)); - let groups: ReadonlyArray | undefined = undefined; + let groups: readonly IEditorGroup[] | undefined = undefined; if (!contexts.length) { groups = editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE); } else { @@ -681,9 +680,8 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ if (typeof args?.viewType === 'string') { const editorGroupsService = accessor.get(IEditorGroupsService); - const textInput = editorService.createEditorInput({ options: { pinned: true } }); const group = editorGroupsService.activeGroup; - await editorService.openEditor(textInput, { override: args.viewType, pinned: true }, group); + await editorService.openEditor({ options: { override: args.viewType, pinned: true } }, group); } else { await editorService.openEditor({ options: { pinned: true } }); // untitled are always pinned } diff --git a/src/vs/workbench/contrib/files/browser/files.contribution.ts b/src/vs/workbench/contrib/files/browser/files.contribution.ts index 0bd5f991..196ff12f 100644 --- a/src/vs/workbench/contrib/files/browser/files.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/files.contribution.ts @@ -8,7 +8,7 @@ import { sep } from 'vs/base/common/path'; import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope, IConfigurationPropertySchema } from 'vs/platform/configuration/common/configurationRegistry'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { EditorInput, IFileEditorInput, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions } from 'vs/workbench/common/editor'; +import { EditorInput, IFileEditorInput, IEditorInputFactoryRegistry, EditorExtensions } from 'vs/workbench/common/editor'; import { AutoSaveConfiguration, HotExitConfiguration, FILES_EXCLUDE_CONFIG, FILES_ASSOCIATIONS_CONFIG } from 'vs/platform/files/common/files'; import { SortOrder, FILE_EDITOR_INPUT_ID } from 'vs/workbench/contrib/files/common/files'; import { TextFileEditorTracker } from 'vs/workbench/contrib/files/browser/editors/textFileEditorTracker'; @@ -19,7 +19,7 @@ import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import * as platform from 'vs/base/common/platform'; import { ExplorerViewletViewsContribution } from 'vs/workbench/contrib/files/browser/explorerViewlet'; -import { IEditorRegistry, EditorDescriptor, Extensions as EditorExtensions } from 'vs/workbench/browser/editor'; +import { IEditorRegistry, EditorDescriptor } from 'vs/workbench/browser/editor'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { ILabelService } from 'vs/platform/label/common/label'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; @@ -31,7 +31,7 @@ import { editorConfigurationBaseNode } from 'vs/editor/common/config/commonEdito import { DirtyFilesIndicator } from 'vs/workbench/contrib/files/common/dirtyFilesIndicator'; import { UndoCommand, RedoCommand } from 'vs/editor/browser/editorExtensions'; import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; -import { FileEditorInputFactory, IExplorerService } from 'vs/workbench/contrib/files/browser/files'; +import { FileEditorInputSerializer, IExplorerService } from 'vs/workbench/contrib/files/browser/files'; class FileUriLabelContribution implements IWorkbenchContribution { @@ -65,7 +65,9 @@ Registry.as(EditorExtensions.Editors).registerEditor( ); // Register default file input factory -Registry.as(EditorInputExtensions.EditorInputFactories).registerFileEditorInputFactory({ +Registry.as(EditorExtensions.EditorInputFactories).registerFileEditorInputFactory({ + + typeId: FILE_EDITOR_INPUT_ID, createFileEditorInput: (resource, preferredResource, preferredName, preferredDescription, preferredEncoding, preferredMode, instantiationService): IFileEditorInput => { return instantiationService.createInstance(FileEditorInput, resource, preferredResource, preferredName, preferredDescription, preferredEncoding, preferredMode); @@ -76,8 +78,8 @@ Registry.as(EditorInputExtensions.EditorInputFactor } }); -// Register Editor Input Factory -Registry.as(EditorInputExtensions.EditorInputFactories).registerEditorInputFactory(FILE_EDITOR_INPUT_ID, FileEditorInputFactory); +// Register Editor Input Serializer +Registry.as(EditorExtensions.EditorInputFactories).registerEditorInputSerializer(FILE_EDITOR_INPUT_ID, FileEditorInputSerializer); // Register Explorer views Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(ExplorerViewletViewsContribution, LifecyclePhase.Starting); diff --git a/src/vs/workbench/contrib/files/browser/files.ts b/src/vs/workbench/contrib/files/browser/files.ts index 95944460..0cf9348d 100644 --- a/src/vs/workbench/contrib/files/browser/files.ts +++ b/src/vs/workbench/contrib/files/browser/files.ts @@ -6,7 +6,7 @@ import { URI, UriComponents } from 'vs/base/common/uri'; import { IListService } from 'vs/platform/list/browser/listService'; import { OpenEditor, SortOrder } from 'vs/workbench/contrib/files/common/files'; -import { EditorResourceAccessor, SideBySideEditor, IEditorIdentifier, EditorInput, IEditorInputFactory } from 'vs/workbench/common/editor'; +import { EditorResourceAccessor, SideBySideEditor, IEditorIdentifier, EditorInput, IEditorInputSerializer } from 'vs/workbench/common/editor'; import { List } from 'vs/base/browser/ui/list/listWidget'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ExplorerItem } from 'vs/workbench/contrib/files/common/explorerModel'; @@ -29,14 +29,14 @@ interface ISerializedFileEditorInput { modeId?: string; } -export class FileEditorInputFactory implements IEditorInputFactory { +export class FileEditorInputSerializer implements IEditorInputSerializer { canSerialize(editorInput: EditorInput): boolean { return true; } serialize(editorInput: EditorInput): string { - const fileEditorInput = editorInput; + const fileEditorInput = editorInput as FileEditorInput; const resource = fileEditorInput.resource; const preferredResource = fileEditorInput.preferredResource; const serializedFileEditorInput: ISerializedFileEditorInput = { @@ -52,7 +52,7 @@ export class FileEditorInputFactory implements IEditorInputFactory { } deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): FileEditorInput { - return instantiationService.invokeFunction(accessor => { + return instantiationService.invokeFunction(accessor => { const serializedFileEditorInput: ISerializedFileEditorInput = JSON.parse(serializedEditorInput); const resource = URI.revive(serializedFileEditorInput.resourceJSON); const preferredResource = URI.revive(serializedFileEditorInput.preferredResourceJSON); diff --git a/src/vs/workbench/contrib/files/browser/files.web.contribution.ts b/src/vs/workbench/contrib/files/browser/files.web.contribution.ts index 494bdac3..72915040 100644 --- a/src/vs/workbench/contrib/files/browser/files.web.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/files.web.contribution.ts @@ -5,10 +5,10 @@ import * as nls from 'vs/nls'; import { Registry } from 'vs/platform/registry/common/platform'; -import { EditorInput } from 'vs/workbench/common/editor'; +import { EditorExtensions, EditorInput } from 'vs/workbench/common/editor'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; -import { IEditorRegistry, EditorDescriptor, Extensions as EditorExtensions } from 'vs/workbench/browser/editor'; +import { IEditorRegistry, EditorDescriptor } from 'vs/workbench/browser/editor'; import { TextFileEditor } from 'vs/workbench/contrib/files/browser/editors/textFileEditor'; // Register file editor diff --git a/src/vs/workbench/contrib/files/browser/views/emptyView.ts b/src/vs/workbench/contrib/files/browser/views/emptyView.ts index 7e70f008..d36b245d 100644 --- a/src/vs/workbench/contrib/files/browser/views/emptyView.ts +++ b/src/vs/workbench/contrib/files/browser/views/emptyView.ts @@ -45,11 +45,11 @@ export class EmptyView extends ViewPane { this._register(this.labelService.onDidChangeFormatters(() => this.refreshTitle())); } - shouldShowWelcome(): boolean { + override shouldShowWelcome(): boolean { return true; } - protected renderBody(container: HTMLElement): void { + protected override renderBody(container: HTMLElement): void { super.renderBody(container); this._register(new DragAndDropObserver(container, { diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index 05f26f13..a519921a 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -175,7 +175,7 @@ export class ExplorerView extends ViewPane { @IConfigurationService configurationService: IConfigurationService, @IDecorationsService private readonly decorationService: IDecorationsService, @ILabelService private readonly labelService: ILabelService, - @IThemeService protected themeService: IWorkbenchThemeService, + @IThemeService themeService: IWorkbenchThemeService, @IMenuService private readonly menuService: IMenuService, @ITelemetryService telemetryService: ITelemetryService, @IExplorerService private readonly explorerService: IExplorerService, @@ -207,11 +207,11 @@ export class ExplorerView extends ViewPane { return this.labelService.getWorkspaceLabel(this.contextService.getWorkspace()); } - get title(): string { + override get title(): string { return this.name; } - set title(_: string) { + override set title(_: string) { // noop } @@ -232,7 +232,7 @@ export class ExplorerView extends ViewPane { // Split view methods - protected renderHeader(container: HTMLElement): void { + protected override renderHeader(container: HTMLElement): void { super.renderHeader(container); // Expand on drag over @@ -252,12 +252,12 @@ export class ExplorerView extends ViewPane { setHeader(); } - protected layoutBody(height: number, width: number): void { + protected override layoutBody(height: number, width: number): void { super.layoutBody(height, width); this.tree.layout(height, width); } - renderBody(container: HTMLElement): void { + override renderBody(container: HTMLElement): void { super.renderBody(container); this.container = container; @@ -297,7 +297,7 @@ export class ExplorerView extends ViewPane { })); } - focus(): void { + override focus(): void { this.tree.domFocus(); const focused = this.tree.getFocus(); @@ -621,7 +621,7 @@ export class ExplorerView extends ViewPane { } } - getOptimalWidth(): number { + override getOptimalWidth(): number { const parentNode = this.tree.getHTMLElement(); const childNodes = ([] as HTMLElement[]).slice.call(parentNode.querySelectorAll('.explorer-item .label-name')); // select all file labels @@ -682,7 +682,7 @@ export class ExplorerView extends ViewPane { this.progressService.withProgress({ location: ProgressLocation.Explorer, - delay: this.layoutService.isRestored() ? 800 : 1200 // less ugly initial startup + delay: this.layoutService.isRestored() ? 800 : 1500 // reduce progress visibility when still restoring }, _progress => promise); await promise; @@ -835,7 +835,7 @@ export class ExplorerView extends ViewPane { } } - dispose(): void { + override dispose(): void { if (this.dragHandler) { this.dragHandler.dispose(); } diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index 8c8b29d8..5ef31be6 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -116,7 +116,7 @@ export class ExplorerDataSource implements IAsyncDataSource promise); return promise; diff --git a/src/vs/workbench/contrib/files/browser/views/media/openeditors.css b/src/vs/workbench/contrib/files/browser/views/media/openeditors.css index 087a47fb..45b8572d 100644 --- a/src/vs/workbench/contrib/files/browser/views/media/openeditors.css +++ b/src/vs/workbench/contrib/files/browser/views/media/openeditors.css @@ -12,21 +12,13 @@ .open-editors .monaco-list .monaco-list-row > .monaco-action-bar .action-label { display: block; + padding: 2px; } .open-editors .monaco-list .monaco-list-row > .monaco-action-bar .codicon { color: inherit; } -.open-editors .monaco-list .monaco-list-row > .monaco-action-bar .codicon-close, -.open-editors .monaco-list .monaco-list-row > .monaco-action-bar .codicon-pinned { - width: 8px; - height: 22px; - display: flex; - align-items: center; - justify-content: center; -} - .open-editors .monaco-list .monaco-list-row.dirty:not(:hover) > .monaco-action-bar .codicon-pinned::before { content: "\ebb2"; /* use `pinned-dirty` icon unicode for sticky-dirty indication */ } diff --git a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts index 81912c52..2aa047e2 100644 --- a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts +++ b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts @@ -41,7 +41,8 @@ import { ElementsDragAndDropData, NativeDragAndDropData } from 'vs/base/browser/ import { URI } from 'vs/base/common/uri'; import { withUndefinedAsNull } from 'vs/base/common/types'; import { isWeb } from 'vs/base/common/platform'; -import { IWorkingCopyService, IWorkingCopy, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { IWorkingCopy, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopy'; import { AutoSaveMode, IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { IViewDescriptorService } from 'vs/workbench/common/views'; import { IOpenerService } from 'vs/platform/opener/common/opener'; @@ -192,7 +193,7 @@ export class OpenEditorsView extends ViewPane { })); } - protected renderHeaderTitle(container: HTMLElement): void { + protected override renderHeaderTitle(container: HTMLElement): void { super.renderHeaderTitle(container, this.title); const count = dom.append(container, $('.count')); @@ -214,7 +215,7 @@ export class OpenEditorsView extends ViewPane { this.updateDirtyIndicator(); } - renderBody(container: HTMLElement): void { + override renderBody(container: HTMLElement): void { super.renderBody(container); container.classList.add('open-editors'); @@ -309,7 +310,7 @@ export class OpenEditorsView extends ViewPane { })); } - focus(): void { + override focus(): void { super.focus(); this.list.domFocus(); } @@ -318,7 +319,7 @@ export class OpenEditorsView extends ViewPane { return this.list; } - protected layoutBody(height: number, width: number): void { + protected override layoutBody(height: number, width: number): void { super.layoutBody(height, width); if (this.list) { this.list.layout(height, width); @@ -467,7 +468,7 @@ export class OpenEditorsView extends ViewPane { this.structuralRefreshDelay = delay; } - getOptimalWidth(): number { + override getOptimalWidth(): number { let parentNode = this.list.getHTMLElement(); let childNodes: HTMLElement[] = [].slice.call(parentNode.querySelectorAll('.open-editor > a')); @@ -492,7 +493,7 @@ interface IEditorGroupTemplateData { class OpenEditorActionRunner extends ActionRunner { public editor: OpenEditor | undefined; - async run(action: IAction): Promise { + override async run(action: IAction): Promise { if (!this.editor) { return; } diff --git a/src/vs/workbench/contrib/files/common/dirtyFilesIndicator.ts b/src/vs/workbench/contrib/files/common/dirtyFilesIndicator.ts index fd9a6919..6dfdf256 100644 --- a/src/vs/workbench/contrib/files/common/dirtyFilesIndicator.ts +++ b/src/vs/workbench/contrib/files/common/dirtyFilesIndicator.ts @@ -9,7 +9,8 @@ import { VIEWLET_ID } from 'vs/workbench/contrib/files/common/files'; import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity'; -import { IWorkingCopyService, IWorkingCopy, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { IWorkingCopy, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopy'; import { IFilesConfigurationService, AutoSaveMode } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; export class DirtyFilesIndicator extends Disposable implements IWorkbenchContribution { @@ -36,7 +37,7 @@ export class DirtyFilesIndicator extends Disposable implements IWorkbenchContrib this._register(this.workingCopyService.onDidChangeDirty(workingCopy => this.onWorkingCopyDidChangeDirty(workingCopy))); // Lifecycle - this.lifecycleService.onShutdown(() => this.dispose()); + this.lifecycleService.onDidShutdown(() => this.dispose()); } private onWorkingCopyDidChangeDirty(workingCopy: IWorkingCopy): void { diff --git a/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts b/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts index 9c8ea0c0..342eaecd 100644 --- a/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts +++ b/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts @@ -5,11 +5,11 @@ import { localize } from 'vs/nls'; import { URI } from 'vs/base/common/uri'; -import { EncodingMode, IFileEditorInput, Verbosity, GroupIdentifier, IMoveResult, isTextEditorPane } from 'vs/workbench/common/editor'; +import { IFileEditorInput, Verbosity, GroupIdentifier, IMoveResult, isTextEditorPane } from 'vs/workbench/common/editor'; import { AbstractTextResourceEditorInput } from 'vs/workbench/common/editor/textResourceEditorInput'; import { BinaryEditorModel } from 'vs/workbench/common/editor/binaryEditorModel'; import { FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files'; -import { ITextFileService, TextFileEditorModelState, TextFileLoadReason, TextFileOperationError, TextFileOperationResult, ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileService, TextFileEditorModelState, TextFileResolveReason, TextFileOperationError, TextFileOperationResult, ITextFileEditorModel, EncodingMode } from 'vs/workbench/services/textfile/common/textfiles'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IReference, dispose, DisposableStore } from 'vs/base/common/lifecycle'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; @@ -34,6 +34,10 @@ const enum ForceOpenAs { */ export class FileEditorInput extends AbstractTextResourceEditorInput implements IFileEditorInput { + override get typeId(): string { + return FILE_EDITOR_INPUT_ID; + } + private preferredName: string | undefined; private preferredDescription: string | undefined; private preferredEncoding: string | undefined; @@ -88,7 +92,7 @@ export class FileEditorInput extends AbstractTextResourceEditorInput implements } } - protected registerListeners(): void { + protected override registerListeners(): void { super.registerListeners(); // Attach to model that matches our resource once created @@ -121,17 +125,13 @@ export class FileEditorInput extends AbstractTextResourceEditorInput implements this.modelListeners.add(model.onDidSaveError(() => this._onDidChangeDirty.fire())); // remove model association once it gets disposed - this.modelListeners.add(Event.once(model.onDispose)(() => { + this.modelListeners.add(Event.once(model.onWillDispose)(() => { this.modelListeners.clear(); this.model = undefined; })); } - getTypeId(): string { - return FILE_EDITOR_INPUT_ID; - } - - getName(): string { + override getName(): string { return this.preferredName || this.decorateLabel(super.getName()); } @@ -155,7 +155,7 @@ export class FileEditorInput extends AbstractTextResourceEditorInput implements return this.preferredName; } - getDescription(verbosity?: Verbosity): string | undefined { + override getDescription(verbosity?: Verbosity): string | undefined { return this.preferredDescription || super.getDescription(verbosity); } @@ -175,7 +175,7 @@ export class FileEditorInput extends AbstractTextResourceEditorInput implements return this.preferredDescription; } - getTitle(verbosity: Verbosity): string { + override getTitle(verbosity: Verbosity): string { switch (verbosity) { case Verbosity.SHORT: return this.decorateLabel(super.getName()); @@ -203,10 +203,10 @@ export class FileEditorInput extends AbstractTextResourceEditorInput implements return this.preferredEncoding; } - setEncoding(encoding: string, mode: EncodingMode): void { + async setEncoding(encoding: string, mode: EncodingMode): Promise { this.setPreferredEncoding(encoding); - this.model?.setEncoding(encoding, mode); + return this.model?.setEncoding(encoding, mode); } setPreferredEncoding(encoding: string): void { @@ -241,11 +241,11 @@ export class FileEditorInput extends AbstractTextResourceEditorInput implements this.forceOpenAs = ForceOpenAs.Binary; } - isDirty(): boolean { + override isDirty(): boolean { return !!(this.model?.isDirty()); } - isReadonly(): boolean { + override isReadonly(): boolean { if (this.model) { return this.model.isReadonly(); } @@ -253,7 +253,7 @@ export class FileEditorInput extends AbstractTextResourceEditorInput implements return super.isReadonly(); } - isSaving(): boolean { + override isSaving(): boolean { if (this.model?.hasState(TextFileEditorModelState.SAVED) || this.model?.hasState(TextFileEditorModelState.CONFLICT) || this.model?.hasState(TextFileEditorModelState.ERROR)) { return false; // require the model to be dirty and not in conflict or error state } @@ -266,11 +266,11 @@ export class FileEditorInput extends AbstractTextResourceEditorInput implements return super.isSaving(); } - getPreferredEditorId(candidates: string[]): string { + override getPreferredEditorId(candidates: string[]): string { return this.forceOpenAs === ForceOpenAs.Binary ? BINARY_FILE_EDITOR_ID : TEXT_FILE_EDITOR_ID; } - resolve(): Promise { + override resolve(): Promise { // Resolve as binary if (this.forceOpenAs === ForceOpenAs.Binary) { @@ -291,7 +291,7 @@ export class FileEditorInput extends AbstractTextResourceEditorInput implements encoding: this.preferredEncoding, reload: { async: true }, // trigger a reload of the model if it exists already but do not wait to show the model allowBinary: this.forceOpenAs === ForceOpenAs.Text, - reason: TextFileLoadReason.EDITOR + reason: TextFileResolveReason.EDITOR }); // This is a bit ugly, because we first resolve the model and then resolve a model reference. the reason being that binary @@ -328,14 +328,17 @@ export class FileEditorInput extends AbstractTextResourceEditorInput implements } private async doResolveAsBinary(): Promise { - return this.instantiationService.createInstance(BinaryEditorModel, this.preferredResource, this.getName()).load(); + const model = this.instantiationService.createInstance(BinaryEditorModel, this.preferredResource, this.getName()); + await model.resolve(); + + return model; } isResolved(): boolean { return !!this.model; } - rename(group: GroupIdentifier, target: URI): IMoveResult { + override rename(group: GroupIdentifier, target: URI): IMoveResult { return { editor: { resource: target, @@ -349,7 +352,7 @@ export class FileEditorInput extends AbstractTextResourceEditorInput implements private getViewStateFor(group: GroupIdentifier): IEditorViewState | undefined { for (const editorPane of this.editorService.visibleEditorPanes) { - if (editorPane.group.id === group && isEqual(editorPane.input.resource, this.resource)) { + if (editorPane.group.id === group && this.matches(editorPane.input)) { if (isTextEditorPane(editorPane)) { return editorPane.getViewState(); } @@ -359,7 +362,7 @@ export class FileEditorInput extends AbstractTextResourceEditorInput implements return undefined; } - matches(otherInput: unknown): boolean { + override matches(otherInput: unknown): boolean { if (otherInput === this) { return true; } @@ -371,7 +374,7 @@ export class FileEditorInput extends AbstractTextResourceEditorInput implements return false; } - dispose(): void { + override dispose(): void { // Model this.model = undefined; diff --git a/src/vs/workbench/contrib/files/common/workspaceWatcher.ts b/src/vs/workbench/contrib/files/common/workspaceWatcher.ts index 1534bdc8..7f6ec9c6 100644 --- a/src/vs/workbench/contrib/files/common/workspaceWatcher.ts +++ b/src/vs/workbench/contrib/files/common/workspaceWatcher.ts @@ -143,7 +143,7 @@ export class WorkspaceWatcher extends Disposable { this.watches.clear(); } - dispose(): void { + override dispose(): void { super.dispose(); this.unwatchWorkspaces(); diff --git a/src/vs/workbench/contrib/files/electron-sandbox/fileActions.contribution.ts b/src/vs/workbench/contrib/files/electron-sandbox/fileActions.contribution.ts index 72211dd0..16178f47 100644 --- a/src/vs/workbench/contrib/files/electron-sandbox/fileActions.contribution.ts +++ b/src/vs/workbench/contrib/files/electron-sandbox/fileActions.contribution.ts @@ -8,7 +8,6 @@ import { URI } from 'vs/base/common/uri'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { isWindows, isMacintosh } from 'vs/base/common/platform'; import { Schemas } from 'vs/base/common/network'; -import { INotificationService } from 'vs/platform/notification/common/notification'; import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; @@ -38,7 +37,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ }, handler: (accessor: ServicesAccessor, resource: URI | object) => { const resources = getMultiSelectedResources(resource, accessor.get(IListService), accessor.get(IEditorService), accessor.get(IExplorerService)); - revealResourcesInOS(resources, accessor.get(INativeHostService), accessor.get(INotificationService), accessor.get(IWorkspaceContextService)); + revealResourcesInOS(resources, accessor.get(INativeHostService), accessor.get(IWorkspaceContextService)); } }); @@ -54,7 +53,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ const activeInput = editorService.activeEditor; const resource = EditorResourceAccessor.getOriginalUri(activeInput, { filterByScheme: Schemas.file, supportSideBySide: SideBySideEditor.PRIMARY }); const resources = resource ? [resource] : []; - revealResourcesInOS(resources, accessor.get(INativeHostService), accessor.get(INotificationService), accessor.get(IWorkspaceContextService)); + revealResourcesInOS(resources, accessor.get(INativeHostService), accessor.get(IWorkspaceContextService)); } }); diff --git a/src/vs/workbench/contrib/files/electron-sandbox/fileCommands.ts b/src/vs/workbench/contrib/files/electron-sandbox/fileCommands.ts index eb9b881a..c0618077 100644 --- a/src/vs/workbench/contrib/files/electron-sandbox/fileCommands.ts +++ b/src/vs/workbench/contrib/files/electron-sandbox/fileCommands.ts @@ -3,17 +3,15 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; import { URI } from 'vs/base/common/uri'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { sequence } from 'vs/base/common/async'; import { Schemas } from 'vs/base/common/network'; -import { INotificationService } from 'vs/platform/notification/common/notification'; import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; // Commands -export function revealResourcesInOS(resources: URI[], nativeHostService: INativeHostService, notificationService: INotificationService, workspaceContextService: IWorkspaceContextService): void { +export function revealResourcesInOS(resources: URI[], nativeHostService: INativeHostService, workspaceContextService: IWorkspaceContextService): void { if (resources.length) { sequence(resources.map(r => async () => { if (r.scheme === Schemas.file || r.scheme === Schemas.userData) { @@ -25,7 +23,5 @@ export function revealResourcesInOS(resources: URI[], nativeHostService: INative if (uri.scheme === Schemas.file) { nativeHostService.showItemInFolder(uri.fsPath); } - } else { - notificationService.info(nls.localize('openFileToReveal', "Open a file first to reveal")); } } diff --git a/src/vs/workbench/contrib/files/electron-sandbox/files.contribution.ts b/src/vs/workbench/contrib/files/electron-sandbox/files.contribution.ts index 3f6524d0..fd0604b5 100644 --- a/src/vs/workbench/contrib/files/electron-sandbox/files.contribution.ts +++ b/src/vs/workbench/contrib/files/electron-sandbox/files.contribution.ts @@ -5,10 +5,10 @@ import * as nls from 'vs/nls'; import { Registry } from 'vs/platform/registry/common/platform'; -import { EditorInput } from 'vs/workbench/common/editor'; +import { EditorExtensions, EditorInput } from 'vs/workbench/common/editor'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; -import { IEditorRegistry, EditorDescriptor, Extensions as EditorExtensions } from 'vs/workbench/browser/editor'; +import { IEditorRegistry, EditorDescriptor } from 'vs/workbench/browser/editor'; import { NativeTextFileEditor } from 'vs/workbench/contrib/files/electron-sandbox/textFileEditor'; // Register file editor diff --git a/src/vs/workbench/contrib/files/electron-sandbox/textFileEditor.ts b/src/vs/workbench/contrib/files/electron-sandbox/textFileEditor.ts index d79e4a08..a47e3abe 100644 --- a/src/vs/workbench/contrib/files/electron-sandbox/textFileEditor.ts +++ b/src/vs/workbench/contrib/files/electron-sandbox/textFileEditor.ts @@ -24,6 +24,7 @@ import { IPreferencesService } from 'vs/workbench/services/preferences/common/pr import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; import { IExplorerService } from 'vs/workbench/contrib/files/browser/files'; +import { IProductService } from 'vs/platform/product/common/productService'; /** * An implementation of editor for file system resources. @@ -45,18 +46,19 @@ export class NativeTextFileEditor extends TextFileEditor { @INativeHostService private readonly nativeHostService: INativeHostService, @IPreferencesService private readonly preferencesService: IPreferencesService, @IExplorerService explorerService: IExplorerService, - @IUriIdentityService uriIdentityService: IUriIdentityService + @IUriIdentityService uriIdentityService: IUriIdentityService, + @IProductService private readonly productService: IProductService ) { super(telemetryService, fileService, viewletService, instantiationService, contextService, storageService, textResourceConfigurationService, editorService, themeService, editorGroupService, textFileService, explorerService, uriIdentityService); } - protected handleSetInputError(error: Error, input: FileEditorInput, options: EditorOptions | undefined): void { + protected override handleSetInputError(error: Error, input: FileEditorInput, options: EditorOptions | undefined): void { // Allow to restart with higher memory limit if the file is too large if ((error).fileOperationResult === FileOperationResult.FILE_EXCEEDS_MEMORY_LIMIT) { const memoryLimit = Math.max(MIN_MAX_MEMORY_SIZE_MB, +this.textResourceConfigurationService.getValue(undefined, 'files.maxMemoryForLargeFilesMB') || FALLBACK_MAX_MEMORY_SIZE_MB); - throw createErrorWithActions(localize('fileTooLargeForHeapError', "To open a file of this size, you need to restart and allow it to use more memory"), { + throw createErrorWithActions(localize('fileTooLargeForHeapError', "To open a file of this size, you need to restart and allow {0} to use more memory", this.productService.nameShort), { actions: [ toAction({ id: 'workbench.window.action.relaunchWithIncreasedMemoryLimit', label: localize('relaunchWithIncreasedMemoryLimit', "Restart with {0} MB", memoryLimit), run: () => { diff --git a/src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts b/src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts index 9bb8ebf0..1543fea7 100644 --- a/src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts @@ -44,7 +44,7 @@ suite('EditorAutoSave', () => { configurationService )); - const part = createEditorPart(instantiationService, disposables); + const part = await createEditorPart(instantiationService, disposables); instantiationService.stub(IEditorGroupsService, part); diff --git a/src/vs/workbench/contrib/files/test/browser/explorerModel.test.ts b/src/vs/workbench/contrib/files/test/browser/explorerModel.test.ts index 743e1f93..d7b872b7 100644 --- a/src/vs/workbench/contrib/files/test/browser/explorerModel.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/explorerModel.test.ts @@ -263,7 +263,7 @@ suite('Files - View Model', function () { (merge2)._isDirectoryResolved = true; ExplorerItem.mergeLocalWithDisk(merge2, merge1); assert.strictEqual(merge1.getChild('foo.html')!.name, 'foo.html'); - assert.deepEqual(merge1.getChild('foo.html')!.parent, merge1, 'Check parent'); + assert.deepStrictEqual(merge1.getChild('foo.html')!.parent, merge1, 'Check parent'); // Verify that merge does not replace existing children, but updates properties in that case const existingChild = merge1.getChild('foo.html'); diff --git a/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts b/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts index d007ae7a..e447235e 100644 --- a/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts @@ -9,8 +9,8 @@ import { toResource } from 'vs/base/test/common/utils'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; import { workbenchInstantiationService, TestServiceAccessor, TestEditorService, getLastResolvedFileStat } from 'vs/workbench/test/browser/workbenchTestServices'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { EncodingMode, IEditorInputFactoryRegistry, Verbosity, Extensions as EditorExtensions } from 'vs/workbench/common/editor'; -import { TextFileOperationError, TextFileOperationResult } from 'vs/workbench/services/textfile/common/textfiles'; +import { IEditorInputFactoryRegistry, Verbosity, EditorExtensions } from 'vs/workbench/common/editor'; +import { EncodingMode, TextFileOperationError, TextFileOperationResult } from 'vs/workbench/services/textfile/common/textfiles'; import { FileOperationResult, FileOperationError } from 'vs/platform/files/common/files'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; import { timeout } from 'vs/base/common/async'; @@ -19,7 +19,7 @@ import { DisposableStore } from 'vs/base/common/lifecycle'; import { BinaryEditorModel } from 'vs/workbench/common/editor/binaryEditorModel'; import { IResourceEditorInput } from 'vs/platform/editor/common/editor'; import { Registry } from 'vs/platform/registry/common/platform'; -import { FileEditorInputFactory } from 'vs/workbench/contrib/files/browser/files'; +import { FileEditorInputSerializer } from 'vs/workbench/contrib/files/browser/files'; suite('Files - FileEditorInput', () => { @@ -34,7 +34,7 @@ suite('Files - FileEditorInput', () => { instantiationService = workbenchInstantiationService({ editorService: () => { return new class extends TestEditorService { - createEditorInput(input: IResourceEditorInput) { + override createEditorInput(input: IResourceEditorInput) { return createFileInput(input.resource); } }; @@ -170,7 +170,7 @@ suite('Files - FileEditorInput', () => { test('getEncoding/setEncoding', async function () { const input = createFileInput(toResource.call(this, '/foo/bar/updatefile.js')); - input.setEncoding('utf16', EncodingMode.Encode); + await input.setEncoding('utf16', EncodingMode.Encode); assert.strictEqual(input.getEncoding(), 'utf16'); const resolved = await input.resolve() as TextFileEditorModel; @@ -261,37 +261,37 @@ suite('Files - FileEditorInput', () => { resolved.dispose(); }); - test('file editor input factory', async function () { + test('file editor input serializer', async function () { instantiationService.invokeFunction(accessor => Registry.as(EditorExtensions.EditorInputFactories).start(accessor)); const input = createFileInput(toResource.call(this, '/foo/bar/updatefile.js')); - const disposable = Registry.as(EditorExtensions.EditorInputFactories).registerEditorInputFactory('workbench.editors.files.fileEditorInput', FileEditorInputFactory); + const disposable = Registry.as(EditorExtensions.EditorInputFactories).registerEditorInputSerializer('workbench.editors.files.fileEditorInput', FileEditorInputSerializer); - const factory = Registry.as(EditorExtensions.EditorInputFactories).getEditorInputFactory(input.getTypeId()); - if (!factory) { - assert.fail('File Editor Input Factory missing'); + const editorSerializer = Registry.as(EditorExtensions.EditorInputFactories).getEditorInputSerializer(input.typeId); + if (!editorSerializer) { + assert.fail('File Editor Input Serializer missing'); } - assert.strictEqual(factory.canSerialize(input), true); + assert.strictEqual(editorSerializer.canSerialize(input), true); - const inputSerialized = factory.serialize(input); + const inputSerialized = editorSerializer.serialize(input); if (!inputSerialized) { assert.fail('Unexpected serialized file input'); } - const inputDeserialized = factory.deserialize(instantiationService, inputSerialized); + const inputDeserialized = editorSerializer.deserialize(instantiationService, inputSerialized); assert.strictEqual(input.matches(inputDeserialized), true); const preferredResource = toResource.call(this, '/foo/bar/UPDATEfile.js'); const inputWithPreferredResource = createFileInput(toResource.call(this, '/foo/bar/updatefile.js'), preferredResource); - const inputWithPreferredResourceSerialized = factory.serialize(inputWithPreferredResource); + const inputWithPreferredResourceSerialized = editorSerializer.serialize(inputWithPreferredResource); if (!inputWithPreferredResourceSerialized) { assert.fail('Unexpected serialized file input'); } - const inputWithPreferredResourceDeserialized = factory.deserialize(instantiationService, inputWithPreferredResourceSerialized) as FileEditorInput; + const inputWithPreferredResourceDeserialized = editorSerializer.deserialize(instantiationService, inputWithPreferredResourceSerialized) as FileEditorInput; assert.strictEqual(inputWithPreferredResource.resource.toString(), inputWithPreferredResourceDeserialized.resource.toString()); assert.strictEqual(inputWithPreferredResource.preferredResource.toString(), inputWithPreferredResourceDeserialized.preferredResource.toString()); diff --git a/src/vs/workbench/contrib/files/test/browser/textFileEditorTracker.test.ts b/src/vs/workbench/contrib/files/test/browser/textFileEditorTracker.test.ts index c959c634..5df9a37e 100644 --- a/src/vs/workbench/contrib/files/test/browser/textFileEditorTracker.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/textFileEditorTracker.test.ts @@ -24,6 +24,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { FILE_EDITOR_INPUT_ID } from 'vs/workbench/contrib/files/common/files'; suite('Files - TextFileEditorTracker', () => { @@ -53,7 +54,7 @@ suite('Files - TextFileEditorTracker', () => { )); } - const part = createEditorPart(instantiationService, disposables); + const part = await createEditorPart(instantiationService, disposables); instantiationService.stub(IEditorGroupsService, part); @@ -63,8 +64,6 @@ suite('Files - TextFileEditorTracker', () => { const accessor = instantiationService.createInstance(TestServiceAccessor); disposables.add((accessor.textFileService.files)); - await part.whenRestored; - disposables.add(instantiationService.createInstance(TextFileEditorTracker)); return accessor; @@ -117,7 +116,7 @@ suite('Files - TextFileEditorTracker', () => { async function testDirtyTextFileModelOpensEditorDependingOnAutoSaveSetting(resource: URI, autoSave: boolean, error: boolean): Promise { const accessor = await createTracker(autoSave); - assert.ok(!accessor.editorService.isOpen(accessor.editorService.createEditorInput({ resource, forceFile: true }))); + assert.ok(!accessor.editorService.isOpened({ resource, typeId: FILE_EDITOR_INPUT_ID })); if (error) { accessor.textFileService.setWriteErrorOnce(new FileOperationError('fail to write', FileOperationResult.FILE_OTHER_ERROR)); @@ -131,13 +130,13 @@ suite('Files - TextFileEditorTracker', () => { await model.save(); await timeout(100); if (error) { - assert.ok(accessor.editorService.isOpen(accessor.editorService.createEditorInput({ resource, forceFile: true }))); + assert.ok(accessor.editorService.isOpened({ resource, typeId: FILE_EDITOR_INPUT_ID })); } else { - assert.ok(!accessor.editorService.isOpen(accessor.editorService.createEditorInput({ resource, forceFile: true }))); + assert.ok(!accessor.editorService.isOpened({ resource, typeId: FILE_EDITOR_INPUT_ID })); } } else { await awaitEditorOpening(accessor.editorService); - assert.ok(accessor.editorService.isOpen(accessor.editorService.createEditorInput({ resource, forceFile: true }))); + assert.ok(accessor.editorService.isOpened({ resource, typeId: FILE_EDITOR_INPUT_ID })); } } @@ -147,12 +146,12 @@ suite('Files - TextFileEditorTracker', () => { const untitledEditor = accessor.editorService.createEditorInput({ forceUntitled: true }) as UntitledTextEditorInput; const model = disposables.add(await untitledEditor.resolve()); - assert.ok(!accessor.editorService.isOpen(untitledEditor)); + assert.ok(!accessor.editorService.isOpened(untitledEditor)); - model.textEditorModel.setValue('Super Good'); + model.textEditorModel?.setValue('Super Good'); await awaitEditorOpening(accessor.editorService); - assert.ok(accessor.editorService.isOpen(untitledEditor)); + assert.ok(accessor.editorService.isOpened(untitledEditor)); }); function awaitEditorOpening(editorService: IEditorService): Promise { @@ -169,12 +168,12 @@ suite('Files - TextFileEditorTracker', () => { accessor.hostService.setFocus(false); accessor.hostService.setFocus(true); - await awaitModelLoadEvent(accessor.textFileService, resource); + await awaitModelResolveEvent(accessor.textFileService, resource); }); - function awaitModelLoadEvent(textFileService: ITextFileService, resource: URI): Promise { + function awaitModelResolveEvent(textFileService: ITextFileService, resource: URI): Promise { return new Promise(resolve => { - const listener = textFileService.files.onDidLoad(e => { + const listener = textFileService.files.onDidResolve(e => { if (isEqual(e.model.resource, resource)) { listener.dispose(); resolve(); diff --git a/src/vs/workbench/contrib/format/browser/formatActionsNone.ts b/src/vs/workbench/contrib/format/browser/formatActionsNone.ts index 1efff633..66487019 100644 --- a/src/vs/workbench/contrib/format/browser/formatActionsNone.ts +++ b/src/vs/workbench/contrib/format/browser/formatActionsNone.ts @@ -15,6 +15,7 @@ import { ICommandService } from 'vs/platform/commands/common/commands'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { VIEWLET_ID, IExtensionsViewPaneContainer } from 'vs/workbench/contrib/extensions/common/extensions'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; async function showExtensionQuery(viewletService: IViewletService, query: string) { const viewlet = await viewletService.openViewlet(VIEWLET_ID, true); @@ -40,7 +41,7 @@ registerEditorAction(class FormatDocumentMultipleAction extends EditorAction { }); } - async run(accessor: ServicesAccessor, editor: ICodeEditor, args: any): Promise { + async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise { if (!editor.hasModel()) { return; } @@ -48,6 +49,8 @@ registerEditorAction(class FormatDocumentMultipleAction extends EditorAction { const commandService = accessor.get(ICommandService); const viewletService = accessor.get(IViewletService); const notificationService = accessor.get(INotificationService); + const dialogService = accessor.get(IDialogService); + const model = editor.getModel(); const formatterCount = DocumentFormattingEditProviderRegistry.all(model).length; @@ -56,15 +59,18 @@ registerEditorAction(class FormatDocumentMultipleAction extends EditorAction { } else if (formatterCount === 1) { return commandService.executeCommand('editor.action.formatDocument'); } else if (model.isTooLargeForSyncing()) { - notificationService.prompt(Severity.Info, nls.localize('too.large', "This file cannot be formatted because it is too large"), []); + notificationService.warn(nls.localize('too.large', "This file cannot be formatted because it is too large")); } else { const langName = model.getLanguageIdentifier().language; const message = nls.localize('no.provider', "There is no formatter for '{0}' files installed.", langName); - const choice = { - label: nls.localize('install.formatter', "Install Formatter..."), - run: () => showExtensionQuery(viewletService, `category:formatters ${langName}`) - }; - notificationService.prompt(Severity.Info, message, [choice]); + const res = await dialogService.show( + Severity.Info, + message, + [nls.localize('cancel', "Cancel"), nls.localize('install.formatter', "Install Formatter...")] + ); + if (res.choice === 1) { + showExtensionQuery(viewletService, `category:formatters ${langName}`); + } } } }); diff --git a/src/vs/workbench/contrib/format/browser/formatModified.ts b/src/vs/workbench/contrib/format/browser/formatModified.ts index e1ae5381..6fa8509b 100644 --- a/src/vs/workbench/contrib/format/browser/formatModified.ts +++ b/src/vs/workbench/contrib/format/browser/formatModified.ts @@ -65,7 +65,7 @@ export async function getModifiedRanges(accessor: ServicesAccessor, modified: IT if (!workerService.canComputeDirtyDiff(original, modified.uri)) { return undefined; } - const changes = await workerService.computeDirtyDiff(original, modified.uri, true); + const changes = await workerService.computeDirtyDiff(original, modified.uri, false); if (!isNonEmptyArray(changes)) { return undefined; } diff --git a/src/vs/workbench/contrib/issue/electron-sandbox/issueActions.ts b/src/vs/workbench/contrib/issue/electron-sandbox/issueActions.ts index 44a39657..79b6607d 100644 --- a/src/vs/workbench/contrib/issue/electron-sandbox/issueActions.ts +++ b/src/vs/workbench/contrib/issue/electron-sandbox/issueActions.ts @@ -20,8 +20,8 @@ export class OpenProcessExplorer extends Action { super(id, label); } - run(): Promise { - return this.issueService.openProcessExplorer().then(() => true); + override run(): Promise { + return this.issueService.openProcessExplorer(); } } @@ -37,7 +37,7 @@ export class ReportPerformanceIssueUsingReporterAction extends Action { super(id, label); } - run(): Promise { - return this.issueService.openReporter({ issueType: IssueType.PerformanceIssue }).then(() => true); + override run(): Promise { + return this.issueService.openReporter({ issueType: IssueType.PerformanceIssue }); } } diff --git a/src/vs/workbench/contrib/localizations/browser/localizationsActions.ts b/src/vs/workbench/contrib/localizations/browser/localizationsActions.ts index 1304e665..240ce105 100644 --- a/src/vs/workbench/contrib/localizations/browser/localizationsActions.ts +++ b/src/vs/workbench/contrib/localizations/browser/localizationsActions.ts @@ -44,7 +44,7 @@ export class ConfigureLocaleAction extends Action { .concat({ label: localize('installAdditionalLanguages', "Install additional languages...") }); } - public async run(): Promise { + public override async run(): Promise { const languageOptions = await this.getLanguageOptions(); const currentLanguageIndex = languageOptions.findIndex(l => l.label === language); diff --git a/src/vs/workbench/contrib/logs/common/logsActions.ts b/src/vs/workbench/contrib/logs/common/logsActions.ts index f19eb8a8..a53f539a 100644 --- a/src/vs/workbench/contrib/logs/common/logsActions.ts +++ b/src/vs/workbench/contrib/logs/common/logsActions.ts @@ -25,7 +25,7 @@ export class SetLogLevelAction extends Action { super(id, label); } - run(): Promise { + override run(): Promise { const current = this.logService.getLevel(); const entries = [ { label: nls.localize('trace', "Trace"), level: LogLevel.Trace, description: this.getDescription(LogLevel.Trace, current) }, @@ -72,7 +72,7 @@ export class OpenWindowSessionLogFileAction extends Action { super(id, label); } - async run(): Promise { + override async run(): Promise { const sessionResult = await this.quickInputService.pick( this.getSessions().then(sessions => sessions.map((s, index) => ({ id: s.toString(), diff --git a/src/vs/workbench/contrib/logs/electron-sandbox/logsActions.ts b/src/vs/workbench/contrib/logs/electron-sandbox/logsActions.ts index cd377536..1ea86c56 100644 --- a/src/vs/workbench/contrib/logs/electron-sandbox/logsActions.ts +++ b/src/vs/workbench/contrib/logs/electron-sandbox/logsActions.ts @@ -23,7 +23,7 @@ export class OpenLogsFolderAction extends Action { super(id, label); } - run(): Promise { + override run(): Promise { return this.nativeHostService.showItemInFolder(URI.file(join(this.environmentService.logsPath, 'main.log')).fsPath); } } @@ -41,7 +41,7 @@ export class OpenExtensionLogsFolderAction extends Action { super(id, label); } - async run(): Promise { + override async run(): Promise { const folderStat = await this.fileService.resolve(this.environmentSerice.extHostLogsPath); if (folderStat.children && folderStat.children[0]) { return this.nativeHostService.showItemInFolder(folderStat.children[0].resource.fsPath); diff --git a/src/vs/workbench/contrib/markdown/common/markdownDocumentRenderer.ts b/src/vs/workbench/contrib/markdown/common/markdownDocumentRenderer.ts index 1adf7d3c..41baffa9 100644 --- a/src/vs/workbench/contrib/markdown/common/markdownDocumentRenderer.ts +++ b/src/vs/workbench/contrib/markdown/common/markdownDocumentRenderer.ts @@ -8,6 +8,166 @@ import { tokenizeToString } from 'vs/editor/common/modes/textToHtmlTokenizer'; import { ITokenizationSupport, TokenizationRegistry } from 'vs/editor/common/modes'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IModeService } from 'vs/editor/common/services/modeService'; +import { insane } from 'vs/base/common/insane/insane'; + +export const DEFAULT_MARKDOWN_STYLES = ` +body { + padding: 10px 20px; + line-height: 22px; + max-width: 882px; + margin: 0 auto; +} + +img { + max-width: 100%; + max-height: 100%; +} + +a { + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + +a:focus, +input:focus, +select:focus, +textarea:focus { + outline: 1px solid -webkit-focus-ring-color; + outline-offset: -1px; +} + +hr { + border: 0; + height: 2px; + border-bottom: 2px solid; +} + +h1 { + padding-bottom: 0.3em; + line-height: 1.2; + border-bottom-width: 1px; + border-bottom-style: solid; +} + +h1, h2, h3 { + font-weight: normal; +} + +table { + border-collapse: collapse; +} + +table > thead > tr > th { + text-align: left; + border-bottom: 1px solid; +} + +table > thead > tr > th, +table > thead > tr > td, +table > tbody > tr > th, +table > tbody > tr > td { + padding: 5px 10px; +} + +table > tbody > tr + tr > td { + border-top-width: 1px; + border-top-style: solid; +} + +blockquote { + margin: 0 7px 0 5px; + padding: 0 16px 0 10px; + border-left-width: 5px; + border-left-style: solid; +} + +code { + font-family: "SF Mono", Monaco, Menlo, Consolas, "Ubuntu Mono", "Liberation Mono", "DejaVu Sans Mono", "Courier New", monospace; + font-size: 14px; + line-height: 19px; +} + +pre code { + font-family: var(--vscode-editor-font-family); + font-weight: var(--vscode-editor-font-weight); + font-size: var(--vscode-editor-font-size); + line-height: 1.5; +} + +code > div { + padding: 16px; + border-radius: 3px; + overflow: auto; +} + +.monaco-tokenized-source { + white-space: pre; +} + +/** Theming */ + +.vscode-light code > div { + background-color: rgba(220, 220, 220, 0.4); +} + +.vscode-dark code > div { + background-color: rgba(10, 10, 10, 0.4); +} + +.vscode-high-contrast code > div { + background-color: rgb(0, 0, 0); +} + +.vscode-high-contrast h1 { + border-color: rgb(0, 0, 0); +} + +.vscode-light table > thead > tr > th { + border-color: rgba(0, 0, 0, 0.69); +} + +.vscode-dark table > thead > tr > th { + border-color: rgba(255, 255, 255, 0.69); +} + +.vscode-light h1, +.vscode-light hr, +.vscode-light table > tbody > tr + tr > td { + border-color: rgba(0, 0, 0, 0.18); +} + +.vscode-dark h1, +.vscode-dark hr, +.vscode-dark table > tbody > tr + tr > td { + border-color: rgba(255, 255, 255, 0.18); +} + +`; + +function removeEmbeddedSVGs(documentContent: string): string { + return insane(documentContent, { + allowedTags: [ + 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'h7', 'h8', 'br', 'b', 'i', 'strong', 'em', 'a', 'pre', 'code', 'img', 'tt', + 'div', 'ins', 'del', 'sup', 'sub', 'p', 'ol', 'ul', 'table', 'thead', 'tbody', 'tfoot', 'blockquote', 'dl', 'dt', + 'dd', 'kbd', 'q', 'samp', 'var', 'hr', 'ruby', 'rt', 'rp', 'li', 'tr', 'td', 'th', 's', 'strike', 'summary', 'details', + 'caption', 'figure', 'figcaption', 'abbr', 'bdo', 'cite', 'dfn', 'mark', 'small', 'span', 'time', 'wbr' + ], + allowedAttributes: { + '*': [ + 'align', + ], + img: ['src', 'alt', 'title', 'aria-label', 'width', 'height'], + span: ['class'], + }, + allowedSchemes: ['http', 'https', 'command',], + filter(token: { tag: string, attrs: { readonly [key: string]: string } }): boolean { + return token.tag !== 'svg'; + } + }); +} /** * Renders a string of markdown as a document. @@ -18,6 +178,7 @@ export async function renderMarkdownDocument( text: string, extensionService: IExtensionService, modeService: IModeService, + shouldRemoveEmbeddedSVGs: boolean = true, ): Promise { const highlight = (code: string, lang: string, callback: ((error: any, code: string) => void) | undefined): any => { @@ -38,5 +199,12 @@ export async function renderMarkdownDocument( return new Promise((resolve, reject) => { marked(text, { highlight }, (err, value) => err ? reject(err) : resolve(value)); + }).then(raw => { + if (shouldRemoveEmbeddedSVGs) { + return removeEmbeddedSVGs(raw); + } + else { + return raw; + } }); } diff --git a/src/vs/workbench/contrib/markers/browser/markersFilterOptions.ts b/src/vs/workbench/contrib/markers/browser/markersFilterOptions.ts index aaee2569..55d1bb3b 100644 --- a/src/vs/workbench/contrib/markers/browser/markersFilterOptions.ts +++ b/src/vs/workbench/contrib/markers/browser/markersFilterOptions.ts @@ -70,6 +70,15 @@ export class FilterOptions { const filesExcludeByRoot = Array.isArray(filesExclude) ? filesExclude : []; const excludesExpression: IExpression = Array.isArray(filesExclude) ? getEmptyExpression() : filesExclude; + for (const { expression } of filesExcludeByRoot) { + for (const pattern of Object.keys(expression)) { + if (!pattern.endsWith('/**')) { + // Append `/**` to pattern to match a parent folder #103631 + expression[`${strings.rtrim(pattern, '/')}/**`] = expression[pattern]; + } + } + } + const negate = filter.startsWith('!'); this.textFilter = { text: (negate ? strings.ltrim(filter, '!') : filter).trim(), negate }; const includeExpression: IExpression = getEmptyExpression(); diff --git a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts index f66d8357..d8852663 100644 --- a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts +++ b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts @@ -262,12 +262,12 @@ const toggleMultilineAction = 'problems.action.toggleMultiline'; class ToggleMultilineActionViewItem extends ActionViewItem { - render(container: HTMLElement): void { + override render(container: HTMLElement): void { super.render(container); this.updateExpandedAttribute(); } - updateClass(): void { + override updateClass(): void { super.updateClass(); this.updateExpandedAttribute(); } @@ -428,7 +428,7 @@ class MarkerWidget extends Disposable { this._register(onOpen(e => { dom.EventHelper.stop(e, true); - this._openerService.open(codeUri); + this._openerService.open(codeUri, { allowCommands: true }); })); const code = new HighlightedLabel(dom.append(this._codeLink, dom.$('.marker-code')), false); @@ -857,7 +857,7 @@ export class MarkersViewModel extends Disposable { } } - dispose(): void { + override dispose(): void { this.markersViewStates.forEach(({ disposables }) => dispose(disposables)); this.markersViewStates.clear(); this.markersPerResource.clear(); diff --git a/src/vs/workbench/contrib/markers/browser/markersView.ts b/src/vs/workbench/contrib/markers/browser/markersView.ts index 5f257dc2..ddab4379 100644 --- a/src/vs/workbench/contrib/markers/browser/markersView.ts +++ b/src/vs/workbench/contrib/markers/browser/markersView.ts @@ -146,7 +146,7 @@ export class MarkersView extends ViewPane implements IMarkersView { })); } - public renderBody(parent: HTMLElement): void { + public override renderBody(parent: HTMLElement): void { super.renderBody(parent); parent.classList.add('markers-panel'); @@ -168,7 +168,7 @@ export class MarkersView extends ViewPane implements IMarkersView { return Messages.MARKERS_PANEL_TITLE_PROBLEMS; } - public layoutBody(height: number, width: number): void { + public override layoutBody(height: number, width: number): void { super.layoutBody(height, width); const wasSmallLayout = this.smallLayout; this.smallLayout = width < 600 && height > 100; @@ -187,7 +187,7 @@ export class MarkersView extends ViewPane implements IMarkersView { this.filters.layout = new dom.Dimension(this.smallLayout ? width : width - 200, height); } - public focus(): void { + public override focus(): void { if (this.tree && this.tree.getHTMLElement() === document.activeElement) { return; } @@ -808,7 +808,7 @@ export class MarkersView extends ViewPane implements IMarkersView { return this.tree?.getFocus()[0] || undefined; } - public getActionViewItem(action: IAction): IActionViewItem | undefined { + public override getActionViewItem(action: IAction): IActionViewItem | undefined { if (action.id === `workbench.actions.treeView.${this.id}.filter`) { return this.instantiationService.createInstance(MarkersFilterActionViewItem, action, this); } @@ -864,7 +864,7 @@ export class MarkersView extends ViewPane implements IMarkersView { this.telemetryService.publicLog('problems.filter', data); } - saveState(): void { + override saveState(): void { this.panelState['filter'] = this.filters.filterText; this.panelState['filterHistory'] = this.filters.filterHistory; this.panelState['showErrors'] = this.filters.showErrors; @@ -877,7 +877,7 @@ export class MarkersView extends ViewPane implements IMarkersView { super.saveState(); } - dispose() { + override dispose() { super.dispose(); } @@ -901,7 +901,7 @@ class MarkersTree extends WorkbenchObjectTree { super(user, container, delegate, renderers, options, contextKeyService, listService, themeService, configurationService, keybindingService, accessibilityService); } - layout(height: number, width: number): void { + override layout(height: number, width: number): void { this.container.style.height = `${height}px`; super.layout(height, width); } diff --git a/src/vs/workbench/contrib/markers/browser/markersViewActions.ts b/src/vs/workbench/contrib/markers/browser/markersViewActions.ts index 708f6d6e..f34cc274 100644 --- a/src/vs/workbench/contrib/markers/browser/markersViewActions.ts +++ b/src/vs/workbench/contrib/markers/browser/markersViewActions.ts @@ -166,7 +166,7 @@ class FiltersDropdownMenuActionViewItem extends DropdownMenuActionViewItem { ); } - render(container: HTMLElement): void { + override render(container: HTMLElement): void { super.render(container); this.updateChecked(); } @@ -227,7 +227,7 @@ class FiltersDropdownMenuActionViewItem extends DropdownMenuActionViewItem { ]; } - updateChecked(): void { + override updateChecked(): void { this.element!.classList.toggle('checked', this._action.checked); } @@ -265,7 +265,7 @@ export class MarkersFilterActionViewItem extends BaseActionViewItem { this._register(markersView.filters.onDidChange(e => this.onDidFiltersChange(e))); } - render(container: HTMLElement): void { + override render(container: HTMLElement): void { this.container = container; this.container.classList.add('markers-panel-action-filter-container'); @@ -278,23 +278,23 @@ export class MarkersFilterActionViewItem extends BaseActionViewItem { this.adjustInputBox(); } - focus(): void { + override focus(): void { if (this.filterInputBox) { this.filterInputBox.focus(); } } - blur(): void { + override blur(): void { if (this.filterInputBox) { this.filterInputBox.blur(); } } - setFocusable(): void { + override setFocusable(): void { // noop input elements are focusable by default } - get trapsArrowNavigation(): boolean { + override get trapsArrowNavigation(): boolean { return true; } @@ -427,7 +427,7 @@ export class MarkersFilterActionViewItem extends BaseActionViewItem { } } - protected updateClass(): void { + protected override updateClass(): void { if (this.element && this.container) { this.element.className = this.class; this.container.classList.toggle('grow', this.element.classList.contains('grow')); @@ -474,7 +474,7 @@ export class QuickFixAction extends Action { super(QuickFixAction.ID, Messages.MARKERS_PANEL_ACTION_TOOLTIP_QUICKFIX, QuickFixAction.CLASS, false); } - run(): Promise { + override run(): Promise { this._onShowQuickFixes.fire(); return Promise.resolve(); } @@ -488,7 +488,7 @@ export class QuickFixActionViewItem extends ActionViewItem { super(null, action, { icon: true, label: false }); } - public onClick(event: DOM.EventLike): void { + public override onClick(event: DOM.EventLike): void { DOM.EventHelper.stop(event, true); this.showQuickFixes(); } diff --git a/src/vs/workbench/contrib/markers/browser/media/markers.css b/src/vs/workbench/contrib/markers/browser/media/markers.css index ec967357..f11c85f7 100644 --- a/src/vs/workbench/contrib/markers/browser/media/markers.css +++ b/src/vs/workbench/contrib/markers/browser/media/markers.css @@ -50,18 +50,7 @@ } .markers-panel-action-filter > .markers-panel-filter-controls > .monaco-action-bar .action-item .action-label.codicon.markers-filters { - line-height: 20px; - height: 20px; - min-width: 22px; - margin-left: 4px; -} - -.pane-header .markers-panel-action-filter > .markers-panel-filter-controls > .monaco-action-bar .action-item .action-label.codicon.markers-filters { - line-height: 18px; - height: 18px; - width: 28px; - margin-right: 0px; - justify-content: center; + padding: 2px; } .panel > .title .monaco-action-bar .action-item.markers-panel-action-filter-container { @@ -85,6 +74,7 @@ .markers-panel-container .monaco-action-bar.markers-panel-filter-container { margin: 10px 20px; + height: initial; } .markers-panel .markers-panel-container .message-box-container { @@ -170,13 +160,6 @@ margin-left: 6px; } -.markers-panel .monaco-tl-contents .codicon { - margin-right: 6px; - display: flex; - align-items: center; - justify-content: center; -} - .markers-panel .markers-panel-container .tree-container .monaco-tl-contents .marker-source, .markers-panel .markers-panel-container .tree-container .monaco-tl-contents .related-info-resource, .markers-panel .markers-panel-container .tree-container .monaco-tl-contents .related-info-resource-separator, @@ -189,8 +172,12 @@ font-weight: bold; } -.markers-panel .monaco-tl-contents .codicon { +.markers-panel .monaco-tl-contents .marker-icon { height: 22px; + margin: 0 6px; + display: flex; + align-items: center; + justify-content: center; } .markers-panel .monaco-tl-contents .actions .monaco-action-bar { @@ -209,13 +196,18 @@ display: block; } -.markers-panel .monaco-tl-contents .multiline-actions .action-label, -.markers-panel .monaco-tl-contents .actions .action-label { - width: 16px; +.markers-panel .monaco-tl-contents .actions, +.markers-panel .monaco-tl-contents .multiline-actions .monaco-action-bar { + height: 22px; } -.markers-panel .monaco-tl-contents .multiline-actions .action-label { - line-height: 22px; +.markers-panel .monaco-tl-contents .actions .action-label, +.markers-panel .monaco-tl-contents .multiline-actions .monaco-action-bar .action-label { + padding: 2px; +} + +.markers-panel .monaco-tl-contents .actions .action-item { + margin: 0 4px; } .markers-panel .monaco-tl-contents .multiline-actions .action-item.disabled, diff --git a/src/vs/workbench/contrib/markers/test/browser/markersModel.test.ts b/src/vs/workbench/contrib/markers/test/browser/markersModel.test.ts index dbc57637..3d018851 100644 --- a/src/vs/workbench/contrib/markers/test/browser/markersModel.test.ts +++ b/src/vs/workbench/contrib/markers/test/browser/markersModel.test.ts @@ -36,12 +36,12 @@ suite('MarkersModel Test', () => { const testObject = new TestMarkersModel([marker1, marker2, marker3, marker4]); const actuals = testObject.resourceMarkers[0].markers; - assert.notEqual(actuals[0].id, actuals[1].id); - assert.notEqual(actuals[0].id, actuals[2].id); - assert.notEqual(actuals[0].id, actuals[3].id); - assert.notEqual(actuals[1].id, actuals[2].id); - assert.notEqual(actuals[1].id, actuals[3].id); - assert.notEqual(actuals[2].id, actuals[3].id); + assert.notStrictEqual(actuals[0].id, actuals[1].id); + assert.notStrictEqual(actuals[0].id, actuals[2].id); + assert.notStrictEqual(actuals[0].id, actuals[3].id); + assert.notStrictEqual(actuals[1].id, actuals[2].id); + assert.notStrictEqual(actuals[1].id, actuals[3].id); + assert.notStrictEqual(actuals[2].id, actuals[3].id); }); test('sort palces resources with no errors at the end', function () { @@ -55,7 +55,7 @@ suite('MarkersModel Test', () => { const actuals = testObject.resourceMarkers; - assert.equal(5, actuals.length); + assert.strictEqual(5, actuals.length); assert.ok(compareResource(actuals[0], 'a/res2')); assert.ok(compareResource(actuals[1], 'b/res3')); assert.ok(compareResource(actuals[2], 'res4')); @@ -74,7 +74,7 @@ suite('MarkersModel Test', () => { const actuals = testObject.resourceMarkers; - assert.equal(5, actuals.length); + assert.strictEqual(5, actuals.length); assert.ok(compareResource(actuals[0], 'a/res1')); assert.ok(compareResource(actuals[1], 'a/res2')); assert.ok(compareResource(actuals[2], 'b/res3')); @@ -102,49 +102,49 @@ suite('MarkersModel Test', () => { const actuals = testObject.resourceMarkers[0].markers; - assert.equal(actuals[0].marker, marker6); - assert.equal(actuals[1].marker, marker14); - assert.equal(actuals[2].marker, marker7); - assert.equal(actuals[3].marker, marker9); - assert.equal(actuals[4].marker, marker11); - assert.equal(actuals[5].marker, marker3); - assert.equal(actuals[6].marker, marker15); - assert.equal(actuals[7].marker, marker10); - assert.equal(actuals[8].marker, marker2); - assert.equal(actuals[9].marker, marker13); - assert.equal(actuals[10].marker, marker1); - assert.equal(actuals[11].marker, marker8); - assert.equal(actuals[12].marker, marker5); - assert.equal(actuals[13].marker, marker12); - assert.equal(actuals[14].marker, marker4); + assert.strictEqual(actuals[0].marker, marker6); + assert.strictEqual(actuals[1].marker, marker14); + assert.strictEqual(actuals[2].marker, marker7); + assert.strictEqual(actuals[3].marker, marker9); + assert.strictEqual(actuals[4].marker, marker11); + assert.strictEqual(actuals[5].marker, marker3); + assert.strictEqual(actuals[6].marker, marker15); + assert.strictEqual(actuals[7].marker, marker10); + assert.strictEqual(actuals[8].marker, marker2); + assert.strictEqual(actuals[9].marker, marker13); + assert.strictEqual(actuals[10].marker, marker1); + assert.strictEqual(actuals[11].marker, marker8); + assert.strictEqual(actuals[12].marker, marker5); + assert.strictEqual(actuals[13].marker, marker12); + assert.strictEqual(actuals[14].marker, marker4); }); test('toString()', () => { let marker = aMarker('a/res1'); marker.code = '1234'; - assert.equal(JSON.stringify({ ...marker, resource: marker.resource.path }, null, '\t'), new Marker('1', marker).toString()); + assert.strictEqual(JSON.stringify({ ...marker, resource: marker.resource.path }, null, '\t'), new Marker('1', marker).toString()); marker = aMarker('a/res2', MarkerSeverity.Warning); - assert.equal(JSON.stringify({ ...marker, resource: marker.resource.path }, null, '\t'), new Marker('2', marker).toString()); + assert.strictEqual(JSON.stringify({ ...marker, resource: marker.resource.path }, null, '\t'), new Marker('2', marker).toString()); marker = aMarker('a/res2', MarkerSeverity.Info, 1, 2, 1, 8, 'Info', ''); - assert.equal(JSON.stringify({ ...marker, resource: marker.resource.path }, null, '\t'), new Marker('3', marker).toString()); + assert.strictEqual(JSON.stringify({ ...marker, resource: marker.resource.path }, null, '\t'), new Marker('3', marker).toString()); marker = aMarker('a/res2', MarkerSeverity.Hint, 1, 2, 1, 8, 'Ignore message', 'Ignore'); - assert.equal(JSON.stringify({ ...marker, resource: marker.resource.path }, null, '\t'), new Marker('4', marker).toString()); + assert.strictEqual(JSON.stringify({ ...marker, resource: marker.resource.path }, null, '\t'), new Marker('4', marker).toString()); marker = aMarker('a/res2', MarkerSeverity.Warning, 1, 2, 1, 8, 'Warning message', '', [{ startLineNumber: 2, startColumn: 5, endLineNumber: 2, endColumn: 10, message: 'some info', resource: URI.file('a/res3') }]); const testObject = new Marker('5', marker, null!); // hack (testObject as any).relatedInformation = marker.relatedInformation!.map(r => new RelatedInformation('6', marker, r)); - assert.equal(JSON.stringify({ ...marker, resource: marker.resource.path, relatedInformation: marker.relatedInformation!.map(r => ({ ...r, resource: r.resource.path })) }, null, '\t'), testObject.toString()); + assert.strictEqual(JSON.stringify({ ...marker, resource: marker.resource.path, relatedInformation: marker.relatedInformation!.map(r => ({ ...r, resource: r.resource.path })) }, null, '\t'), testObject.toString()); }); test('Markers for same-document but different fragment', function () { const model = new TestMarkersModel([anErrorWithRange(1)]); - assert.equal(model.total, 1); + assert.strictEqual(model.total, 1); const document = URI.parse('foo://test/path/file'); const frag1 = URI.parse('foo://test/path/file#1'); @@ -152,7 +152,7 @@ suite('MarkersModel Test', () => { model.setResourceMarkers([[document, [{ ...aMarker(), resource: frag1 }, { ...aMarker(), resource: frag2 }]]]); - assert.equal(model.total, 3); + assert.strictEqual(model.total, 3); let a = model.getResourceMarkers(document); let b = model.getResourceMarkers(frag1); let c = model.getResourceMarkers(frag2); @@ -160,12 +160,12 @@ suite('MarkersModel Test', () => { assert.ok(a === c); model.setResourceMarkers([[document, [{ ...aMarker(), resource: frag2 }]]]); - assert.equal(model.total, 2); + assert.strictEqual(model.total, 2); }); test('Problems are no sorted correctly #99135', function () { const model = new TestMarkersModel([]); - assert.equal(model.total, 0); + assert.strictEqual(model.total, 0); const document = URI.parse('foo://test/path/file'); const frag1 = URI.parse('foo://test/path/file#1'); @@ -180,10 +180,10 @@ suite('MarkersModel Test', () => { { ...aMarker(), resource: frag2 } ]]]); - assert.equal(model.total, 3); + assert.strictEqual(model.total, 3); const markers = model.getResourceMarkers(document)?.markers; - assert.deepEqual(markers?.map(m => m.marker.severity), [MarkerSeverity.Error, MarkerSeverity.Error, MarkerSeverity.Warning]); - assert.deepEqual(markers?.map(m => m.marker.resource.toString()), [frag1.toString(), frag2.toString(), frag1.toString()]); + assert.deepStrictEqual(markers?.map(m => m.marker.severity), [MarkerSeverity.Error, MarkerSeverity.Error, MarkerSeverity.Warning]); + assert.deepStrictEqual(markers?.map(m => m.marker.resource.toString()), [frag1.toString(), frag2.toString(), frag1.toString()]); }); function compareResource(a: ResourceMarkers, b: string): boolean { diff --git a/src/vs/workbench/contrib/notebook/browser/constants.ts b/src/vs/workbench/contrib/notebook/browser/constants.ts index 23eec637..06627113 100644 --- a/src/vs/workbench/contrib/notebook/browser/constants.ts +++ b/src/vs/workbench/contrib/notebook/browser/constants.ts @@ -8,8 +8,17 @@ export const SCROLLABLE_ELEMENT_PADDING_TOP = 20; // export const SCROLLABLE_ELEMENT_PADDING_TOP_WITH_TOOLBAR = 8; +// Code cell layout: +// [CODE_CELL_LEFT_MARGIN][CELL_RUN_GUTTER][editorWidth][CELL_RIGHT_MARGIN] + +// Markdown cell layout: +// [CELL_MARGIN][content][CELL_RIGHT_MARGIN] + +// Markdown editor cell layout: +// [CODE_CELL_LEFT_MARGIN][content][CELL_RIGHT_MARGIN] + // Cell sizing related -export const CELL_MARGIN = 8; +export const CELL_RIGHT_MARGIN = 16; export const CELL_RUN_GUTTER = 28; export const CODE_CELL_LEFT_MARGIN = 32; diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/cellOperations/cellOperations.ts b/src/vs/workbench/contrib/notebook/browser/contrib/cellOperations/cellOperations.ts index fca087b8..d3ba97f5 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/cellOperations/cellOperations.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/cellOperations/cellOperations.ts @@ -11,10 +11,11 @@ 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 { CellEditState, expandCellRangesWithHiddenCells, ICellViewModel, NOTEBOOK_CELL_EDITABLE, 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 { CellEditType, CellKind, SelectionStateType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { cellRangeContains, cellRangesToIndexes, ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; 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'; @@ -74,7 +75,7 @@ export async function moveCellRange(context: INotebookCellActionContext, directi return; } - if (!viewModel.metadata.editable) { + if (viewModel.options.isReadOnly) { return; } @@ -194,7 +195,7 @@ export async function copyCellRange(context: INotebookCellActionContext, directi return; } - if (!viewModel.metadata.editable) { + if (viewModel.options.isReadOnly) { return; } @@ -223,7 +224,7 @@ export async function copyCellRange(context: INotebookCellActionContext, directi editType: CellEditType.Replace, index: range.end, count: 0, - cells: cellRangesToIndexes([range]).map(index => cloneNotebookCellTextModel(viewModel.viewCells[index].model)) + cells: cellRangesToIndexes([range]).map(index => cloneNotebookCellTextModel(viewModel.cellAt(index)!.model)) }], true, { @@ -238,7 +239,7 @@ export async function copyCellRange(context: INotebookCellActionContext, directi // insert down, move selections const focus = viewModel.getFocus(); const selections = viewModel.getSelections(); - const newCells = cellRangesToIndexes([range]).map(index => cloneNotebookCellTextModel(viewModel.viewCells[index].model)); + const newCells = cellRangesToIndexes([range]).map(index => cloneNotebookCellTextModel(viewModel.cellAt(index)!.model)); const countDelta = newCells.length; const newFocus = context.ui ? focus : { start: focus.start + countDelta, end: focus.end + countDelta }; const newSelections = context.ui ? selections : [{ start: range.start + countDelta, end: range.end + countDelta }]; @@ -247,7 +248,7 @@ export async function copyCellRange(context: INotebookCellActionContext, directi editType: CellEditType.Replace, index: range.end, count: 0, - cells: cellRangesToIndexes([range]).map(index => cloneNotebookCellTextModel(viewModel.viewCells[index].model)) + cells: cellRangesToIndexes([range]).map(index => cloneNotebookCellTextModel(viewModel.cellAt(index)!.model)) }], true, { @@ -279,17 +280,13 @@ registerAction2(class extends NotebookCellAction { 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()), + when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_CELL_EDITABLE), order: CellToolbarOrder.SplitCell, - group: CELL_TITLE_CELL_GROUP_ID, - // alt: { - // id: JOIN_CELL_BELOW_COMMAND_ID, - // title: localize('notebookActions.joinCellBelow', "Join with Next Cell") - // } + group: CELL_TITLE_CELL_GROUP_ID }, 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()), + when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_CELL_EDITABLE), primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_BACKSLASH), weight: KeybindingWeight.WorkbenchContrib }, @@ -302,11 +299,11 @@ registerAction2(class extends NotebookCellAction { }); 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) { + if (!viewModel || viewModel.options.isReadOnly) { return null; } - const cells = viewModel.viewCells.slice(range.start, range.end); + const cells = viewModel.getCells(range); if (!cells.length) { return null; @@ -322,9 +319,6 @@ export async function joinNotebookCells(viewModel: NotebookViewModel, range: ICe 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; @@ -332,15 +326,11 @@ export async function joinNotebookCells(viewModel: NotebookViewModel, range: ICe } if (direction === 'above') { - const above = viewModel.viewCells[range.start - 1] as CellViewModel; + const above = viewModel.cellAt(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); @@ -362,15 +352,11 @@ export async function joinNotebookCells(viewModel: NotebookViewModel, range: ICe endSelections: [{ start: range.start - 1, end: range.start }] }; } else { - const below = viewModel.viewCells[range.end] as CellViewModel; + const below = viewModel.cellAt(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(''); @@ -418,7 +404,7 @@ export async function joinCellsWithSurrounds(bulkEditService: IBulkEditService, { quotableLabel: 'Join Notebook Cells' } ); viewModel.updateSelectionsState({ kind: SelectionStateType.Index, focus: ret.endFocus, selections: ret.endSelections }); - ret.cell.editState = CellEditState.Editing; + ret.cell.updateEditState(CellEditState.Editing, 'joinCellsWithSurrounds'); context.notebookEditor.revealCellRangeInView(viewModel.getFocus()); } else { const selections = viewModel.getSelections(); @@ -440,10 +426,10 @@ export async function joinCellsWithSurrounds(bulkEditService: IBulkEditService, || selection.start === 0 && direction === 'above' ) { if (containFocus) { - cell = viewModel.getCellByIndex(focus.start); + cell = viewModel.cellAt(focus.start)!; } - cells.push(...viewModel.viewCells.slice(selection.start, selection.end)); + cells.push(...viewModel.getCells(selection)); continue; } @@ -475,7 +461,7 @@ export async function joinCellsWithSurrounds(bulkEditService: IBulkEditService, ); cells.forEach(cell => { - cell.editState = CellEditState.Editing; + cell.updateEditState(CellEditState.Editing, 'joinCellsWithSurrounds'); }); viewModel.updateSelectionsState({ kind: SelectionStateType.Handle, primary: cell.handle, selections: cells.map(cell => cell.handle) }); @@ -496,7 +482,7 @@ registerAction2(class extends NotebookCellAction { }, menu: { id: MenuId.NotebookCellTitle, - when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_CELL_EDITABLE), + when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_EDITABLE), group: CellOverflowToolbarGroups.Edit, order: 10 } @@ -522,7 +508,7 @@ registerAction2(class extends NotebookCellAction { }, menu: { id: MenuId.NotebookCellTitle, - when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_CELL_EDITABLE), + when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_EDITABLE), group: CellOverflowToolbarGroups.Edit, order: 11 } 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 index 2860405c..32671d6b 100644 --- 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 @@ -8,6 +8,7 @@ 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 { runDeleteAction } from 'vs/workbench/contrib/notebook/browser/contrib/coreActions'; 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'; @@ -25,8 +26,8 @@ suite('CellOperations', () => { 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;'); + await moveCellRange({ notebookEditor: editor, cell: viewModel.cellAt(1)! }, 'down'); + assert.strictEqual(viewModel.cellAt(2)?.getText(), 'var b = 1;'); }); }); @@ -42,10 +43,10 @@ suite('CellOperations', () => { 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;'); + await moveCellRange({ notebookEditor: editor, cell: viewModel.cellAt(1)! }, 'down'); + assert.strictEqual(viewModel.cellAt(0)?.getText(), '# header b'); + assert.strictEqual(viewModel.cellAt(1)?.getText(), '# header a'); + assert.strictEqual(viewModel.cellAt(2)?.getText(), 'var b = 1;'); }); }); @@ -69,10 +70,10 @@ suite('CellOperations', () => { 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;'); + await moveCellRange({ notebookEditor: editor, cell: viewModel.cellAt(1)! }, 'down'); + assert.strictEqual(viewModel.cellAt(0)?.getText(), '# header b'); + assert.strictEqual(viewModel.cellAt(1)?.getText(), '# header a'); + assert.strictEqual(viewModel.cellAt(2)?.getText(), 'var b = 1;'); }); }); @@ -88,10 +89,10 @@ suite('CellOperations', () => { 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;'); + await copyCellRange({ notebookEditor: editor, cell: viewModel.cellAt(1)! }, 'down'); + assert.strictEqual(viewModel.length, 6); + assert.strictEqual(viewModel.cellAt(1)?.getText(), 'var b = 1;'); + assert.strictEqual(viewModel.cellAt(2)?.getText(), 'var b = 1;'); }); }); @@ -107,10 +108,10 @@ suite('CellOperations', () => { async (editor) => { const viewModel = editor.viewModel; viewModel.updateSelectionsState({ kind: SelectionStateType.Index, focus: { start: 0, end: 1 }, selections: [{ start: 0, end: 1 }] }); - await copyCellRange({ notebookEditor: editor, cell: viewModel.viewCells[1], ui: true }, '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;'); + await copyCellRange({ notebookEditor: editor, cell: viewModel.cellAt(1)!, ui: true }, 'down'); + assert.strictEqual(viewModel.length, 6); + assert.strictEqual(viewModel.cellAt(1)?.getText(), 'var b = 1;'); + assert.strictEqual(viewModel.cellAt(2)?.getText(), 'var b = 1;'); }); }); @@ -126,12 +127,12 @@ suite('CellOperations', () => { 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;'); + await copyCellRange({ notebookEditor: editor, cell: viewModel.cellAt(1)! }, 'down'); + assert.strictEqual(viewModel.length, 7); + assert.strictEqual(viewModel.cellAt(0)?.getText(), '# header a'); + assert.strictEqual(viewModel.cellAt(1)?.getText(), 'var b = 1;'); + assert.strictEqual(viewModel.cellAt(2)?.getText(), '# header a'); + assert.strictEqual(viewModel.cellAt(3)?.getText(), 'var b = 1;'); }); }); @@ -155,12 +156,12 @@ suite('CellOperations', () => { 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;'); + await copyCellRange({ notebookEditor: editor, cell: viewModel.cellAt(1)! }, 'down'); + assert.strictEqual(viewModel.length, 7); + assert.strictEqual(viewModel.cellAt(0)?.getText(), '# header a'); + assert.strictEqual(viewModel.cellAt(1)?.getText(), 'var b = 1;'); + assert.strictEqual(viewModel.cellAt(2)?.getText(), '# header a'); + assert.strictEqual(viewModel.cellAt(3)?.getText(), 'var b = 1;'); }); }); @@ -178,8 +179,8 @@ suite('CellOperations', () => { 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[0], new ResourceTextEdit(viewModel.cellAt(3)!.uri, { + range: new Range(1, 11, 1, 11), text: viewModel.cellAt(4)!.textBuffer.getEOL() + 'var c = 3;' })); assert.deepStrictEqual(ret?.edits[1], new ResourceNotebookCellEdit(viewModel.notebookDocument.uri, { @@ -206,8 +207,8 @@ suite('CellOperations', () => { 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[0], new ResourceTextEdit(viewModel.cellAt(3)!.uri, { + range: new Range(1, 11, 1, 11), text: viewModel.cellAt(4)!.textBuffer.getEOL() + 'var c = 3;' })); assert.deepStrictEqual(ret?.edits[1], new ResourceNotebookCellEdit(viewModel.notebookDocument.uri, { @@ -232,8 +233,8 @@ suite('CellOperations', () => { 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[0], new ResourceTextEdit(viewModel.cellAt(0)!.uri, { + range: new Range(1, 11, 1, 11), text: viewModel.cellAt(1)!.textBuffer.getEOL() + 'var b = 2;' + viewModel.cellAt(2)!.textBuffer.getEOL() + 'var c = 3;' })); assert.deepStrictEqual(ret?.edits[1], new ResourceNotebookCellEdit(viewModel.notebookDocument.uri, { @@ -258,8 +259,8 @@ suite('CellOperations', () => { 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[0], new ResourceTextEdit(viewModel.cellAt(0)!.uri, { + range: new Range(1, 11, 1, 11), text: viewModel.cellAt(1)!.textBuffer.getEOL() + 'var b = 2;' + viewModel.cellAt(2)!.textBuffer.getEOL() + 'var c = 3;' })); assert.deepStrictEqual(ret?.edits[1], new ResourceNotebookCellEdit(viewModel.notebookDocument.uri, { @@ -271,5 +272,177 @@ suite('CellOperations', () => { )); }); }); + + test('Delete focus cell', async function () { + await withTestNotebook( + [ + ['var a = 1;', 'javascript', CellKind.Code, [], {}], + ['var b = 2;', 'javascript', CellKind.Code, [], {}], + ['var c = 3;', 'javascript', CellKind.Code, [], {}] + ], + async (editor) => { + const viewModel = editor.viewModel; + viewModel.setSelections({ start: 0, end: 1 }, [{ start: 0, end: 1 }]); + runDeleteAction(viewModel, viewModel.cellAt(0)!); + assert.strictEqual(viewModel.length, 2); + }); + }); + + test('Delete selected 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) => { + const viewModel = editor.viewModel; + viewModel.setSelections({ start: 0, end: 1 }, [{ start: 0, end: 2 }]); + runDeleteAction(viewModel, viewModel.cellAt(0)!); + assert.strictEqual(viewModel.length, 1); + }); + }); + + test('Delete focus cell out of a selection', async function () { + await withTestNotebook( + [ + ['var a = 1;', 'javascript', CellKind.Code, [], {}], + ['var b = 2;', 'javascript', CellKind.Code, [], {}], + ['var c = 3;', 'javascript', CellKind.Code, [], {}], + ['var d = 4;', 'javascript', CellKind.Code, [], {}], + ], + async (editor) => { + const viewModel = editor.viewModel; + viewModel.setSelections({ start: 0, end: 1 }, [{ start: 2, end: 4 }]); + runDeleteAction(viewModel, viewModel.cellAt(0)!); + assert.strictEqual(viewModel.length, 3); + }); + }); + + test('Delete UI target', async function () { + await withTestNotebook( + [ + ['var a = 1;', 'javascript', CellKind.Code, [], {}], + ['var b = 2;', 'javascript', CellKind.Code, [], {}], + ['var c = 3;', 'javascript', CellKind.Code, [], {}] + ], + async (editor) => { + const viewModel = editor.viewModel; + viewModel.setSelections({ start: 0, end: 1 }, [{ start: 0, end: 1 }]); + runDeleteAction(viewModel, viewModel.cellAt(2)!); + assert.strictEqual(viewModel.length, 2); + assert.strictEqual(viewModel.cellAt(0)?.getText(), 'var a = 1;'); + assert.strictEqual(viewModel.cellAt(1)?.getText(), 'var b = 2;'); + }); + }); + + test('Delete UI target 2', async function () { + await withTestNotebook( + [ + ['var a = 1;', 'javascript', CellKind.Code, [], {}], + ['var b = 2;', 'javascript', CellKind.Code, [], {}], + ['var c = 3;', 'javascript', CellKind.Code, [], {}], + ['var d = 4;', 'javascript', CellKind.Code, [], {}], + ['var e = 5;', 'javascript', CellKind.Code, [], {}], + ], + async (editor) => { + const viewModel = editor.viewModel; + viewModel.setSelections({ start: 0, end: 1 }, [{ start: 0, end: 1 }, { start: 3, end: 5 }]); + runDeleteAction(viewModel, viewModel.cellAt(1)!); + assert.strictEqual(viewModel.length, 4); + assert.deepStrictEqual(viewModel.getFocus(), { start: 0, end: 1 }); + assert.deepStrictEqual(viewModel.getSelections(), [{ start: 0, end: 1 }, { start: 2, end: 4 }]); + }); + }); + + test('Delete UI target 3', async function () { + await withTestNotebook( + [ + ['var a = 1;', 'javascript', CellKind.Code, [], {}], + ['var b = 2;', 'javascript', CellKind.Code, [], {}], + ['var c = 3;', 'javascript', CellKind.Code, [], {}], + ['var d = 4;', 'javascript', CellKind.Code, [], {}], + ['var e = 5;', 'javascript', CellKind.Code, [], {}], + ], + async (editor) => { + const viewModel = editor.viewModel; + viewModel.setSelections({ start: 0, end: 1 }, [{ start: 2, end: 3 }]); + runDeleteAction(viewModel, viewModel.cellAt(0)!); + assert.strictEqual(viewModel.length, 4); + assert.deepStrictEqual(viewModel.getFocus(), { start: 0, end: 1 }); + assert.deepStrictEqual(viewModel.getSelections(), [{ start: 1, end: 2 }]); + }); + }); + + test('Delete UI target 4', async function () { + await withTestNotebook( + [ + ['var a = 1;', 'javascript', CellKind.Code, [], {}], + ['var b = 2;', 'javascript', CellKind.Code, [], {}], + ['var c = 3;', 'javascript', CellKind.Code, [], {}], + ['var d = 4;', 'javascript', CellKind.Code, [], {}], + ['var e = 5;', 'javascript', CellKind.Code, [], {}], + ], + async (editor) => { + const viewModel = editor.viewModel; + viewModel.setSelections({ start: 2, end: 3 }, [{ start: 3, end: 5 }]); + runDeleteAction(viewModel, viewModel.cellAt(0)!); + assert.strictEqual(viewModel.length, 4); + assert.deepStrictEqual(viewModel.getFocus(), { start: 1, end: 2 }); + assert.deepStrictEqual(viewModel.getSelections(), [{ start: 2, end: 4 }]); + }); + }); + + + test('Delete last cell sets selection correctly', async function () { + await withTestNotebook( + [ + ['var a = 1;', 'javascript', CellKind.Code, [], {}], + ['var b = 2;', 'javascript', CellKind.Code, [], {}], + ['var c = 3;', 'javascript', CellKind.Code, [], {}] + ], + async (editor) => { + const viewModel = editor.viewModel; + viewModel.setSelections({ start: 2, end: 3 }, [{ start: 2, end: 3 }]); + runDeleteAction(viewModel, viewModel.cellAt(2)!); + assert.strictEqual(viewModel.length, 2); + assert.deepStrictEqual(viewModel.getFocus(), { start: 1, end: 2 }); + }); + }); + + test('#120187. Delete should work on multiple distinct selection', async function () { + await withTestNotebook( + [ + ['var a = 1;', 'javascript', CellKind.Code, [], {}], + ['var b = 2;', 'javascript', CellKind.Code, [], {}], + ['var c = 3;', 'javascript', CellKind.Code, [], {}], + ['var d = 4;', 'javascript', CellKind.Code, [], {}] + ], + async (editor) => { + const viewModel = editor.viewModel; + viewModel.setSelections({ start: 0, end: 1 }, [{ start: 0, end: 1 }, { start: 3, end: 4 }]); + runDeleteAction(viewModel, viewModel.cellAt(0)!); + assert.strictEqual(viewModel.length, 2); + assert.deepStrictEqual(viewModel.getFocus(), { start: 0, end: 1 }); + }); + }); + + test('#120187. Delete should work on multiple distinct selection 2', async function () { + await withTestNotebook( + [ + ['var a = 1;', 'javascript', CellKind.Code, [], {}], + ['var b = 2;', 'javascript', CellKind.Code, [], {}], + ['var c = 3;', 'javascript', CellKind.Code, [], {}], + ['var d = 4;', 'javascript', CellKind.Code, [], {}], + ['var e = 5;', 'javascript', CellKind.Code, [], {}], + ], + async (editor) => { + const viewModel = editor.viewModel; + viewModel.setSelections({ start: 1, end: 2 }, [{ start: 1, end: 2 }, { start: 3, end: 5 }]); + runDeleteAction(viewModel, viewModel.cellAt(1)!); + assert.strictEqual(viewModel.length, 2); + assert.deepStrictEqual(viewModel.getFocus(), { start: 1, end: 2 }); + }); + }); }); 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 2f774ca3..c1e818ab 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/clipboard/notebookClipboard.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/clipboard/notebookClipboard.ts @@ -9,12 +9,13 @@ 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 { expandCellRangesWithHiddenCells, getNotebookEditorFromEditorPane, ICellViewModel, NOTEBOOK_CELL_EDITABLE, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_FOCUSED } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { expandCellRangesWithHiddenCells, getNotebookEditorFromEditorPane, ICellViewModel, INotebookEditor, 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, NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; -import { cloneNotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; -import { CellEditType, ICellEditOperation, ICellRange, ISelectionState, SelectionStateType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { cloneNotebookCellTextModel, NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; +import { CellEditType, ICellEditOperation, ISelectionState, SelectionStateType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; 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'; @@ -24,191 +25,319 @@ 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'; +import { RedoCommand, UndoCommand } from 'vs/editor/browser/editorExtensions'; +import { Webview } from 'vs/workbench/contrib/webview/browser/webview'; -class NotebookClipboardContribution extends Disposable { +function getFocusedWebviewDelegate(accessor: ServicesAccessor): Webview | undefined { + const editorService = accessor.get(IEditorService); + const editor = getNotebookEditorFromEditorPane(editorService.activeEditorPane); + if (!editor?.hasFocus()) { + return; + } + + if (!editor?.hasWebviewFocus()) { + return; + } + + const webview = editor?.getInnerWebview(); + return webview; +} + +function withWebview(accessor: ServicesAccessor, f: (webviewe: Webview) => void) { + const webview = getFocusedWebviewDelegate(accessor); + if (webview) { + f(webview); + return true; + } + return false; +} + +const PRIORITY = 105; + +UndoCommand.addImplementation(PRIORITY, 'notebook-webview', accessor => { + return withWebview(accessor, webview => webview.undo()); +}); + +RedoCommand.addImplementation(PRIORITY, 'notebook-webview', accessor => { + return withWebview(accessor, webview => webview.redo()); +}); + +CopyAction?.addImplementation(PRIORITY, 'notebook-webview', accessor => { + return withWebview(accessor, webview => webview.copy()); +}); + +PasteAction?.addImplementation(PRIORITY, 'notebook-webview', accessor => { + return withWebview(accessor, webview => webview.paste()); +}); + +CutAction?.addImplementation(PRIORITY, 'notebook-webview', accessor => { + return withWebview(accessor, webview => webview.cut()); +}); + + +export function runPasteCells(editor: INotebookEditor, activeCell: ICellViewModel | undefined, pasteCells: { + items: NotebookCellTextModel[]; + isCopy: boolean; +}): boolean { + const viewModel = editor.viewModel; + + if (!viewModel || viewModel.options.isReadOnly) { + return false; + } + + const originalState: ISelectionState = { + kind: SelectionStateType.Index, + focus: viewModel.getFocus(), + selections: viewModel.getSelections() + }; + + if (activeCell) { + const currCellIndex = viewModel.getCellIndex(activeCell); + const newFocusIndex = typeof currCellIndex === 'number' ? currCellIndex + 1 : 0; + viewModel.notebookDocument.applyEdits([ + { + editType: CellEditType.Replace, + index: newFocusIndex, + count: 0, + cells: pasteCells.items.map(cell => cloneNotebookCellTextModel(cell)) + } + ], true, originalState, () => ({ + kind: SelectionStateType.Index, + focus: { start: newFocusIndex, end: newFocusIndex + 1 }, + selections: [{ start: newFocusIndex, end: newFocusIndex + pasteCells.items.length }] + }), undefined); + } else { + if (viewModel.length !== 0) { + return false; + } + + viewModel.notebookDocument.applyEdits([ + { + editType: CellEditType.Replace, + index: 0, + count: 0, + cells: pasteCells.items.map(cell => cloneNotebookCellTextModel(cell)) + } + ], true, originalState, () => ({ + kind: SelectionStateType.Index, + focus: { start: 0, end: 1 }, + selections: [{ start: 1, end: pasteCells.items.length + 1 }] + }), undefined); + } + + return true; +} + +function cellRangeToViewCells(viewModel: NotebookViewModel, ranges: ICellRange[]) { + const cells: ICellViewModel[] = []; + ranges.forEach(range => { + cells.push(...viewModel.getCells(range)); + }); + + return cells; +} +export function runCopyCells(accessor: ServicesAccessor, editor: INotebookEditor, targetCell: ICellViewModel | undefined): boolean { + if (!editor.hasModel()) { + return false; + } + + if (editor.hasOutputTextSelection()) { + document.execCommand('copy'); + return true; + } + + const clipboardService = accessor.get(IClipboardService); + const notebookService = accessor.get(INotebookService); + const viewModel = editor.viewModel; + const selections = viewModel.getSelections(); + + if (targetCell) { + const targetCellIndex = viewModel.getCellIndex(targetCell); + const containingSelection = selections.find(selection => selection.start <= targetCellIndex && targetCellIndex < selection.end); + + if (!containingSelection) { + clipboardService.writeText(targetCell.getText()); + notebookService.setToCopy([targetCell.model], true); + return true; + } + } + + const selectionRanges = expandCellRangesWithHiddenCells(editor, editor.viewModel, editor.viewModel.getSelections()); + const selectedCells = cellRangeToViewCells(editor.viewModel, selectionRanges); + + if (!selectedCells.length) { + return false; + } + + clipboardService.writeText(selectedCells.map(cell => cell.getText()).join('\n')); + notebookService.setToCopy(selectedCells.map(cell => cell.model), true); + + return true; +} +export function runCutCells(accessor: ServicesAccessor, editor: INotebookEditor, targetCell: ICellViewModel | undefined): boolean { + const viewModel = editor.viewModel; + + if (!viewModel || viewModel.options.isReadOnly) { + return false; + } + + const clipboardService = accessor.get(IClipboardService); + const notebookService = accessor.get(INotebookService); + const selections = viewModel.getSelections(); + + if (targetCell) { + // from ui + const targetCellIndex = viewModel.getCellIndex(targetCell); + const containingSelection = selections.find(selection => selection.start <= targetCellIndex && targetCellIndex < selection.end); + + if (!containingSelection) { + clipboardService.writeText(targetCell.getText()); + // delete cell + const focus = viewModel.getFocus(); + const newFocus = focus.end <= targetCellIndex ? focus : { start: focus.start - 1, end: focus.end - 1 }; + const newSelections = selections.map(selection => (selection.end <= targetCellIndex ? selection : { start: selection.start - 1, end: selection.end - 1 })); + + viewModel.notebookDocument.applyEdits([ + { editType: CellEditType.Replace, index: targetCellIndex, count: 1, cells: [] } + ], true, { kind: SelectionStateType.Index, focus: viewModel.getFocus(), selections: selections }, () => ({ kind: SelectionStateType.Index, focus: newFocus, selections: newSelections }), undefined, true); + + notebookService.setToCopy([targetCell.model], false); + return true; + } + } + + const focus = viewModel.getFocus(); + const containingSelection = selections.find(selection => selection.start <= focus.start && focus.end <= selection.end); + + if (!containingSelection) { + // focus is out of any selection, we should only cut this cell + const targetCell = viewModel.cellAt(focus.start)!; + clipboardService.writeText(targetCell.getText()); + const newFocus = focus.end === viewModel.length ? { start: focus.start - 1, end: focus.end - 1 } : focus; + const newSelections = selections.map(selection => (selection.end <= focus.start ? selection : { start: selection.start - 1, end: selection.end - 1 })); + viewModel.notebookDocument.applyEdits([ + { editType: CellEditType.Replace, index: focus.start, count: 1, cells: [] } + ], true, { kind: SelectionStateType.Index, focus: viewModel.getFocus(), selections: selections }, () => ({ kind: SelectionStateType.Index, focus: newFocus, selections: newSelections }), undefined, true); + + notebookService.setToCopy([targetCell.model], false); + return true; + } + + const selectionRanges = expandCellRangesWithHiddenCells(editor, viewModel, viewModel.getSelections()); + const selectedCells = cellRangeToViewCells(viewModel, selectionRanges); + + if (!selectedCells.length) { + return false; + } + + clipboardService.writeText(selectedCells.map(cell => cell.getText()).join('\n')); + 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 - 1 + ? firstSelectIndex + : Math.max(viewModel.notebookDocument.cells.length - 2, 0); + + 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); + notebookService.setToCopy(selectedCells.map(cell => cell.model), false); + + return true; +} + +export class NotebookClipboardContribution extends Disposable { constructor(@IEditorService private readonly _editorService: IEditorService) { super(); - const getContext = () => { - const editor = getNotebookEditorFromEditorPane(this._editorService.activeEditorPane); - const activeCell = editor?.getActiveCell(); - - return { - editor, - activeCell - }; - }; - const PRIORITY = 105; if (CopyAction) { this._register(CopyAction.addImplementation(PRIORITY, 'notebook-clipboard', accessor => { - const activeElement = document.activeElement; - if (activeElement && ['input', 'textarea'].indexOf(activeElement.tagName.toLowerCase()) >= 0) { - return false; - } - - const { editor } = getContext(); - if (!editor) { - return false; - } - - if (!editor.hasModel()) { - return false; - } - - if (editor.hasOutputTextSelection()) { - document.execCommand('copy'); - return true; - } - - const clipboardService = accessor.get(IClipboardService); - const notebookService = accessor.get(INotebookService); - const selectionRanges = expandCellRangesWithHiddenCells(editor, editor.viewModel, editor.viewModel.getSelections()); - const selectedCells = this._cellRangeToViewCells(editor.viewModel, selectionRanges); - - if (!selectedCells.length) { - return false; - } - - clipboardService.writeText(selectedCells.map(cell => cell.getText()).join('\n')); - notebookService.setToCopy(selectedCells.map(cell => cell.model), true); - - return true; + return this.runCopyAction(accessor); })); } if (PasteAction) { PasteAction.addImplementation(PRIORITY, 'notebook-clipboard', accessor => { - const activeElement = document.activeElement; - if (activeElement && ['input', 'textarea'].indexOf(activeElement.tagName.toLowerCase()) >= 0) { - return false; - } - - const notebookService = accessor.get(INotebookService); - const pasteCells = notebookService.getToCopy(); - - if (!pasteCells) { - return false; - } - - const { editor, activeCell } = getContext(); - if (!editor) { - return false; - } - - const viewModel = editor.viewModel; - - if (!viewModel || !viewModel.metadata.editable) { - return false; - } - - const originalState: ISelectionState = { - kind: SelectionStateType.Index, - focus: viewModel.getFocus(), - selections: viewModel.getSelections() - }; - - if (activeCell) { - const currCellIndex = viewModel.getCellIndex(activeCell); - const newFocusIndex = typeof currCellIndex === 'number' ? currCellIndex + 1 : 0; - viewModel.notebookDocument.applyEdits([ - { - editType: CellEditType.Replace, - index: newFocusIndex, - count: 0, - cells: pasteCells.items.map(cell => cloneNotebookCellTextModel(cell)) - } - ], true, originalState, () => ({ - kind: SelectionStateType.Index, - focus: { start: newFocusIndex, end: newFocusIndex + 1 }, - selections: [{ start: newFocusIndex, end: newFocusIndex + pasteCells.items.length }] - }), undefined); - } else { - if (viewModel.length !== 0) { - return false; - } - - viewModel.notebookDocument.applyEdits([ - { - editType: CellEditType.Replace, - index: 0, - count: 0, - cells: pasteCells.items.map(cell => cloneNotebookCellTextModel(cell)) - } - ], true, originalState, () => ({ - kind: SelectionStateType.Index, - focus: { start: 0, end: 1 }, - selections: [{ start: 1, end: pasteCells.items.length + 1 }] - }), undefined); - } - - return true; + return this.runPasteAction(accessor); }); } if (CutAction) { CutAction.addImplementation(PRIORITY, 'notebook-clipboard', accessor => { - const activeElement = document.activeElement; - if (activeElement && ['input', 'textarea'].indexOf(activeElement.tagName.toLowerCase()) >= 0) { - return false; - } - - const { editor } = getContext(); - if (!editor) { - return false; - } - - const viewModel = editor.viewModel; - - if (!viewModel || !viewModel.metadata.editable) { - return false; - } - - const clipboardService = accessor.get(IClipboardService); - const notebookService = accessor.get(INotebookService); - 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 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 - 1 - ? firstSelectIndex - : Math.max(viewModel.notebookDocument.cells.length - 2, 0); - - 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); - notebookService.setToCopy(selectedCells.map(cell => cell.model), false); - - return true; + return this.runCutAction(accessor); }); } } - private _cellRangeToViewCells(viewModel: NotebookViewModel, ranges: ICellRange[]) { - const cells: ICellViewModel[] = []; - ranges.forEach(range => { - cells.push(...viewModel.viewCells.slice(range.start, range.end)); - }); + private _getContext() { + const editor = getNotebookEditorFromEditorPane(this._editorService.activeEditorPane); + const activeCell = editor?.getActiveCell(); - return cells; + return { + editor, + activeCell + }; + } + + runCopyAction(accessor: ServicesAccessor) { + const activeElement = document.activeElement; + if (activeElement && ['input', 'textarea'].indexOf(activeElement.tagName.toLowerCase()) >= 0) { + return false; + } + + const { editor } = this._getContext(); + if (!editor) { + return false; + } + + return runCopyCells(accessor, editor, undefined); + } + + runPasteAction(accessor: ServicesAccessor) { + const activeElement = document.activeElement; + if (activeElement && ['input', 'textarea'].indexOf(activeElement.tagName.toLowerCase()) >= 0) { + return false; + } + + const notebookService = accessor.get(INotebookService); + const pasteCells = notebookService.getToCopy(); + + if (!pasteCells) { + return false; + } + + const { editor, activeCell } = this._getContext(); + if (!editor) { + return false; + } + + return runPasteCells(editor, activeCell, pasteCells); + } + + runCutAction(accessor: ServicesAccessor) { + const activeElement = document.activeElement; + if (activeElement && ['input', 'textarea'].indexOf(activeElement.tagName.toLowerCase()) >= 0) { + return false; + } + + const { editor } = this._getContext(); + if (!editor) { + return false; + } + + return runCutCells(accessor, editor, undefined); } } @@ -241,27 +370,7 @@ registerAction2(class extends NotebookCellAction { } 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); - } + runCopyCells(accessor, context.notebookEditor, context.cell); } }); @@ -286,49 +395,7 @@ registerAction2(class extends NotebookCellAction { } 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); - } + runCutCells(accessor, context.notebookEditor, context.cell); } }); @@ -359,7 +426,7 @@ registerAction2(class extends NotebookAction { const viewModel = context.notebookEditor.viewModel; - if (!viewModel || !viewModel.metadata.editable) { + if (!viewModel || viewModel.options.isReadOnly) { return; } @@ -367,32 +434,7 @@ registerAction2(class extends NotebookAction { 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'); - } + runPasteCells(context.notebookEditor, context.cell, pasteCells); } }); @@ -416,7 +458,7 @@ registerAction2(class extends NotebookCellAction { const viewModel = context.notebookEditor.viewModel; - if (!viewModel || !viewModel.metadata.editable) { + if (!viewModel || viewModel.options.isReadOnly) { return; } @@ -427,22 +469,7 @@ registerAction2(class extends NotebookCellAction { 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 => { + pasteCells.items.reverse().map(cell => cloneNotebookCellTextModel(cell)).forEach(pasteCell => { topPastedCell = viewModel.createCell(currCellIndex, pasteCell.source, pasteCell.language, pasteCell.cellKind, pasteCell.metadata, pasteCell.outputs, true); return; }); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/clipboard/test/notebookClipboard.test.ts b/src/vs/workbench/contrib/notebook/browser/contrib/clipboard/test/notebookClipboard.test.ts new file mode 100644 index 00000000..c881379f --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/contrib/clipboard/test/notebookClipboard.test.ts @@ -0,0 +1,304 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { NotebookClipboardContribution, runCopyCells, runCutCells } from 'vs/workbench/contrib/notebook/browser/contrib/clipboard/notebookClipboard'; +import { CellKind, SelectionStateType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { withTestNotebook } from 'vs/workbench/contrib/notebook/test/testNotebookEditor'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IActiveNotebookEditor, INotebookEditor, NOTEBOOK_EDITOR_ID } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { IVisibleEditorPane } from 'vs/workbench/common/editor'; +import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; +import { FoldingModel, updateFoldingStateAtIndex } from 'vs/workbench/contrib/notebook/browser/contrib/fold/foldingModel'; +import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; + +suite('Notebook Clipboard', () => { + const createEditorService = (editor: IActiveNotebookEditor) => { + const visibleEditorPane = new class extends mock() { + override getId(): string { + return NOTEBOOK_EDITOR_ID; + } + override getControl(): INotebookEditor { + return editor; + } + }; + + const editorService: IEditorService = new class extends mock() { + override get activeEditorPane(): IVisibleEditorPane | undefined { + return visibleEditorPane; + } + }; + + return editorService; + }; + + test('Cut multiple selected cells', async function () { + await withTestNotebook( + [ + ['# header 1', 'markdown', CellKind.Markdown, [], {}], + ['paragraph 1', 'markdown', CellKind.Markdown, [], {}], + ['paragraph 2', 'markdown', CellKind.Markdown, [], {}], + ], + async (editor, accessor) => { + accessor.stub(INotebookService, new class extends mock() { override setToCopy() { } }); + + const clipboardContrib = new NotebookClipboardContribution(createEditorService(editor)); + + const viewModel = editor.viewModel; + viewModel.updateSelectionsState({ kind: SelectionStateType.Index, focus: { start: 0, end: 2 }, selections: [{ start: 0, end: 2 }] }, 'model'); + assert.ok(clipboardContrib.runCutAction(accessor)); + assert.deepStrictEqual(viewModel.getFocus(), { start: 0, end: 1 }); + assert.strictEqual(viewModel.length, 1); + assert.strictEqual(viewModel.cellAt(0)?.getText(), 'paragraph 2'); + }); + }); + + test('Cut should take folding info into account', 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, [], {}], + ], + async (editor, accessor) => { + const viewModel = editor.viewModel; + const foldingModel = new FoldingModel(); + foldingModel.attachViewModel(viewModel); + + updateFoldingStateAtIndex(foldingModel, 0, true); + updateFoldingStateAtIndex(foldingModel, 2, true); + viewModel.updateFoldingRanges(foldingModel.regions); + editor.setHiddenAreas(viewModel.getHiddenRanges()); + viewModel.updateSelectionsState({ kind: SelectionStateType.Index, focus: { start: 0, end: 1 }, selections: [{ start: 0, end: 1 }] }, 'model'); + + accessor.stub(INotebookService, new class extends mock() { override setToCopy() { } }); + + const clipboardContrib = new NotebookClipboardContribution(createEditorService(editor)); + clipboardContrib.runCutAction(accessor); + assert.strictEqual(viewModel.length, 5); + await viewModel.undo(); + assert.strictEqual(viewModel.length, 7); + }); + }); + + test('Copy should take folding info into account', 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, [], {}], + ], + async (editor, accessor) => { + const viewModel = editor.viewModel; + const foldingModel = new FoldingModel(); + foldingModel.attachViewModel(viewModel); + + updateFoldingStateAtIndex(foldingModel, 0, true); + updateFoldingStateAtIndex(foldingModel, 2, true); + viewModel.updateFoldingRanges(foldingModel.regions); + editor.setHiddenAreas(viewModel.getHiddenRanges()); + viewModel.updateSelectionsState({ kind: SelectionStateType.Index, focus: { start: 0, end: 1 }, selections: [{ start: 0, end: 1 }] }, 'model'); + + let _cells: NotebookCellTextModel[] = []; + accessor.stub(INotebookService, new class extends mock() { + override setToCopy(cells: NotebookCellTextModel[]) { _cells = cells; } + override getToCopy() { return { items: _cells, isCopy: true }; } + }); + + const clipboardContrib = new NotebookClipboardContribution(createEditorService(editor)); + clipboardContrib.runCopyAction(accessor); + viewModel.updateSelectionsState({ kind: SelectionStateType.Index, focus: { start: 6, end: 7 }, selections: [{ start: 6, end: 7 }] }, 'model'); + clipboardContrib.runPasteAction(accessor); + + assert.strictEqual(viewModel.length, 9); + assert.strictEqual(viewModel.cellAt(8)?.getText(), 'var b = 1;'); + }); + }); + + test('#119773, cut last item should not focus on the top first cell', async function () { + await withTestNotebook( + [ + ['# header 1', 'markdown', CellKind.Markdown, [], {}], + ['paragraph 1', 'markdown', CellKind.Markdown, [], {}], + ['paragraph 2', 'markdown', CellKind.Markdown, [], {}], + ], + async (editor, accessor) => { + accessor.stub(INotebookService, new class extends mock() { override setToCopy() { } }); + const clipboardContrib = new NotebookClipboardContribution(createEditorService(editor)); + + const viewModel = editor.viewModel; + viewModel.updateSelectionsState({ kind: SelectionStateType.Index, focus: { start: 2, end: 3 }, selections: [{ start: 2, end: 3 }] }, 'model'); + assert.ok(clipboardContrib.runCutAction(accessor)); + // it should be the last cell, other than the first one. + assert.deepStrictEqual(viewModel.getFocus(), { start: 1, end: 2 }); + }); + }); + + test('#119771, undo paste should restore selections', async function () { + await withTestNotebook( + [ + ['# header 1', 'markdown', CellKind.Markdown, [], {}], + ['paragraph 1', 'markdown', CellKind.Markdown, [], {}], + ['paragraph 2', 'markdown', CellKind.Markdown, [], {}], + ], + async (editor, accessor) => { + accessor.stub(INotebookService, new class extends mock() { + override setToCopy() { } + override getToCopy() { + return { + items: [ + editor.viewModel.cellAt(0)!.model + ], + isCopy: true + }; + } + }); + + const clipboardContrib = new NotebookClipboardContribution(createEditorService(editor)); + + const viewModel = editor.viewModel; + viewModel.updateSelectionsState({ kind: SelectionStateType.Index, focus: { start: 2, end: 3 }, selections: [{ start: 2, end: 3 }] }, 'model'); + assert.ok(clipboardContrib.runPasteAction(accessor)); + + assert.strictEqual(viewModel.length, 4); + assert.deepStrictEqual(viewModel.getFocus(), { start: 3, end: 4 }); + assert.strictEqual(viewModel.cellAt(3)?.getText(), '# header 1'); + await viewModel.undo(); + assert.strictEqual(viewModel.length, 3); + assert.deepStrictEqual(viewModel.getFocus(), { start: 2, end: 3 }); + }); + }); + + test('copy cell from ui still works if the target cell is not part of a selection', async () => { + await withTestNotebook( + [ + ['# header 1', 'markdown', CellKind.Markdown, [], {}], + ['paragraph 1', 'markdown', CellKind.Markdown, [], {}], + ['paragraph 2', 'markdown', CellKind.Markdown, [], {}], + ], + async (editor, accessor) => { + let _toCopy: NotebookCellTextModel[] = []; + accessor.stub(INotebookService, new class extends mock() { + override setToCopy(toCopy: NotebookCellTextModel[]) { _toCopy = toCopy; } + override getToCopy() { + return { + items: _toCopy, + isCopy: true + }; + } + }); + + const viewModel = editor.viewModel; + viewModel.updateSelectionsState({ kind: SelectionStateType.Index, focus: { start: 0, end: 1 }, selections: [{ start: 0, end: 2 }] }, 'model'); + assert.ok(runCopyCells(accessor, editor, viewModel.cellAt(0))); + assert.deepStrictEqual(_toCopy, [editor.viewModel.cellAt(0)!.model, editor.viewModel.cellAt(1)!.model]); + + assert.ok(runCopyCells(accessor, editor, viewModel.cellAt(2))); + assert.deepStrictEqual(_toCopy.length, 1); + assert.deepStrictEqual(_toCopy, [editor.viewModel.cellAt(2)!.model]); + }); + }); + + test('cut cell from ui still works if the target cell is not part of a selection', async () => { + await withTestNotebook( + [ + ['# header 1', 'markdown', CellKind.Markdown, [], {}], + ['paragraph 1', 'markdown', CellKind.Markdown, [], {}], + ['paragraph 2', 'markdown', CellKind.Markdown, [], {}], + ['paragraph 3', 'markdown', CellKind.Markdown, [], {}], + ], + async (editor, accessor) => { + accessor.stub(INotebookService, new class extends mock() { + override setToCopy() { } + override getToCopy() { + return { items: [], isCopy: true }; + } + }); + + const viewModel = editor.viewModel; + viewModel.updateSelectionsState({ kind: SelectionStateType.Index, focus: { start: 0, end: 1 }, selections: [{ start: 0, end: 2 }] }, 'model'); + assert.ok(runCutCells(accessor, editor, viewModel.cellAt(0))); + assert.strictEqual(viewModel.length, 2); + await viewModel.undo(); + assert.strictEqual(viewModel.length, 4); + + assert.deepStrictEqual(viewModel.getFocus(), { start: 0, end: 1 }); + assert.deepStrictEqual(viewModel.getSelections(), [{ start: 0, end: 2 }]); + assert.ok(runCutCells(accessor, editor, viewModel.cellAt(2))); + assert.strictEqual(viewModel.length, 3); + assert.deepStrictEqual(viewModel.getFocus(), { start: 0, end: 1 }); + assert.strictEqual(viewModel.cellAt(0)?.getText(), '# header 1'); + assert.strictEqual(viewModel.cellAt(1)?.getText(), 'paragraph 1'); + assert.strictEqual(viewModel.cellAt(2)?.getText(), 'paragraph 3'); + + await viewModel.undo(); + assert.strictEqual(viewModel.length, 4); + viewModel.updateSelectionsState({ kind: SelectionStateType.Index, focus: { start: 2, end: 3 }, selections: [{ start: 2, end: 4 }] }, 'model'); + assert.deepStrictEqual(viewModel.getFocus(), { start: 2, end: 3 }); + assert.ok(runCutCells(accessor, editor, viewModel.cellAt(0))); + assert.deepStrictEqual(viewModel.getFocus(), { start: 1, end: 2 }); + assert.deepStrictEqual(viewModel.getSelections(), [{ start: 1, end: 3 }]); + }); + }); + + test('cut focus cell still works if the focus is not part of any selection', async () => { + await withTestNotebook( + [ + ['# header 1', 'markdown', CellKind.Markdown, [], {}], + ['paragraph 1', 'markdown', CellKind.Markdown, [], {}], + ['paragraph 2', 'markdown', CellKind.Markdown, [], {}], + ['paragraph 3', 'markdown', CellKind.Markdown, [], {}], + ], + async (editor, accessor) => { + accessor.stub(INotebookService, new class extends mock() { + override setToCopy() { } + override getToCopy() { + return { items: [], isCopy: true }; + } + }); + + const viewModel = editor.viewModel; + viewModel.updateSelectionsState({ kind: SelectionStateType.Index, focus: { start: 0, end: 1 }, selections: [{ start: 2, end: 4 }] }, 'model'); + assert.ok(runCutCells(accessor, editor, undefined)); + assert.strictEqual(viewModel.length, 3); + assert.deepStrictEqual(viewModel.getFocus(), { start: 0, end: 1 }); + assert.deepStrictEqual(viewModel.getSelections(), [{ start: 1, end: 3 }]); + }); + }); + + test('cut focus cell still works if the focus is not part of any selection 2', async () => { + await withTestNotebook( + [ + ['# header 1', 'markdown', CellKind.Markdown, [], {}], + ['paragraph 1', 'markdown', CellKind.Markdown, [], {}], + ['paragraph 2', 'markdown', CellKind.Markdown, [], {}], + ['paragraph 3', 'markdown', CellKind.Markdown, [], {}], + ], + async (editor, accessor) => { + accessor.stub(INotebookService, new class extends mock() { + override setToCopy() { } + override getToCopy() { + return { items: [], isCopy: true }; + } + }); + + const viewModel = editor.viewModel; + viewModel.updateSelectionsState({ kind: SelectionStateType.Index, focus: { start: 3, end: 4 }, selections: [{ start: 0, end: 2 }] }, 'model'); + assert.ok(runCutCells(accessor, editor, undefined)); + assert.strictEqual(viewModel.length, 3); + assert.deepStrictEqual(viewModel.getFocus(), { start: 2, end: 3 }); + assert.deepStrictEqual(viewModel.getSelections(), [{ start: 0, end: 2 }]); + }); + }); +}); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts b/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts index 9b16fd34..2d9af978 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts @@ -18,28 +18,25 @@ import { InputFocusedContext, InputFocusedContextKey } from 'vs/platform/context 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_INPUT_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_EXECUTION_STATE, NOTEBOOK_CELL_TYPE, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_KERNEL_COUNT, NOTEBOOK_OUTPUT_FOCUSED, NOTEBOOK_INTERRUPTIBLE_KERNEL, NOTEBOOK_HAS_RUNNING_CELL } 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, NotebookCellExecutionState, NOTEBOOK_EDITOR_CURSOR_BOUNDARY, TransientMetadata, SelectionStateType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { BaseCellRenderTemplate, CellEditState, CellFocusMode, EXECUTE_CELL_COMMAND_ID, EXPAND_CELL_INPUT_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_EXECUTION_STATE, NOTEBOOK_CELL_TYPE, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_KERNEL_COUNT, NOTEBOOK_INTERRUPTIBLE_KERNEL, NOTEBOOK_HAS_RUNNING_CELL, CHANGE_CELL_LANGUAGE, QUIT_EDIT_CELL_COMMAND_ID } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellEditType, CellKind, ICellEditOperation, isDocumentExcludePattern, NotebookCellMetadata, NotebookCellExecutionState, TransientCellMetadata, TransientDocumentMetadata, SelectionStateType, ICellReplaceEdit } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; 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'; import * as icons from 'vs/workbench/contrib/notebook/browser/notebookIcons'; -import { CancellationTokenSource } from 'vs/base/common/cancellation'; -import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/browser/notebookEditorInput'; +import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput'; import { EditorsOrder } from 'vs/workbench/common/editor'; import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/notebookEditorService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { WorkbenchActionExecutedClassification, WorkbenchActionExecutedEvent } from 'vs/base/common/actions'; +import { NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; +import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { Iterable } from 'vs/base/common/iterator'; // Notebook Commands const EXECUTE_NOTEBOOK_COMMAND_ID = 'notebook.execute'; const CANCEL_NOTEBOOK_COMMAND_ID = 'notebook.cancelExecution'; -const NOTEBOOK_FOCUS_TOP = 'notebook.focusTop'; -const NOTEBOOK_FOCUS_BOTTOM = 'notebook.focusBottom'; -const NOTEBOOK_FOCUS_PREVIOUS_EDITOR = 'notebook.focusPreviousEditor'; -const NOTEBOOK_FOCUS_NEXT_EDITOR = 'notebook.focusNextEditor'; const CLEAR_ALL_CELLS_OUTPUTS_COMMAND_ID = 'notebook.clearAllCellsOutputs'; const RENDER_ALL_MARKDOWN_CELLS = 'notebook.renderAllMarkdownCells'; @@ -54,19 +51,14 @@ const CHANGE_CELL_TO_CODE_COMMAND_ID = 'notebook.cell.changeToCode'; const CHANGE_CELL_TO_MARKDOWN_COMMAND_ID = 'notebook.cell.changeToMarkdown'; 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 CANCEL_CELL_COMMAND_ID = 'notebook.cell.cancelExecution'; const EXECUTE_CELL_SELECT_BELOW = 'notebook.cell.executeAndSelectBelow'; const EXECUTE_CELL_INSERT_BELOW = 'notebook.cell.executeAndInsertBelow'; const CLEAR_CELL_OUTPUTS_COMMAND_ID = 'notebook.cell.clearOutputs'; -const CHANGE_CELL_LANGUAGE = 'notebook.cell.changeLanguage'; const CENTER_ACTIVE_CELL = 'notebook.centerActiveCell'; -const FOCUS_IN_OUTPUT_COMMAND_ID = 'notebook.cell.focusInOutput'; -const FOCUS_OUT_OUTPUT_COMMAND_ID = 'notebook.cell.focusOutOutput'; - const COLLAPSE_CELL_INPUT_COMMAND_ID = 'notebook.cell.collapseCellInput'; const COLLAPSE_CELL_OUTPUT_COMMAND_ID = 'notebook.cell.collapseCellOutput'; const EXPAND_CELL_OUTPUT_COMMAND_ID = 'notebook.cell.expandCellOutput'; @@ -97,25 +89,24 @@ export interface INotebookActionContext { readonly cell?: ICellViewModel; readonly notebookEditor: IActiveNotebookEditor; readonly ui?: boolean; + readonly selectedCells?: ICellViewModel[]; } export interface INotebookCellActionContext extends INotebookActionContext { cell: ICellViewModel; } -function getContextFromActiveEditor(editorService: IEditorService) { +function getContextFromActiveEditor(editorService: IEditorService): INotebookActionContext | undefined { const editor = getNotebookEditorFromEditorPane(editorService.activeEditorPane); - if (!editor) { - return; - } - - if (!editor.hasModel()) { + if (!editor || !editor.hasModel()) { return; } const activeCell = editor.getActiveCell(); + const selectedCells = editor.getSelectionViewModels(); return { cell: activeCell, + selectedCells, notebookEditor: editor }; } @@ -222,7 +213,7 @@ export abstract class NotebookCellAction extends return undefined; } - async run(accessor: ServicesAccessor, context?: INotebookCellActionContext, ...additionalArgs: any[]): Promise { + override async run(accessor: ServicesAccessor, context?: INotebookCellActionContext, ...additionalArgs: any[]): Promise { if (this.isCellActionContext(context)) { const telemetryService = accessor.get(ITelemetryService); telemetryService.publicLog2('workbenchActionExecuted', { id: this.desc.id, from: 'cellToolbar' }); @@ -242,7 +233,7 @@ export abstract class NotebookCellAction extends } } - abstract runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise; + abstract override runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise; } const executeCellCondition = ContextKeyExpr.or( @@ -306,7 +297,7 @@ registerAction2(class ExecuteCell extends NotebookCellAction { }); } - getCellContextFromArgs(accessor: ServicesAccessor, context?: ICellRange, ...additionalArgs: any[]): INotebookCellActionContext | undefined { + override getCellContextFromArgs(accessor: ServicesAccessor, context?: ICellRange, ...additionalArgs: any[]): INotebookCellActionContext | undefined { if (!context) { return; } @@ -324,11 +315,9 @@ registerAction2(class ExecuteCell extends NotebookCellAction { const widget = getWidgetFromUri(accessor, uri); if (widget) { - const cells = widget.viewModel.viewCells; - return { notebookEditor: widget, - cell: cells[context.start] + cell: widget.viewModel.cellAt(context.start)! }; } else { throw new Error(`There is no editor opened for resource ${uri}`); @@ -337,16 +326,14 @@ registerAction2(class ExecuteCell extends NotebookCellAction { const activeEditorContext = this.getEditorContextFromArgsOrActive(accessor); - if (!activeEditorContext || !activeEditorContext.notebookEditor.viewModel || context.start >= activeEditorContext.notebookEditor.viewModel.viewCells.length) { + if (!activeEditorContext || !activeEditorContext.notebookEditor.viewModel || context.start >= activeEditorContext.notebookEditor.viewModel.length) { return; } - const cells = activeEditorContext.notebookEditor.viewModel.viewCells; - // TODO@rebornix, support multiple cells return { notebookEditor: activeEditorContext.notebookEditor, - cell: cells[context.start] + cell: activeEditorContext.notebookEditor.viewModel.cellAt(context.start)! }; } @@ -401,7 +388,7 @@ registerAction2(class CancelExecuteCell extends NotebookCellAction { }); } - getCellContextFromArgs(accessor: ServicesAccessor, context?: ICellRange, ...additionalArgs: any[]): INotebookCellActionContext | undefined { + override getCellContextFromArgs(accessor: ServicesAccessor, context?: ICellRange, ...additionalArgs: any[]): INotebookCellActionContext | undefined { if (!context || typeof context.start !== 'number' || typeof context.end !== 'number' || context.start >= context.end) { return; } @@ -412,11 +399,9 @@ registerAction2(class CancelExecuteCell extends NotebookCellAction { if (uri) { const widget = getWidgetFromUri(accessor, uri); if (widget) { - const cells = widget.viewModel.viewCells; - return { notebookEditor: widget, - cell: cells[context.start] + cell: widget.viewModel.cellAt(context.start)! }; } } @@ -424,21 +409,19 @@ registerAction2(class CancelExecuteCell extends NotebookCellAction { const activeEditorContext = this.getEditorContextFromArgsOrActive(accessor); - if (!activeEditorContext || !activeEditorContext.notebookEditor.viewModel || context.start >= activeEditorContext.notebookEditor.viewModel.viewCells.length) { + if (!activeEditorContext || !activeEditorContext.notebookEditor.viewModel || context.start >= activeEditorContext.notebookEditor.viewModel.length) { return; } - const cells = activeEditorContext.notebookEditor.viewModel.viewCells; - // TODO@rebornix, support multiple cells return { notebookEditor: activeEditorContext.notebookEditor, - cell: cells[context.start] + cell: activeEditorContext.notebookEditor.viewModel.cellAt(context.start)! }; } async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise { - return context.notebookEditor.cancelNotebookCellExecution(context.cell); + return context.notebookEditor.cancelNotebookCells(Iterable.single(context.cell)); } }); @@ -484,7 +467,7 @@ registerAction2(class ExecuteCellSelectBelow extends NotebookCellAction { const executionP = runCell(accessor, context); // Try to select below, fall back on inserting - const nextCell = context.notebookEditor.viewModel.viewCells[idx + 1]; + const nextCell = context.notebookEditor.viewModel.cellAt(idx + 1); if (nextCell) { context.notebookEditor.focusNotebookCell(nextCell, 'container'); } else { @@ -542,9 +525,10 @@ registerAction2(class extends NotebookAction { constructor() { super({ id: EXECUTE_NOTEBOOK_COMMAND_ID, - title: localize('notebookActions.executeNotebook', "Execute Notebook"), + title: localize('notebookActions.executeNotebook', "Execute Notebook (Run all cells)"), + icon: icons.executeAllIcon, description: { - description: localize('notebookActions.executeNotebook', "Execute Notebook"), + description: localize('notebookActions.executeNotebook', "Execute Notebook (Run all cells)"), args: [ { name: 'uri', @@ -553,10 +537,16 @@ registerAction2(class extends NotebookAction { } ] }, + menu: { + id: MenuId.EditorTitle, + order: -1, + group: 'navigation', + when: ContextKeyExpr.and(NOTEBOOK_IS_ACTIVE_EDITOR, executeNotebookCondition, ContextKeyExpr.or(NOTEBOOK_INTERRUPTIBLE_KERNEL.toNegated(), NOTEBOOK_HAS_RUNNING_CELL.toNegated())), + } }); } - getEditorContextFromArgsOrActive(accessor: ServicesAccessor, context?: UriComponents): INotebookActionContext | undefined { + override getEditorContextFromArgsOrActive(accessor: ServicesAccessor, context?: UriComponents): INotebookActionContext | undefined { return getContextFromUri(accessor, context) ?? getContextFromActiveEditor(accessor.get(IEditorService)); } @@ -573,14 +563,14 @@ registerAction2(class extends NotebookAction { group?.pinEditor(editor.editor); } - return context.notebookEditor.executeNotebook(); + return context.notebookEditor.executeNotebookCells(); } }); function renderAllMarkdownCells(context: INotebookActionContext): void { context.notebookEditor.viewModel.viewCells.forEach(cell => { if (cell.cellKind === CellKind.Markdown) { - cell.editState = CellEditState.Preview; + cell.updateEditState(CellEditState.Preview, 'renderAllMarkdownCells'); } }); } @@ -589,9 +579,10 @@ registerAction2(class CancelNotebook extends NotebookAction { constructor() { super({ id: CANCEL_NOTEBOOK_COMMAND_ID, - title: localize('notebookActions.cancelNotebook', "Cancel Notebook Execution"), + title: localize('notebookActions.cancelNotebook', "Stop Notebook Execution"), + icon: icons.stopIcon, description: { - description: localize('notebookActions.cancelNotebook', "Cancel Notebook Execution"), + description: localize('notebookActions.cancelNotebook', "Stop Notebook Execution"), args: [ { name: 'uri', @@ -600,15 +591,21 @@ registerAction2(class CancelNotebook extends NotebookAction { } ] }, + menu: { + id: MenuId.EditorTitle, + order: -1, + group: 'navigation', + when: ContextKeyExpr.and(NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_HAS_RUNNING_CELL, NOTEBOOK_INTERRUPTIBLE_KERNEL) + } }); } - getEditorContextFromArgsOrActive(accessor: ServicesAccessor, context?: UriComponents): INotebookActionContext | undefined { + override getEditorContextFromArgsOrActive(accessor: ServicesAccessor, context?: UriComponents): INotebookActionContext | undefined { return getContextFromUri(accessor, context) ?? getContextFromActiveEditor(accessor.get(IEditorService)); } async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext): Promise { - return context.notebookEditor.cancelNotebookExecution(); + return context.notebookEditor.cancelNotebookCells(); } }); @@ -626,28 +623,6 @@ MenuRegistry.appendMenuItem(MenuId.EditorContext, { when: NOTEBOOK_EDITOR_FOCUSED }); -MenuRegistry.appendMenuItem(MenuId.EditorTitle, { - command: { - id: EXECUTE_NOTEBOOK_COMMAND_ID, - title: localize('notebookActions.menu.executeNotebook', "Execute Notebook (Run all cells)"), - icon: icons.executeAllIcon, - }, - order: -1, - group: 'navigation', - when: ContextKeyExpr.and(NOTEBOOK_IS_ACTIVE_EDITOR, executeNotebookCondition, ContextKeyExpr.or(NOTEBOOK_INTERRUPTIBLE_KERNEL.toNegated(), NOTEBOOK_HAS_RUNNING_CELL.toNegated())) -}); - -MenuRegistry.appendMenuItem(MenuId.EditorTitle, { - command: { - id: CANCEL_NOTEBOOK_COMMAND_ID, - title: localize('notebookActions.menu.cancelNotebook', "Stop Notebook Execution"), - icon: icons.stopIcon, - }, - order: -1, - group: 'navigation', - when: ContextKeyExpr.and(NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_HAS_RUNNING_CELL, NOTEBOOK_INTERRUPTIBLE_KERNEL) -}); - registerAction2(class extends NotebookCellAction { constructor() { super({ @@ -710,7 +685,12 @@ async function runCell(accessor: ServicesAccessor, context: INotebookCellActionC } } - return context.notebookEditor.executeNotebookCell(context.cell); + if (context.cell.cellKind === CellKind.Markdown) { + context.notebookEditor.focusNotebookCell(context.cell, 'container'); + return; + } else { + return context.notebookEditor.executeNotebookCells(Iterable.single(context.cell)); + } } export async function changeCellToKind(kind: CellKind, context: INotebookCellActionContext, language?: string): Promise { @@ -724,7 +704,7 @@ export async function changeCellToKind(kind: CellKind, context: INotebookCellAct return null; } - if (!notebookEditor.viewModel.metadata.editable) { + if (notebookEditor.viewModel.options.isReadOnly) { return null; } @@ -750,13 +730,13 @@ export async function changeCellToKind(kind: CellKind, context: INotebookCellAct }] } ], true, undefined, () => undefined, undefined, true); - const newCell = notebookEditor.viewModel.viewCells[idx]; + const newCell = notebookEditor.viewModel.cellAt(idx); if (!newCell) { return null; } - notebookEditor.focusNotebookCell(newCell, cell.editState === CellEditState.Editing ? 'editor' : 'container'); + notebookEditor.focusNotebookCell(newCell, cell.getEditState() === CellEditState.Editing ? 'editor' : 'container'); return newCell; } @@ -771,10 +751,7 @@ abstract class InsertCellCommand extends NotebookAction { } async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext): Promise { - const newCell = context.notebookEditor.insertNotebookCell(context.cell, this.kind, this.direction, undefined, true); - if (newCell) { - context.notebookEditor.focusNotebookCell(newCell, 'editor'); - } + context.notebookEditor.insertNotebookCell(context.cell, this.kind, this.direction, undefined, true); } } @@ -830,8 +807,8 @@ registerAction2(class extends NotebookAction { }); } - async run(accessor: ServicesAccessor): Promise { - const context = this.getEditorContextFromArgsOrActive(accessor); + override async run(accessor: ServicesAccessor, context?: INotebookActionContext): Promise { + context = context ?? this.getEditorContextFromArgsOrActive(accessor); if (context) { this.runWithContext(accessor, context); } @@ -855,8 +832,8 @@ registerAction2(class extends NotebookAction { }); } - async run(accessor: ServicesAccessor): Promise { - const context = this.getEditorContextFromArgsOrActive(accessor); + override async run(accessor: ServicesAccessor, context?: INotebookActionContext): Promise { + context = context ?? this.getEditorContextFromArgsOrActive(accessor); if (context) { this.runWithContext(accessor, context); } @@ -1007,13 +984,76 @@ registerAction2(class extends NotebookCellAction { async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { if (context.cell.cellKind === CellKind.Markdown) { - context.cell.editState = CellEditState.Preview; + context.cell.updateEditState(CellEditState.Preview, QUIT_EDIT_CELL_COMMAND_ID); } return context.notebookEditor.focusNotebookCell(context.cell, 'container'); } }); +export function runDeleteAction(viewModel: NotebookViewModel, cell: ICellViewModel) { + const selections = viewModel.getSelections(); + const targetCellIndex = viewModel.getCellIndex(cell); + const containingSelection = selections.find(selection => selection.start <= targetCellIndex && targetCellIndex < selection.end); + + if (containingSelection) { + const edits: ICellReplaceEdit[] = selections.reverse().map(selection => ({ + editType: CellEditType.Replace, index: selection.start, count: selection.end - selection.start, cells: [] + })); + + const nextCellAfterContainingSelection = viewModel.cellAt(containingSelection.end); + + viewModel.notebookDocument.applyEdits(edits, true, { kind: SelectionStateType.Index, focus: viewModel.getFocus(), selections: viewModel.getSelections() }, () => { + if (nextCellAfterContainingSelection) { + const cellIndex = viewModel.notebookDocument.cells.findIndex(cell => cell.handle === nextCellAfterContainingSelection.handle); + return { kind: SelectionStateType.Index, focus: { start: cellIndex, end: cellIndex + 1 }, selections: [{ start: cellIndex, end: cellIndex + 1 }] }; + } else { + if (viewModel.notebookDocument.length) { + const lastCellIndex = viewModel.notebookDocument.length - 1; + return { kind: SelectionStateType.Index, focus: { start: lastCellIndex, end: lastCellIndex + 1 }, selections: [{ start: lastCellIndex, end: lastCellIndex + 1 }] }; + + } else { + return { kind: SelectionStateType.Index, focus: { start: 0, end: 0 }, selections: [{ start: 0, end: 0 }] }; + } + } + }, undefined); + } else { + const focus = viewModel.getFocus(); + const edits: ICellReplaceEdit[] = [{ + editType: CellEditType.Replace, index: targetCellIndex, count: 1, cells: [] + }]; + + let finalSelections: ICellRange[] = []; + 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 - 1, end: selection.end - 1 }); + } else { + finalSelections.push({ start: targetCellIndex, end: targetCellIndex + 1 }); + } + } + + if (viewModel.cellAt(focus.start) === cell) { + // focus is the target, focus is also not part of any selection + const newFocus = focus.end === viewModel.length ? { start: focus.start - 1, end: focus.end - 1 } : focus; + + viewModel.notebookDocument.applyEdits(edits, true, { kind: SelectionStateType.Index, focus: viewModel.getFocus(), selections: viewModel.getSelections() }, () => ({ + kind: SelectionStateType.Index, focus: newFocus, selections: finalSelections + }), undefined); + } else { + // users decide to delete a cell out of current focus/selection + const newFocus = focus.start > targetCellIndex ? { start: focus.start - 1, end: focus.end - 1 } : focus; + + viewModel.notebookDocument.applyEdits(edits, true, { kind: SelectionStateType.Index, focus: viewModel.getFocus(), selections: viewModel.getSelections() }, () => ({ + kind: SelectionStateType.Index, focus: newFocus, selections: finalSelections + }), undefined); + } + } +} + registerAction2(class extends NotebookCellAction { constructor() { super( @@ -1029,7 +1069,7 @@ registerAction2(class extends NotebookCellAction { mac: { primary: KeyMod.CtrlCmd | KeyCode.Backspace }, - when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey)), + when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_EDITABLE, ContextKeyExpr.not(InputFocusedContextKey)), weight: KeybindingWeight.WorkbenchContrib }, icon: icons.deleteCellIcon @@ -1038,256 +1078,11 @@ registerAction2(class extends NotebookCellAction { async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { const viewModel = context.notebookEditor.viewModel; - if (!viewModel || !viewModel.metadata.editable) { + if (!viewModel || viewModel.options.isReadOnly) { return; } - 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 }); - } - } - - 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); - } else { - let finalSelections: ICellRange[] = []; - 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 - 1, end: selection.end - 1 }); - } else { - finalSelections.push({ start: targetCellIndex, end: targetCellIndex + 1 }); - } - } - - 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); - - // const result = await context.notebookEditor.deleteNotebookCell(context.cell); - - - // 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'); - // } - // } - } - } -}); - -registerAction2(class extends NotebookCellAction { - constructor() { - super({ - id: NOTEBOOK_FOCUS_NEXT_EDITOR, - title: localize('cursorMoveDown', 'Focus Next Cell Editor'), - keybinding: [ - { - when: ContextKeyExpr.and( - NOTEBOOK_EDITOR_FOCUSED, - ContextKeyExpr.has(InputFocusedContextKey), - EditorContextKeys.editorTextFocus, - NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('top'), - NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('none')), - primary: KeyCode.DownArrow, - weight: NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT - }, - { - when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_OUTPUT_FOCUSED), - primary: KeyMod.CtrlCmd | KeyCode.DownArrow, - mac: { primary: KeyMod.WinCtrl | KeyMod.CtrlCmd | KeyCode.DownArrow, }, - weight: KeybindingWeight.WorkbenchContrib - } - ] - }); - } - - async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise { - const editor = context.notebookEditor; - const activeCell = context.cell; - - const idx = editor.viewModel.getCellIndex(activeCell); - if (typeof idx !== 'number') { - return; - } - - const newCell = editor.viewModel.viewCells[idx + 1]; - - if (!newCell) { - return; - } - - const newFocusMode = newCell.cellKind === CellKind.Markdown && newCell.editState === CellEditState.Preview ? 'container' : 'editor'; - editor.focusNotebookCell(newCell, newFocusMode); - editor.cursorNavigationMode = true; - } -}); - -registerAction2(class extends NotebookCellAction { - constructor() { - super({ - id: NOTEBOOK_FOCUS_PREVIOUS_EDITOR, - title: localize('cursorMoveUp', 'Focus Previous Cell Editor'), - keybinding: { - when: ContextKeyExpr.and( - NOTEBOOK_EDITOR_FOCUSED, - ContextKeyExpr.has(InputFocusedContextKey), - EditorContextKeys.editorTextFocus, - NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('bottom'), - NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('none')), - primary: KeyCode.UpArrow, - weight: NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT - }, - }); - } - - async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise { - const editor = context.notebookEditor; - const activeCell = context.cell; - - const idx = editor.viewModel.getCellIndex(activeCell); - if (typeof idx !== 'number') { - return; - } - - if (idx < 1) { - // we don't do loop - return; - } - - const newCell = editor.viewModel.viewCells[idx - 1]; - - if (!newCell) { - return; - } - - const newFocusMode = newCell.cellKind === CellKind.Markdown && newCell.editState === CellEditState.Preview ? 'container' : 'editor'; - editor.focusNotebookCell(newCell, newFocusMode); - editor.cursorNavigationMode = true; - } -}); - -registerAction2(class extends NotebookCellAction { - constructor() { - super({ - id: FOCUS_IN_OUTPUT_COMMAND_ID, - title: localize('focusOutput', 'Focus In Active Cell Output'), - keybinding: { - when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_CELL_HAS_OUTPUTS), - primary: KeyMod.CtrlCmd | KeyCode.DownArrow, - mac: { primary: KeyMod.WinCtrl | KeyMod.CtrlCmd | KeyCode.DownArrow, }, - weight: KeybindingWeight.WorkbenchContrib - }, - }); - } - - async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise { - const editor = context.notebookEditor; - const activeCell = context.cell; - editor.focusNotebookCell(activeCell, 'output'); - } -}); - -registerAction2(class extends NotebookCellAction { - constructor() { - super({ - id: FOCUS_OUT_OUTPUT_COMMAND_ID, - title: localize('focusOutputOut', 'Focus Out Active Cell Output'), - keybinding: { - when: NOTEBOOK_EDITOR_FOCUSED, - primary: KeyMod.CtrlCmd | KeyCode.UpArrow, - mac: { primary: KeyMod.WinCtrl | KeyMod.CtrlCmd | KeyCode.UpArrow, }, - weight: KeybindingWeight.WorkbenchContrib - }, - }); - } - - async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise { - const editor = context.notebookEditor; - const activeCell = context.cell; - editor.focusNotebookCell(activeCell, 'editor'); - } -}); - - -registerAction2(class extends NotebookAction { - constructor() { - super({ - id: NOTEBOOK_FOCUS_TOP, - title: localize('focusFirstCell', 'Focus First Cell'), - keybinding: { - when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey)), - primary: KeyMod.CtrlCmd | KeyCode.Home, - mac: { primary: KeyMod.CtrlCmd | KeyCode.UpArrow }, - weight: KeybindingWeight.WorkbenchContrib - }, - }); - } - - async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext): Promise { - const editor = context.notebookEditor; - if (!editor.viewModel || !editor.viewModel.length) { - return; - } - - const firstCell = editor.viewModel.viewCells[0]; - editor.focusNotebookCell(firstCell, 'container'); - } -}); - -registerAction2(class extends NotebookAction { - constructor() { - super({ - id: NOTEBOOK_FOCUS_BOTTOM, - title: localize('focusLastCell', 'Focus Last Cell'), - keybinding: { - when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey)), - primary: KeyMod.CtrlCmd | KeyCode.End, - mac: { primary: KeyMod.CtrlCmd | KeyCode.DownArrow }, - weight: KeybindingWeight.WorkbenchContrib - }, - }); - } - - async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext): Promise { - const editor = context.notebookEditor; - if (!editor.viewModel || !editor.viewModel.length) { - return; - } - - const firstCell = editor.viewModel.viewCells[editor.viewModel.length - 1]; - editor.focusNotebookCell(firstCell, 'container'); + runDeleteAction(viewModel, context.cell); } }); @@ -1332,9 +1127,10 @@ registerAction2(class extends NotebookCellAction { ...context.cell.metadata, runState: NotebookCellExecutionState.Idle, runStartTime: undefined, - lastRunDuration: undefined, - statusMessage: undefined, - executionOrder: undefined + runStartTimeAdjustment: undefined, + runEndTime: undefined, + executionOrder: undefined, + lastRunSuccess: undefined } }], true, undefined, () => undefined, undefined); } @@ -1353,7 +1149,7 @@ interface IChangeCellContext extends INotebookCellActionContext { language?: string; } -export class ChangeCellLanguageAction extends NotebookCellAction { +registerAction2(class ChangeCellLanguageAction extends NotebookCellAction { constructor() { super({ id: CHANGE_CELL_LANGUAGE, @@ -1389,7 +1185,7 @@ export class ChangeCellLanguageAction extends NotebookCellAction { }); } - protected getCellContextFromArgs(accessor: ServicesAccessor, context?: ICellRange, ...additionalArgs: any[]): IChangeCellContext | undefined { + protected override getCellContextFromArgs(accessor: ServicesAccessor, context?: ICellRange, ...additionalArgs: any[]): IChangeCellContext | undefined { if (!context || typeof context.start !== 'number' || typeof context.end !== 'number' || context.start >= context.end) { return; } @@ -1397,16 +1193,14 @@ export class ChangeCellLanguageAction extends NotebookCellAction { const language = additionalArgs.length && typeof additionalArgs[0] === 'string' ? additionalArgs[0] : undefined; const activeEditorContext = this.getEditorContextFromArgsOrActive(accessor); - if (!activeEditorContext || !activeEditorContext.notebookEditor.viewModel || context.start >= activeEditorContext.notebookEditor.viewModel.viewCells.length) { + if (!activeEditorContext || !activeEditorContext.notebookEditor.viewModel || context.start >= activeEditorContext.notebookEditor.viewModel.length) { return; } - const cells = activeEditorContext.notebookEditor.viewModel.viewCells; - // TODO@rebornix, support multiple cells return { notebookEditor: activeEditorContext.notebookEditor, - cell: cells[context.start], + cell: activeEditorContext.notebookEditor.viewModel.cellAt(context.start)!, language }; } @@ -1428,10 +1222,11 @@ export class ChangeCellLanguageAction extends NotebookCellAction { const modelService = accessor.get(IModelService); const quickInputService = accessor.get(IQuickInputService); - const providerLanguages = [ + const providerLanguages = new Set([ ...(context.notebookEditor.activeKernel?.supportedLanguages ?? modeService.getRegisteredModes()), 'markdown' - ]; + ]); + providerLanguages.forEach(languageId => { let description: string; if (context.cell.cellKind === CellKind.Markdown ? (languageId === 'markdown') : (languageId === context.cell.language)) { @@ -1511,8 +1306,7 @@ export class ChangeCellLanguageAction extends NotebookCellAction { return fakeResource; } -} -registerAction2(ChangeCellLanguageAction); +}); registerAction2(class extends NotebookAction { constructor() { @@ -1547,8 +1341,8 @@ registerAction2(class extends NotebookAction { ...cell.metadata, runState: NotebookCellExecutionState.Idle, runStartTime: undefined, - lastRunDuration: undefined, - statusMessage: undefined, + runStartTimeAdjustment: undefined, + runEndTime: undefined, executionOrder: undefined } }; @@ -1585,19 +1379,22 @@ registerAction2(class extends NotebookCellAction { abstract class ChangeNotebookCellMetadataAction extends NotebookCellAction { async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise { - const cell = context.cell; const textModel = context.notebookEditor.viewModel.notebookDocument; if (!textModel) { return; } - const index = textModel.cells.indexOf(cell.model); - - if (index < 0) { - return; + const metadataDelta = this.getMetadataDelta(); + const edits: ICellEditOperation[] = []; + const targetCells = (context.cell ? [context.cell] : context.selectedCells) ?? []; + for (const cell of targetCells) { + const index = textModel.cells.indexOf(cell.model); + if (index >= 0) { + edits.push({ editType: CellEditType.Metadata, index, metadata: { ...context.cell.metadata, ...metadataDelta } }); + } } - textModel.applyEdits([{ editType: CellEditType.Metadata, index, metadata: { ...context.cell.metadata, ...this.getMetadataDelta() } }], true, undefined, () => undefined, undefined); + textModel.applyEdits(edits, true, undefined, () => undefined, undefined); } abstract getMetadataDelta(): NotebookCellMetadata; @@ -1695,53 +1492,6 @@ registerAction2(class extends ChangeNotebookCellMetadataAction { } }); -registerAction2(class extends Action2 { - constructor() { - super({ - id: 'notebook.inspectLayout', - title: localize('notebookActions.inspectLayout', "Inspect Notebook Layout"), - category: CATEGORIES.Developer, - f1: true - }); - } - - protected getActiveEditorContext(accessor: ServicesAccessor): INotebookActionContext | undefined { - const editorService = accessor.get(IEditorService); - - const editor = getNotebookEditorFromEditorPane(editorService.activeEditorPane); - if (!editor) { - return; - } - - if (!editor.hasModel()) { - return; - } - - const activeCell = editor.getActiveCell(); - return { - cell: activeCell, - notebookEditor: editor - }; - } - - run(accessor: ServicesAccessor) { - const activeEditorContext = this.getActiveEditorContext(accessor); - - if (activeEditorContext) { - const viewModel = activeEditorContext.notebookEditor.viewModel; - console.log('--- notebook ---'); - console.log(viewModel.layoutInfo); - console.log('--- cells ---'); - for (let i = 0; i < viewModel.length; i++) { - const cell = viewModel.viewCells[i] as CellViewModel; - console.log(`--- cell: ${cell.handle} ---`); - console.log(cell.layoutInfo); - } - - } - } -}); - // Revisit once we have a story for trusted workspace CommandsRegistry.registerCommand('notebook.trust', (accessor, args) => { const uri = URI.revive(args as UriComponents); @@ -1758,7 +1508,7 @@ CommandsRegistry.registerCommand('notebook.trust', (accessor, args) => { CommandsRegistry.registerCommand('_resolveNotebookContentProvider', (accessor, args): { viewType: string; displayName: string; - options: { transientOutputs: boolean; transientMetadata: TransientMetadata; }; + options: { transientOutputs: boolean; transientCellMetadata: TransientCellMetadata; transientDocumentMetadata: TransientDocumentMetadata; }; filenamePattern: (string | glob.IRelativePattern | { include: string | glob.IRelativePattern, exclude: string | glob.IRelativePattern; })[]; }[] => { const notebookService = accessor.get(INotebookService); @@ -1787,25 +1537,15 @@ CommandsRegistry.registerCommand('_resolveNotebookContentProvider', (accessor, a viewType: provider.id, displayName: provider.displayName, filenamePattern: filenamePatterns, - options: { transientMetadata: provider.options.transientMetadata, transientOutputs: provider.options.transientOutputs } + options: { + transientCellMetadata: provider.options.transientCellMetadata, + transientDocumentMetadata: provider.options.transientDocumentMetadata, + transientOutputs: provider.options.transientOutputs + } }; }); }); -CommandsRegistry.registerCommand('_resolveNotebookKernelProviders', async (accessor, args): Promise<{ - extensionId: string; - description?: string; - selector: INotebookDocumentFilter; -}[]> => { - const notebookService = accessor.get(INotebookService); - const providers = await notebookService.getContributedNotebookKernelProviders(); - return providers.map(provider => ({ - extensionId: provider.providerExtensionId, - description: provider.providerDescription, - selector: provider.selector - })); -}); - CommandsRegistry.registerCommand('_resolveNotebookKernels', async (accessor, args: { viewType: string; uri: UriComponents; @@ -1817,18 +1557,16 @@ CommandsRegistry.registerCommand('_resolveNotebookKernels', async (accessor, arg isPreferred?: boolean; preloads?: URI[]; }[]> => { - const notebookService = accessor.get(INotebookService); + const notebookKernelService = accessor.get(INotebookKernelService); const uri = URI.revive(args.uri as UriComponents); - const source = new CancellationTokenSource(); - const kernels = await notebookService.getNotebookKernels(args.viewType, uri, source.token); - source.dispose(); + const kernels = notebookKernelService.getMatchingKernel({ uri, viewType: args.viewType }); - return kernels.map(provider => ({ - id: provider.friendlyId, + return kernels.all.map(provider => ({ + id: provider.id, label: provider.label, description: provider.description, detail: provider.detail, - isPreferred: provider.isPreferred, - preloads: provider.preloads?.map(preload => URI.revive(preload)) || [] + isPreferred: false, // todo@jrieken,@rebornix + preloads: provider.preloadUris, })); }); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/find/findController.ts b/src/vs/workbench/contrib/notebook/browser/contrib/find/findController.ts index 1fcab222..bf46cd65 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/find/findController.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/findController.ts @@ -4,9 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/notebookFind'; +import { alert as alertFn } from 'vs/base/browser/ui/aria/aria'; +import * as strings from 'vs/base/common/strings'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IContextKeyService, IContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { KEYBINDING_CONTEXT_NOTEBOOK_FIND_WIDGET_FOCUSED, INotebookEditor, CellFindMatch, CellEditState, INotebookEditorContribution, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_OPEN, getNotebookEditorFromEditorPane } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { KEYBINDING_CONTEXT_NOTEBOOK_FIND_WIDGET_FOCUSED, INotebookEditor, CellFindMatch, CellEditState, INotebookEditorContribution, NOTEBOOK_EDITOR_FOCUSED, getNotebookEditorFromEditorPane, NOTEBOOK_IS_ACTIVE_EDITOR } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { Range } from 'vs/editor/common/core/range'; +import { MATCHES_LIMIT } from 'vs/editor/contrib/find/findModel'; import { FindDecorations } from 'vs/editor/contrib/find/findDecorations'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import { IModelDeltaDecoration } from 'vs/editor/common/model'; @@ -27,9 +31,12 @@ import { FindReplaceState } from 'vs/editor/contrib/find/findState'; import { INotebookSearchOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { StartFindAction, StartFindReplaceAction } from 'vs/editor/contrib/find/findController'; +import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { NLS_MATCHES_LOCATION, NLS_NO_RESULTS } from 'vs/editor/contrib/find/findWidget'; const FIND_HIDE_TRANSITION = 'find-hide-transition'; const FIND_SHOW_TRANSITION = 'find-show-transition'; +let MAX_MATCHES_COUNT_WIDTH = 69; export class NotebookFindWidget extends SimpleFindReplaceWidget implements INotebookEditorContribution { @@ -115,18 +122,33 @@ export class NotebookFindWidget extends SimpleFindReplaceWidget implements INote return; } + // let currCell; if (!this._findMatchesStarts) { this.set(this._findMatches, true); } else { + // const currIndex = this._findMatchesStarts!.getIndexOf(this._currentMatch); + // currCell = this._findMatches[currIndex.index].cell; + const totalVal = this._findMatchesStarts.getTotalValue(); const nextVal = (this._currentMatch + (previous ? -1 : 1) + totalVal) % totalVal; this._currentMatch = nextVal; } - const nextIndex = this._findMatchesStarts!.getIndexOf(this._currentMatch); + // const newFocusedCell = this._findMatches[nextIndex.index].cell; this.setCurrentFindMatchDecoration(nextIndex.index, nextIndex.remainder); this.revealCellRange(nextIndex.index, nextIndex.remainder); + + this._state.changeMatchInfo( + this._currentMatch, + this._findMatches.reduce((p, c) => p + c.matches.length, 0), + undefined + ); + + // if (currCell && currCell !== newFocusedCell && currCell.getEditState() === CellEditState.Editing && currCell.editStateSource === 'find') { + // currCell.updateEditState(CellEditState.Preview, 'find'); + // } + // this._updateMatchesCount(); } protected replaceOne() { @@ -158,7 +180,7 @@ export class NotebookFindWidget extends SimpleFindReplaceWidget implements INote } private revealCellRange(cellIndex: number, matchIndex: number) { - this._findMatches[cellIndex].cell.editState = CellEditState.Editing; + this._findMatches[cellIndex].cell.updateEditState(CellEditState.Editing, 'find'); this._notebookEditor.focusElement(this._findMatches[cellIndex].cell); this._notebookEditor.setCellEditorSelection(this._findMatches[cellIndex].cell, this._findMatches[cellIndex].matches[matchIndex].range); this._notebookEditor.revealRangeInCenterIfOutsideViewportAsync(this._findMatches[cellIndex].cell, this._findMatches[cellIndex].matches[matchIndex].range); @@ -220,6 +242,12 @@ export class NotebookFindWidget extends SimpleFindReplaceWidget implements INote this._currentMatch = 0; this.setCurrentFindMatchDecoration(0, 0); } + + this._state.changeMatchInfo( + this._currentMatch, + this._findMatches.reduce((p, c) => p + c.matches.length, 0), + undefined + ); } private setCurrentFindMatchDecoration(cellIndex: number, matchIndex: number) { @@ -270,7 +298,7 @@ export class NotebookFindWidget extends SimpleFindReplaceWidget implements INote }); } - show(initialInput?: string): void { + override show(initialInput?: string): void { super.show(initialInput); this._findInput.select(); @@ -312,7 +340,7 @@ export class NotebookFindWidget extends SimpleFindReplaceWidget implements INote } } - hide() { + override hide() { super.hide(); this.set([], false); @@ -334,6 +362,55 @@ export class NotebookFindWidget extends SimpleFindReplaceWidget implements INote this._previousFocusElement.focus(); this._previousFocusElement = undefined; } + + this._notebookEditor.viewModel?.viewCells.forEach(cell => { + if (cell.getEditState() === CellEditState.Editing && cell.editStateSource === 'find') { + cell.updateEditState(CellEditState.Preview, 'find'); + } + }); + } + + override _updateMatchesCount(): void { + if (!this._findMatches) { + return; + } + + this._matchesCount.style.minWidth = MAX_MATCHES_COUNT_WIDTH + 'px'; + this._matchesCount.title = ''; + + // remove previous content + if (this._matchesCount.firstChild) { + this._matchesCount.removeChild(this._matchesCount.firstChild); + } + + let label: string; + + if (this._state.matchesCount > 0) { + let matchesCount: string = String(this._state.matchesCount); + if (this._state.matchesCount >= MATCHES_LIMIT) { + matchesCount += '+'; + } + let matchesPosition: string = this._currentMatch < 0 ? '?' : String((this._currentMatch + 1)); + label = strings.format(NLS_MATCHES_LOCATION, matchesPosition, matchesCount); + } else { + label = NLS_NO_RESULTS; + } + + this._matchesCount.appendChild(document.createTextNode(label)); + + alertFn(this._getAriaLabel(label, this._state.currentMatch, this._state.searchString)); + MAX_MATCHES_COUNT_WIDTH = Math.max(MAX_MATCHES_COUNT_WIDTH, this._matchesCount.clientWidth); + } + + private _getAriaLabel(label: string, currentMatch: Range | null, searchString: string): string { + if (label === NLS_NO_RESULTS) { + return searchString === '' + ? localize('ariaSearchNoResultEmpty', "{0} found", label) + : localize('ariaSearchNoResult', "{0} found for '{1}'", label, searchString); + } + + // TODO@rebornix, aria for `cell ${index}, line {line}` + return localize('ariaSearchNoResultWithLineNumNoCurrentMatch', "{0} found for '{1}'", label, searchString); } clear() { @@ -341,7 +418,7 @@ export class NotebookFindWidget extends SimpleFindReplaceWidget implements INote this._findMatches = []; } - dispose() { + override dispose() { this._notebookEditor?.removeClassName(FIND_SHOW_TRANSITION); this._notebookEditor?.removeClassName(FIND_HIDE_TRANSITION); super.dispose(); @@ -384,7 +461,7 @@ registerAction2(class extends Action2 { id: 'notebook.find', title: { value: localize('notebookActions.findInNotebook', "Find in Notebook"), original: 'Find in Notebook' }, keybinding: { - when: ContextKeyExpr.or(NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_OPEN), + when: ContextKeyExpr.or(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.and(NOTEBOOK_IS_ACTIVE_EDITOR, EditorContextKeys.focus.toNegated())), primary: KeyCode.KEY_F | KeyMod.CtrlCmd, weight: KeybindingWeight.WorkbenchContrib } diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/find/media/notebookFind.css b/src/vs/workbench/contrib/notebook/browser/contrib/find/media/notebookFind.css index 98fa75e3..2ef879b4 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/find/media/notebookFind.css +++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/media/notebookFind.css @@ -10,3 +10,7 @@ .monaco-workbench .notebookOverlay.notebook-editor.find-show-transition { overflow-y: hidden; } + +.monaco-workbench .notebookOverlay.notebook-editor .simple-fr-find-part-wrapper .matchesCount { + text-align: center; +} diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/fold/folding.ts b/src/vs/workbench/contrib/notebook/browser/contrib/fold/folding.ts index 7d2bf8a2..04f01b05 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/fold/folding.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/fold/folding.ts @@ -6,7 +6,8 @@ import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { INotebookEditor, INotebookEditorMouseEvent, INotebookEditorContribution, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_IS_ACTIVE_EDITOR, getNotebookEditorFromEditorPane } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellFoldingState, FoldingModel } from 'vs/workbench/contrib/notebook/browser/contrib/fold/foldingModel'; -import { CellKind, ICellRange } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; import { registerNotebookContribution } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions'; import { registerAction2, Action2 } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/fold/foldingModel.ts b/src/vs/workbench/contrib/notebook/browser/contrib/fold/foldingModel.ts index 766b6907..b409e7eb 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/fold/foldingModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/fold/foldingModel.ts @@ -9,7 +9,8 @@ import { TrackedRangeStickiness } from 'vs/editor/common/model'; import { FoldingRegion, FoldingRegions } from 'vs/editor/contrib/folding/foldingRanges'; import { IFoldingRangeData, sanitizeRanges } from 'vs/editor/contrib/folding/syntaxRangeProvider'; import { CellViewModel, NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; -import { CellKind, cellRangesToIndexes, ICellRange } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { cellRangesToIndexes, ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; type RegionFilter = (r: FoldingRegion) => boolean; type RegionFilterWithLevel = (r: FoldingRegion, level: number) => boolean; 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 73eeb0fa..7457d5f0 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 @@ -29,13 +29,13 @@ suite('Notebook Folding', () => { const foldingController = new FoldingModel(); foldingController.attachViewModel(viewModel); - assert.equal(foldingController.regions.findRange(1), 0); - assert.equal(foldingController.regions.findRange(2), 0); - assert.equal(foldingController.regions.findRange(3), 1); - assert.equal(foldingController.regions.findRange(4), 1); - assert.equal(foldingController.regions.findRange(5), 1); - assert.equal(foldingController.regions.findRange(6), 2); - assert.equal(foldingController.regions.findRange(7), 2); + assert.strictEqual(foldingController.regions.findRange(1), 0); + assert.strictEqual(foldingController.regions.findRange(2), 0); + assert.strictEqual(foldingController.regions.findRange(3), 1); + assert.strictEqual(foldingController.regions.findRange(4), 1); + assert.strictEqual(foldingController.regions.findRange(5), 1); + assert.strictEqual(foldingController.regions.findRange(6), 2); + assert.strictEqual(foldingController.regions.findRange(7), 2); } ); }); @@ -56,18 +56,18 @@ suite('Notebook Folding', () => { const foldingController = new FoldingModel(); foldingController.attachViewModel(viewModel); - assert.equal(foldingController.regions.findRange(1), 0); - assert.equal(foldingController.regions.findRange(2), 0); - assert.equal(foldingController.regions.getEndLineNumber(0), 2); + assert.strictEqual(foldingController.regions.findRange(1), 0); + assert.strictEqual(foldingController.regions.findRange(2), 0); + assert.strictEqual(foldingController.regions.getEndLineNumber(0), 2); - assert.equal(foldingController.regions.findRange(3), 1); - assert.equal(foldingController.regions.findRange(4), 1); - assert.equal(foldingController.regions.findRange(5), 1); - assert.equal(foldingController.regions.getEndLineNumber(1), 7); + assert.strictEqual(foldingController.regions.findRange(3), 1); + assert.strictEqual(foldingController.regions.findRange(4), 1); + assert.strictEqual(foldingController.regions.findRange(5), 1); + assert.strictEqual(foldingController.regions.getEndLineNumber(1), 7); - assert.equal(foldingController.regions.findRange(6), 2); - assert.equal(foldingController.regions.findRange(7), 2); - assert.equal(foldingController.regions.getEndLineNumber(2), 7); + assert.strictEqual(foldingController.regions.findRange(6), 2); + assert.strictEqual(foldingController.regions.findRange(7), 2); + assert.strictEqual(foldingController.regions.getEndLineNumber(2), 7); } ); }); @@ -89,7 +89,7 @@ suite('Notebook Folding', () => { foldingModel.attachViewModel(viewModel); updateFoldingStateAtIndex(foldingModel, 0, true); viewModel.updateFoldingRanges(foldingModel.regions); - assert.deepEqual(viewModel.getHiddenRanges(), [ + assert.deepStrictEqual(viewModel.getHiddenRanges(), [ { start: 1, end: 6 } ]); } @@ -112,7 +112,7 @@ suite('Notebook Folding', () => { updateFoldingStateAtIndex(foldingModel, 2, true); viewModel.updateFoldingRanges(foldingModel.regions); - assert.deepEqual(viewModel.getHiddenRanges(), [ + assert.deepStrictEqual(viewModel.getHiddenRanges(), [ { start: 3, end: 4 } ]); } @@ -135,7 +135,7 @@ suite('Notebook Folding', () => { updateFoldingStateAtIndex(foldingModel, 2, true); viewModel.updateFoldingRanges(foldingModel.regions); - assert.deepEqual(viewModel.getHiddenRanges(), [ + assert.deepStrictEqual(viewModel.getHiddenRanges(), [ { start: 3, end: 6 } ]); } @@ -160,7 +160,7 @@ suite('Notebook Folding', () => { updateFoldingStateAtIndex(foldingModel, 0, true); viewModel.updateFoldingRanges(foldingModel.regions); - assert.deepEqual(viewModel.getHiddenRanges(), [ + assert.deepStrictEqual(viewModel.getHiddenRanges(), [ { start: 1, end: 1 } ]); @@ -168,27 +168,27 @@ suite('Notebook Folding', () => { updateFoldingStateAtIndex(foldingModel, 2, true); viewModel.updateFoldingRanges(foldingModel.regions); - assert.deepEqual(viewModel.getHiddenRanges(), [ + assert.deepStrictEqual(viewModel.getHiddenRanges(), [ { start: 1, end: 1 }, { start: 3, end: 6 } ]); updateFoldingStateAtIndex(foldingModel, 2, false); viewModel.updateFoldingRanges(foldingModel.regions); - assert.deepEqual(viewModel.getHiddenRanges(), [ + assert.deepStrictEqual(viewModel.getHiddenRanges(), [ { start: 1, end: 1 }, { start: 6, end: 6 } ]); // viewModel.insertCell(7, new TestCell(viewModel.viewType, 7, ['var c = 8;'], 'markdown', CellKind.Code, []), true); - // assert.deepEqual(viewModel.getHiddenRanges(), [ + // assert.deepStrictEqual(viewModel.getHiddenRanges(), [ // { start: 1, end: 1 }, // { start: 6, end: 7 } // ]); // viewModel.insertCell(1, new TestCell(viewModel.viewType, 8, ['var c = 9;'], 'markdown', CellKind.Code, []), true); - // assert.deepEqual(viewModel.getHiddenRanges(), [ + // assert.deepStrictEqual(viewModel.getHiddenRanges(), [ // // the first collapsed range is now expanded as we insert content into it. // // { start: 1,}, // { start: 7, end: 8 } @@ -221,7 +221,7 @@ suite('Notebook Folding', () => { viewModel.updateFoldingRanges(foldingModel.regions); // Note that hidden ranges !== folding ranges - assert.deepEqual(viewModel.getHiddenRanges(), [ + assert.deepStrictEqual(viewModel.getHiddenRanges(), [ { start: 3, end: 6 } ]); } @@ -253,7 +253,7 @@ suite('Notebook Folding', () => { viewModel.updateFoldingRanges(foldingModel.regions); // Note that hidden ranges !== folding ranges - assert.deepEqual(viewModel.getHiddenRanges(), [ + assert.deepStrictEqual(viewModel.getHiddenRanges(), [ { start: 6, end: 6 }, { start: 11, end: 11 } ]); @@ -286,7 +286,7 @@ suite('Notebook Folding', () => { viewModel.updateFoldingRanges(foldingModel.regions); // Note that hidden ranges !== folding ranges - assert.deepEqual(viewModel.getHiddenRanges(), [ + assert.deepStrictEqual(viewModel.getHiddenRanges(), [ { start: 6, end: 6 }, { start: 8, end: 11 } ]); @@ -318,17 +318,17 @@ suite('Notebook Folding', () => { viewModel.updateFoldingRanges(foldingModel.regions); // Note that hidden ranges !== folding ranges - assert.deepEqual(viewModel.getHiddenRanges(), [ + assert.deepStrictEqual(viewModel.getHiddenRanges(), [ { start: 3, end: 6 } ]); - assert.equal(viewModel.getNextVisibleCellIndex(1), 2); - assert.equal(viewModel.getNextVisibleCellIndex(2), 7); - assert.equal(viewModel.getNextVisibleCellIndex(3), 7); - assert.equal(viewModel.getNextVisibleCellIndex(4), 7); - assert.equal(viewModel.getNextVisibleCellIndex(5), 7); - assert.equal(viewModel.getNextVisibleCellIndex(6), 7); - assert.equal(viewModel.getNextVisibleCellIndex(7), 8); + assert.strictEqual(viewModel.getNextVisibleCellIndex(1), 2); + assert.strictEqual(viewModel.getNextVisibleCellIndex(2), 7); + assert.strictEqual(viewModel.getNextVisibleCellIndex(3), 7); + assert.strictEqual(viewModel.getNextVisibleCellIndex(4), 7); + assert.strictEqual(viewModel.getNextVisibleCellIndex(5), 7); + assert.strictEqual(viewModel.getNextVisibleCellIndex(6), 7); + assert.strictEqual(viewModel.getNextVisibleCellIndex(7), 8); } ); @@ -359,7 +359,7 @@ suite('Notebook Folding', () => { viewModel.updateFoldingRanges(foldingModel.regions); // Note that hidden ranges !== folding ranges - assert.deepEqual(viewModel.getHiddenRanges(), [ + assert.deepStrictEqual(viewModel.getHiddenRanges(), [ { start: 6, end: 6 }, { start: 11, end: 11 } ]); @@ -367,13 +367,13 @@ suite('Notebook Folding', () => { // folding ranges // [5, 6] // [10, 11] - assert.equal(viewModel.getNextVisibleCellIndex(4), 5); - assert.equal(viewModel.getNextVisibleCellIndex(5), 7); - assert.equal(viewModel.getNextVisibleCellIndex(6), 7); + assert.strictEqual(viewModel.getNextVisibleCellIndex(4), 5); + assert.strictEqual(viewModel.getNextVisibleCellIndex(5), 7); + assert.strictEqual(viewModel.getNextVisibleCellIndex(6), 7); - assert.equal(viewModel.getNextVisibleCellIndex(9), 10); - assert.equal(viewModel.getNextVisibleCellIndex(10), 12); - assert.equal(viewModel.getNextVisibleCellIndex(11), 12); + assert.strictEqual(viewModel.getNextVisibleCellIndex(9), 10); + assert.strictEqual(viewModel.getNextVisibleCellIndex(10), 12); + assert.strictEqual(viewModel.getNextVisibleCellIndex(11), 12); } ); }); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/layout/layoutActions.ts b/src/vs/workbench/contrib/notebook/browser/contrib/layout/layoutActions.ts new file mode 100644 index 00000000..b9cadaff --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/contrib/layout/layoutActions.ts @@ -0,0 +1,74 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { localize } from 'vs/nls'; +import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { INotebookActionContext, NOTEBOOK_ACTIONS_CATEGORY } from 'vs/workbench/contrib/notebook/browser/contrib/coreActions'; +import { CellToolbarLocKey } from 'vs/workbench/contrib/notebook/common/notebookCommon'; + +const TOGGLE_CELL_TOOLBAR_POSITION = 'notebook.toggleCellToolbarPosition'; + +export class ToggleCellToolbarPositionAction extends Action2 { + constructor() { + super({ + id: TOGGLE_CELL_TOOLBAR_POSITION, + title: { value: localize('notebook.toggleCellToolbarPosition', "Toggle Cell Toolbar Position"), original: 'Toggle Cell Toolbar Position' }, + menu: [{ + id: MenuId.NotebookCellTitle, + group: 'View', + order: 1 + }], + category: NOTEBOOK_ACTIONS_CATEGORY, + f1: false + }); + } + + async run(accessor: ServicesAccessor, context: any): Promise { + const editor = context && context.ui ? (context as INotebookActionContext).notebookEditor : undefined; + if (editor && editor.hasModel()) { + // from toolbar + const viewType = editor.viewModel.viewType; + const configurationService = accessor.get(IConfigurationService); + const toolbarPosition = configurationService.getValue(CellToolbarLocKey); + const newConfig = this.togglePosition(viewType, toolbarPosition); + await configurationService.updateValue(CellToolbarLocKey, newConfig); + } + } + + togglePosition(viewType: string, toolbarPosition: string | { [key: string]: string }): { [key: string]: string } { + if (typeof toolbarPosition === 'string') { + // legacy + if (['left', 'right', 'hidden'].indexOf(toolbarPosition) >= 0) { + // valid position + const newViewValue = toolbarPosition === 'right' ? 'left' : 'right'; + let config: { [key: string]: string } = { + default: toolbarPosition + }; + config[viewType] = newViewValue; + return config; + } else { + // invalid position + let config: { [key: string]: string } = { + default: 'right', + }; + config[viewType] = 'left'; + return config; + } + } else { + const oldValue = toolbarPosition[viewType] ?? toolbarPosition['default'] ?? 'right'; + const newViewValue = oldValue === 'right' ? 'left' : 'right'; + let newConfig = { + ...toolbarPosition + }; + newConfig[viewType] = newViewValue; + return newConfig; + } + + } +} +registerAction2(ToggleCellToolbarPositionAction); + diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/layout/test/layoutActions.test.ts b/src/vs/workbench/contrib/notebook/browser/contrib/layout/test/layoutActions.test.ts new file mode 100644 index 00000000..a35bee79 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/contrib/layout/test/layoutActions.test.ts @@ -0,0 +1,61 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { ToggleCellToolbarPositionAction } from 'vs/workbench/contrib/notebook/browser/contrib/layout/layoutActions'; + +suite('Notebook Layout Actions', () => { + test('Toggle Cell Toolbar Position', async function () { + const action = new ToggleCellToolbarPositionAction(); + + // "notebook.cellToolbarLocation": "right" + assert.deepStrictEqual(action.togglePosition('test-nb', 'right'), { + default: 'right', + 'test-nb': 'left' + }); + + // "notebook.cellToolbarLocation": "left" + assert.deepStrictEqual(action.togglePosition('test-nb', 'left'), { + default: 'left', + 'test-nb': 'right' + }); + + // "notebook.cellToolbarLocation": "hidden" + assert.deepStrictEqual(action.togglePosition('test-nb', 'hidden'), { + default: 'hidden', + 'test-nb': 'right' + }); + + // invalid + assert.deepStrictEqual(action.togglePosition('test-nb', ''), { + default: 'right', + 'test-nb': 'left' + }); + + // no user config, default value + assert.deepStrictEqual(action.togglePosition('test-nb', { + default: 'right' + }), { + default: 'right', + 'test-nb': 'left' + }); + + // user config, default to left + assert.deepStrictEqual(action.togglePosition('test-nb', { + default: 'left' + }), { + default: 'left', + 'test-nb': 'right' + }); + + // user config, default to hidden + assert.deepStrictEqual(action.togglePosition('test-nb', { + default: 'hidden' + }), { + default: 'hidden', + 'test-nb': 'right' + }); + }); +}); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/navigation/arrow.ts b/src/vs/workbench/contrib/notebook/browser/contrib/navigation/arrow.ts new file mode 100644 index 00000000..703fa826 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/contrib/navigation/arrow.ts @@ -0,0 +1,234 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { localize } from 'vs/nls'; +import { registerAction2 } from 'vs/platform/actions/common/actions'; +import { Extensions as ConfigurationExtensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkeys'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { INotebookActionContext, INotebookCellActionContext, NotebookAction, NotebookCellAction, NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT } from 'vs/workbench/contrib/notebook/browser/contrib/coreActions'; +import { CellEditState, NOTEBOOK_CELL_HAS_OUTPUTS, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_OUTPUT_FOCUSED } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellKind, NOTEBOOK_EDITOR_CURSOR_BOUNDARY } from 'vs/workbench/contrib/notebook/common/notebookCommon'; + +const NOTEBOOK_FOCUS_TOP = 'notebook.focusTop'; +const NOTEBOOK_FOCUS_BOTTOM = 'notebook.focusBottom'; +const NOTEBOOK_FOCUS_PREVIOUS_EDITOR = 'notebook.focusPreviousEditor'; +const NOTEBOOK_FOCUS_NEXT_EDITOR = 'notebook.focusNextEditor'; +const FOCUS_IN_OUTPUT_COMMAND_ID = 'notebook.cell.focusInOutput'; +const FOCUS_OUT_OUTPUT_COMMAND_ID = 'notebook.cell.focusOutOutput'; + + +registerAction2(class extends NotebookCellAction { + constructor() { + super({ + id: NOTEBOOK_FOCUS_NEXT_EDITOR, + title: localize('cursorMoveDown', 'Focus Next Cell Editor'), + keybinding: [ + { + when: ContextKeyExpr.and( + NOTEBOOK_EDITOR_FOCUSED, + ContextKeyExpr.has(InputFocusedContextKey), + EditorContextKeys.editorTextFocus, + NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('top'), + NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('none'), + ContextKeyExpr.equals('config.notebook.navigation.allowNavigateToSurroundingCells', true) + ), + primary: KeyCode.DownArrow, + weight: NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT + }, + { + when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_OUTPUT_FOCUSED), + primary: KeyMod.CtrlCmd | KeyCode.DownArrow, + mac: { primary: KeyMod.WinCtrl | KeyMod.CtrlCmd | KeyCode.DownArrow, }, + weight: KeybindingWeight.WorkbenchContrib + } + ] + }); + } + + async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise { + const editor = context.notebookEditor; + const activeCell = context.cell; + + const idx = editor.viewModel.getCellIndex(activeCell); + if (typeof idx !== 'number') { + return; + } + + const newCell = editor.viewModel.cellAt(idx + 1); + + if (!newCell) { + return; + } + + const newFocusMode = newCell.cellKind === CellKind.Markdown && newCell.getEditState() === CellEditState.Preview ? 'container' : 'editor'; + editor.focusNotebookCell(newCell, newFocusMode); + editor.cursorNavigationMode = true; + } +}); + + +registerAction2(class extends NotebookCellAction { + constructor() { + super({ + id: NOTEBOOK_FOCUS_PREVIOUS_EDITOR, + title: localize('cursorMoveUp', 'Focus Previous Cell Editor'), + keybinding: { + when: ContextKeyExpr.and( + NOTEBOOK_EDITOR_FOCUSED, + ContextKeyExpr.has(InputFocusedContextKey), + EditorContextKeys.editorTextFocus, + NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('bottom'), + NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('none'), + ContextKeyExpr.equals('config.notebook.navigation.allowNavigateToSurroundingCells', true) + ), + primary: KeyCode.UpArrow, + weight: NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT + }, + }); + } + + async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise { + const editor = context.notebookEditor; + const activeCell = context.cell; + + const idx = editor.viewModel.getCellIndex(activeCell); + if (typeof idx !== 'number') { + return; + } + + if (idx < 1) { + // we don't do loop + return; + } + + const newCell = editor.viewModel.cellAt(idx - 1); + + if (!newCell) { + return; + } + + const newFocusMode = newCell.cellKind === CellKind.Markdown && newCell.getEditState() === CellEditState.Preview ? 'container' : 'editor'; + editor.focusNotebookCell(newCell, newFocusMode); + editor.cursorNavigationMode = true; + } +}); + + +registerAction2(class extends NotebookAction { + constructor() { + super({ + id: NOTEBOOK_FOCUS_TOP, + title: localize('focusFirstCell', 'Focus First Cell'), + keybinding: { + when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey)), + primary: KeyMod.CtrlCmd | KeyCode.Home, + mac: { primary: KeyMod.CtrlCmd | KeyCode.UpArrow }, + weight: KeybindingWeight.WorkbenchContrib + }, + }); + } + + async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext): Promise { + const editor = context.notebookEditor; + if (!editor.viewModel || !editor.viewModel.length) { + return; + } + + const firstCell = editor.viewModel.cellAt(0); + if (firstCell) { + editor.focusNotebookCell(firstCell, 'container'); + } + } +}); + +registerAction2(class extends NotebookAction { + constructor() { + super({ + id: NOTEBOOK_FOCUS_BOTTOM, + title: localize('focusLastCell', 'Focus Last Cell'), + keybinding: { + when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey)), + primary: KeyMod.CtrlCmd | KeyCode.End, + mac: { primary: KeyMod.CtrlCmd | KeyCode.DownArrow }, + weight: KeybindingWeight.WorkbenchContrib + }, + }); + } + + async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext): Promise { + const editor = context.notebookEditor; + if (!editor.viewModel || !editor.viewModel.length) { + return; + } + + const firstCell = editor.viewModel.cellAt(editor.viewModel.length - 1); + if (firstCell) { + editor.focusNotebookCell(firstCell, 'container'); + } + } +}); + + +registerAction2(class extends NotebookCellAction { + constructor() { + super({ + id: FOCUS_IN_OUTPUT_COMMAND_ID, + title: localize('focusOutput', 'Focus In Active Cell Output'), + keybinding: { + when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_CELL_HAS_OUTPUTS), + primary: KeyMod.CtrlCmd | KeyCode.DownArrow, + mac: { primary: KeyMod.WinCtrl | KeyMod.CtrlCmd | KeyCode.DownArrow, }, + weight: KeybindingWeight.WorkbenchContrib + }, + }); + } + + async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise { + const editor = context.notebookEditor; + const activeCell = context.cell; + editor.focusNotebookCell(activeCell, 'output'); + } +}); + +registerAction2(class extends NotebookCellAction { + constructor() { + super({ + id: FOCUS_OUT_OUTPUT_COMMAND_ID, + title: localize('focusOutputOut', 'Focus Out Active Cell Output'), + keybinding: { + when: NOTEBOOK_EDITOR_FOCUSED, + primary: KeyMod.CtrlCmd | KeyCode.UpArrow, + mac: { primary: KeyMod.WinCtrl | KeyMod.CtrlCmd | KeyCode.UpArrow, }, + weight: KeybindingWeight.WorkbenchContrib + }, + }); + } + + async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise { + const editor = context.notebookEditor; + const activeCell = context.cell; + editor.focusNotebookCell(activeCell, 'editor'); + } +}); + + +Registry.as(ConfigurationExtensions.Configuration).registerConfiguration({ + id: 'notebook', + order: 100, + type: 'object', + 'properties': { + 'notebook.navigation.allowNavigateToSurroundingCells': { + type: 'boolean', + default: true, + markdownDescription: localize('notebook.navigation.allowNavigateToSurroundingCells', "When enabled cursor can navigate to the next/previous cell when the current cursor in the cell editor is at the first/last line.") + } + } +}); 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 979399fd..f4299895 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts @@ -391,10 +391,10 @@ export class NotebookCellOutline implements IOutline { } const focusedCellIndex = viewModel.getFocus().start; - const focused = viewModel.getCellByIndex(focusedCellIndex)?.handle; + const focused = viewModel.cellAt(focusedCellIndex)?.handle; const entries: OutlineEntry[] = []; - for (let i = 0; i < viewModel.viewCells.length; i++) { + for (let i = 0; i < viewModel.length; i++) { const cell = viewModel.viewCells[i]; const isMarkdown = cell.cellKind === CellKind.Markdown; if (!isMarkdown && !includeCodeCells) { @@ -518,7 +518,7 @@ export class NotebookCellOutline implements IOutline { const { viewModel } = this._editor; if (viewModel) { - const cell = viewModel.getCellByIndex(viewModel.getFocus().start); + const cell = viewModel.cellAt(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 f06e3f8a..f1e7fe84 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 @@ -23,8 +23,8 @@ suite('Notebook Outline', function () { instantiationService.set(IEditorService, new class extends mock() { }); instantiationService.set(IMarkerService, new MarkerService()); instantiationService.set(IThemeService, new class extends mock() { - onDidFileIconThemeChange = Event.None; - getFileIconTheme(): IFileIconTheme { + override onDidFileIconThemeChange = Event.None; + override getFileIconTheme(): IFileIconTheme { return { hasFileIcons: true, hasFolderIcons: true, hidesExplorerArrows: false }; } }); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/status/editorStatus.ts b/src/vs/workbench/contrib/notebook/browser/contrib/status/editorStatus.ts index 2ed6547f..cc3f20a7 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/status/editorStatus.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/status/editorStatus.ts @@ -7,23 +7,22 @@ import * as nls from 'vs/nls'; import { Registry } from 'vs/platform/registry/common/platform'; import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { IQuickInputService, IQuickPickItem, QuickPickInput } from 'vs/platform/quickinput/common/quickInput'; +import { IQuickInputButton, IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { NOTEBOOK_ACTIONS_CATEGORY } from 'vs/workbench/contrib/notebook/browser/contrib/coreActions'; -import { getNotebookEditorFromEditorPane, NOTEBOOK_IS_ACTIVE_EDITOR } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; - -import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; +import { getNotebookEditorFromEditorPane, INotebookEditor, NOTEBOOK_IS_ACTIVE_EDITOR } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { CancellationTokenSource } from 'vs/base/common/cancellation'; -import { INotebookKernel } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; -import { Disposable, DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; +import { combinedDisposable, Disposable, DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; import { IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment } from 'vs/workbench/services/statusbar/common/statusbar'; -import { NotebookKernelProviderAssociation, NotebookKernelProviderAssociations, notebookKernelProviderAssociationsSettingId } from 'vs/workbench/contrib/notebook/browser/notebookKernelAssociation'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { configureKernelIcon, selectKernelIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; - +import { NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; +import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { INotebookKernel, INotebookTextModel } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { ILabelService } from 'vs/platform/label/common/label'; +import { ILogService } from 'vs/platform/log/common/log'; registerAction2(class extends Action2 { constructor() { @@ -59,170 +58,249 @@ registerAction2(class extends Action2 { }); } - async run(accessor: ServicesAccessor, context?: { id: string, extension: string }): Promise { - const editorService = accessor.get(IEditorService); - const quickInputService = accessor.get(IQuickInputService); - const configurationService = accessor.get(IConfigurationService); + async run(accessor: ServicesAccessor, context?: { id: string, extension: string }): Promise { + const notebookKernelService = accessor.get(INotebookKernelService); + const editorService = accessor.get(IEditorService); + const quickInputService = accessor.get(IQuickInputService); + const labelService = accessor.get(ILabelService); + const logService = accessor.get(ILogService); const editor = getNotebookEditorFromEditorPane(editorService.activeEditorPane); - if (!editor) { - return; + if (!editor || !editor.hasModel()) { + return false; } - if (!editor.hasModel()) { - return; + if (context && (typeof context.id !== 'string' || typeof context.extension !== 'string')) { + // validate context: id & extension MUST be strings + context = undefined; } - const activeKernel = editor.activeKernel; + const notebook = editor.viewModel.notebookDocument; + const { selected, all } = notebookKernelService.getMatchingKernel(notebook); - const picker = quickInputService.createQuickPick<(IQuickPickItem & { run(): void; kernelProviderId?: string })>(); - picker.placeholder = nls.localize('notebook.runCell.selectKernel', "Select a notebook kernel to run this notebook"); - picker.matchOnDetail = true; - - - if (context && context.id) { - } else { - picker.show(); + if (selected && context && selected.id === context.id && ExtensionIdentifier.equals(selected.extension, context.extension)) { + // current kernel is wanted kernel -> done + return true; } - picker.busy = true; - - const tokenSource = new CancellationTokenSource(); - const availableKernels = await editor.beginComputeContributedKernels(); - - const selectedKernel = availableKernels.length ? availableKernels.find( - kernel => kernel.id && context?.id && kernel.id === context?.id && kernel.extension.value === context?.extension - ) : undefined; - - if (selectedKernel) { - editor.activeKernel = selectedKernel; - return selectedKernel.resolve(editor.viewModel.uri, editor.getId(), tokenSource.token); - } else { - picker.show(); + let newKernel: INotebookKernel | undefined; + if (context) { + const wantedId = `${context.extension}/${context.id}`; + for (let candidate of all) { + if (candidate.id === wantedId) { + newKernel = candidate; + break; + } + } + if (!newKernel) { + logService.warn(`wanted kernel DOES NOT EXIST, wanted: ${wantedId}, all: ${all.map(k => k.id)}`); + return false; + } } - const picks: QuickPickInput[] = [...availableKernels].map((a) => { - return { - id: a.friendlyId, - label: a.label, - picked: a.friendlyId === activeKernel?.friendlyId, - description: - a.description - ? a.description - : a.extension.value + (a.friendlyId === activeKernel?.friendlyId - ? nls.localize('currentActiveKernel', " (Currently Active)") - : ''), - detail: a.detail, - kernelProviderId: a.extension.value, - run: async () => { - editor.activeKernel = a; - a.resolve(editor.viewModel.uri, editor.getId(), tokenSource.token); - }, - buttons: [{ - iconClass: ThemeIcon.asClassName(configureKernelIcon), - tooltip: nls.localize('notebook.promptKernel.setDefaultTooltip', "Set as default kernel provider for '{0}'", editor.viewModel.viewType) - }] + if (!newKernel) { + type KernelPick = IQuickPickItem & { kernel: INotebookKernel }; + const configButton: IQuickInputButton = { + iconClass: ThemeIcon.asClassName(configureKernelIcon), + tooltip: nls.localize('notebook.promptKernel.setDefaultTooltip', "Set as default for '{0}' notebooks", editor.viewModel.viewType) }; - }); - - picker.items = picks; - picker.busy = false; - picker.activeItems = picks.filter(pick => (pick as IQuickPickItem).picked) as (IQuickPickItem & { run(): void; kernelProviderId?: string; })[]; - - const pickedItem = await new Promise<(IQuickPickItem & { run(): void; kernelProviderId?: string; }) | undefined>(resolve => { - picker.onDidAccept(() => { - resolve(picker.selectedItems.length === 1 ? picker.selectedItems[0] : undefined); - picker.dispose(); - }); - - picker.onDidTriggerItemButton(e => { - const pick = e.item; - const id = pick.id; - resolve(pick); // open the view - picker.dispose(); - - // And persist the setting - if (pick && id && pick.kernelProviderId) { - const newAssociation: NotebookKernelProviderAssociation = { viewType: editor.viewModel.viewType, kernelProvider: pick.kernelProviderId }; - const currentAssociations = [...configurationService.getValue(notebookKernelProviderAssociationsSettingId)]; - - // First try updating existing association - for (let i = 0; i < currentAssociations.length; ++i) { - const existing = currentAssociations[i]; - if (existing.viewType === newAssociation.viewType) { - currentAssociations.splice(i, 1, newAssociation); - configurationService.updateValue(notebookKernelProviderAssociationsSettingId, currentAssociations); - return; - } + const picks = all.map(kernel => { + const res = { + kernel, + picked: kernel.id === selected?.id, + label: kernel.label, + description: kernel.description, + detail: kernel.detail, + buttons: [configButton] + }; + if (kernel.id === selected?.id) { + if (!res.description) { + res.description = nls.localize('current1', "Currently Selected"); + } else { + res.description = nls.localize('current2', "{0} - Currently Selected", res.description); } - - // Otherwise, create a new one - currentAssociations.unshift(newAssociation); - configurationService.updateValue(notebookKernelProviderAssociationsSettingId, currentAssociations); + } + { return res; } + }); + const pick = await quickInputService.pick(picks, { + placeHolder: selected + ? nls.localize('prompt.placeholder.change', "Change kernel for '{0}'", labelService.getUriLabel(notebook.uri, { relative: true })) + : nls.localize('prompt.placeholder.select', "Select kernel for '{0}'", labelService.getUriLabel(notebook.uri, { relative: true })), + onDidTriggerItemButton: (context) => { + notebookKernelService.selectKernelForNotebookType(context.item.kernel, notebook.viewType); } }); - }); + if (pick) { + newKernel = pick.kernel; + } + } - tokenSource.dispose(); - return pickedItem?.run(); + if (newKernel) { + notebookKernelService.selectKernelForNotebook(newKernel, notebook); + return true; + } + return false; } }); export class KernelStatus extends Disposable implements IWorkbenchContribution { - private readonly _editorDisposable = this._register(new DisposableStore()); - private readonly kernelInfoElement = this._register(new MutableDisposable()); + + private readonly _editorDisposables = this._register(new DisposableStore()); + private readonly _kernelInfoElement = this._register(new MutableDisposable()); + constructor( @IEditorService private readonly _editorService: IEditorService, - @INotebookService private readonly _notebookService: INotebookService, @IStatusbarService private readonly _statusbarService: IStatusbarService, + @INotebookKernelService private readonly _notebookKernelService: INotebookKernelService, ) { super(); - this.registerListeners(); + this._register(this._editorService.onDidActiveEditorChange(() => this._updateStatusbar())); } - registerListeners() { - this._register(this._editorService.onDidActiveEditorChange(() => this.updateStatusbar())); - this._register(this._notebookService.onDidChangeKernels(() => this.updateStatusbar())); - } + private _updateStatusbar() { + this._editorDisposables.clear(); - updateStatusbar() { - this._editorDisposable.clear(); const activeEditor = getNotebookEditorFromEditorPane(this._editorService.activeEditorPane); - - if (activeEditor) { - this._editorDisposable.add(activeEditor.onDidChangeKernel(() => { - if (activeEditor.multipleKernelsAvailable) { - this.showKernelStatus(activeEditor.activeKernel); - } else { - this.kernelInfoElement.clear(); - } - })); - - this._editorDisposable.add(activeEditor.onDidChangeAvailableKernels(() => { - if (activeEditor.multipleKernelsAvailable) { - this.showKernelStatus(activeEditor.activeKernel); - } else { - this.kernelInfoElement.clear(); - } - })); + if (!activeEditor) { + // not a notebook -> clean-up, done + this._kernelInfoElement.clear(); + return; } - if (activeEditor && activeEditor.multipleKernelsAvailable) { - this.showKernelStatus(activeEditor.activeKernel); - } else { - this.kernelInfoElement.clear(); - } + const updateStatus = () => { + const notebook = activeEditor.viewModel?.notebookDocument; + if (notebook) { + this._showKernelStatus(notebook); + } else { + this._kernelInfoElement.clear(); + } + }; + + this._editorDisposables.add(this._notebookKernelService.onDidAddKernel(updateStatus)); + this._editorDisposables.add(this._notebookKernelService.onDidChangeNotebookKernelBinding(updateStatus)); + this._editorDisposables.add(this._notebookKernelService.onDidChangeNotebookAffinity(updateStatus)); + this._editorDisposables.add(activeEditor.onDidChangeModel(updateStatus)); + updateStatus(); } - showKernelStatus(kernel: INotebookKernel | undefined) { - this.kernelInfoElement.value = this._statusbarService.addEntry({ - text: kernel ? kernel.label : 'Choose Kernel', - ariaLabel: kernel ? kernel.label : 'Choose Kernel', - tooltip: nls.localize('chooseActiveKernel', "Choose kernel for current notebook"), - command: 'notebook.selectKernel', - }, 'notebook.selectKernel', nls.localize('notebook.selectKernel', "Choose kernel for current notebook"), StatusbarAlignment.RIGHT, 100); + private _showKernelStatus(notebook: INotebookTextModel) { + + let { selected, all } = this._notebookKernelService.getMatchingKernel(notebook); + let isSuggested = false; + + if (all.length === 0) { + // no kernel -> no status + this._kernelInfoElement.clear(); + return; + + } else if (selected || all.length === 1) { + // selected or single kernel + if (!selected) { + selected = all[0]; + isSuggested = true; + } + const text = `$(notebook-kernel-select) ${selected.label}`; + const tooltip = selected.description ?? selected.detail ?? selected.label; + const registration = this._statusbarService.addEntry( + { + text, + ariaLabel: selected.label, + tooltip: isSuggested ? nls.localize('tooltop', "{0} (suggestion)", tooltip) : tooltip, + command: 'notebook.selectKernel', + }, + 'notebook.selectKernel', + nls.localize('notebook.info', "Notebook Kernel Info"), + StatusbarAlignment.RIGHT, + 1000 + ); + const listener = selected.onDidChange(() => this._showKernelStatus(notebook)); + this._kernelInfoElement.value = combinedDisposable(listener, registration); + + } else { + // multiple kernels -> show selection hint + const registration = this._statusbarService.addEntry( + { + text: nls.localize('kernel.select.label', "Select Kernel"), + ariaLabel: nls.localize('kernel.select.label', "Select Kernel"), + command: 'notebook.selectKernel', + backgroundColor: { id: 'statusBarItem.prominentBackground' } + }, + 'notebook.selectKernel', + nls.localize('notebook.select', "Notebook Kernel Selection"), + StatusbarAlignment.RIGHT, + 1000 + ); + this._kernelInfoElement.value = registration; + } } } Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(KernelStatus, LifecyclePhase.Ready); + +export class ActiveCellStatus extends Disposable implements IWorkbenchContribution { + + private readonly _itemDisposables = this._register(new DisposableStore()); + private readonly _accessor = this._register(new MutableDisposable()); + + constructor( + @IEditorService private readonly _editorService: IEditorService, + @IStatusbarService private readonly _statusbarService: IStatusbarService, + ) { + super(); + this._register(this._editorService.onDidActiveEditorChange(() => this._update())); + } + + private _update() { + this._itemDisposables.clear(); + const activeEditor = getNotebookEditorFromEditorPane(this._editorService.activeEditorPane); + if (activeEditor) { + this._itemDisposables.add(activeEditor.onDidChangeSelection(() => this._show(activeEditor))); + this._itemDisposables.add(activeEditor.onDidChangeActiveCell(() => this._show(activeEditor))); + this._show(activeEditor); + } else { + this._accessor.clear(); + } + } + + private _show(editor: INotebookEditor) { + const vm = editor.viewModel; + if (!vm) { + this._accessor.clear(); + return; + } + + const newText = this._getSelectionsText(editor, vm); + if (!newText) { + this._accessor.clear(); + return; + } + + const entry = { text: newText, ariaLabel: newText }; + if (!this._accessor.value) { + this._accessor.value = this._statusbarService.addEntry( + entry, + 'notebook.activeCellStatus', + nls.localize('notebook.activeCellStatusName', "Notebook Editor Selections"), + StatusbarAlignment.RIGHT, + 100); + } else { + this._accessor.value.update(entry); + } + } + + private _getSelectionsText(editor: INotebookEditor, vm: NotebookViewModel): string | undefined { + const activeCell = editor.getActiveCell(); + if (!activeCell) { + return undefined; + } + + const idxFocused = vm.getCellIndex(activeCell) + 1; + const numSelected = vm.getSelections().reduce((prev, range) => prev + (range.end - range.start), 0); + return numSelected > 1 ? + nls.localize('notebook.multiActiveCellIndicator', "Cell {0} ({1} selected)", idxFocused, numSelected) : + nls.localize('notebook.singleActiveCellIndicator', "Cell {0}", idxFocused); + } +} + +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(ActiveCellStatus, LifecyclePhase.Ready); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/statusBar/cellStatusBar.ts b/src/vs/workbench/contrib/notebook/browser/contrib/statusBar/cellStatusBar.ts new file mode 100644 index 00000000..edce9fb6 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/contrib/statusBar/cellStatusBar.ts @@ -0,0 +1,142 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { flatten } from 'vs/base/common/arrays'; +import { Throttler } from 'vs/base/common/async'; +import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; +import { ICellViewModel, INotebookEditor, INotebookEditorContribution } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { registerNotebookContribution } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions'; +import { CellViewModel, NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; +import { INotebookCellStatusBarService } from 'vs/workbench/contrib/notebook/common/notebookCellStatusBarService'; +import { INotebookCellStatusBarItemList } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { cellRangesToIndexes } from 'vs/workbench/contrib/notebook/common/notebookRange'; + +export class NotebookStatusBarController extends Disposable implements INotebookEditorContribution { + static id: string = 'workbench.notebook.statusBar'; + + private readonly _visibleCells = new Map(); + + private readonly _viewModelDisposables = new DisposableStore(); + + constructor( + private readonly _notebookEditor: INotebookEditor, + @INotebookCellStatusBarService private readonly _notebookCellStatusBarService: INotebookCellStatusBarService + ) { + super(); + this._updateVisibleCells(); + this._register(this._notebookEditor.onDidChangeVisibleRanges(this._updateVisibleCells, this)); + this._register(this._notebookEditor.onDidChangeModel(this._onModelChange, this)); + this._register(this._notebookCellStatusBarService.onDidChangeProviders(this._updateEverything, this)); + this._register(this._notebookCellStatusBarService.onDidChangeItems(this._updateEverything, this)); + } + + private _onModelChange() { + this._viewModelDisposables.clear(); + const vm = this._notebookEditor.viewModel; + if (!vm) { + return; + } + + this._viewModelDisposables.add(vm.onDidChangeViewCells(() => this._updateEverything())); + this._updateEverything(); + } + + private _updateEverything(): void { + this._visibleCells.forEach(cell => cell.dispose()); + this._visibleCells.clear(); + this._updateVisibleCells(); + } + + private _updateVisibleCells(): void { + const vm = this._notebookEditor.viewModel; + if (!vm) { + return; + } + + const newVisibleCells = new Set(); + const rangesWithEnd = this._notebookEditor.visibleRanges + .map(range => ({ start: range.start, end: range.end + 1 })); + cellRangesToIndexes(rangesWithEnd) + .map(index => vm.cellAt(index)) + .filter((cell: CellViewModel | undefined): cell is CellViewModel => !!cell) + .map(cell => { + if (!this._visibleCells.has(cell.handle)) { + const helper = new CellStatusBarHelper(vm, cell, this._notebookCellStatusBarService); + this._visibleCells.set(cell.handle, helper); + } + newVisibleCells.add(cell.handle); + }); + + for (let handle of this._visibleCells.keys()) { + if (!newVisibleCells.has(handle)) { + this._visibleCells.get(handle)?.dispose(); + this._visibleCells.delete(handle); + } + } + } + + override dispose(): void { + this._visibleCells.forEach(cell => cell.dispose()); + this._visibleCells.clear(); + } +} + +class CellStatusBarHelper extends Disposable { + private _currentItemIds: string[] = []; + private _currentItemLists: INotebookCellStatusBarItemList[] = []; + + private readonly _cancelTokenSource: CancellationTokenSource; + + private readonly _updateThrottler = new Throttler(); + + constructor( + private readonly _notebookViewModel: NotebookViewModel, + private readonly _cell: ICellViewModel, + private readonly _notebookCellStatusBarService: INotebookCellStatusBarService + ) { + super(); + + this._cancelTokenSource = new CancellationTokenSource(); + this._register(toDisposable(() => this._cancelTokenSource.dispose(true))); + + this._updateSoon(); + this._register(this._cell.model.onDidChangeContent(() => this._updateSoon())); + this._register(this._cell.model.onDidChangeLanguage(() => this._updateSoon())); + this._register(this._cell.model.onDidChangeMetadata(() => this._updateSoon())); + this._register(this._cell.model.onDidChangeOutputs(() => this._updateSoon())); + } + + private _updateSoon(): void { + this._updateThrottler.queue(() => this._update()); + } + + private async _update() { + const cellIndex = this._notebookViewModel.getCellIndex(this._cell); + const docUri = this._notebookViewModel.notebookDocument.uri; + const viewType = this._notebookViewModel.notebookDocument.viewType; + const itemLists = await this._notebookCellStatusBarService.getStatusBarItemsForCell(docUri, cellIndex, viewType, this._cancelTokenSource.token); + if (this._cancelTokenSource.token.isCancellationRequested) { + itemLists.forEach(itemList => itemList.dispose && itemList.dispose()); + return; + } + + const items = flatten(itemLists.map(itemList => itemList.items)); + const newIds = this._notebookViewModel.deltaCellStatusBarItems(this._currentItemIds, [{ handle: this._cell.handle, items }]); + + this._currentItemLists.forEach(itemList => itemList.dispose && itemList.dispose()); + this._currentItemLists = itemLists; + this._currentItemIds = newIds; + } + + override dispose() { + super.dispose(); + + this._notebookViewModel.deltaCellStatusBarItems(this._currentItemIds, [{ handle: this._cell.handle, items: [] }]); + this._currentItemLists.forEach(itemList => itemList.dispose && itemList.dispose()); + } +} + +registerNotebookContribution(NotebookStatusBarController.id, NotebookStatusBarController); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/statusBar/contributedStatusBarItemController.ts b/src/vs/workbench/contrib/notebook/browser/contrib/statusBar/contributedStatusBarItemController.ts new file mode 100644 index 00000000..4358ff31 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/contrib/statusBar/contributedStatusBarItemController.ts @@ -0,0 +1,126 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { flatten } from 'vs/base/common/arrays'; +import { Throttler } from 'vs/base/common/async'; +import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; +import { ICellVisibilityChangeEvent, NotebookVisibleCellObserver } from 'vs/workbench/contrib/notebook/browser/contrib/statusBar/notebookVisibleCellObserver'; +import { ICellViewModel, INotebookEditor, INotebookEditorContribution } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { registerNotebookContribution } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions'; +import { NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; +import { INotebookCellStatusBarService } from 'vs/workbench/contrib/notebook/common/notebookCellStatusBarService'; +import { INotebookCellStatusBarItemList } from 'vs/workbench/contrib/notebook/common/notebookCommon'; + +export class ContributedStatusBarItemController extends Disposable implements INotebookEditorContribution { + static id: string = 'workbench.notebook.statusBar'; + + private readonly _visibleCells = new Map(); + + private readonly _observer: NotebookVisibleCellObserver; + + constructor( + private readonly _notebookEditor: INotebookEditor, + @INotebookCellStatusBarService private readonly _notebookCellStatusBarService: INotebookCellStatusBarService + ) { + super(); + this._observer = this._register(new NotebookVisibleCellObserver(this._notebookEditor)); + this._register(this._observer.onDidChangeVisibleCells(this._updateVisibleCells, this)); + + this._updateEverything(); + this._register(this._notebookCellStatusBarService.onDidChangeProviders(this._updateEverything, this)); + this._register(this._notebookCellStatusBarService.onDidChangeItems(this._updateEverything, this)); + } + + private _updateEverything(): void { + this._visibleCells.forEach(cell => cell.dispose()); + this._visibleCells.clear(); + this._updateVisibleCells({ added: this._observer.visibleCells, removed: [] }); + } + + private _updateVisibleCells(e: ICellVisibilityChangeEvent): void { + const vm = this._notebookEditor.viewModel; + if (!vm) { + return; + } + + for (let newCell of e.added) { + const helper = new CellStatusBarHelper(vm, newCell, this._notebookCellStatusBarService); + this._visibleCells.set(newCell.handle, helper); + } + + for (let oldCell of e.removed) { + this._visibleCells.get(oldCell.handle)?.dispose(); + this._visibleCells.delete(oldCell.handle); + } + } + + override dispose(): void { + super.dispose(); + + this._visibleCells.forEach(cell => cell.dispose()); + this._visibleCells.clear(); + } +} + +class CellStatusBarHelper extends Disposable { + private _currentItemIds: string[] = []; + private _currentItemLists: INotebookCellStatusBarItemList[] = []; + + private readonly _cancelTokenSource: CancellationTokenSource; + + private readonly _updateThrottler = new Throttler(); + + constructor( + private readonly _notebookViewModel: NotebookViewModel, + private readonly _cell: ICellViewModel, + private readonly _notebookCellStatusBarService: INotebookCellStatusBarService + ) { + super(); + + this._cancelTokenSource = new CancellationTokenSource(); + this._register(toDisposable(() => this._cancelTokenSource.dispose(true))); + + this._updateSoon(); + this._register(this._cell.model.onDidChangeContent(() => this._updateSoon())); + this._register(this._cell.model.onDidChangeLanguage(() => this._updateSoon())); + this._register(this._cell.model.onDidChangeMetadata(() => this._updateSoon())); + this._register(this._cell.model.onDidChangeOutputs(() => this._updateSoon())); + } + + private _updateSoon(): void { + // Wait a tick to make sure that the event is fired to the EH before triggering status bar providers + setTimeout(() => { + this._updateThrottler.queue(() => this._update()); + }, 0); + } + + private async _update() { + const cellIndex = this._notebookViewModel.getCellIndex(this._cell); + const docUri = this._notebookViewModel.notebookDocument.uri; + const viewType = this._notebookViewModel.notebookDocument.viewType; + const itemLists = await this._notebookCellStatusBarService.getStatusBarItemsForCell(docUri, cellIndex, viewType, this._cancelTokenSource.token); + if (this._cancelTokenSource.token.isCancellationRequested) { + itemLists.forEach(itemList => itemList.dispose && itemList.dispose()); + return; + } + + const items = flatten(itemLists.map(itemList => itemList.items)); + const newIds = this._notebookViewModel.deltaCellStatusBarItems(this._currentItemIds, [{ handle: this._cell.handle, items }]); + + this._currentItemLists.forEach(itemList => itemList.dispose && itemList.dispose()); + this._currentItemLists = itemLists; + this._currentItemIds = newIds; + } + + override dispose() { + super.dispose(); + + this._notebookViewModel.deltaCellStatusBarItems(this._currentItemIds, [{ handle: this._cell.handle, items: [] }]); + this._currentItemLists.forEach(itemList => itemList.dispose && itemList.dispose()); + } +} + +registerNotebookContribution(ContributedStatusBarItemController.id, ContributedStatusBarItemController); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/statusBar/executionStatusBarItemController.ts b/src/vs/workbench/contrib/notebook/browser/contrib/statusBar/executionStatusBarItemController.ts new file mode 100644 index 00000000..438389b3 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/contrib/statusBar/executionStatusBarItemController.ts @@ -0,0 +1,225 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { RunOnceScheduler } from 'vs/base/common/async'; +import { Event } from 'vs/base/common/event'; +import { Disposable, dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { localize } from 'vs/nls'; +import { themeColorFromId } from 'vs/platform/theme/common/themeService'; +import { ICellVisibilityChangeEvent, NotebookVisibleCellObserver } from 'vs/workbench/contrib/notebook/browser/contrib/statusBar/notebookVisibleCellObserver'; +import { ICellViewModel, INotebookEditor, INotebookEditorContribution } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { registerNotebookContribution } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions'; +import { cellStatusIconError, cellStatusIconSuccess } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget'; +import { NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; +import { CellStatusbarAlignment, INotebookCellStatusBarItem, NotebookCellExecutionState } from 'vs/workbench/contrib/notebook/common/notebookCommon'; + +export class NotebookStatusBarController extends Disposable implements INotebookEditorContribution { + static id: string = 'workbench.notebook.statusBar'; + + private readonly _visibleCells = new Map(); + + private readonly _observer: NotebookVisibleCellObserver; + + constructor( + private readonly _notebookEditor: INotebookEditor, + ) { + super(); + this._observer = this._register(new NotebookVisibleCellObserver(this._notebookEditor)); + this._register(this._observer.onDidChangeVisibleCells(this._updateVisibleCells, this)); + + this._updateEverything(); + } + + private _updateEverything(): void { + this._visibleCells.forEach(dispose); + this._visibleCells.clear(); + this._updateVisibleCells({ added: this._observer.visibleCells, removed: [] }); + } + + private _updateVisibleCells(e: ICellVisibilityChangeEvent): void { + const vm = this._notebookEditor.viewModel; + if (!vm) { + return; + } + + for (let newCell of e.added) { + const helpers = [ + new ExecutionStateCellStatusBarHelper(vm, newCell), + new TimerCellStatusBarHelper(vm, newCell) + ]; + this._visibleCells.set(newCell.handle, helpers); + } + + for (let oldCell of e.removed) { + this._visibleCells.get(oldCell.handle)?.forEach(dispose); + this._visibleCells.delete(oldCell.handle); + } + } + + override dispose(): void { + super.dispose(); + + this._visibleCells.forEach(dispose); + this._visibleCells.clear(); + } +} + +/** + * Shows the cell's execution state in the cell status bar. When the "executing" state is shown, it will be shown for a minimum brief time. + */ +class ExecutionStateCellStatusBarHelper extends Disposable { + private static readonly MIN_SPINNER_TIME = 500; + + private _currentItemIds: string[] = []; + + private _currentExecutingStateTimer: any; + + constructor( + private readonly _notebookViewModel: NotebookViewModel, + private readonly _cell: ICellViewModel, + ) { + super(); + + this._update(); + this._register(this._cell.model.onDidChangeMetadata(() => this._update())); + } + + private async _update() { + const items = this._getItemsForCell(this._cell); + if (Array.isArray(items)) { + this._currentItemIds = this._notebookViewModel.deltaCellStatusBarItems(this._currentItemIds, [{ handle: this._cell.handle, items }]); + } + } + + /** + * Returns undefined if there should be no change, and an empty array if all items should be removed. + */ + private _getItemsForCell(cell: ICellViewModel): INotebookCellStatusBarItem[] | undefined { + if (this._currentExecutingStateTimer) { + return; + } + + const item = this._getItemForState(cell.metadata?.runState, cell.metadata?.lastRunSuccess); + + // Show the execution spinner for a minimum time + if (cell.metadata?.runState === NotebookCellExecutionState.Executing) { + this._currentExecutingStateTimer = setTimeout(() => { + this._currentExecutingStateTimer = undefined; + if (cell.metadata?.runState !== NotebookCellExecutionState.Executing) { + this._update(); + } + }, ExecutionStateCellStatusBarHelper.MIN_SPINNER_TIME); + } + + return item ? [item] : []; + } + + private _getItemForState(runState: NotebookCellExecutionState | undefined, lastRunSuccess: boolean | undefined): INotebookCellStatusBarItem | undefined { + if (runState === NotebookCellExecutionState.Idle && lastRunSuccess) { + return { + text: '$(notebook-state-success)', + color: themeColorFromId(cellStatusIconSuccess), + tooltip: localize('notebook.cell.status.success', "Success"), + alignment: CellStatusbarAlignment.Left, + priority: Number.MAX_SAFE_INTEGER + }; + } else if (runState === NotebookCellExecutionState.Idle && lastRunSuccess === false) { + return { + text: '$(notebook-state-error)', + color: themeColorFromId(cellStatusIconError), + tooltip: localize('notebook.cell.status.failed', "Failed"), + alignment: CellStatusbarAlignment.Left, + priority: Number.MAX_SAFE_INTEGER + }; + } else if (runState === NotebookCellExecutionState.Pending) { + return { + text: '$(notebook-state-pending)', + tooltip: localize('notebook.cell.status.pending', "Pending"), + alignment: CellStatusbarAlignment.Left, + priority: Number.MAX_SAFE_INTEGER + }; + } else if (runState === NotebookCellExecutionState.Executing) { + return { + text: '$(notebook-state-executing~spin)', + tooltip: localize('notebook.cell.status.executing', "Executing"), + alignment: CellStatusbarAlignment.Left, + priority: Number.MAX_SAFE_INTEGER + }; + } + + return; + } + + override dispose() { + super.dispose(); + + this._notebookViewModel.deltaCellStatusBarItems(this._currentItemIds, [{ handle: this._cell.handle, items: [] }]); + } +} + +class TimerCellStatusBarHelper extends Disposable { + private static UPDATE_INTERVAL = 100; + private _currentItemIds: string[] = []; + + private _scheduler: RunOnceScheduler; + + constructor( + private readonly _notebookViewModel: NotebookViewModel, + private readonly _cell: ICellViewModel, + ) { + super(); + + this._scheduler = this._register(new RunOnceScheduler(() => this._update(), TimerCellStatusBarHelper.UPDATE_INTERVAL)); + this._update(); + this._register( + Event.filter(this._cell.model.onDidChangeMetadata, e => !!e.runStateChanged) + (() => this._update())); + } + + private async _update() { + let item: INotebookCellStatusBarItem | undefined; + if (this._cell.metadata?.runState === NotebookCellExecutionState.Executing) { + const startTime = this._cell.metadata.runStartTime; + const adjustment = this._cell.metadata.runStartTimeAdjustment; + if (typeof startTime === 'number') { + item = this._getTimeItem(startTime, Date.now(), adjustment); + this._scheduler.schedule(); + } + } else if (this._cell.metadata?.runState === NotebookCellExecutionState.Idle) { + const startTime = this._cell.metadata.runStartTime; + const endTime = this._cell.metadata.runEndTime; + if (typeof startTime === 'number' && typeof endTime === 'number') { + item = this._getTimeItem(startTime, endTime); + } + } + + const items = item ? [item] : []; + this._currentItemIds = this._notebookViewModel.deltaCellStatusBarItems(this._currentItemIds, [{ handle: this._cell.handle, items }]); + } + + private _getTimeItem(startTime: number, endTime: number, adjustment: number = 0): INotebookCellStatusBarItem { + const duration = endTime - startTime + adjustment; + return { + text: this._formatDuration(duration), + alignment: CellStatusbarAlignment.Left, + priority: Number.MAX_SAFE_INTEGER - 1 + }; + } + + private _formatDuration(duration: number) { + const seconds = Math.floor(duration / 1000); + const tenths = String(duration - seconds * 1000).charAt(0); + + return `${seconds}.${tenths}s`; + } + + override dispose() { + super.dispose(); + + this._notebookViewModel.deltaCellStatusBarItems(this._currentItemIds, [{ handle: this._cell.handle, items: [] }]); + } +} + +registerNotebookContribution(NotebookStatusBarController.id, NotebookStatusBarController); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/statusBar/notebookVisibleCellObserver.ts b/src/vs/workbench/contrib/notebook/browser/contrib/statusBar/notebookVisibleCellObserver.ts new file mode 100644 index 00000000..c0827f60 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/contrib/statusBar/notebookVisibleCellObserver.ts @@ -0,0 +1,83 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { diffSets } from 'vs/base/common/collections'; +import { Emitter } from 'vs/base/common/event'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { isDefined } from 'vs/base/common/types'; +import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; +import { cellRangesToIndexes } from 'vs/workbench/contrib/notebook/common/notebookRange'; + +export interface ICellVisibilityChangeEvent { + added: CellViewModel[]; + removed: CellViewModel[]; +} + +export class NotebookVisibleCellObserver extends Disposable { + private readonly _onDidChangeVisibleCells = this._register(new Emitter()); + readonly onDidChangeVisibleCells = this._onDidChangeVisibleCells.event; + + private readonly _viewModelDisposables = this._register(new DisposableStore()); + + private _visibleCells: CellViewModel[] = []; + + get visibleCells(): CellViewModel[] { + return this._visibleCells; + } + + constructor(private readonly _notebookEditor: INotebookEditor) { + super(); + + this._register(this._notebookEditor.onDidChangeVisibleRanges(this._updateVisibleCells, this)); + this._register(this._notebookEditor.onDidChangeModel(this._onModelChange, this)); + this._updateVisibleCells(); + } + + private _onModelChange() { + this._viewModelDisposables.clear(); + const vm = this._notebookEditor.viewModel; + if (vm) { + this._viewModelDisposables.add(vm.onDidChangeViewCells(() => this.updateEverything())); + } + + this.updateEverything(); + } + + protected updateEverything(): void { + this._onDidChangeVisibleCells.fire({ added: [], removed: Array.from(this._visibleCells) }); + this._visibleCells = []; + this._updateVisibleCells(); + } + + private _updateVisibleCells(): void { + const vm = this._notebookEditor.viewModel; + if (!vm) { + return; + } + + const rangesWithEnd = this._notebookEditor.visibleRanges + .map(range => ({ start: range.start, end: range.end + 1 })); + const newVisibleCells = cellRangesToIndexes(rangesWithEnd) + .map(index => vm.cellAt(index)) + .filter(isDefined); + const newVisibleHandles = new Set(newVisibleCells.map(cell => cell.handle)); + const oldVisibleHandles = new Set(this._visibleCells.map(cell => cell.handle)); + const diff = diffSets(oldVisibleHandles, newVisibleHandles); + + const added = diff.added + .map(handle => vm.getCellByHandle(handle)) + .filter(isDefined); + const removed = diff.removed + .map(handle => vm.getCellByHandle(handle)) + .filter(isDefined); + + this._visibleCells = newVisibleCells; + this._onDidChangeVisibleCells.fire({ + added, + removed + }); + } +} diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/statusBar/statusBarProviders.ts b/src/vs/workbench/contrib/notebook/browser/contrib/statusBar/statusBarProviders.ts new file mode 100644 index 00000000..172ea36e --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/contrib/statusBar/statusBarProviders.ts @@ -0,0 +1,119 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { localize } from 'vs/nls'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { URI } from 'vs/base/common/uri'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; +import { INotebookCellStatusBarService } from 'vs/workbench/contrib/notebook/common/notebookCellStatusBarService'; +import { CellKind, CellStatusbarAlignment, INotebookCellStatusBarItem, INotebookCellStatusBarItemList, INotebookCellStatusBarItemProvider } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; +import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { IModeService } from 'vs/editor/common/services/modeService'; +import { CHANGE_CELL_LANGUAGE, EXECUTE_CELL_COMMAND_ID, QUIT_EDIT_CELL_COMMAND_ID } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { NotebookSelector } from 'vs/workbench/contrib/notebook/common/notebookSelector'; + +class CellStatusBarPlaceholderProvider implements INotebookCellStatusBarItemProvider { + readonly selector: NotebookSelector = { + pattern: '**/*' + }; + + constructor( + @INotebookService private readonly _notebookService: INotebookService, + @IKeybindingService private readonly _keybindingService: IKeybindingService, + ) { } + + async provideCellStatusBarItems(uri: URI, index: number, token: CancellationToken): Promise { + const doc = this._notebookService.getNotebookTextModel(uri); + const cell = doc?.cells[index]; + if (!cell || typeof cell.metadata.runState !== 'undefined' || typeof cell.metadata.lastRunSuccess !== 'undefined') { + return; + } + + let text: string; + if (cell.cellKind === CellKind.Code) { + const keybinding = this._keybindingService.lookupKeybinding(EXECUTE_CELL_COMMAND_ID)?.getLabel(); + if (!keybinding) { + return; + } + + text = localize('notebook.cell.status.codeExecuteTip', "Press {0} to execute cell", keybinding); + } else { + const keybinding = this._keybindingService.lookupKeybinding(QUIT_EDIT_CELL_COMMAND_ID)?.getLabel(); + if (!keybinding) { + return; + } + + text = localize('notebook.cell.status.markdownExecuteTip', "Press {0} to stop editing", keybinding); + } + + const item = { + text, + tooltip: text, + alignment: CellStatusbarAlignment.Left, + opacity: '0.7', + onlyShowWhenActive: true + }; + return { + items: [item] + }; + } +} + +class CellStatusBarLanguagePickerProvider implements INotebookCellStatusBarItemProvider { + readonly selector: NotebookSelector = { + pattern: '**/*' + }; + + constructor( + @INotebookService private readonly _notebookService: INotebookService, + @IModeService private readonly _modeService: IModeService, + ) { } + + async provideCellStatusBarItems(uri: URI, index: number, _token: CancellationToken): Promise { + const doc = this._notebookService.getNotebookTextModel(uri); + const cell = doc?.cells[index]; + if (!cell) { + return; + } + + const modeId = cell.cellKind === CellKind.Markdown ? + 'markdown' : + (this._modeService.getModeIdForLanguageName(cell.language) || cell.language); + const text = this._modeService.getLanguageName(modeId) || this._modeService.getLanguageName('plaintext'); + const item = { + text, + command: CHANGE_CELL_LANGUAGE, + tooltip: localize('notebook.cell.status.language', "Select Cell Language Mode"), + alignment: CellStatusbarAlignment.Right, + priority: -Number.MAX_SAFE_INTEGER + }; + return { + items: [item] + }; + } +} + +class BuiltinCellStatusBarProviders extends Disposable { + constructor( + @IInstantiationService instantiationService: IInstantiationService, + @INotebookCellStatusBarService notebookCellStatusBarService: INotebookCellStatusBarService) { + super(); + + const builtinProviders = [ + CellStatusBarPlaceholderProvider, + CellStatusBarLanguagePickerProvider, + ]; + builtinProviders.forEach(p => { + this._register(notebookCellStatusBarService.registerCellStatusBarItemProvider(instantiationService.createInstance(p))); + }); + } +} + +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(BuiltinCellStatusBarProviders, LifecyclePhase.Restored); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/troubleshoot/layout.ts b/src/vs/workbench/contrib/notebook/browser/contrib/troubleshoot/layout.ts new file mode 100644 index 00000000..b2e8c57a --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/contrib/troubleshoot/layout.ts @@ -0,0 +1,131 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { CATEGORIES } from 'vs/workbench/common/actions'; +import { getNotebookEditorFromEditorPane, ICellViewModel, INotebookEditor, INotebookEditorContribution } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { registerNotebookContribution } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; + +export class TroubleshootController extends Disposable implements INotebookEditorContribution { + static id: string = 'workbench.notebook.troubleshoot'; + + private readonly _localStore = this._register(new DisposableStore()); + private _cellStateListeners: IDisposable[] = []; + private _logging: boolean = false; + + constructor(private readonly _notebookEditor: INotebookEditor) { + super(); + + this._register(this._notebookEditor.onDidChangeModel(() => { + this._localStore.clear(); + this._cellStateListeners.forEach(listener => listener.dispose()); + + if (!this._notebookEditor.viewModel) { + return; + } + + this._updateListener(); + })); + + this._updateListener(); + } + + toggleLogging(): void { + this._logging = !this._logging; + } + + private _log(cell: ICellViewModel, e: any) { + if (this._logging) { + const oldHeight = this._notebookEditor.getViewHeight(cell); + console.log(`cell#${cell.handle}`, e, `${oldHeight} -> ${cell.layoutInfo.totalHeight}`); + } + } + + private _updateListener() { + if (!this._notebookEditor.viewModel) { + return; + } + + const viewModel = this._notebookEditor.viewModel; + + for (let i = 0; i < viewModel.length; i++) { + const cell = viewModel.viewCells[i]; + + this._cellStateListeners.push(cell.onDidChangeLayout(e => { + this._log(cell, e); + })); + } + + this._localStore.add(viewModel.onDidChangeViewCells(e => { + e.splices.reverse().forEach(splice => { + const [start, deleted, newCells] = splice; + const deletedCells = this._cellStateListeners.splice(start, deleted, ...newCells.map(cell => { + return cell.onDidChangeLayout(e => { + this._log(cell, e); + }); + })); + + dispose(deletedCells); + }); + })); + } + + override dispose() { + dispose(this._cellStateListeners); + super.dispose(); + } +} + +registerNotebookContribution(TroubleshootController.id, TroubleshootController); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'notebook.toggleLayoutTroubleshoot', + title: 'Toggle Notebook Layout Troubleshoot', + category: CATEGORIES.Developer, + f1: true + }); + } + + async run(accessor: ServicesAccessor): Promise { + const editorService = accessor.get(IEditorService); + const editor = getNotebookEditorFromEditorPane(editorService.activeEditorPane); + + if (!editor) { + return; + } + + const controller = editor.getContribution(TroubleshootController.id); + controller?.toggleLogging(); + } +}); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'notebook.inspectLayout', + title: 'Inspect Notebook Layout', + category: CATEGORIES.Developer, + f1: true + }); + } + + async run(accessor: ServicesAccessor): Promise { + const editorService = accessor.get(IEditorService); + const editor = getNotebookEditorFromEditorPane(editorService.activeEditorPane); + + if (!editor || !editor.viewModel) { + return; + } + + editor.viewModel.viewCells.forEach(cell => { + console.log(`cell#${cell.handle}`, cell.layoutInfo); + }); + } +}); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/undoRedo/notebookUndoRedo.ts b/src/vs/workbench/contrib/notebook/browser/contrib/undoRedo/notebookUndoRedo.ts index d22364d9..b198ae8d 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/undoRedo/notebookUndoRedo.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/undoRedo/notebookUndoRedo.ts @@ -25,7 +25,7 @@ class NotebookUndoRedoContribution extends Disposable { if (cellResources?.length) { editor?.viewModel?.viewCells.forEach(cell => { if (cell.cellKind === CellKind.Markdown && cellResources.find(resource => resource.fragment === cell.model.uri.fragment)) { - cell.editState = CellEditState.Editing; + cell.updateEditState(CellEditState.Editing, 'undo'); } }); @@ -44,7 +44,7 @@ class NotebookUndoRedoContribution extends Disposable { if (cellResources?.length) { editor?.viewModel?.viewCells.forEach(cell => { if (cell.cellKind === CellKind.Markdown && cellResources.find(resource => resource.fragment === cell.model.uri.fragment)) { - cell.editState = CellEditState.Editing; + cell.updateEditState(CellEditState.Editing, 'redo'); } }); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/undoRedo/test/notebookUndoRedo.test.ts b/src/vs/workbench/contrib/notebook/browser/contrib/undoRedo/test/notebookUndoRedo.test.ts new file mode 100644 index 00000000..5c435869 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/contrib/undoRedo/test/notebookUndoRedo.test.ts @@ -0,0 +1,132 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { IModeService } from 'vs/editor/common/services/modeService'; +import { CellEditType, CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { TestCell, withTestNotebook } from 'vs/workbench/contrib/notebook/test/testNotebookEditor'; + +suite('Notebook Undo/Redo', () => { + test('Basics', async function () { + await withTestNotebook( + [ + ['# header 1', 'markdown', CellKind.Markdown, [], {}], + ['body', 'markdown', CellKind.Markdown, [], {}], + ], + async (editor, accessor) => { + const modeService = accessor.get(IModeService); + const viewModel = editor.viewModel; + assert.strictEqual(viewModel.length, 2); + assert.strictEqual(viewModel.getVersionId(), 0); + assert.strictEqual(viewModel.getAlternativeId(), '0_0,1;1,1'); + + viewModel.notebookDocument.applyEdits([{ + editType: CellEditType.Replace, index: 0, count: 2, cells: [] + }], true, undefined, () => undefined, undefined, true); + assert.strictEqual(viewModel.length, 0); + assert.strictEqual(viewModel.getVersionId(), 1); + assert.strictEqual(viewModel.getAlternativeId(), '1_'); + + await viewModel.undo(); + assert.strictEqual(viewModel.length, 2); + assert.strictEqual(viewModel.getVersionId(), 2); + assert.strictEqual(viewModel.getAlternativeId(), '0_0,1;1,1'); + + await viewModel.redo(); + assert.strictEqual(viewModel.length, 0); + assert.strictEqual(viewModel.getVersionId(), 3); + assert.strictEqual(viewModel.getAlternativeId(), '1_'); + + viewModel.notebookDocument.applyEdits([{ + editType: CellEditType.Replace, index: 0, count: 0, cells: [ + new TestCell(viewModel.viewType, 3, '# header 2', 'markdown', CellKind.Code, [], modeService), + ] + }], true, undefined, () => undefined, undefined, true); + assert.strictEqual(viewModel.getVersionId(), 4); + assert.strictEqual(viewModel.getAlternativeId(), '4_2,1'); + + await viewModel.undo(); + assert.strictEqual(viewModel.getVersionId(), 5); + assert.strictEqual(viewModel.getAlternativeId(), '1_'); + } + ); + }); + + test('Invalid replace count should not throw', async function () { + await withTestNotebook( + [ + ['# header 1', 'markdown', CellKind.Markdown, [], {}], + ['body', 'markdown', CellKind.Markdown, [], {}], + ], + async (editor, accessor) => { + const modeService = accessor.get(IModeService); + const viewModel = editor.viewModel; + viewModel.notebookDocument.applyEdits([{ + editType: CellEditType.Replace, index: 0, count: 2, cells: [] + }], true, undefined, () => undefined, undefined, true); + + assert.doesNotThrow(() => { + viewModel.notebookDocument.applyEdits([{ + editType: CellEditType.Replace, index: 0, count: 2, cells: [ + new TestCell(viewModel.viewType, 3, '# header 2', 'markdown', CellKind.Code, [], modeService), + ] + }], true, undefined, () => undefined, undefined, true); + }); + } + ); + }); + + test('Replace beyond length', async function () { + await withTestNotebook( + [ + ['# header 1', 'markdown', CellKind.Markdown, [], {}], + ['body', 'markdown', CellKind.Markdown, [], {}], + ], + async (editor) => { + const viewModel = editor.viewModel; + viewModel.notebookDocument.applyEdits([{ + editType: CellEditType.Replace, index: 1, count: 2, cells: [] + }], true, undefined, () => undefined, undefined, true); + + assert.deepStrictEqual(viewModel.length, 1); + await viewModel.undo(); + assert.deepStrictEqual(viewModel.length, 2); + } + ); + }); + + test('Invalid replace count should not affect undo/redo', async function () { + await withTestNotebook( + [ + ['# header 1', 'markdown', CellKind.Markdown, [], {}], + ['body', 'markdown', CellKind.Markdown, [], {}], + ], + async (editor, accessor) => { + const modeService = accessor.get(IModeService); + const viewModel = editor.viewModel; + viewModel.notebookDocument.applyEdits([{ + editType: CellEditType.Replace, index: 0, count: 2, cells: [] + }], true, undefined, () => undefined, undefined, true); + + viewModel.notebookDocument.applyEdits([{ + editType: CellEditType.Replace, index: 0, count: 2, cells: [ + new TestCell(viewModel.viewType, 3, '# header 2', 'markdown', CellKind.Code, [], modeService), + ] + }], true, undefined, () => undefined, undefined, true); + + assert.deepStrictEqual(viewModel.length, 1); + + await viewModel.undo(); + await viewModel.undo(); + + assert.deepStrictEqual(viewModel.length, 2); + viewModel.notebookDocument.applyEdits([{ + editType: CellEditType.Replace, index: 1, count: 2, cells: [] + }], true, undefined, () => undefined, undefined, true); + assert.deepStrictEqual(viewModel.length, 1); + } + ); + }); +}); 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 6ee60d65..ac8c52e3 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/viewportCustomMarkdown/viewportCustomMarkdown.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/viewportCustomMarkdown/viewportCustomMarkdown.ts @@ -3,17 +3,22 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as DOM from 'vs/base/browser/dom'; import { RunOnceScheduler } from 'vs/base/common/async'; import { Disposable } from 'vs/base/common/lifecycle'; -import { CellEditState, INotebookEditor, INotebookEditorContribution } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellEditState, IInsetRenderOutput, INotebookEditor, INotebookEditorContribution, RenderOutputType } 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'; +import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel'; +import { BUILTIN_RENDERER_ID, CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { cellRangesToIndexes } from 'vs/workbench/contrib/notebook/common/notebookRange'; +import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; class NotebookClipboardContribution extends Disposable implements INotebookEditorContribution { static id: string = 'workbench.notebook.viewportCustomMarkdown'; private readonly _warmupViewport: RunOnceScheduler; - constructor(private readonly _notebookEditor: INotebookEditor) { + constructor(private readonly _notebookEditor: INotebookEditor, + @INotebookService private readonly _notebookService: INotebookService) { super(); this._warmupViewport = new RunOnceScheduler(() => this._warmupViewportNow(), 200); @@ -28,8 +33,40 @@ class NotebookClipboardContribution extends Disposable implements INotebookEdito cellRangesToIndexes(visibleRanges).forEach(index => { const cell = this._notebookEditor.viewModel?.viewCells[index]; - if (cell?.cellKind === CellKind.Markdown && cell?.editState === CellEditState.Preview) { + if (cell?.cellKind === CellKind.Markdown && cell?.getEditState() === CellEditState.Preview && !cell.metadata?.inputCollapsed) { this._notebookEditor.createMarkdownPreview(cell); + } else if (cell?.cellKind === CellKind.Code) { + const viewCell = (cell as CodeCellViewModel); + const outputs = viewCell.outputsViewModels; + for (let output of outputs) { + const [mimeTypes, pick] = output.resolveMimeTypes(this._notebookEditor.textModel!, undefined); + if (!mimeTypes.find(mimeType => mimeType.isTrusted) || mimeTypes.length === 0) { + continue; + } + + const pickedMimeTypeRenderer = mimeTypes[pick]; + + if (!pickedMimeTypeRenderer) { + return; + } + + if (pickedMimeTypeRenderer.rendererId === BUILTIN_RENDERER_ID) { + const renderer = this._notebookEditor.getOutputRenderer().getContribution(pickedMimeTypeRenderer.mimeType); + if (renderer?.getType() === RenderOutputType.Html) { + const renderResult = renderer!.render(output, output.model.outputs.filter(op => op.mime === pickedMimeTypeRenderer.mimeType), DOM.$(''), undefined) as IInsetRenderOutput; + this._notebookEditor.createOutput(viewCell, renderResult, 0); + } + return; + } + const renderer = this._notebookService.getRendererInfo(pickedMimeTypeRenderer.rendererId); + + if (!renderer) { + return; + } + + const result: IInsetRenderOutput = { type: RenderOutputType.Extension, renderer, source: output, mimeType: pickedMimeTypeRenderer.mimeType }; + this._notebookEditor.createOutput(viewCell, result, 0); + } } }); } diff --git a/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts b/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts index 299ba0f0..da02d040 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts @@ -481,9 +481,6 @@ abstract class AbstractElementRenderer extends Disposable { const keys = new Set([...Object.keys(newMetadataObj)]); for (let key of keys) { switch (key as keyof NotebookCellMetadata) { - case 'breakpointMargin': - case 'editable': - case 'hasExecutionOrder': case 'inputCollapsed': case 'outputCollapsed': // boolean @@ -495,7 +492,6 @@ abstract class AbstractElementRenderer extends Disposable { break; case 'executionOrder': - case 'lastRunDuration': // number if (typeof newMetadataObj[key] === 'number') { result[key] = newMetadataObj[key]; @@ -511,14 +507,6 @@ abstract class AbstractElementRenderer extends Disposable { result[key] = currentMetadata[key as keyof NotebookCellMetadata]; } break; - case 'statusMessage': - // string - if (typeof newMetadataObj[key] === 'string') { - result[key] = newMetadataObj[key]; - } else { - result[key] = currentMetadata[key as keyof NotebookCellMetadata]; - } - break; default: result[key] = newMetadataObj[key]; break; @@ -745,7 +733,7 @@ abstract class AbstractElementRenderer extends Disposable { this.templateData.bottomBorder.style.top = `${this.cell.layoutInfo.totalHeight - 32}px`; } - dispose() { + override dispose() { if (this._outputEditor) { this.cell.saveOutputEditorViewState(this._outputEditor.saveViewState()); } @@ -766,24 +754,25 @@ abstract class AbstractElementRenderer extends Disposable { } abstract class SingleSideDiffElement extends AbstractElementRenderer { + + override readonly cell: SingleSideDiffElementViewModel; + override readonly templateData: CellDiffSingleSideRenderTemplate; + constructor( - readonly notebookEditor: INotebookTextDiffEditor, - readonly cell: SingleSideDiffElementViewModel, - readonly templateData: CellDiffSingleSideRenderTemplate, - readonly style: 'left' | 'right' | 'full', - protected readonly instantiationService: IInstantiationService, - protected readonly modeService: IModeService, - protected readonly modelService: IModelService, - protected readonly textModelService: ITextModelService, - protected readonly contextMenuService: IContextMenuService, - protected readonly keybindingService: IKeybindingService, - protected readonly notificationService: INotificationService, - protected readonly menuService: IMenuService, - protected readonly contextKeyService: IContextKeyService, - protected readonly configurationService: IConfigurationService, - - - + notebookEditor: INotebookTextDiffEditor, + cell: SingleSideDiffElementViewModel, + templateData: CellDiffSingleSideRenderTemplate, + style: 'left' | 'right' | 'full', + instantiationService: IInstantiationService, + modeService: IModeService, + modelService: IModelService, + textModelService: ITextModelService, + contextMenuService: IContextMenuService, + keybindingService: IKeybindingService, + notificationService: INotificationService, + menuService: IMenuService, + contextKeyService: IContextKeyService, + configurationService: IConfigurationService, ) { super( notebookEditor, @@ -801,13 +790,15 @@ abstract class SingleSideDiffElement extends AbstractElementRenderer { contextKeyService, configurationService ); + this.cell = cell; + this.templateData = templateData; } init() { this._diagonalFill = this.templateData.diagonalFill; } - buildBody() { + override buildBody() { const body = this.templateData.body; this._diffEditorContainer = this.templateData.diffEditorContainer; body.classList.remove('left', 'right', 'full'); @@ -961,19 +952,19 @@ abstract class SingleSideDiffElement extends AbstractElementRenderer { export class DeletedElement extends SingleSideDiffElement { private _editor!: CodeEditorWidget; constructor( - readonly notebookEditor: INotebookTextDiffEditor, - readonly cell: SingleSideDiffElementViewModel, - readonly templateData: CellDiffSingleSideRenderTemplate, - @IModeService readonly modeService: IModeService, - @IModelService readonly modelService: IModelService, - @ITextModelService readonly textModelService: ITextModelService, - @IInstantiationService protected readonly instantiationService: IInstantiationService, - @IContextMenuService protected readonly contextMenuService: IContextMenuService, - @IKeybindingService protected readonly keybindingService: IKeybindingService, - @INotificationService protected readonly notificationService: INotificationService, - @IMenuService protected readonly menuService: IMenuService, - @IContextKeyService protected readonly contextKeyService: IContextKeyService, - @IConfigurationService protected readonly configurationService: IConfigurationService, + notebookEditor: INotebookTextDiffEditor, + cell: SingleSideDiffElementViewModel, + templateData: CellDiffSingleSideRenderTemplate, + @IModeService modeService: IModeService, + @IModelService modelService: IModelService, + @ITextModelService textModelService: ITextModelService, + @IInstantiationService instantiationService: IInstantiationService, + @IContextMenuService contextMenuService: IContextMenuService, + @IKeybindingService keybindingService: IKeybindingService, + @INotificationService notificationService: INotificationService, + @IMenuService menuService: IMenuService, + @IContextKeyService contextKeyService: IContextKeyService, + @IConfigurationService configurationService: IConfigurationService, ) { super(notebookEditor, cell, templateData, 'left', instantiationService, modeService, modelService, textModelService, contextMenuService, keybindingService, notificationService, menuService, contextKeyService, configurationService); @@ -1004,7 +995,7 @@ export class DeletedElement extends SingleSideDiffElement { } })); - originalCell.textModel.resolveTextModelRef().then(ref => { + this.textModelService.createModelReference(originalCell.uri).then(ref => { if (this._isDisposed) { return; } @@ -1101,7 +1092,7 @@ export class DeletedElement extends SingleSideDiffElement { } } - dispose() { + override dispose() { if (this._editor) { this.cell.saveSpirceEditorViewState(this._editor.saveViewState()); } @@ -1113,19 +1104,19 @@ export class DeletedElement extends SingleSideDiffElement { export class InsertElement extends SingleSideDiffElement { private _editor!: CodeEditorWidget; constructor( - readonly notebookEditor: INotebookTextDiffEditor, - readonly cell: SingleSideDiffElementViewModel, - readonly templateData: CellDiffSingleSideRenderTemplate, - @IInstantiationService protected readonly instantiationService: IInstantiationService, - @IModeService readonly modeService: IModeService, - @IModelService readonly modelService: IModelService, - @ITextModelService readonly textModelService: ITextModelService, - @IContextMenuService protected readonly contextMenuService: IContextMenuService, - @IKeybindingService protected readonly keybindingService: IKeybindingService, - @INotificationService protected readonly notificationService: INotificationService, - @IMenuService protected readonly menuService: IMenuService, - @IContextKeyService protected readonly contextKeyService: IContextKeyService, - @IConfigurationService protected readonly configurationService: IConfigurationService, + notebookEditor: INotebookTextDiffEditor, + cell: SingleSideDiffElementViewModel, + templateData: CellDiffSingleSideRenderTemplate, + @IInstantiationService instantiationService: IInstantiationService, + @IModeService modeService: IModeService, + @IModelService modelService: IModelService, + @ITextModelService textModelService: ITextModelService, + @IContextMenuService contextMenuService: IContextMenuService, + @IKeybindingService keybindingService: IKeybindingService, + @INotificationService notificationService: INotificationService, + @IMenuService menuService: IMenuService, + @IContextKeyService contextKeyService: IContextKeyService, + @IConfigurationService configurationService: IConfigurationService, ) { super(notebookEditor, cell, templateData, 'right', instantiationService, modeService, modelService, textModelService, contextMenuService, keybindingService, notificationService, menuService, contextKeyService, configurationService); } @@ -1157,7 +1148,7 @@ export class InsertElement extends SingleSideDiffElement { } })); - modifiedCell.textModel.resolveTextModelRef().then(ref => { + this.textModelService.createModelReference(modifiedCell.uri).then(ref => { if (this._isDisposed) { return; } @@ -1251,7 +1242,7 @@ export class InsertElement extends SingleSideDiffElement { }); } - dispose() { + override dispose() { if (this._editor) { this.cell.saveSpirceEditorViewState(this._editor.saveViewState()); } @@ -1267,22 +1258,27 @@ export class ModifiedElement extends AbstractElementRenderer { protected _toolbar!: ToolBar; protected _menu!: IMenu; + override readonly cell: SideBySideDiffElementViewModel; + override readonly templateData: CellDiffSideBySideRenderTemplate; + constructor( - readonly notebookEditor: INotebookTextDiffEditor, - readonly cell: SideBySideDiffElementViewModel, - readonly templateData: CellDiffSideBySideRenderTemplate, - @IInstantiationService protected readonly instantiationService: IInstantiationService, - @IModeService readonly modeService: IModeService, - @IModelService readonly modelService: IModelService, - @ITextModelService readonly textModelService: ITextModelService, - @IContextMenuService protected readonly contextMenuService: IContextMenuService, - @IKeybindingService protected readonly keybindingService: IKeybindingService, - @INotificationService protected readonly notificationService: INotificationService, - @IMenuService protected readonly menuService: IMenuService, - @IContextKeyService protected readonly contextKeyService: IContextKeyService, - @IConfigurationService protected readonly configurationService: IConfigurationService, + notebookEditor: INotebookTextDiffEditor, + cell: SideBySideDiffElementViewModel, + templateData: CellDiffSideBySideRenderTemplate, + @IInstantiationService instantiationService: IInstantiationService, + @IModeService modeService: IModeService, + @IModelService modelService: IModelService, + @ITextModelService textModelService: ITextModelService, + @IContextMenuService contextMenuService: IContextMenuService, + @IKeybindingService keybindingService: IKeybindingService, + @INotificationService notificationService: INotificationService, + @IMenuService menuService: IMenuService, + @IContextKeyService contextKeyService: IContextKeyService, + @IConfigurationService configurationService: IConfigurationService, ) { super(notebookEditor, cell, templateData, 'full', instantiationService, modeService, modelService, textModelService, contextMenuService, keybindingService, notificationService, menuService, contextKeyService, configurationService); + this.cell = cell; + this.templateData = templateData; } init() { } @@ -1525,8 +1521,8 @@ export class ModifiedElement extends AbstractElementRenderer { const originalCell = this.cell.original!; const modifiedCell = this.cell.modified!; - const originalRef = await originalCell.textModel.resolveTextModelRef(); - const modifiedRef = await modifiedCell.textModel.resolveTextModelRef(); + const originalRef = await this.textModelService.createModelReference(originalCell.uri); + const modifiedRef = await this.textModelService.createModelReference(modifiedCell.uri); if (this._isDisposed) { return; @@ -1598,7 +1594,7 @@ export class ModifiedElement extends AbstractElementRenderer { }); } - dispose() { + override dispose() { if (this._editor) { this.cell.saveSpirceEditorViewState(this._editor.saveViewState()); } diff --git a/src/vs/workbench/contrib/notebook/browser/diff/diffElementOutputs.ts b/src/vs/workbench/contrib/notebook/browser/diff/diffElementOutputs.ts index d0ad7693..160389b8 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/diffElementOutputs.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/diffElementOutputs.ts @@ -49,7 +49,7 @@ export class OutputElement extends Disposable { const outputItemDiv = document.createElement('div'); let result: IRenderOutput | undefined = undefined; - const [mimeTypes, pick] = this.output.resolveMimeTypes(this._notebookTextModel); + const [mimeTypes, pick] = this.output.resolveMimeTypes(this._notebookTextModel, undefined); const pickedMimeTypeRenderer = mimeTypes[pick]; if (mimeTypes.length > 1) { outputItemDiv.style.position = 'relative'; @@ -156,7 +156,7 @@ export class OutputElement extends Disposable { } private async pickActiveMimeTypeRenderer(notebookTextModel: NotebookTextModel, viewModel: ICellOutputViewModel) { - const [mimeTypes, currIndex] = viewModel.resolveMimeTypes(notebookTextModel); + const [mimeTypes, currIndex] = viewModel.resolveMimeTypes(notebookTextModel, undefined); const items = mimeTypes.filter(mimeType => mimeType.isTrusted).map((mimeType, index): IMimeTypeRenderer => ({ label: mimeType.mimeType, diff --git a/src/vs/workbench/contrib/notebook/browser/diff/diffElementViewModel.ts b/src/vs/workbench/contrib/notebook/browser/diff/diffElementViewModel.ts index 72c0e31b..925415cd 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/diffElementViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/diffElementViewModel.ts @@ -288,13 +288,17 @@ export class SideBySideDiffElementViewModel extends DiffElementViewModelBase { return this.mainDocumentTextModel; } + override readonly original: DiffNestedCellViewModel; + override readonly modified: DiffNestedCellViewModel; + override readonly type: 'unchanged' | 'modified'; + constructor( - readonly mainDocumentTextModel: NotebookTextModel, + mainDocumentTextModel: NotebookTextModel, readonly otherDocumentTextModel: NotebookTextModel, - readonly original: DiffNestedCellViewModel, - readonly modified: DiffNestedCellViewModel, - readonly type: 'unchanged' | 'modified', - readonly editorEventDispatcher: NotebookDiffEditorEventDispatcher + original: DiffNestedCellViewModel, + modified: DiffNestedCellViewModel, + type: 'unchanged' | 'modified', + editorEventDispatcher: NotebookDiffEditorEventDispatcher ) { super( mainDocumentTextModel, @@ -303,6 +307,10 @@ export class SideBySideDiffElementViewModel extends DiffElementViewModelBase { type, editorEventDispatcher); + this.original = original; + this.modified = modified; + this.type = type; + this.metadataFoldingState = PropertyFoldingState.Collapsed; this.outputFoldingState = PropertyFoldingState.Collapsed; @@ -411,15 +419,19 @@ export class SingleSideDiffElementViewModel extends DiffElementViewModelBase { } } + override readonly type: 'insert' | 'delete'; + constructor( - readonly mainDocumentTextModel: NotebookTextModel, + mainDocumentTextModel: NotebookTextModel, readonly otherDocumentTextModel: NotebookTextModel, - readonly original: DiffNestedCellViewModel | undefined, - readonly modified: DiffNestedCellViewModel | undefined, - readonly type: 'insert' | 'delete', - readonly editorEventDispatcher: NotebookDiffEditorEventDispatcher + original: DiffNestedCellViewModel | undefined, + modified: DiffNestedCellViewModel | undefined, + type: 'insert' | 'delete', + editorEventDispatcher: NotebookDiffEditorEventDispatcher ) { super(mainDocumentTextModel, original, modified, type, editorEventDispatcher); + this.type = type; + this._register(this.cellViewModel!.onDidChangeOutputLayout(() => { this._layout({ recomputeOutput: true }); })); @@ -481,11 +493,11 @@ export function getFormatedMetadataJSON(documentTextModel: NotebookTextModel, me let filteredMetadata: { [key: string]: any } = {}; if (documentTextModel) { - const transientMetadata = documentTextModel.transientOptions.transientMetadata; + const transientCellMetadata = documentTextModel.transientOptions.transientCellMetadata; const keys = new Set([...Object.keys(metadata)]); for (let key of keys) { - if (!(transientMetadata[key as keyof NotebookCellMetadata]) + if (!(transientCellMetadata[key as keyof NotebookCellMetadata]) ) { filteredMetadata[key] = metadata[key as keyof NotebookCellMetadata]; } diff --git a/src/vs/workbench/contrib/notebook/browser/diff/diffNestedCellViewModel.ts b/src/vs/workbench/contrib/notebook/browser/diff/diffNestedCellViewModel.ts index bd9f8d2a..84ef80fb 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/diffNestedCellViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/diffNestedCellViewModel.ts @@ -50,6 +50,17 @@ export class DiffNestedCellViewModel extends Disposable implements IDiffNestedCe this._hoveringOutput = v; this._onDidChangeState.fire({ outputIsHoveredChanged: true }); } + + private _focusOnOutput: boolean = false; + public get outputIsFocused(): boolean { + return this._focusOnOutput; + } + + public set outputIsFocused(v: boolean) { + this._focusOnOutput = v; + this._onDidChangeState.fire({ outputIsFocusedChanged: true }); + } + private _outputViewModels: ICellOutputViewModel[]; get outputsViewModels() { diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffActions.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffActions.ts index d4e03075..ededf656 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffActions.ts @@ -19,6 +19,7 @@ import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editor import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; +import { EditorOverride } from 'vs/platform/editor/common/editor'; // ActiveEditorContext.isEqualTo(SearchEditorConstants.SearchEditorID) @@ -51,7 +52,8 @@ registerAction2(class extends Action2 { }; const label = diffEditorInput.textDiffName; - await editorService.openEditor({ leftResource, rightResource, label, options }, viewColumnToEditorGroup(editorGroupService, undefined)); + const input = editorService.createEditorInput({ leftResource, rightResource, label, options }); + await editorService.openEditor(input, { override: EditorOverride.DISABLED }, viewColumnToEditorGroup(editorGroupService, undefined)); } } }); diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts index a8ed243a..03d33ccf 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts @@ -21,16 +21,16 @@ import { diffDiagonalFill, diffInserted, diffRemoved, editorBackground, focusBor import { INotebookEditorWorkerService } from 'vs/workbench/contrib/notebook/common/services/notebookWorkerService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; -import { BareFontInfo } from 'vs/editor/common/config/fontInfo'; +import { BareFontInfo, FontInfo } from 'vs/editor/common/config/fontInfo'; import { getPixelRatio, getZoomLevel } from 'vs/base/browser/browser'; import { CellEditState, ICellOutputViewModel, IDisplayOutputLayoutUpdateRequest, IGenericCellViewModel, IInsetRenderOutput, NotebookLayoutInfo, NOTEBOOK_DIFF_EDITOR_ID } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { DiffSide, DIFF_CELL_MARGIN, IDiffCellInfo, INotebookTextDiffEditor } from 'vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser'; import { Emitter, Event } from 'vs/base/common/event'; import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; -import { CellUri, INotebookDiffEditorModel } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellUri, INotebookDiffEditorModel, INotebookDiffResult } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { URI } from 'vs/base/common/uri'; -import { IDiffChange } from 'vs/base/common/diff/diff'; +import { IDiffChange, IDiffResult } from 'vs/base/common/diff/diff'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { OutputRenderer } from 'vs/workbench/contrib/notebook/browser/view/output/outputRenderer'; import { SequencerByKey } from 'vs/base/common/async'; @@ -40,6 +40,7 @@ import { DiffNestedCellViewModel } from 'vs/workbench/contrib/notebook/browser/d import { BackLayerWebView } from 'vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView'; import { CELL_OUTPUT_PADDING, MARKDOWN_PREVIEW_PADDING } from 'vs/workbench/contrib/notebook/browser/constants'; import { NotebookDiffEditorEventDispatcher, NotebookDiffLayoutChangedEvent } from 'vs/workbench/contrib/notebook/browser/diff/eventDispatcher'; +import { readFontInfo } from 'vs/editor/browser/config/configuration'; const $ = DOM.$; @@ -54,14 +55,14 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD private _modifiedWebview: BackLayerWebView | null = null; private _originalWebview: BackLayerWebView | null = null; private _webviewTransparentCover: HTMLElement | null = null; - private _fontInfo: BareFontInfo | undefined; + private _fontInfo: FontInfo | undefined; private readonly _onMouseUp = this._register(new Emitter<{ readonly event: MouseEvent; readonly target: DiffElementViewModelBase; }>()); public readonly onMouseUp = this._onMouseUp.event; private _eventDispatcher: NotebookDiffEditorEventDispatcher | undefined; protected _scopeContextKeyService!: IContextKeyService; private _model: INotebookDiffEditorModel | null = null; - private _modifiedResourceDisposableStore = new DisposableStore(); + private readonly _modifiedResourceDisposableStore = this._register(new DisposableStore()); private _outputRenderer: OutputRenderer; get textModel() { @@ -74,7 +75,7 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD protected _onDidDynamicOutputRendered = new Emitter<{ cell: IGenericCellViewModel, output: ICellOutputViewModel }>(); onDidDynamicOutputRendered = this._onDidDynamicOutputRendered.event; - private _localStore: DisposableStore = this._register(new DisposableStore()); + private readonly _localStore = this._register(new DisposableStore()); private _isDisposed: boolean = false; @@ -84,7 +85,7 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD constructor( @IInstantiationService readonly instantiationService: IInstantiationService, - @IThemeService readonly themeService: IThemeService, + @IThemeService themeService: IThemeService, @IContextKeyService readonly contextKeyService: IContextKeyService, @INotebookEditorWorkerService readonly notebookEditorWorkerService: INotebookEditorWorkerService, @IConfigurationService private readonly configurationService: IConfigurationService, @@ -93,13 +94,16 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD ) { super(NotebookTextDiffEditor.ID, telemetryService, themeService, storageService); const editorOptions = this.configurationService.getValue('editor'); - this._fontInfo = BareFontInfo.createFromRawSettings(editorOptions, getZoomLevel(), getPixelRatio()); + this._fontInfo = readFontInfo(BareFontInfo.createFromRawSettings(editorOptions, getZoomLevel(), getPixelRatio())); this._revealFirst = true; - this._register(this._modifiedResourceDisposableStore); this._outputRenderer = new OutputRenderer(this, this.instantiationService); } + toggleNotebookCellSelection(cell: IGenericCellViewModel) { + // throw new Error('Method not implemented.'); + } + focusNotebookCell(cell: IGenericCellViewModel, focus: 'output' | 'editor' | 'container'): void { // throw new Error('Method not implemented.'); } @@ -260,29 +264,28 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD removedItems.push(key); } else { const cellTop = this._list.getAbsoluteTopOfElement(value.cellInfo.diffElement); - if (activeWebview.shouldUpdateInset(cell, key, cellTop)) { - const outputIndex = cell.outputsViewModels.indexOf(key); - const outputOffset = cellTop + value.cellInfo.diffElement.getOutputOffsetInCell(diffSide, outputIndex); - - updateItems.push({ - output: key, - cellTop: cellTop, - outputOffset: outputOffset - }); - } + const outputIndex = cell.outputsViewModels.indexOf(key); + const outputOffset = value.cellInfo.diffElement.getOutputOffsetInCell(diffSide, outputIndex); + updateItems.push({ + cell, + output: key, + cellTop: cellTop, + outputOffset: outputOffset, + forceDisplay: false + }); } }); - removedItems.forEach(output => activeWebview.removeInset(output)); + activeWebview.removeInsets(removedItems); if (updateItems.length) { - activeWebview.updateViewScrollTop(-scrollTop, false, updateItems); + activeWebview.updateScrollTops(updateItems, []); } } } - async setInput(input: NotebookDiffEditorInput, options: EditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise { + override async setInput(input: NotebookDiffEditorInput, options: EditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise { await super.setInput(input, options, context, token); const model = await input.resolve(); @@ -299,6 +302,8 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD this._revealFirst = true; + this._modifiedResourceDisposableStore.clear(); + this._modifiedResourceDisposableStore.add(Event.any(this._model.original.notebook.onDidChangeContent, this._model.modified.notebook.onDidChangeContent)(e => { if (this._model !== null) { this.updateLayout(); @@ -306,7 +311,14 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD })); await this._createOriginalWebview(generateUuid(), this._model.original.resource); + if (this._originalWebview) { + this._modifiedResourceDisposableStore.add(this._originalWebview); + } await this._createModifiedWebview(generateUuid(), this._model.modified.resource); + if (this._modifiedWebview) { + this._modifiedResourceDisposableStore.add(this._modifiedWebview); + } + await this.updateLayout(); } @@ -359,11 +371,15 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD outputNodeLeftPadding: 32, previewNodePadding: MARKDOWN_PREVIEW_PADDING, leftMargin: 0, - cellMargin: 0, + rightMargin: 0, runGutter: 0 }; private async _createModifiedWebview(id: string, resource: URI): Promise { + if (this._modifiedWebview) { + this._modifiedWebview.dispose(); + } + this._modifiedWebview = this.instantiationService.createInstance(BackLayerWebView, this, id, resource, this.webviewOptions) as BackLayerWebView; // attach the webview container to the DOM tree first this._list.rowsContainer.insertAdjacentElement('afterbegin', this._modifiedWebview.element); @@ -373,6 +389,10 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD } private async _createOriginalWebview(id: string, resource: URI): Promise { + if (this._originalWebview) { + this._originalWebview.dispose(); + } + this._originalWebview = this.instantiationService.createInstance(BackLayerWebView, this, id, resource, this.webviewOptions) as BackLayerWebView; // attach the webview container to the DOM tree first this._list.rowsContainer.insertAdjacentElement('afterbegin', this._originalWebview.element); @@ -387,11 +407,65 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD } const diffResult = await this.notebookEditorWorkerService.computeDiff(this._model.original.resource, this._model.modified.resource); - const cellChanges = diffResult.cellsDiff.changes; + NotebookTextDiffEditor.prettyChanges(this._model, diffResult.cellsDiff); + const { viewModels, firstChangeIndex } = NotebookTextDiffEditor.computeDiff(this.instantiationService, this._model, this._eventDispatcher!, diffResult); + this._originalWebview?.removeInsets([...this._originalWebview?.insetMapping.keys()]); + this._modifiedWebview?.removeInsets([...this._modifiedWebview?.insetMapping.keys()]); + + this._diffElementViewModels = viewModels; + this._list.splice(0, this._list.length, this._diffElementViewModels); + + if (this._revealFirst && firstChangeIndex !== -1) { + this._revealFirst = false; + this._list.setFocus([firstChangeIndex]); + this._list.reveal(firstChangeIndex, 0.3); + } + } + + /** + * making sure that swapping cells are always translated to `insert+delete`. + */ + static prettyChanges(model: INotebookDiffEditorModel, diffResult: IDiffResult) { + const changes = diffResult.changes; + for (let i = 0; i < diffResult.changes.length - 1; i++) { + // then we know there is another change after current one + const curr = changes[i]; + const next = changes[i + 1]; + const x = curr.originalStart; + const y = curr.modifiedStart; + + if ( + curr.originalLength === 1 + && curr.modifiedLength === 0 + && next.originalStart === x + 2 + && next.originalLength === 0 + && next.modifiedStart === y + 1 + && next.modifiedLength === 1 + && model.original.notebook.cells[x].getHashValue() === model.modified.notebook.cells[y + 1].getHashValue() + && model.original.notebook.cells[x + 1].getHashValue() === model.modified.notebook.cells[y].getHashValue() + ) { + // this is a swap + curr.originalStart = x; + curr.originalLength = 0; + curr.modifiedStart = y; + curr.modifiedLength = 1; + + next.originalStart = x + 1; + next.originalLength = 1; + next.modifiedStart = y + 2; + next.modifiedLength = 0; + + i++; + } + } + } + + static computeDiff(instantiationService: IInstantiationService, model: INotebookDiffEditorModel, eventDispatcher: NotebookDiffEditorEventDispatcher, diffResult: INotebookDiffResult) { + const cellChanges = diffResult.cellsDiff.changes; const diffElementViewModels: DiffElementViewModelBase[] = []; - const originalModel = this._model.original.notebook; - const modifiedModel = this._model.modified.notebook; + const originalModel = model.original.notebook; + const modifiedModel = model.modified.notebook; let originalCellIndex = 0; let modifiedCellIndex = 0; @@ -406,12 +480,12 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD const modifiedCell = modifiedModel.cells[modifiedCellIndex + j]; if (originalCell.getHashValue() === modifiedCell.getHashValue()) { diffElementViewModels.push(new SideBySideDiffElementViewModel( - this._model.modified.notebook, - this._model.original.notebook, - this.instantiationService.createInstance(DiffNestedCellViewModel, originalCell), - this.instantiationService.createInstance(DiffNestedCellViewModel, modifiedCell), + model.modified.notebook, + model.original.notebook, + instantiationService.createInstance(DiffNestedCellViewModel, originalCell), + instantiationService.createInstance(DiffNestedCellViewModel, modifiedCell), 'unchanged', - this._eventDispatcher! + eventDispatcher )); } else { if (firstChangeIndex === -1) { @@ -419,17 +493,17 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD } diffElementViewModels.push(new SideBySideDiffElementViewModel( - this._model.modified.notebook, - this._model.original.notebook, - this.instantiationService.createInstance(DiffNestedCellViewModel, originalCell), - this.instantiationService.createInstance(DiffNestedCellViewModel, modifiedCell), + model.modified.notebook, + model.original.notebook, + instantiationService.createInstance(DiffNestedCellViewModel, originalCell), + instantiationService.createInstance(DiffNestedCellViewModel, modifiedCell), 'modified', - this._eventDispatcher! + eventDispatcher! )); } } - const modifiedLCS = this._computeModifiedLCS(change, originalModel, modifiedModel); + const modifiedLCS = NotebookTextDiffEditor.computeModifiedLCS(instantiationService, change, originalModel, modifiedModel, eventDispatcher); if (modifiedLCS.length && firstChangeIndex === -1) { firstChangeIndex = diffElementViewModels.length; } @@ -441,46 +515,35 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD for (let i = originalCellIndex; i < originalModel.cells.length; i++) { diffElementViewModels.push(new SideBySideDiffElementViewModel( - this._model.modified.notebook, - this._model.original.notebook, - this.instantiationService.createInstance(DiffNestedCellViewModel, originalModel.cells[i]), - this.instantiationService.createInstance(DiffNestedCellViewModel, modifiedModel.cells[i - originalCellIndex + modifiedCellIndex]), + model.modified.notebook, + model.original.notebook, + instantiationService.createInstance(DiffNestedCellViewModel, originalModel.cells[i]), + instantiationService.createInstance(DiffNestedCellViewModel, modifiedModel.cells[i - originalCellIndex + modifiedCellIndex]), 'unchanged', - this._eventDispatcher! + eventDispatcher )); } - this._originalWebview?.insetMapping.forEach((value, key) => { - this._originalWebview?.removeInset(key); - }); - - this._modifiedWebview?.insetMapping.forEach((value, key) => { - this._modifiedWebview?.removeInset(key); - }); - - this._diffElementViewModels = diffElementViewModels; - this._list.splice(0, this._list.length, diffElementViewModels); - - if (this._revealFirst && firstChangeIndex !== -1) { - this._revealFirst = false; - this._list.setFocus([firstChangeIndex]); - this._list.reveal(firstChangeIndex, 0.3); - } + return { + viewModels: diffElementViewModels, + firstChangeIndex + }; } - private _computeModifiedLCS(change: IDiffChange, originalModel: NotebookTextModel, modifiedModel: NotebookTextModel) { + static computeModifiedLCS(instantiationService: IInstantiationService, change: IDiffChange, originalModel: NotebookTextModel, modifiedModel: NotebookTextModel, eventDispatcher: NotebookDiffEditorEventDispatcher) { const result: DiffElementViewModelBase[] = []; // modified cells const modifiedLen = Math.min(change.originalLength, change.modifiedLength); for (let j = 0; j < modifiedLen; j++) { + const isTheSame = originalModel.cells[change.originalStart + j].getHashValue() === modifiedModel.cells[change.modifiedStart + j].getHashValue(); result.push(new SideBySideDiffElementViewModel( modifiedModel, originalModel, - this.instantiationService.createInstance(DiffNestedCellViewModel, originalModel.cells[change.originalStart + j]), - this.instantiationService.createInstance(DiffNestedCellViewModel, modifiedModel.cells[change.modifiedStart + j]), - 'modified', - this._eventDispatcher! + instantiationService.createInstance(DiffNestedCellViewModel, originalModel.cells[change.originalStart + j]), + instantiationService.createInstance(DiffNestedCellViewModel, modifiedModel.cells[change.modifiedStart + j]), + isTheSame ? 'unchanged' : 'modified', + eventDispatcher )); } @@ -489,10 +552,10 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD result.push(new SingleSideDiffElementViewModel( originalModel, modifiedModel, - this.instantiationService.createInstance(DiffNestedCellViewModel, originalModel.cells[change.originalStart + j]), + instantiationService.createInstance(DiffNestedCellViewModel, originalModel.cells[change.originalStart + j]), undefined, 'delete', - this._eventDispatcher! + eventDispatcher )); } @@ -502,15 +565,38 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD modifiedModel, originalModel, undefined, - this.instantiationService.createInstance(DiffNestedCellViewModel, modifiedModel.cells[change.modifiedStart + j]), + instantiationService.createInstance(DiffNestedCellViewModel, modifiedModel.cells[change.modifiedStart + j]), 'insert', - this._eventDispatcher! + eventDispatcher )); } return result; } + scheduleOutputHeightAck(cellInfo: IDiffCellInfo, outputId: string, height: number) { + const diffElement = cellInfo.diffElement; + // const activeWebview = diffSide === DiffSide.Modified ? this._modifiedWebview : this._originalWebview; + let diffSide = DiffSide.Original; + + if (diffElement instanceof SideBySideDiffElementViewModel) { + const info = CellUri.parse(cellInfo.cellUri); + if (!info) { + return; + } + + diffSide = info.notebook.toString() === this._model?.original.resource.toString() ? DiffSide.Original : DiffSide.Modified; + } else { + diffSide = diffElement.type === 'insert' ? DiffSide.Modified : DiffSide.Original; + } + + const webview = diffSide === DiffSide.Modified ? this._modifiedWebview : this._originalWebview; + + DOM.scheduleAtNextAnimationFrame(() => { + webview?.ackHeight(cellInfo.cellId, outputId, height); + }, 10); + } + private pendingLayouts = new WeakMap(); @@ -555,10 +641,15 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD await activeWebview.createOutput({ diffElement: cellDiffViewModel, cellHandle: cellViewModel.handle, cellId: cellViewModel.id, cellUri: cellViewModel.uri }, output, cellTop, getOffset()); } else { const cellTop = this._list.getAbsoluteTopOfElement(cellDiffViewModel); - const scrollTop = this._list.scrollTop; const outputIndex = cellViewModel.outputsViewModels.indexOf(output.source); - const outputOffset = cellTop + cellDiffViewModel.getOutputOffsetInCell(diffSide, outputIndex); - activeWebview.updateViewScrollTop(-scrollTop, true, [{ output: output.source, cellTop, outputOffset }]); + const outputOffset = cellDiffViewModel.getOutputOffsetInCell(diffSide, outputIndex); + activeWebview.updateScrollTops([{ + cell: cellViewModel, + output: output.source, + cellTop, + outputOffset, + forceDisplay: true + }], []); } }); } @@ -586,7 +677,7 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD return; } - activeWebview.removeInset(displayOutput); + activeWebview.removeInsets([displayOutput]); }); } @@ -602,10 +693,15 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD } const cellTop = this._list.getAbsoluteTopOfElement(cellDiffViewModel); - const scrollTop = this._list.scrollTop; const outputIndex = cellViewModel.outputsViewModels.indexOf(displayOutput); - const outputOffset = cellTop + cellDiffViewModel.getOutputOffsetInCell(diffSide, outputIndex); - activeWebview.updateViewScrollTop(-scrollTop, true, [{ output: displayOutput, cellTop, outputOffset }]); + const outputOffset = cellDiffViewModel.getOutputOffsetInCell(diffSide, outputIndex); + activeWebview.updateScrollTops([{ + cell: cellViewModel, + output: displayOutput, + cellTop, + outputOffset, + forceDisplay: true, + }], []); }); } @@ -628,19 +724,19 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD return this._overflowContainer; } - getControl(): NotebookEditorWidget | undefined { + override getControl(): NotebookEditorWidget | undefined { return undefined; } - setEditorVisible(visible: boolean, group: IEditorGroup | undefined): void { + override setEditorVisible(visible: boolean, group: IEditorGroup | undefined): void { super.setEditorVisible(visible, group); } - focus() { + override focus() { super.focus(); } - clearInput(): void { + override clearInput(): void { super.clearInput(); this._modifiedResourceDisposableStore.clear(); @@ -748,7 +844,7 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD this._eventDispatcher?.emit([new NotebookDiffLayoutChangedEvent({ width: true, fontInfo: true }, this.getLayoutInfo())]); } - dispose() { + override dispose() { this._isDisposed = true; super.dispose(); } diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffList.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffList.ts index ce2bc9c6..4d5dd5f0 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffList.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffList.ts @@ -321,7 +321,7 @@ export class NotebookTextDiffList extends WorkbenchList( +export const notebookMarkupRendererExtensionPoint = ExtensionsRegistry.registerExtensionPoint( { - extensionPoint: 'notebookMarkdownRenderer', - jsonSchema: notebookMarkdownRendererContribution + extensionPoint: 'notebookMarkupRenderers', + jsonSchema: notebookMarkupRendererContribution }); diff --git a/src/vs/workbench/contrib/notebook/browser/media/notebook.css b/src/vs/workbench/contrib/notebook/browser/media/notebook.css index 17b51160..128c3f17 100644 --- a/src/vs/workbench/contrib/notebook/browser/media/notebook.css +++ b/src/vs/workbench/contrib/notebook/browser/media/notebook.css @@ -349,7 +349,7 @@ right: 0; } -.monaco-workbench.hc-black .notebookOverlay .monaco-list-row .cell-editor-focus .cell-editor-part:before { +.monaco-workbench.hc-black .notebookOverlay .monaco-list-row.focused .cell-editor-focus .cell-editor-part:before { outline-style: dashed; } @@ -359,13 +359,13 @@ } .monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-title-toolbar { - visibility: hidden; + opacity: 0; display: inline-flex; position: absolute; height: 26px; top: -14px; /* this lines up the bottom toolbar border with the current line when on line 01 */ - z-index: 30; + z-index: 50; } .monaco-workbench .notebookOverlay.cell-title-toolbar-right > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-title-toolbar { @@ -407,6 +407,7 @@ font-size: 12px; display: flex; position: relative; + overflow: hidden; } .monaco-workbench .notebookOverlay.cell-statusbar-hidden .cell-statusbar-container { @@ -420,7 +421,6 @@ .monaco-workbench .notebookOverlay .cell-statusbar-container .cell-status-left, .monaco-workbench .notebookOverlay .cell-statusbar-container .cell-status-right { - padding-right: 12px; display: flex; z-index: 26; } @@ -441,65 +441,42 @@ white-space: pre; height: 21px; /* Editor outline is -1px in, don't overlap */ - padding: 0px 6px; + margin: 0px 3px; + padding: 0px 3px; + overflow: hidden; + text-overflow: clip; } .monaco-workbench .notebookOverlay .cell-statusbar-container .cell-status-item.cell-status-item-has-command { cursor: pointer; } -.monaco-workbench .notebookOverlay .cell-statusbar-container .cell-language-picker { - cursor: pointer; -} - -.monaco-workbench .notebookOverlay .cell-statusbar-container .cell-run-duration { - margin-right: 8px; -} - -.monaco-workbench .notebookOverlay .cell-statusbar-container .cell-run-duration, -.monaco-workbench .notebookOverlay .cell-statusbar-container .cell-status-message { - display: flex; - align-items: center; -} - -.monaco-workbench .notebookOverlay .cell-statusbar-container .cell-status-message { - margin-right: 6px; -} - -.monaco-workbench .notebookOverlay .cell-statusbar-container .cell-run-status { - height: 100%; - display: flex; - align-items: center; - margin-left: 18px; - width: 18px; - margin-right: 2px; -} - -.monaco-workbench .notebookOverlay .cell-statusbar-container .cell-status-left > div:first-child { - margin-left: 18px; +.monaco-workbench .notebookOverlay .cell-statusbar-container .cell-status-left > .cell-contributed-items { + margin-left: 10px; } .monaco-workbench .notebookOverlay .cell-statusbar-container .codicon { font-size: 14px; + color: unset; /* Inherit from parent cell-status-item */ } -.monaco-workbench .notebookOverlay .cell-status-placeholder { - position: absolute; - left: 18px; - display: flex; - align-items: center; - bottom: 0px; - top: 0px; +.monaco-workbench .notebookOverlay .cell-statusbar-container .cell-status-item-show-when-active { + display: none; } + +.monaco-workbench .notebookOverlay .cell-statusbar-container.is-active-cell .cell-status-item-show-when-active { + display: initial; +} + .monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .run-button-container { - position: relative; + position: absolute; flex-shrink: 0; - z-index: 27; - /* Above the drag handle */ + z-index: 27; /* Above the drag handle */ } .monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .run-button-container .monaco-toolbar { visibility: hidden; + height: initial; } .monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .run-button-container .monaco-toolbar .codicon { @@ -557,11 +534,12 @@ .monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row:hover .cell-has-toolbar-actions .cell-title-toolbar, .monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .markdown-cell-hover.cell-has-toolbar-actions .cell-title-toolbar, .monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-has-toolbar-actions.cell-output-hover .cell-title-toolbar, -.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-has-toolbar-actions:hover .cell-title-toolbar { - visibility: visible; +.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-has-toolbar-actions:hover .cell-title-toolbar, +.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-title-toolbar:hover, +.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-toolbar-dropdown-active .cell-title-toolbar { + opacity: 1; } - .monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list:not(.element-focused):focus:before { outline: none !important; } @@ -577,6 +555,16 @@ bottom: 0px; } +.monaco-workbench .notebookOverlay .monaco-list .webview-backed-markdown-cell .cell-focus-indicator-side { + /* Disable pointer events for the folding container */ + pointer-events: none; +} + +.monaco-workbench .notebookOverlay .monaco-list .webview-backed-markdown-cell .cell-focus-indicator-side .notebook-folding-indicator { + /* But allow clicking on the folding indicator itself */ + pointer-events: all; +} + .monaco-workbench .notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator-top, .monaco-workbench .notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator-bottom { width: 100%; @@ -648,10 +636,11 @@ align-items: center; justify-content: center; z-index: 28; /* over the focus outline on the editor, below the title toolbar */ - width: 100%; + width: calc(100% - 32px); opacity: 0; transition: opacity 0.2s ease-in-out; padding: 0; + margin: 0 16px 0 16px; } .monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.cell-drag-image .cell-bottom-toolbar-container { @@ -692,6 +681,8 @@ margin: 0px; display: inline-flex; padding: 0px 4px; + border-radius: 0; + align-items: center; } .monaco-workbench .notebookOverlay .cell-list-top-cell-toolbar-container .monaco-toolbar .action-label .codicon, @@ -919,7 +910,8 @@ width: 1px !important; height: 16px !important; margin: 5px 4px !important; - cursor: none; + cursor: default; + min-width: 1px !important; } .monaco-workbench .notebookOverlay .monaco-list .monaco-list-row .cell-decoration { @@ -936,6 +928,10 @@ margin-left: 4px; } +.cell-contributed-items.cell-contributed-items-right { + flex-direction: row-reverse; +} + .monaco-workbench .notebookOverlay .output .error::-webkit-scrollbar, .monaco-workbench .notebookOverlay .output-plaintext::-webkit-scrollbar { width: 10px; /* width of the entire scrollbar */ @@ -950,6 +946,11 @@ .monaco-workbench .notebookOverlay .output .error, .monaco-workbench .notebookOverlay .output .output-plaintext { - margin: 4px 0; overflow-x: auto; } + +/* high contrast border for multi-select */ +.hc-black .notebookOverlay .monaco-list.selection-multiple:focus-within .monaco-list-row.selected:not(.focused) .cell-focus-indicator-top:before { border-top-style: dotted; } +.hc-black .notebookOverlay .monaco-list.selection-multiple:focus-within .monaco-list-row.selected:not(.focused) .cell-focus-indicator-bottom:before { border-bottom-style: dotted; } +.hc-black .notebookOverlay .monaco-list.selection-multiple:focus-within .monaco-list-row.selected:not(.focused) .cell-inner-container:not(.cell-editor-focus) .cell-focus-indicator-left:before { border-left-style: dotted; } +.hc-black .notebookOverlay .monaco-list.selection-multiple:focus-within .monaco-list-row.selected:not(.focused) .cell-inner-container:not(.cell-editor-focus) .cell-focus-indicator-right:before { border-right-style: dotted; } diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index 8f277fae..2d2887db 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { coalesce, distinct } from 'vs/base/common/arrays'; import { Schemas } from 'vs/base/common/network'; import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; import { parse } from 'vs/base/common/marshalling'; @@ -16,30 +15,23 @@ import { IModeService } from 'vs/editor/common/services/modeService'; import { ITextModelContentProvider, ITextModelService } from 'vs/editor/common/services/resolverService'; import * as nls from 'vs/nls'; import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; -import { IEditorOptions, ITextEditorOptions, IResourceEditorInput, EditorOverride } from 'vs/platform/editor/common/editor'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { Registry } from 'vs/platform/registry/common/platform'; -import { EditorDescriptor, EditorsAssociations, editorsAssociationsSettingId, Extensions as EditorExtensions, IEditorRegistry } from 'vs/workbench/browser/editor'; +import { EditorDescriptor, IEditorRegistry } from 'vs/workbench/browser/editor'; import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; -import { EditorInput, Extensions as EditorInputExtensions, ICustomEditorInputFactory, IEditorInput, IEditorInputFactory, IEditorInputFactoryRegistry } from 'vs/workbench/common/editor'; -import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; +import { EditorInput, ICustomEditorInputFactory, IEditorInput, IEditorInputSerializer, IEditorInputFactoryRegistry, IEditorInputWithOptions, EditorExtensions } from 'vs/workbench/common/editor'; +import { IWorkingCopyBackupService } from 'vs/workbench/services/workingCopy/common/workingCopyBackup'; import { NotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookEditor'; -import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/browser/notebookEditorInput'; +import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { NotebookService } from 'vs/workbench/contrib/notebook/browser/notebookServiceImpl'; -import { CellKind, CellToolbarLocKey, CellUri, DisplayOrderKey, getCellUndoRedoComparisonKey, NotebookDocumentBackupData, NotebookEditorPriority, NotebookTextDiffEditorPreview, ShowCellStatusBarKey } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { IEditorService, IOpenEditorOverride } from 'vs/workbench/services/editor/common/editorService'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { CustomEditorInfo } from 'vs/workbench/contrib/customEditor/common/customEditor'; -import { IN_NOTEBOOK_TEXT_DIFF_EDITOR, NotebookEditorOptions, NOTEBOOK_DIFF_EDITOR_ID, NOTEBOOK_EDITOR_ID, NOTEBOOK_EDITOR_OPEN } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellKind, CellToolbarLocKey, CellUri, DisplayOrderKey, ExperimentalUseMarkdownRenderer, getCellUndoRedoComparisonKey, IResolvedNotebookEditorModel, NotebookDocumentBackupData, NotebookTextDiffEditorPreview, NOTEBOOK_WORKING_COPY_TYPE_PREFIX, ShowCellStatusBarKey } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; -import { INotebookEditorModelResolverService, NotebookModelResolverService } from 'vs/workbench/contrib/notebook/common/notebookEditorModelResolverService'; -import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; -import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; +import { INotebookEditorModelResolverService } from 'vs/workbench/contrib/notebook/common/notebookEditorModelResolverService'; import { NotebookDiffEditorInput } from 'vs/workbench/contrib/notebook/browser/notebookDiffEditorInput'; import { NotebookTextDiffEditor } from 'vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor'; import { INotebookEditorWorkerService } from 'vs/workbench/contrib/notebook/common/services/notebookWorkerService'; @@ -50,11 +42,15 @@ import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/no import { NotebookEditorWidgetService } from 'vs/workbench/contrib/notebook/browser/notebookEditorServiceImpl'; import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; -import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { Event } from 'vs/base/common/event'; -import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; -import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { getFormatedMetadataJSON } from 'vs/workbench/contrib/notebook/browser/diff/diffElementViewModel'; +import { NotebookModelResolverServiceImpl } from 'vs/workbench/contrib/notebook/common/notebookEditorModelResolverServiceImpl'; +import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { NotebookKernelService } from 'vs/workbench/contrib/notebook/browser/notebookKernelServiceImpl'; +import { IWorkingCopyIdentifier, NO_TYPE_ID } from 'vs/workbench/services/workingCopy/common/workingCopy'; +import { EditorOverride } from 'vs/platform/editor/common/editor'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { IWorkingCopyEditorService } from 'vs/workbench/services/workingCopy/common/workingCopyEditorService'; // Editor Contribution import 'vs/workbench/contrib/notebook/browser/contrib/clipboard/notebookClipboard'; @@ -62,12 +58,18 @@ import 'vs/workbench/contrib/notebook/browser/contrib/coreActions'; import 'vs/workbench/contrib/notebook/browser/contrib/find/findController'; import 'vs/workbench/contrib/notebook/browser/contrib/fold/folding'; import 'vs/workbench/contrib/notebook/browser/contrib/format/formatting'; +import 'vs/workbench/contrib/notebook/browser/contrib/layout/layoutActions'; import 'vs/workbench/contrib/notebook/browser/contrib/marker/markerProvider'; +import 'vs/workbench/contrib/notebook/browser/contrib/navigation/arrow'; import 'vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline'; +import 'vs/workbench/contrib/notebook/browser/contrib/statusBar/statusBarProviders'; +import 'vs/workbench/contrib/notebook/browser/contrib/statusBar/contributedStatusBarItemController'; +import 'vs/workbench/contrib/notebook/browser/contrib/statusBar/executionStatusBarItemController'; 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'; +import 'vs/workbench/contrib/notebook/browser/contrib/troubleshoot/layout'; // Diff Editor Contribution @@ -100,7 +102,7 @@ Registry.as(EditorExtensions.Editors).registerEditor( ] ); -class NotebookDiffEditorFactory implements IEditorInputFactory { +class NotebookDiffEditorSerializer implements IEditorInputSerializer { canSerialize(): boolean { return true; } @@ -139,7 +141,7 @@ class NotebookDiffEditorFactory implements IEditorInputFactory { } } -class NotebookEditorFactory implements IEditorInputFactory { +class NotebookEditorSerializer implements IEditorInputSerializer { canSerialize(): boolean { return true; } @@ -167,19 +169,19 @@ class NotebookEditorFactory implements IEditorInputFactory { } } -Registry.as(EditorInputExtensions.EditorInputFactories).registerEditorInputFactory( +Registry.as(EditorExtensions.EditorInputFactories).registerEditorInputSerializer( NotebookEditorInput.ID, - NotebookEditorFactory + NotebookEditorSerializer ); -Registry.as(EditorInputExtensions.EditorInputFactories).registerCustomEditorInputFactory( +Registry.as(EditorExtensions.EditorInputFactories).registerCustomEditorInputFactory( Schemas.vscodeNotebook, new class implements ICustomEditorInputFactory { async createCustomEditorInput(resource: URI, instantiationService: IInstantiationService): Promise { return instantiationService.invokeFunction(async accessor => { - const backupFileService = accessor.get(IBackupFileService); + const workingCopyBackupService = accessor.get(IWorkingCopyBackupService); - const backup = await backupFileService.resolve(resource); + const backup = await workingCopyBackupService.resolve({ resource, typeId: NO_TYPE_ID }); if (!backup?.meta) { throw new Error(`No backup found for Notebook editor: ${resource}`); } @@ -191,7 +193,7 @@ Registry.as(EditorInputExtensions.EditorInputFactor canResolveBackup(editorInput: IEditorInput, backupResource: URI): boolean { if (editorInput instanceof NotebookEditorInput) { - if (isEqual(editorInput.resource.with({ scheme: Schemas.vscodeNotebook }), backupResource)) { + if (isEqual(URI.from({ scheme: Schemas.vscodeNotebook, path: editorInput.resource.toString() }), backupResource)) { return true; } } @@ -201,305 +203,22 @@ Registry.as(EditorInputExtensions.EditorInputFactor } ); -Registry.as(EditorInputExtensions.EditorInputFactories).registerEditorInputFactory( +Registry.as(EditorExtensions.EditorInputFactories).registerEditorInputSerializer( NotebookDiffEditorInput.ID, - NotebookDiffEditorFactory + NotebookDiffEditorSerializer ); export class NotebookContribution extends Disposable implements IWorkbenchContribution { - private _notebookEditorIsOpen: IContextKey; - private _notebookDiffEditorIsOpen: IContextKey; - constructor( - @IContextKeyService private readonly contextKeyService: IContextKeyService, - @IEditorService private readonly editorService: IEditorService, - @INotebookService private readonly notebookService: INotebookService, - @IInstantiationService private readonly instantiationService: IInstantiationService, - @IConfigurationService private readonly configurationService: IConfigurationService, - @IAccessibilityService private readonly _accessibilityService: IAccessibilityService, - @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, @IUndoRedoService undoRedoService: IUndoRedoService, ) { super(); - this._notebookEditorIsOpen = NOTEBOOK_EDITOR_OPEN.bindTo(this.contextKeyService); - this._notebookDiffEditorIsOpen = IN_NOTEBOOK_TEXT_DIFF_EDITOR.bindTo(this.contextKeyService); - this._register(undoRedoService.registerUriComparisonKeyComputer(CellUri.scheme, { getComparisonKey: (uri: URI): string => { return getCellUndoRedoComparisonKey(uri); } })); - - this._register(this.editorService.overrideOpenEditor({ - getEditorOverrides: (resource: URI, options: IEditorOptions | undefined, group: IEditorGroup | undefined) => { - - const currentEditorsForResource = group && this.editorService.findEditors(resource, group); - const currentEditorForResource = currentEditorsForResource && currentEditorsForResource.length ? currentEditorsForResource[0] : undefined; - - const associatedEditors = distinct([ - ...this.getUserAssociatedNotebookEditors(resource), - ...this.getContributedEditors(resource) - ], editor => editor.id).sort((a, b) => { - // if a content provider is exclusive, it has higher order - if (a.exclusive && b.exclusive) { - return 0; - } - - if (a.exclusive) { - return -1; - } - - if (b.exclusive) { - return 1; - } - - return 0; - }); - - return associatedEditors.map(info => { - return { - label: info.displayName, - id: info.id, - active: currentEditorForResource instanceof NotebookEditorInput && currentEditorForResource.viewType === info.id, - detail: info.providerDisplayName - }; - }); - }, - open: (editor, options, group) => { - return this.onEditorOpening(editor, options, group); - } - })); - - this.editorGroupService.whenRestored.then(() => this._updateContextKeys()); - this._register(this.editorService.onDidActiveEditorChange(() => this._updateContextKeys())); - this._register(this.editorService.onDidVisibleEditorsChange(() => this._updateContextKeys())); - - this._register(this.editorGroupService.onDidAddGroup(() => this._updateContextKeys())); - this._register(this.editorGroupService.onDidRemoveGroup(() => this._updateContextKeys())); - } - - private _updateContextKeys() { - this._notebookEditorIsOpen.set(this.editorService.activeEditorPane?.getId() === NOTEBOOK_EDITOR_ID); - this._notebookDiffEditorIsOpen.set(this.editorService.activeEditorPane?.getId() === NOTEBOOK_DIFF_EDITOR_ID); - } - - getUserAssociatedEditors(resource: URI) { - const rawAssociations = this.configurationService.getValue(editorsAssociationsSettingId) || []; - - return coalesce(rawAssociations - .filter(association => CustomEditorInfo.selectorMatches(association, resource))); - } - - getUserAssociatedNotebookEditors(resource: URI) { - const rawAssociations = this.configurationService.getValue(editorsAssociationsSettingId) || []; - - return coalesce(rawAssociations - .filter(association => CustomEditorInfo.selectorMatches(association, resource)) - .map(association => this.notebookService.getContributedNotebookProvider(association.viewType))); - } - - getContributedEditors(resource: URI) { - return this.notebookService.getContributedNotebookProviders(resource); - } - - private onEditorOpening(originalInput: IEditorInput, options: IEditorOptions | ITextEditorOptions | undefined, group: IEditorGroup): IOpenEditorOverride | undefined { - - let id = typeof options?.override === 'string' ? options.override : undefined; - if (id === undefined && originalInput.resource?.scheme === Schemas.untitled) { - return undefined; - } - - if (originalInput instanceof DiffEditorInput && this.configurationService.getValue(NotebookTextDiffEditorPreview) && !this._accessibilityService.isScreenReaderOptimized()) { - return this._handleDiffEditorInput(originalInput, options, group); - } - - if (!originalInput.resource) { - return undefined; - } - - // Run reopen with ... - if (id) { - // from the editor tab context menu - if (originalInput instanceof NotebookEditorInput) { - if (originalInput.viewType === id) { - // reopen with the same type - return undefined; - } else { - return { - override: (async () => { - const notebookInput = NotebookEditorInput.create(this.instantiationService, originalInput.resource, id); - const originalEditorIndex = group.getIndexOfEditor(originalInput); - - await group.closeEditor(originalInput); - originalInput.dispose(); - const newEditor = await group.openEditor(notebookInput, { ...options, index: originalEditorIndex, override: EditorOverride.DISABLED }); - if (newEditor) { - return newEditor; - } else { - return undefined; - } - })() - }; - } - } else { - // from the file explorer - const existingEditors = this.editorService.findEditors(originalInput.resource, group).filter(editor => editor instanceof NotebookEditorInput) as NotebookEditorInput[]; - - if (existingEditors.length) { - // there are notebook editors with the same resource - - if (existingEditors.find(editor => editor.viewType === id)) { - return { override: this.editorService.openEditor(existingEditors.find(editor => editor.viewType === id)!, { ...options, override: EditorOverride.DISABLED }, group) }; - } else { - return { - override: (async () => { - const firstEditor = existingEditors[0]!; - const originalEditorIndex = group.getIndexOfEditor(firstEditor); - - await group.closeEditor(firstEditor); - firstEditor.dispose(); - const notebookInput = NotebookEditorInput.create(this.instantiationService, originalInput.resource!, id); - const newEditor = await group.openEditor(notebookInput, { ...options, index: originalEditorIndex, override: EditorOverride.DISABLED }); - - if (newEditor) { - return newEditor; - } else { - return undefined; - } - })() - }; - } - } - } - } - - // Click on the editor tab - if (id === undefined && originalInput instanceof NotebookEditorInput) { - const existingEditors = this.editorService.findEditors(originalInput.resource, group).filter(editor => editor instanceof NotebookEditorInput && editor === originalInput); - - if (existingEditors.length) { - // same - return undefined; - } - } - - if (originalInput instanceof NotebookDiffEditorInput) { - return undefined; - } - - let notebookUri: URI = originalInput.resource; - let cellOptions: IResourceEditorInput | undefined; - - const data = CellUri.parse(originalInput.resource); - if (data) { - notebookUri = data.notebook; - cellOptions = { resource: originalInput.resource, options }; - } - - if (id === undefined && originalInput instanceof ResourceEditorInput) { - const exitingNotebookEditor = group.editors.find(editor => editor instanceof NotebookEditorInput && isEqual(editor.resource, notebookUri)); - id = exitingNotebookEditor?.viewType; - } - - if (id === undefined) { - const existingEditors = this.editorService.findEditors(notebookUri, group).filter(editor => - !(editor instanceof NotebookEditorInput) - && !(editor instanceof NotebookDiffEditorInput) - ); - - if (existingEditors.length) { - return undefined; - } - - const userAssociatedEditors = this.getUserAssociatedEditors(notebookUri); - const notebookEditor = userAssociatedEditors.filter(association => this.notebookService.getContributedNotebookProvider(association.viewType)); - - if (userAssociatedEditors.length && !notebookEditor.length) { - // user pick a non-notebook editor for this resource - return undefined; - } - - // user might pick a notebook editor - - const associatedEditors = distinct([ - ...this.getUserAssociatedNotebookEditors(notebookUri), - ...(this.getContributedEditors(notebookUri).filter(editor => editor.priority === NotebookEditorPriority.default)) - ], editor => editor.id); - - if (!associatedEditors.length) { - // there is no notebook editor contribution which is enabled by default - return undefined; - } - } - - const infos = this.notebookService.getContributedNotebookProviders(notebookUri); - let info = infos.find(info => (!id || info.id === id) && info.exclusive) || infos.find(info => !id || info.id === id); - - if (!info && id !== undefined) { - info = this.notebookService.getContributedNotebookProvider(id); - } - - if (!info) { - return undefined; - } - - - /** - * Scenario: we are reopening a file editor input which is pinned, we should open in a new editor tab. - */ - let index = undefined; - if (group.activeEditor === originalInput && isEqual(originalInput.resource, notebookUri)) { - const originalEditorIndex = group.getIndexOfEditor(originalInput); - index = group.isPinned(originalInput) ? originalEditorIndex + 1 : originalEditorIndex; - } - - const notebookInput = NotebookEditorInput.create(this.instantiationService, notebookUri, info.id); - const notebookOptions = new NotebookEditorOptions({ ...options, cellOptions, override: EditorOverride.DISABLED, index }); - return { override: this.editorService.openEditor(notebookInput, notebookOptions, group) }; - } - - private _handleDiffEditorInput(diffEditorInput: DiffEditorInput, options: IEditorOptions | ITextEditorOptions | undefined, group: IEditorGroup): IOpenEditorOverride | undefined { - const modifiedInput = diffEditorInput.modifiedInput; - const originalInput = diffEditorInput.originalInput; - const notebookUri = modifiedInput.resource; - const originalNotebookUri = originalInput.resource; - - if (!notebookUri || !originalNotebookUri) { - return undefined; - } - - const existingEditors = this.editorService.findEditors(notebookUri, group).filter(editor => !(editor instanceof NotebookEditorInput)); - - if (existingEditors.length) { - return undefined; - } - - const userAssociatedEditors = this.getUserAssociatedEditors(notebookUri); - const notebookEditor = userAssociatedEditors.filter(association => this.notebookService.getContributedNotebookProvider(association.viewType)); - - if (userAssociatedEditors.length && !notebookEditor.length) { - // user pick a non-notebook editor for this resource - return undefined; - } - - // user might pick a notebook editor - - const associatedEditors = distinct([ - ...this.getUserAssociatedNotebookEditors(notebookUri), - ...(this.getContributedEditors(notebookUri).filter(editor => editor.priority === NotebookEditorPriority.default)) - ], editor => editor.id); - - if (!associatedEditors.length) { - // there is no notebook editor contribution which is enabled by default - return undefined; - } - - const info = associatedEditors[0]; - - const notebookInput = NotebookDiffEditorInput.create(this.instantiationService, notebookUri, modifiedInput.getName(), originalNotebookUri, originalInput.getName(), diffEditorInput.getName(), info.id); - const notebookOptions = new NotebookEditorOptions({ ...options, override: EditorOverride.DISABLED }); - return { override: this.editorService.openEditor(notebookInput, notebookOptions, group) }; } } @@ -636,38 +355,6 @@ class RegisterSchemasContribution extends Disposable implements IWorkbenchContri type: 'string', description: 'The language for the cell' }, - ['editable']: { - type: 'boolean', - description: `Controls whether a cell's editor is editable/readonly` - }, - ['breakpointMargin']: { - type: 'boolean', - description: 'Controls if the cell has a margin to support the breakpoint UI' - }, - ['hasExecutionOrder']: { - type: 'boolean', - description: 'Whether the execution order indicator will be displayed' - }, - ['executionOrder']: { - type: 'number', - description: 'The order in which this cell was executed' - }, - ['statusMessage']: { - type: 'string', - description: `A status message to be shown in the cell's status bar` - }, - ['runState']: { - type: 'integer', - description: `The cell's current run state` - }, - ['runStartTime']: { - type: 'number', - description: 'If the cell is running, the time at which the cell started running' - }, - ['lastRunDuration']: { - type: 'number', - description: `The total duration of the cell's last run` - }, ['inputCollapsed']: { type: 'boolean', description: `Whether a code cell's editor is collapsed` @@ -693,32 +380,67 @@ class NotebookFileTracker implements IWorkbenchContribution { private readonly _dirtyListener: IDisposable; constructor( - @INotebookService private readonly _notebookService: INotebookService, + @IInstantiationService private readonly _instantiationService: IInstantiationService, @IEditorService private readonly _editorService: IEditorService, - @IWorkingCopyService private readonly _workingCopyService: IWorkingCopyService, + @INotebookEditorModelResolverService private readonly _notebookEditorModelService: INotebookEditorModelResolverService, ) { - this._dirtyListener = Event.debounce(_workingCopyService.onDidChangeDirty, () => { }, 100)(() => { - const inputs = this._createMissingNotebookEditors(); - this._editorService.openEditors(inputs); - }); + + type E = IResolvedNotebookEditorModel; + this._dirtyListener = Event.debounce( + this._notebookEditorModelService.onDidChangeDirty, + (last, current) => !last ? [current] : [...last, current], + 100 + )(this._openMissingDirtyNotebookEditors, this); } dispose(): void { this._dirtyListener.dispose(); } - private _createMissingNotebookEditors(): IResourceEditorInput[] { - const result: IResourceEditorInput[] = []; - - for (const notebook of this._notebookService.getNotebookTextModels()) { - if (this._workingCopyService.isDirty(notebook.uri.with({ scheme: Schemas.vscodeNotebook })) && !this._editorService.isOpen({ resource: notebook.uri })) { + private _openMissingDirtyNotebookEditors(models: IResolvedNotebookEditorModel[]): void { + const result: IEditorInputWithOptions[] = []; + for (let model of models) { + if (model.isDirty() && !this._editorService.isOpened({ resource: model.resource, typeId: NotebookEditorInput.ID })) { result.push({ - resource: notebook.uri, - options: { inactive: true, preserveFocus: true, pinned: true } + editor: NotebookEditorInput.create(this._instantiationService, model.resource, model.viewType), + options: { inactive: true, preserveFocus: true, pinned: true, override: EditorOverride.DISABLED } }); } } - return result; + if (result.length > 0) { + this._editorService.openEditors(result); + } + } +} + +class NotebookWorkingCopyEditorHandler extends Disposable implements IWorkbenchContribution { + + constructor( + @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IWorkingCopyEditorService private readonly _workingCopyEditorService: IWorkingCopyEditorService, + @IExtensionService private readonly _extensionService: IExtensionService + ) { + super(); + + this._installHandler(); + } + + private async _installHandler(): Promise { + await this._extensionService.whenInstalledExtensionsRegistered(); + + this._register(this._workingCopyEditorService.registerHandler({ + handles: workingCopy => typeof this.getViewType(workingCopy) === 'string', + isOpen: (workingCopy, editor) => editor instanceof NotebookEditorInput && editor.viewType === this.getViewType(workingCopy), + createEditor: workingCopy => NotebookEditorInput.create(this._instantiationService, workingCopy.resource, this.getViewType(workingCopy)!) + })); + } + + private getViewType(workingCopy: IWorkingCopyIdentifier): string | undefined { + if (workingCopy.typeId.startsWith(NOTEBOOK_WORKING_COPY_TYPE_PREFIX)) { + return workingCopy.typeId.substr(NOTEBOOK_WORKING_COPY_TYPE_PREFIX.length); + } + + return undefined; } } @@ -728,12 +450,14 @@ workbenchContributionsRegistry.registerWorkbenchContribution(CellContentProvider workbenchContributionsRegistry.registerWorkbenchContribution(CellMetadataContentProvider, LifecyclePhase.Starting); workbenchContributionsRegistry.registerWorkbenchContribution(RegisterSchemasContribution, LifecyclePhase.Starting); workbenchContributionsRegistry.registerWorkbenchContribution(NotebookFileTracker, LifecyclePhase.Ready); +workbenchContributionsRegistry.registerWorkbenchContribution(NotebookWorkingCopyEditorHandler, LifecyclePhase.Ready); registerSingleton(INotebookService, NotebookService); registerSingleton(INotebookEditorWorkerService, NotebookEditorWorkerServiceImpl); -registerSingleton(INotebookEditorModelResolverService, NotebookModelResolverService, true); +registerSingleton(INotebookEditorModelResolverService, NotebookModelResolverServiceImpl, true); registerSingleton(INotebookCellStatusBarService, NotebookCellStatusBarService, true); registerSingleton(INotebookEditorService, NotebookEditorWidgetService, true); +registerSingleton(INotebookKernelService, NotebookKernelService, true); const configurationRegistry = Registry.as(Extensions.Configuration); configurationRegistry.registerConfiguration({ @@ -752,9 +476,15 @@ configurationRegistry.registerConfiguration({ }, [CellToolbarLocKey]: { description: nls.localize('notebook.cellToolbarLocation.description', "Where the cell toolbar should be shown, or whether it should be hidden."), - type: 'string', - enum: ['left', 'right', 'hidden'], - default: 'right' + type: 'object', + additionalProperties: { + markdownDescription: nls.localize('notebook.cellToolbarLocation.viewType', "Configure the cell toolbar position for for specific file types"), + type: 'string', + enum: ['left', 'right', 'hidden'] + }, + default: { + 'default': 'right' + } }, [ShowCellStatusBarKey]: { description: nls.localize('notebook.showCellStatusbar.description', "Whether the cell status bar should be shown."), @@ -765,6 +495,11 @@ configurationRegistry.registerConfiguration({ description: nls.localize('notebook.diff.enablePreview.description', "Whether to use the enhanced text diff editor for notebook."), type: 'boolean', default: true - } + }, + [ExperimentalUseMarkdownRenderer]: { + description: nls.localize('notebook.experimental.useMarkdownRenderer.description', "Enable/disable using the new extensible markdown renderer."), + type: 'boolean', + default: true + }, } }); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index 43ca7cb0..01ea269f 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -13,16 +13,16 @@ import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { ScrollEvent } from 'vs/base/common/scrollable'; import { URI } from 'vs/base/common/uri'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { BareFontInfo } from 'vs/editor/common/config/fontInfo'; +import { FontInfo } from 'vs/editor/common/config/fontInfo'; import { IPosition } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { FindMatch, IReadonlyTextBuffer, ITextModel } from 'vs/editor/common/model'; import { ContextKeyExpr, RawContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { OutputRenderer } from 'vs/workbench/contrib/notebook/browser/view/output/outputRenderer'; -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, cellRangesToIndexes, reduceRanges } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellKind, NotebookCellMetadata, INotebookKernel, IOrderedMimeType, INotebookRendererInfo, ICellOutput, IOutputItemDto, INotebookCellStatusBarItem } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { ICellRange, cellRangesToIndexes, reduceRanges } from 'vs/workbench/contrib/notebook/common/notebookRange'; 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'; @@ -30,24 +30,25 @@ import { EditorOptions, IEditorPane } from 'vs/workbench/common/editor'; import { IResourceEditorInput } from 'vs/platform/editor/common/editor'; import { IConstructorSignature1 } from 'vs/platform/instantiation/common/instantiation'; import { CellEditorStatusBar } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellWidgets'; +import { INotebookWebviewMessage } from 'vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView'; + +export const NOTEBOOK_EDITOR_ID = 'workbench.editor.notebook'; +export const NOTEBOOK_DIFF_EDITOR_ID = 'workbench.editor.notebookTextDiffEditor'; //#region Context Keys export const KEYBINDING_CONTEXT_NOTEBOOK_FIND_WIDGET_FOCUSED = new RawContextKey('notebookFindWidgetFocused', false); // Is Notebook -export const NOTEBOOK_IS_ACTIVE_EDITOR = ContextKeyExpr.equals('activeEditor', 'workbench.editor.notebook'); +export const NOTEBOOK_IS_ACTIVE_EDITOR = ContextKeyExpr.equals('activeEditor', NOTEBOOK_EDITOR_ID); +export const NOTEBOOK_DIFF_IS_ACTIVE_EDITOR = ContextKeyExpr.equals('activeEditor', NOTEBOOK_DIFF_EDITOR_ID); // Editor keys export const NOTEBOOK_EDITOR_FOCUSED = new RawContextKey('notebookEditorFocused', false); -export const NOTEBOOK_EDITOR_OPEN = new RawContextKey('notebookEditorOpen', false); export const NOTEBOOK_CELL_LIST_FOCUSED = new RawContextKey('notebookCellListFocused', false); export const NOTEBOOK_OUTPUT_FOCUSED = new RawContextKey('notebookOutputFocused', false); export const NOTEBOOK_EDITOR_EDITABLE = new RawContextKey('notebookEditable', true); export const NOTEBOOK_HAS_RUNNING_CELL = new RawContextKey('notebookHasRunningCell', false); -// Diff Editor Keys -export const IN_NOTEBOOK_TEXT_DIFF_EDITOR = new RawContextKey('isInNotebookTextDiffEditor', false); - // Cell keys export const NOTEBOOK_VIEW_TYPE = new RawContextKey('notebookViewType', undefined); export const NOTEBOOK_CELL_TYPE = new RawContextKey('notebookCellType', undefined); // code, markdown @@ -55,13 +56,13 @@ export const NOTEBOOK_CELL_EDITABLE = new RawContextKey('notebookCellEd export const NOTEBOOK_CELL_FOCUSED = new RawContextKey('notebookCellFocused', false); // bool export const NOTEBOOK_CELL_EDITOR_FOCUSED = new RawContextKey('notebookCellEditorFocused', false); // bool export const NOTEBOOK_CELL_MARKDOWN_EDIT_MODE = new RawContextKey('notebookCellMarkdownEditMode', false); // bool +export const NOTEBOOK_CELL_LINE_NUMBERS = new RawContextKey<'on' | 'off' | 'inherit'>('notebookCellLineNumbers', 'inherit'); // off, none, inherit export type NotebookCellExecutionStateContext = 'idle' | 'pending' | 'executing' | 'succeeded' | 'failed'; export const NOTEBOOK_CELL_EXECUTION_STATE = new RawContextKey('notebookCellExecutionState', undefined); export const NOTEBOOK_CELL_HAS_OUTPUTS = new RawContextKey('notebookCellHasOutputs', false); // bool export const NOTEBOOK_CELL_INPUT_COLLAPSED = new RawContextKey('notebookCellInputIsCollapsed', false); // bool export const NOTEBOOK_CELL_OUTPUT_COLLAPSED = new RawContextKey('notebookCellOutputIsCollapsed', false); // bool // Kernels -export const NOTEBOOK_HAS_MULTIPLE_KERNELS = new RawContextKey('notebookHasMultipleKernels', false); export const NOTEBOOK_KERNEL_COUNT = new RawContextKey('notebookKernelCount', 0); export const NOTEBOOK_INTERRUPTIBLE_KERNEL = new RawContextKey('notebookInterruptibleKernel', false); @@ -70,6 +71,8 @@ export const NOTEBOOK_INTERRUPTIBLE_KERNEL = new RawContextKey('noteboo //#region Shared commands export const EXPAND_CELL_INPUT_COMMAND_ID = 'notebook.cell.expandCellInput'; export const EXECUTE_CELL_COMMAND_ID = 'notebook.cell.execute'; +export const CHANGE_CELL_LANGUAGE = 'notebook.cell.changeLanguage'; +export const QUIT_EDIT_CELL_COMMAND_ID = 'notebook.cell.quitEdit'; //#endregion @@ -83,6 +86,7 @@ export const enum RenderOutputType { export interface IRenderMainframeOutput { type: RenderOutputType.Mainframe; supportAppend?: boolean; + initHeight?: number; } export interface IRenderPlainHtmlOutput { @@ -107,14 +111,14 @@ export interface ICellOutputViewModel { * When rendering an output, `model` should always be used as we convert legacy `text/error` output to `display_data` output under the hood. */ model: ICellOutput; - resolveMimeTypes(textModel: NotebookTextModel): [readonly IOrderedMimeType[], number]; + resolveMimeTypes(textModel: NotebookTextModel, kernelProvides: readonly string[] | undefined): [readonly IOrderedMimeType[], number]; pickedMimeType: number; supportAppend(): boolean; toRawJSON(): any; } export interface IDisplayOutputViewModel extends ICellOutputViewModel { - resolveMimeTypes(textModel: NotebookTextModel): [readonly IOrderedMimeType[], number]; + resolveMimeTypes(textModel: NotebookTextModel, kernelProvides: readonly string[] | undefined): [readonly IOrderedMimeType[], number]; pickedMimeType: number; } @@ -129,15 +133,18 @@ export interface IGenericCellViewModel { uri: URI; metadata: NotebookCellMetadata | undefined; outputIsHovered: boolean; + outputIsFocused: boolean; outputsViewModels: ICellOutputViewModel[]; getOutputOffset(index: number): number; - updateOutputHeight(index: number, height: number): void; + updateOutputHeight(index: number, height: number, source?: string): void; } export interface IDisplayOutputLayoutUpdateRequest { + readonly cell: IGenericCellViewModel; output: IDisplayOutputViewModel; cellTop: number; outputOffset: number; + forceDisplay: boolean; } export interface ICommonCellInfo { @@ -149,7 +156,7 @@ export interface ICommonCellInfo { export interface INotebookCellOutputLayoutInfo { width: number; height: number; - fontInfo: BareFontInfo; + fontInfo: FontInfo; } export interface IFocusNotebookCellOptions { @@ -161,9 +168,11 @@ export interface ICommonNotebookEditor { triggerScroll(event: IMouseWheelEvent): void; getCellByInfo(cellInfo: ICommonCellInfo): IGenericCellViewModel; getCellById(cellId: string): IGenericCellViewModel | undefined; + toggleNotebookCellSelection(cell: IGenericCellViewModel): void; focusNotebookCell(cell: IGenericCellViewModel, focus: 'editor' | 'container' | 'output', options?: IFocusNotebookCellOptions): void; focusNextNotebookCell(cell: IGenericCellViewModel, focus: 'editor' | 'container' | 'output'): void; - updateOutputHeight(cellInfo: ICommonCellInfo, output: IDisplayOutputViewModel, height: number, isInit: boolean): void; + updateOutputHeight(cellInfo: ICommonCellInfo, output: IDisplayOutputViewModel, height: number, isInit: boolean, source?: string): void; + scheduleOutputHeightAck(cellInfo: ICommonCellInfo, outputId: string, height: number): void; updateMarkdownCellHeight(cellId: string, height: number, isInit: boolean): void; setMarkdownCellEditState(cellId: string, editState: CellEditState): void; markdownCellDragStart(cellId: string, position: { clientY: number }): void; @@ -177,7 +186,7 @@ export interface ICommonNotebookEditor { export interface NotebookLayoutInfo { width: number; height: number; - fontInfo: BareFontInfo; + fontInfo: FontInfo; } export interface NotebookLayoutChangeEvent { @@ -194,7 +203,7 @@ export enum CodeCellLayoutState { } export interface CodeCellLayoutInfo { - readonly fontInfo: BareFontInfo | null; + readonly fontInfo: FontInfo | null; readonly editorHeight: number; readonly editorWidth: number; readonly totalHeight: number; @@ -208,16 +217,17 @@ export interface CodeCellLayoutInfo { } export interface CodeCellLayoutChangeEvent { + source?: string; editorHeight?: boolean; outputHeight?: boolean; outputShowMoreContainerHeight?: number; totalHeight?: boolean; outerWidth?: number; - font?: BareFontInfo; + font?: FontInfo; } export interface MarkdownCellLayoutInfo { - readonly fontInfo: BareFontInfo | null; + readonly fontInfo: FontInfo | null; readonly editorWidth: number; readonly editorHeight: number; readonly bottomToolbarOffset: number; @@ -225,7 +235,7 @@ export interface MarkdownCellLayoutInfo { } export interface MarkdownCellLayoutChangeEvent { - font?: BareFontInfo; + font?: FontInfo; outerWidth?: number; totalHeight?: number; } @@ -235,12 +245,15 @@ export interface ICellViewModel extends IGenericCellViewModel { readonly id: string; readonly textBuffer: IReadonlyTextBuffer; readonly layoutInfo: { totalHeight: number; }; + readonly onDidChangeLayout: Event<{ totalHeight?: boolean | number; outerWidth?: number; }>; + readonly onDidChangeCellStatusBarItems: Event; + readonly editStateSource: string; dragging: boolean; handle: number; uri: URI; language: string; cellKind: CellKind; - editState: CellEditState; + lineNumbers: 'on' | 'off' | 'inherit'; focusMode: CellFocusMode; outputIsHovered: boolean; getText(): string; @@ -250,9 +263,11 @@ export interface ICellViewModel extends IGenericCellViewModel { textModel: ITextModel | undefined; hasModel(): this is IEditableCellViewModel; resolveTextModel(): Promise; - getEvaluatedMetadata(documentMetadata: NotebookDocumentMetadata | undefined): NotebookCellMetadata; getSelectionsStartPosition(): IPosition[] | undefined; getCellDecorations(): INotebookCellDecorationOptions[]; + getCellStatusBarItems(): INotebookCellStatusBarItem[]; + getEditState(): CellEditState; + updateEditState(state: CellEditState, source: string): void; } export interface IEditableCellViewModel extends ICellViewModel { @@ -291,16 +306,23 @@ export interface INotebookDeltaDecoration { options: INotebookCellDecorationOptions; } +export interface INotebookDeltaCellStatusBarItems { + handle: number; + items: INotebookCellStatusBarItem[]; +} + export class NotebookEditorOptions extends EditorOptions { readonly cellOptions?: IResourceEditorInput; readonly cellSelections?: ICellRange[]; + readonly isReadOnly?: boolean; constructor(options: Partial) { super(); this.overwrite(options); this.cellOptions = options.cellOptions; this.cellSelections = options.cellSelections; + this.isReadOnly = options.isReadOnly; } with(options: Partial): NotebookEditorOptions { @@ -325,10 +347,6 @@ export interface IActiveNotebookEditor extends INotebookEditor { getFocus(): ICellRange; } -export const NOTEBOOK_EDITOR_ID = 'workbench.editor.notebook'; - -export const NOTEBOOK_DIFF_EDITOR_ID = 'workbench.editor.notebookTextDiffEditor'; - export interface INotebookEditor extends ICommonNotebookEditor { // from the old IEditor @@ -356,13 +374,11 @@ export interface INotebookEditor extends ICommonNotebookEditor { */ readonly onDidChangeModel: Event; readonly onDidFocusEditorWidget: Event; - activeKernel: INotebookKernel | undefined; - multipleKernelsAvailable: boolean; readonly onDidScroll: Event; - readonly onDidChangeAvailableKernels: Event; - readonly onDidChangeKernel: Event; + readonly onDidChangeActiveCell: Event; isDisposed: boolean; + dispose(): void; getId(): string; getDomNode(): HTMLElement; @@ -398,11 +414,6 @@ export interface INotebookEditor extends ICommonNotebookEditor { */ getOutputRenderer(): OutputRenderer; - /** - * Fetch the contributed kernels for this notebook - */ - beginComputeContributedKernels(): Promise; - /** * Insert a new cell around `cell` */ @@ -440,25 +451,17 @@ export interface INotebookEditor extends ICommonNotebookEditor { focusNextNotebookCell(cell: ICellViewModel, focus: 'editor' | 'container' | 'output'): void; - /** - * Execute the given notebook cell - */ - executeNotebookCell(cell: ICellViewModel): Promise; + readonly activeKernel: INotebookKernel | undefined; /** - * Cancel the cell execution + * Execute the given notebook cells */ - cancelNotebookCellExecution(cell: ICellViewModel): void; + executeNotebookCells(cells?: Iterable): Promise /** - * Executes all notebook cells in order + * Cancel the given notebook cells */ - executeNotebook(): Promise; - - /** - * Cancel the notebook execution - */ - cancelNotebookExecution(): void; + cancelNotebookCells(cells?: Iterable): Promise /** * Get current active cell @@ -471,10 +474,8 @@ export interface INotebookEditor extends ICommonNotebookEditor { layoutNotebookCell(cell: ICellViewModel, height: number): Promise; createMarkdownPreview(cell: ICellViewModel): Promise; - unhideMarkdownPreview(cell: ICellViewModel): Promise; - hideMarkdownPreview(cell: ICellViewModel): Promise; - removeMarkdownPreview(cell: ICellViewModel): Promise; - updateMarkdownPreviewSelectionState(cell: ICellViewModel, isSelected: boolean): Promise; + unhideMarkdownPreviews(cells: readonly ICellViewModel[]): Promise; + hideMarkdownPreviews(cells: readonly ICellViewModel[]): Promise; /** * Render the output in webview layer @@ -491,6 +492,9 @@ export interface INotebookEditor extends ICommonNotebookEditor { */ hideInset(output: IDisplayOutputViewModel): void; + + onDidReceiveMessage: Event; + /** * Send message to the webview for outputs. */ @@ -573,6 +577,11 @@ export interface INotebookEditor extends ICommonNotebookEditor { */ getViewIndex(cell: ICellViewModel): number; + /** + * Get the view height of a cell (from the list view) + */ + getViewHeight(cell: ICellViewModel): number; + /** * @param startIndex Inclusive * @param endIndex Exclusive @@ -583,7 +592,7 @@ export interface INotebookEditor extends ICommonNotebookEditor { * @param startIndex Inclusive * @param endIndex Exclusive */ - getCellsFromViewRange(startIndex: number, endIndex: number): ICellViewModel[]; + getCellsFromViewRange(startIndex: number, endIndex: number): ReadonlyArray; /** * Set hidden areas on cell text models. @@ -624,7 +633,7 @@ export interface INotebookEditor extends ICommonNotebookEditor { getCellByInfo(cellInfo: ICommonCellInfo): ICellViewModel; getCellById(cellId: string): ICellViewModel | undefined; - updateOutputHeight(cellInfo: ICommonCellInfo, output: IDisplayOutputViewModel, height: number, isInit: boolean): void; + updateOutputHeight(cellInfo: ICommonCellInfo, output: IDisplayOutputViewModel, height: number, isInit: boolean, source?: string): void; } export interface INotebookCellList { @@ -645,9 +654,9 @@ export interface INotebookCellList { scrollLeft: number; length: number; rowsContainer: HTMLElement; - readonly onDidRemoveOutput: Event; - readonly onDidHideOutput: Event; - readonly onDidRemoveCellFromView: Event; + readonly onDidRemoveOutputs: Event; + readonly onDidHideOutputs: Event; + readonly onDidRemoveCellsFromView: Event; readonly onMouseUp: Event>; readonly onMouseDown: Event>; readonly onContextMenu: Event>; @@ -660,7 +669,7 @@ export interface INotebookCellList { getModelIndex2(viewIndex: number): number | undefined; getVisibleRangesPlusViewportAboveBelow(): ICellRange[]; focusElement(element: ICellViewModel): void; - selectElement(element: ICellViewModel): void; + selectElements(elements: ICellViewModel[]): void; getFocusedElements(): ICellViewModel[]; getSelectedElements(): ICellViewModel[]; revealElementsInView(range: ICellRange): void; @@ -684,12 +693,10 @@ export interface INotebookCellList { domFocus(): void; setCellSelection(element: ICellViewModel, range: Range): void; style(styles: IListStyles): void; + getRenderHeight(): number; updateOptions(options: IListOptions): void; layout(height?: number, width?: number): void; dispose(): void; - - // TODO@roblourens resolve differences between List and INotebookCellList - getFocus(): number[]; } export interface BaseCellRenderTemplate { @@ -724,7 +731,6 @@ export interface MarkdownCellRenderTemplate extends BaseCellRenderTemplate { } export interface CodeCellRenderTemplate extends BaseCellRenderTemplate { - cellRunState: RunStateRenderer; runToolbar: ToolBar; runButtonContainer: HTMLElement; executionOrderLabel: HTMLElement; @@ -733,7 +739,6 @@ export interface CodeCellRenderTemplate extends BaseCellRenderTemplate { focusSinkElement: HTMLElement; editor: ICodeEditor; progressBar: ProgressBar; - timer: TimerRenderer; focusIndicatorRight: HTMLElement; focusIndicatorBottom: HTMLElement; dragHandle: HTMLElement; @@ -744,6 +749,7 @@ export function isCodeCellRenderTemplate(templateData: BaseCellRenderTemplate): } export interface IOutputTransformContribution { + getType(): RenderOutputType; getMimetypes(): string[]; /** * Dispose this contribution. @@ -811,7 +817,9 @@ export interface CellViewModelStateChangeEvent { readonly foldingStateChanged?: boolean; readonly contentChanged?: boolean; readonly outputIsHoveredChanged?: boolean; + readonly outputIsFocusedChanged?: boolean; readonly cellIsHoveredChanged?: boolean; + readonly cellLineNumberChanged?: boolean; } export function cellRangesEqual(a: ICellRange[], b: ICellRange[]) { diff --git a/src/vs/workbench/contrib/notebook/browser/notebookCellStatusBarServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookCellStatusBarServiceImpl.ts index 27abf1c0..05d2a1fc 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookCellStatusBarServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookCellStatusBarServiceImpl.ts @@ -3,51 +3,55 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { CancellationToken } from 'vs/base/common/cancellation'; +import { onUnexpectedExternalError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; -import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; -import { ResourceMap } from 'vs/base/common/map'; +import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { INotebookCellStatusBarService } from 'vs/workbench/contrib/notebook/common/notebookCellStatusBarService'; -import { INotebookCellStatusBarEntry } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookCellStatusBarItemList, INotebookCellStatusBarItemProvider } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { score } from 'vs/workbench/contrib/notebook/common/notebookSelector'; export class NotebookCellStatusBarService extends Disposable implements INotebookCellStatusBarService { - private _onDidChangeEntriesForCell = new Emitter(); - readonly onDidChangeEntriesForCell: Event = this._onDidChangeEntriesForCell.event; + private _onDidChangeProviders = new Emitter(); + readonly onDidChangeProviders: Event = this._onDidChangeProviders.event; - private _entries = new ResourceMap>(); + private _onDidChangeItems = new Emitter(); + readonly onDidChangeItems: Event = this._onDidChangeItems.event; - private removeEntry(entry: INotebookCellStatusBarEntry) { - const existingEntries = this._entries.get(entry.cellResource); - if (existingEntries) { - existingEntries.delete(entry); - if (!existingEntries.size) { - this._entries.delete(entry.cellResource); - } + private _providers: INotebookCellStatusBarItemProvider[] = []; + + constructor() { + super(); + } + + registerCellStatusBarItemProvider(provider: INotebookCellStatusBarItemProvider): IDisposable { + this._providers.push(provider); + let changeListener: IDisposable | undefined; + if (provider.onDidChangeStatusBarItems) { + changeListener = provider.onDidChangeStatusBarItems(() => this._onDidChangeItems.fire()); } - this._onDidChangeEntriesForCell.fire(entry.cellResource); + this._onDidChangeProviders.fire(); + + return toDisposable(() => { + changeListener?.dispose(); + const idx = this._providers.findIndex(p => p === provider); + this._providers.splice(idx, 1); + }); } - addEntry(entry: INotebookCellStatusBarEntry): IDisposable { - const existingEntries = this._entries.get(entry.cellResource) ?? new Set(); - existingEntries.add(entry); - this._entries.set(entry.cellResource, existingEntries); - - this._onDidChangeEntriesForCell.fire(entry.cellResource); - - return { - dispose: () => { - this.removeEntry(entry); + async getStatusBarItemsForCell(docUri: URI, cellIndex: number, viewType: string, token: CancellationToken): Promise { + const providers = this._providers.filter(p => score(p.selector, docUri, viewType) > 0); + return await Promise.all(providers.map(async p => { + try { + return await p.provideCellStatusBarItems(docUri, cellIndex, token) ?? { items: [] }; + } catch (e) { + onUnexpectedExternalError(e); + return { items: [] }; } - }; - } - - getEntries(cell: URI): INotebookCellStatusBarEntry[] { - const existingEntries = this._entries.get(cell); - return existingEntries ? - Array.from(existingEntries.values()) : - []; + })); } readonly _serviceBrand: undefined; diff --git a/src/vs/workbench/contrib/notebook/browser/notebookDiffEditorInput.ts b/src/vs/workbench/contrib/notebook/browser/notebookDiffEditorInput.ts index 1dc39cb7..71510384 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookDiffEditorInput.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookDiffEditorInput.ts @@ -13,6 +13,7 @@ import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { INotebookEditorModelResolverService } from 'vs/workbench/contrib/notebook/common/notebookEditorModelResolverService'; import { IReference } from 'vs/base/common/lifecycle'; import { INotebookDiffEditorModel, IResolvedNotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { Schemas } from 'vs/base/common/network'; interface NotebookEditorInputOptions { startDirty?: boolean; @@ -41,7 +42,7 @@ class NotebookDiffEditorModel extends EditorModel implements INotebookDiffEditor await this.modified.load({ forceReadFromFile: true }); } - dispose(): void { + override dispose(): void { super.dispose(); } } @@ -73,30 +74,30 @@ export class NotebookDiffEditorInput extends EditorInput { this._defaultDirtyState = !!options.startDirty; } - getTypeId(): string { + override get typeId(): string { return NotebookDiffEditorInput.ID; } - getName(): string { + override getName(): string { return this.textDiffName; } - isDirty() { + override isDirty() { if (!this._modifiedTextModel) { return this._defaultDirtyState; } return this._modifiedTextModel.object.isDirty(); } - isUntitled(): boolean { - return this._modifiedTextModel?.object.isUntitled() || false; + override isUntitled(): boolean { + return this._modifiedTextModel?.object.resource.scheme === Schemas.untitled; } - isReadonly() { + override isReadonly() { return false; } - async save(group: GroupIdentifier, options?: ISaveOptions): Promise { + override async save(group: GroupIdentifier, options?: ISaveOptions): Promise { if (this._modifiedTextModel) { if (this.isUntitled()) { @@ -111,7 +112,7 @@ export class NotebookDiffEditorInput extends EditorInput { return undefined; } - async saveAs(group: GroupIdentifier, options?: ISaveOptions): Promise { + override async saveAs(group: GroupIdentifier, options?: ISaveOptions): Promise { if (!this._modifiedTextModel || !this.viewType) { return undefined; } @@ -155,7 +156,7 @@ ${patterns} } // called when users rename a notebook document - rename(group: GroupIdentifier, target: URI): IMoveResult | undefined { + override rename(group: GroupIdentifier, target: URI): IMoveResult | undefined { if (this._modifiedTextModel) { const contributedNotebookProviders = this._notebookService.getContributedNotebookProviders(target); @@ -170,7 +171,7 @@ ${patterns} return undefined; } - async revert(group: GroupIdentifier, options?: IRevertOptions): Promise { + override async revert(group: GroupIdentifier, options?: IRevertOptions): Promise { if (this._modifiedTextModel && this._modifiedTextModel.object.isDirty()) { await this._modifiedTextModel.object.revert(options); } @@ -178,7 +179,7 @@ ${patterns} return; } - async resolve(): Promise { + override async resolve(): Promise { if (!await this._notebookService.canResolve(this.viewType!)) { return null; } @@ -198,7 +199,7 @@ ${patterns} return new NotebookDiffEditorModel(this._originalTextModel.object, this._modifiedTextModel.object); } - matches(otherInput: unknown): boolean { + override matches(otherInput: unknown): boolean { if (this === otherInput) { return true; } @@ -209,7 +210,7 @@ ${patterns} return false; } - dispose() { + override dispose() { this._modifiedTextModel?.dispose(); this._modifiedTextModel = null; this._originalTextModel?.dispose(); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts index 32c71e76..989cec38 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts @@ -9,8 +9,9 @@ import { Emitter, Event } from 'vs/base/common/event'; import { DisposableStore } from 'vs/base/common/lifecycle'; import 'vs/css!./media/notebook'; import { localize } from 'vs/nls'; +import { extname } from 'vs/base/common/resources'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IEditorOptions, ITextEditorOptions, EditorOverride } from 'vs/platform/editor/common/editor'; +import { EditorOverride } from 'vs/platform/editor/common/editor'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { IStorageService } from 'vs/platform/storage/common/storage'; @@ -18,15 +19,16 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; import { EditorOptions, IEditorInput, IEditorMemento, IEditorOpenContext } from 'vs/workbench/common/editor'; -import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/browser/notebookEditorInput'; +import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput'; import { NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget'; import { INotebookEditorViewState, NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; import { IEditorDropService } from 'vs/workbench/services/editor/browser/editorDropService'; import { IEditorGroup, IEditorGroupsService, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { NotebookEditorOptions, NOTEBOOK_EDITOR_ID } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; -import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { IBorrowValue, INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/notebookEditorService'; +import { clearMarks, getAndClearMarks, mark } from 'vs/workbench/contrib/notebook/common/notebookPerformance'; +import { IFileService } from 'vs/platform/files/common/files'; const NOTEBOOK_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'NotebookEditorViewState'; @@ -42,7 +44,7 @@ export class NotebookEditor extends EditorPane { // todo@rebornix is there a reason that `super.fireOnDidFocus` isn't used? private readonly _onDidFocusWidget = this._register(new Emitter()); - get onDidFocus(): Event { return this._onDidFocusWidget.event; } + override get onDidFocus(): Event { return this._onDidFocusWidget.event; } private readonly _onDidChangeModel = this._register(new Emitter()); readonly onDidChangeModel: Event = this._onDidChangeModel.event; @@ -56,27 +58,36 @@ export class NotebookEditor extends EditorPane { @IEditorGroupsService private readonly _editorGroupService: IEditorGroupsService, @IEditorDropService private readonly _editorDropService: IEditorDropService, @INotificationService private readonly _notificationService: INotificationService, - @INotebookService private readonly _notebookService: INotebookService, @INotebookEditorService private readonly _notebookWidgetService: INotebookEditorService, @IContextKeyService private readonly _contextKeyService: IContextKeyService, + @IFileService private readonly fileService: IFileService, ) { super(NotebookEditor.ID, telemetryService, themeService, storageService); this._editorMemento = this.getEditorMemento(_editorGroupService, NOTEBOOK_EDITOR_VIEW_STATE_PREFERENCE_KEY); + + this._register(this.fileService.onDidChangeFileSystemProviderCapabilities(e => this.onDidFileSystemProviderChange(e.scheme))); + this._register(this.fileService.onDidChangeFileSystemProviderRegistrations(e => this.onDidFileSystemProviderChange(e.scheme))); + } + + private onDidFileSystemProviderChange(scheme: string): void { + if (this.input?.resource?.scheme === scheme && this._widget.value) { + this._widget.value.setOptions(new NotebookEditorOptions({ isReadOnly: this.input.isReadonly() })); + } } get viewModel(): NotebookViewModel | undefined { return this._widget.value?.viewModel; } - get minimumWidth(): number { return 375; } - get maximumWidth(): number { return Number.POSITIVE_INFINITY; } + override get minimumWidth(): number { return 375; } + override get maximumWidth(): number { return Number.POSITIVE_INFINITY; } // these setters need to exist because this extends from EditorPane - set minimumWidth(value: number) { /*noop*/ } - set maximumWidth(value: number) { /*noop*/ } + override set minimumWidth(value: number) { /*noop*/ } + override set maximumWidth(value: number) { /*noop*/ } //#region Editor Core - get scopedContextKeyService(): IContextKeyService | undefined { + override get scopedContextKeyService(): IContextKeyService | undefined { return this._widget.value?.scopedContextKeyService; } @@ -92,11 +103,11 @@ export class NotebookEditor extends EditorPane { return this._rootElement; } - getControl(): NotebookEditorWidget | undefined { + override getControl(): NotebookEditorWidget | undefined { return this._widget.value; } - setEditorVisible(visible: boolean, group: IEditorGroup | undefined): void { + override setEditorVisible(visible: boolean, group: IEditorGroup | undefined): void { super.setEditorVisible(visible, group); if (group) { this._groupListener.clear(); @@ -117,20 +128,21 @@ export class NotebookEditor extends EditorPane { } } - focus() { + override focus() { super.focus(); this._widget.value?.focus(); } - hasFocus(): boolean { + override hasFocus(): boolean { const activeElement = document.activeElement; const value = this._widget.value; return !!value && (DOM.isAncestor(activeElement, value.getDomNode() || DOM.isAncestor(activeElement, value.getOverflowContainerDomNode()))); } - async setInput(input: NotebookEditorInput, options: EditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise { - + override async setInput(input: NotebookEditorInput, options: EditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise { + clearMarks(input.resource); + mark(input.resource, 'startTime'); const group = this.group!; this._saveEditorViewState(this.input); @@ -153,8 +165,9 @@ export class NotebookEditor extends EditorPane { // only now `setInput` and yield/await. this is AFTER the actual widget is ready. This is very important // so that others synchronously receive a notebook editor with the correct widget being set await super.setInput(input, options, context, token); - const model = await input.resolve(); + mark(input.resource, 'inputLoaded'); + // Check for cancellation if (token.isCancellationRequested) { return undefined; @@ -167,30 +180,84 @@ export class NotebookEditor extends EditorPane { [{ label: localize('fail.reOpen', "Reopen file with VS Code standard text editor"), run: async () => { - const fileEditorInput = this._editorService.createEditorInput({ resource: input.resource, forceFile: true }); - const textOptions: IEditorOptions | ITextEditorOptions = { ...options, override: EditorOverride.DISABLED }; - await this._editorService.openEditor(fileEditorInput, textOptions); + await this._editorService.openEditor({ resource: input.resource, forceFile: true, options: { ...options, override: EditorOverride.DISABLED } }); } }] ); return; } - await this._notebookService.resolveNotebookEditor(model.viewType, model.resource, this._widget.value!.getId()); + const viewState = this._loadNotebookEditorViewState(input); this._widget.value?.setParentContextKeyService(this._contextKeyService); await this._widget.value!.setModel(model.notebook, viewState); - await this._widget.value!.setOptions(options instanceof NotebookEditorOptions ? options : undefined); + const isReadonly = input.isReadonly(); + await this._widget.value!.setOptions(options instanceof NotebookEditorOptions ? options.with({ isReadOnly: isReadonly }) : new NotebookEditorOptions({ isReadOnly: isReadonly })); this._widgetDisposableStore.add(this._widget.value!.onDidFocus(() => this._onDidFocusWidget.fire())); this._widgetDisposableStore.add(this._editorDropService.createEditorDropTarget(this._widget.value!.getDomNode(), { - containsGroup: (group) => this.group?.id === group.group.id + containsGroup: (group) => this.group?.id === group.id })); + + mark(input.resource, 'editorLoaded'); + + type WorkbenchNotebookOpenClassification = { + scheme: { classification: 'SystemMetaData', purpose: 'FeatureInsight'; }; + ext: { classification: 'SystemMetaData', purpose: 'FeatureInsight'; }; + viewType: { classification: 'SystemMetaData', purpose: 'FeatureInsight'; }; + extensionActivated: { classification: 'SystemMetaData', purpose: 'FeatureInsight'; }; + inputLoaded: { classification: 'SystemMetaData', purpose: 'FeatureInsight'; }; + webviewCommLoaded: { classification: 'SystemMetaData', purpose: 'FeatureInsight'; }; + customMarkdownLoaded: { classification: 'SystemMetaData', purpose: 'FeatureInsight'; }; + editorLoaded: { classification: 'SystemMetaData', purpose: 'FeatureInsight'; }; + }; + + type WorkbenchNotebookOpenEvent = { + scheme: string; + ext: string; + viewType: string; + extensionActivated: number; + inputLoaded: number; + webviewCommLoaded: number; + customMarkdownLoaded: number; + editorLoaded: number; + }; + + const perfMarks = getAndClearMarks(input.resource); + + if (perfMarks) { + const startTime = perfMarks['startTime']; + const extensionActivated = perfMarks['extensionActivated']; + const inputLoaded = perfMarks['inputLoaded']; + const webviewCommLoaded = perfMarks['webviewCommLoaded']; + const customMarkdownLoaded = perfMarks['customMarkdownLoaded']; + const editorLoaded = perfMarks['editorLoaded']; + + if ( + startTime !== undefined + && extensionActivated !== undefined + && inputLoaded !== undefined + && webviewCommLoaded !== undefined + && customMarkdownLoaded !== undefined + && editorLoaded !== undefined + ) { + this.telemetryService.publicLog2('notebook/editorOpenPerf', { + scheme: model.notebook.uri.scheme, + ext: extname(model.notebook.uri), + viewType: model.notebook.viewType, + extensionActivated: extensionActivated - startTime, + inputLoaded: inputLoaded - startTime, + webviewCommLoaded: webviewCommLoaded - startTime, + customMarkdownLoaded: customMarkdownLoaded - startTime, + editorLoaded: editorLoaded - startTime + }); + } + } } - clearInput(): void { + override clearInput(): void { if (this._widget.value) { this._saveEditorViewState(this.input); this._widget.value.onWillHide(); @@ -198,14 +265,14 @@ export class NotebookEditor extends EditorPane { super.clearInput(); } - setOptions(options: EditorOptions | undefined): void { + override setOptions(options: EditorOptions | undefined): void { if (options instanceof NotebookEditorOptions) { this._widget.value?.setOptions(options); } super.setOptions(options); } - protected saveState(): void { + protected override saveState(): void { this._saveEditorViewState(this.input); super.saveState(); } @@ -267,7 +334,7 @@ export class NotebookEditor extends EditorPane { //#endregion - dispose() { + override dispose() { super.dispose(); } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorKernelManager.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorKernelManager.ts index 7b303408..59786544 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorKernelManager.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorKernelManager.ts @@ -4,504 +4,80 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import { IQuickInputService, IQuickPickItem, QuickPickInput } from 'vs/platform/quickinput/common/quickInput'; -import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; -import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; -import { Emitter, Event } from 'vs/base/common/event'; -import { Disposable, DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle'; -import { URI } from 'vs/base/common/uri'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; -import { ThemeIcon } from 'vs/platform/theme/common/themeService'; -import { Memento } from 'vs/workbench/common/memento'; -import { ICellViewModel, NOTEBOOK_HAS_MULTIPLE_KERNELS, NOTEBOOK_HAS_RUNNING_CELL, NOTEBOOK_INTERRUPTIBLE_KERNEL, NOTEBOOK_KERNEL_COUNT, getRanges } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; -import { configureKernelIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons'; -import { NotebookKernelProviderAssociation, NotebookKernelProviderAssociations, notebookKernelProviderAssociationsSettingId } from 'vs/workbench/contrib/notebook/browser/notebookKernelAssociation'; -import { CellViewModel, NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; -import { cellIndexesToRanges, CellKind, ICellRange, INotebookKernel, NotebookCellExecutionState } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookProvider'; - -const NotebookEditorActiveKernelCache = 'workbench.editor.notebook.activeKernel'; - -export interface IKernelManagerDelegate { - viewModel: NotebookViewModel | undefined; - onDidChangeViewModel: Event; - getId(): string; - getContributedNotebookProviders(resource?: URI): readonly NotebookProviderInfo[]; - getContributedNotebookProvider(viewType: string): NotebookProviderInfo | undefined; - getNotebookKernels(viewType: string, resource: URI, token: CancellationToken): Promise; - loadKernelPreloads(extensionLocation: URI, kernel: INotebookKernel): Promise; -} +import { Disposable } from 'vs/base/common/lifecycle'; +import { ICellViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellKind, INotebookKernel, INotebookTextModel } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { IWorkspaceTrustRequestService } from 'vs/platform/workspace/common/workspaceTrust'; export class NotebookEditorKernelManager extends Disposable { - private _isDisposed: boolean = false; - - private _activeKernelExecuted: boolean = false; - private _activeKernel: INotebookKernel | undefined = undefined; - private readonly _onDidChangeKernel = this._register(new Emitter()); - readonly onDidChangeKernel: Event = this._onDidChangeKernel.event; - private readonly _onDidChangeAvailableKernels = this._register(new Emitter()); - readonly onDidChangeAvailableKernels: Event = this._onDidChangeAvailableKernels.event; - - private _contributedKernelsComputePromise: CancelablePromise | null = null; - private _initialKernelComputationDone: boolean = false; - - private readonly _notebookHasMultipleKernels: IContextKey; - private readonly _notebookKernelCount: IContextKey; - private readonly _interruptibleKernel: IContextKey; - private readonly _someCellRunning: IContextKey; - - private _cellStateListeners: IDisposable[] = []; - private _executionCount = 0; - private _viewModelDisposables: DisposableStore; - - get activeKernel() { - return this._activeKernel; - } - - set activeKernel(kernel: INotebookKernel | undefined) { - if (this._isDisposed) { - return; - } - - if (!this._delegate.viewModel) { - return; - } - - if (this._activeKernel === kernel) { - return; - } - - this._interruptibleKernel.set(!!kernel?.implementsInterrupt); - - this._activeKernel = kernel; - this._activeKernelResolvePromise = undefined; - - const memento = this._activeKernelMemento.getMemento(StorageScope.GLOBAL, StorageTarget.MACHINE); - memento[this._delegate.viewModel.viewType] = this._activeKernel?.friendlyId; - this._activeKernelMemento.saveMemento(); - this._onDidChangeKernel.fire(); - if (this._activeKernel) { - this._delegate.loadKernelPreloads(this._activeKernel.extensionLocation, this._activeKernel); - } - } - - private _activeKernelResolvePromise: Promise | undefined = undefined; - - private _multipleKernelsAvailable: boolean = false; - - get multipleKernelsAvailable() { - return this._multipleKernelsAvailable; - } - - set multipleKernelsAvailable(state: boolean) { - this._multipleKernelsAvailable = state; - this._onDidChangeAvailableKernels.fire(); - } - - private readonly _activeKernelMemento: Memento; constructor( - private readonly _delegate: IKernelManagerDelegate, - @IStorageService storageService: IStorageService, - @IContextKeyService contextKeyService: IContextKeyService, - @IQuickInputService private readonly _quickInputService: IQuickInputService, - @IConfigurationService private readonly _configurationService: IConfigurationService,) { + @ICommandService private readonly _commandService: ICommandService, + @INotebookKernelService private readonly _notebookKernelService: INotebookKernelService, + @IWorkspaceTrustRequestService private readonly _workspaceTrustRequestService: IWorkspaceTrustRequestService, + ) { super(); - - this._activeKernelMemento = new Memento(NotebookEditorActiveKernelCache, storageService); - - this._notebookHasMultipleKernels = NOTEBOOK_HAS_MULTIPLE_KERNELS.bindTo(contextKeyService); - this._notebookKernelCount = NOTEBOOK_KERNEL_COUNT.bindTo(contextKeyService); - this._interruptibleKernel = NOTEBOOK_INTERRUPTIBLE_KERNEL.bindTo(contextKeyService); - this._someCellRunning = NOTEBOOK_HAS_RUNNING_CELL.bindTo(contextKeyService); - - this._viewModelDisposables = this._register(new DisposableStore()); - this._register(this._delegate.onDidChangeViewModel(() => { - this._viewModelDisposables.clear(); - this.initCellListeners(); - })); } - private initCellListeners(): void { - dispose(this._cellStateListeners); - this._cellStateListeners = []; - - if (!this._delegate.viewModel) { - return; + getSelectedOrSuggestedKernel(notebook: INotebookTextModel): INotebookKernel | undefined { + // returns SELECTED or the ONLY available kernel + const info = this._notebookKernelService.getMatchingKernel(notebook); + if (info.selected) { + return info.selected; } - - const addCellStateListener = (c: ICellViewModel) => { - return (c as CellViewModel).onDidChangeState(e => { - if (!e.runStateChanged) { - return; - } - - if (c.metadata?.runState === NotebookCellExecutionState.Pending) { - this._executionCount++; - } else if (c.metadata?.runState === NotebookCellExecutionState.Idle) { - this._executionCount--; - } - - this._someCellRunning.set(this._executionCount > 0); - }); - }; - - this._cellStateListeners = this._delegate.viewModel.viewCells.map(addCellStateListener); - - this._viewModelDisposables.add(this._delegate.viewModel.onDidChangeViewCells(e => { - e.splices.reverse().forEach(splice => { - const [start, deleted, newCells] = splice; - const deletedCells = this._cellStateListeners.splice(start, deleted, ...newCells.map(addCellStateListener)); - dispose(deletedCells); - }); - })); + if (info.all.length === 1) { + return info.all[0]; + } + return undefined; } - public async setKernels(tokenSource: CancellationTokenSource) { - if (!this._delegate.viewModel) { - return; - } - - if (this._activeKernel !== undefined && this._activeKernelExecuted) { - // kernel already executed, we should not change it automatically - return; - } - - const provider = this._delegate.getContributedNotebookProvider(this._delegate.viewModel.viewType) || this._delegate.getContributedNotebookProviders(this._delegate.viewModel.uri)[0]; - const availableKernels = await this.beginComputeContributedKernels(); - - if (tokenSource.token.isCancellationRequested) { - return; - } - - this._notebookKernelCount.set(availableKernels.length); - if (availableKernels.length > 1) { - this._notebookHasMultipleKernels.set(true); - this.multipleKernelsAvailable = true; - } else { - this._notebookHasMultipleKernels.set(false); - this.multipleKernelsAvailable = false; - } - - const activeKernelStillExist = [...availableKernels].find(kernel => kernel.friendlyId === this.activeKernel?.friendlyId && this.activeKernel?.friendlyId !== undefined); - - if (activeKernelStillExist) { - // the kernel still exist, we don't want to modify the selection otherwise user's temporary preference is lost - return; - } - - if (availableKernels.length) { - return this._setKernelsFromProviders(provider, availableKernels, tokenSource); - } - - this._initialKernelComputationDone = true; - - tokenSource.dispose(); - } - - async beginComputeContributedKernels() { - if (this._contributedKernelsComputePromise) { - return this._contributedKernelsComputePromise; - } - - this._contributedKernelsComputePromise = createCancelablePromise(token => { - return this._delegate.getNotebookKernels(this._delegate.viewModel!.viewType, this._delegate.viewModel!.uri, token); + async executeNotebookCells(notebook: INotebookTextModel, cells: Iterable): Promise { + const message = nls.localize('notebookRunTrust', "Executing a notebook cell will run code from this workspace."); + const trust = await this._workspaceTrustRequestService.requestWorkspaceTrust({ + modal: true, + message }); - - const result = await this._contributedKernelsComputePromise; - this._initialKernelComputationDone = true; - this._contributedKernelsComputePromise = null; - - return result; - } - - private async _setKernelsFromProviders(provider: NotebookProviderInfo, kernels: INotebookKernel[], tokenSource: CancellationTokenSource) { - const rawAssociations = this._configurationService.getValue(notebookKernelProviderAssociationsSettingId) || []; - const userSetKernelProvider = rawAssociations.filter(e => e.viewType === this._delegate.viewModel?.viewType)[0]?.kernelProvider; - const memento = this._activeKernelMemento.getMemento(StorageScope.GLOBAL, StorageTarget.MACHINE); - - if (userSetKernelProvider) { - const filteredKernels = kernels.filter(kernel => kernel.extension.value === userSetKernelProvider); - - if (filteredKernels.length) { - const cachedKernelId = memento[provider.id]; - this.activeKernel = - filteredKernels.find(kernel => kernel.isPreferred) - || filteredKernels.find(kernel => kernel.friendlyId === cachedKernelId) - || filteredKernels[0]; - } else { - this.activeKernel = undefined; - } - - if (this.activeKernel) { - await this._delegate.loadKernelPreloads(this.activeKernel.extensionLocation, this.activeKernel); - - if (tokenSource.token.isCancellationRequested) { - return; - } - - this._activeKernelResolvePromise = this.activeKernel.resolve(this._delegate.viewModel!.uri, this._delegate.getId(), tokenSource.token); - await this._activeKernelResolvePromise; - - if (tokenSource.token.isCancellationRequested) { - return; - } - } - - memento[provider.id] = this._activeKernel?.friendlyId; - this._activeKernelMemento.saveMemento(); - - tokenSource.dispose(); + if (!trust) { return; } - // choose a preferred kernel - const kernelsFromSameExtension = kernels.filter(kernel => kernel.extension.value === provider.providerExtensionId); - if (kernelsFromSameExtension.length) { - const cachedKernelId = memento[provider.id]; - - const preferedKernel = kernelsFromSameExtension.find(kernel => kernel.isPreferred) - || kernelsFromSameExtension.find(kernel => kernel.friendlyId === cachedKernelId) - || kernelsFromSameExtension[0]; - this.activeKernel = preferedKernel; - if (this.activeKernel) { - await this._delegate.loadKernelPreloads(this.activeKernel.extensionLocation, this.activeKernel); - } - - if (tokenSource.token.isCancellationRequested) { - return; - } - - await preferedKernel.resolve(this._delegate.viewModel!.uri, this._delegate.getId(), tokenSource.token); - - if (tokenSource.token.isCancellationRequested) { - return; - } - - memento[provider.id] = this._activeKernel?.friendlyId; - this._activeKernelMemento.saveMemento(); - tokenSource.dispose(); + if (!notebook.metadata.trusted) { return; } - // the provider doesn't have a builtin kernel, choose a kernel - this.activeKernel = kernels[0]; - if (this.activeKernel) { - await this._delegate.loadKernelPreloads(this.activeKernel.extensionLocation, this.activeKernel); - if (tokenSource.token.isCancellationRequested) { - return; - } - - await this.activeKernel.resolve(this._delegate.viewModel!.uri, this._delegate.getId(), tokenSource.token); - if (tokenSource.token.isCancellationRequested) { - return; - } + let kernel = this.getSelectedOrSuggestedKernel(notebook); + if (!kernel) { + await this._commandService.executeCommand('notebook.selectKernel'); + kernel = this.getSelectedOrSuggestedKernel(notebook); } - tokenSource.dispose(); - } - - private async _ensureActiveKernel() { - if (this._activeKernel) { + if (!kernel) { return; } - if (this._activeKernelResolvePromise) { - await this._activeKernelResolvePromise; - - if (this._activeKernel) { - return; + const cellHandles: number[] = []; + for (const cell of cells) { + if (cell.cellKind !== CellKind.Code) { + continue; } - } - - - if (!this._initialKernelComputationDone) { - await this.setKernels(new CancellationTokenSource()); - - if (this._activeKernel) { - return; + if (!kernel.supportedLanguages.includes(cell.language)) { + continue; } + cellHandles.push(cell.handle); } - // pick active kernel - - const picker = this._quickInputService.createQuickPick<(IQuickPickItem & { run(): void; kernelProviderId?: string })>(); - picker.placeholder = nls.localize('notebook.runCell.selectKernel', "Select a notebook kernel to run this notebook"); - picker.matchOnDetail = true; - - const tokenSource = new CancellationTokenSource(); - const availableKernels = await this.beginComputeContributedKernels(); - const picks: QuickPickInput[] = availableKernels.map((a) => { - return { - id: a.friendlyId, - label: a.label, - picked: false, - description: - a.description - ? a.description - : a.extension.value, - detail: a.detail, - kernelProviderId: a.extension.value, - run: async () => { - this.activeKernel = a; - this._activeKernelResolvePromise = this.activeKernel.resolve(this._delegate.viewModel!.uri, this._delegate.getId(), tokenSource.token); - }, - buttons: [{ - iconClass: ThemeIcon.asClassName(configureKernelIcon), - tooltip: nls.localize('notebook.promptKernel.setDefaultTooltip', "Set as default kernel provider for '{0}'", this._delegate.viewModel!.viewType) - }] - }; - }); - - picker.items = picks; - picker.busy = false; - - const pickedItem = await new Promise<(IQuickPickItem & { run(): void; kernelProviderId?: string; }) | undefined>(resolve => { - picker.onDidAccept(() => { - resolve(picker.selectedItems.length === 1 ? picker.selectedItems[0] : undefined); - picker.dispose(); - }); - - picker.onDidTriggerItemButton(e => { - const pick = e.item; - const id = pick.id; - resolve(pick); // open the view - picker.dispose(); - - // And persist the setting - if (pick && id && pick.kernelProviderId) { - const newAssociation: NotebookKernelProviderAssociation = { viewType: this._delegate.viewModel!.viewType, kernelProvider: pick.kernelProviderId }; - const currentAssociations = [...this._configurationService.getValue(notebookKernelProviderAssociationsSettingId)]; - - // First try updating existing association - for (let i = 0; i < currentAssociations.length; ++i) { - const existing = currentAssociations[i]; - if (existing.viewType === newAssociation.viewType) { - currentAssociations.splice(i, 1, newAssociation); - this._configurationService.updateValue(notebookKernelProviderAssociationsSettingId, currentAssociations); - return; - } - } - - // Otherwise, create a new one - currentAssociations.unshift(newAssociation); - this._configurationService.updateValue(notebookKernelProviderAssociationsSettingId, currentAssociations); - } - }); - - }); - - tokenSource.dispose(); - - if (pickedItem) { - await pickedItem.run(); - } - - return; - } - - async cancelNotebookExecution(): Promise { - if (!this._delegate.viewModel) { - return; - } - - await this._ensureActiveKernel(); - - const fullRange: ICellRange = { - start: 0, end: this._delegate.viewModel.length - }; - await this._activeKernel?.cancelNotebookCellExecution!(this._delegate.viewModel.uri, [fullRange]); - } - - async executeNotebook(): Promise { - if (!this._delegate.viewModel) { - return; - } - - await this._ensureActiveKernel(); - if (!this.canExecuteNotebook()) { - return; - } - - const codeCellRanges = getRanges(this._delegate.viewModel.viewCells, cell => cell.cellKind === CellKind.Code); - if (codeCellRanges.length) { - this._activeKernelExecuted = true; - await this._activeKernel?.executeNotebookCellsRequest(this._delegate.viewModel.uri, codeCellRanges); + if (cellHandles.length > 0) { + this._notebookKernelService.selectKernelForNotebook(kernel, notebook); + await kernel.executeNotebookCellsRequest(notebook.uri, cellHandles); } } - async cancelNotebookCellExecution(cell: ICellViewModel): Promise { - if (!this._delegate.viewModel) { - return; + async cancelNotebookCells(notebook: INotebookTextModel, cells: Iterable): Promise { + let kernel = this.getSelectedOrSuggestedKernel(notebook); + if (kernel) { + await kernel.cancelNotebookCellExecution(notebook.uri, Array.from(cells, cell => cell.handle)); } - - if (cell.cellKind !== CellKind.Code) { - return; - } - - const metadata = cell.getEvaluatedMetadata(this._delegate.viewModel.metadata); - if (metadata.runState === NotebookCellExecutionState.Idle) { - return; - } - - await this._ensureActiveKernel(); - - const idx = this._delegate.viewModel.getCellIndex(cell); - const ranges = cellIndexesToRanges([idx]); - await this._activeKernel?.cancelNotebookCellExecution!(this._delegate.viewModel.uri, ranges); - } - - async executeNotebookCell(cell: ICellViewModel): Promise { - if (!this._delegate.viewModel) { - return; - } - - await this._ensureActiveKernel(); - if (!this.canExecuteCell(cell)) { - throw new Error('Cell is not executable: ' + cell.uri); - } - - if (!this.activeKernel) { - return; - } - - const idx = this._delegate.viewModel.getCellIndex(cell); - const range = cellIndexesToRanges([idx]); - this._activeKernelExecuted = true; - await this._activeKernel!.executeNotebookCellsRequest(this._delegate.viewModel.uri, range); - } - - private canExecuteNotebook(): boolean { - if (!this.activeKernel) { - return false; - } - - if (!this._delegate.viewModel?.trusted) { - return false; - } - - return true; - } - - private canExecuteCell(cell: ICellViewModel): boolean { - if (!this.activeKernel) { - return false; - } - - if (cell.cellKind !== CellKind.Code) { - return false; - } - - if (!this.activeKernel.supportedLanguages) { - return true; - } - - if (this.activeKernel.supportedLanguages.includes(cell.language)) { - return true; - } - - return false; } } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorService.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorService.ts index 0073499c..355f227e 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorService.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorService.ts @@ -6,7 +6,7 @@ import { NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget'; import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { createDecorator, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/browser/notebookEditorInput'; +import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput'; import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { Event } from 'vs/base/common/event'; import { INotebookDecorationRenderOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon'; diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorServiceImpl.ts index 640c3d3b..17bcdd78 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorServiceImpl.ts @@ -6,14 +6,14 @@ import { ResourceMap } from 'vs/base/common/map'; import { NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget'; import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; -import { IEditorGroupsService, IEditorGroup, GroupChangeKind, OpenEditorContext } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IEditorGroupsService, IEditorGroup, GroupChangeKind } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/browser/notebookEditorInput'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput'; import { IBorrowValue, INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/notebookEditorService'; import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { Emitter } from 'vs/base/common/event'; import { INotebookDecorationRenderOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { GroupIdentifier } from 'vs/workbench/common/editor'; export class NotebookEditorWidgetService implements INotebookEditorService { @@ -34,13 +34,13 @@ export class NotebookEditorWidgetService implements INotebookEditorService { constructor( @IEditorGroupsService editorGroupService: IEditorGroupsService, - @IEditorService editorService: IEditorService, ) { - const groupListener = new Map(); + const groupListener = new Map(); const onNewGroup = (group: IEditorGroup) => { const { id } = group; - const listener = group.onDidGroupChange(e => { + const listeners: IDisposable[] = []; + listeners.push(group.onDidGroupChange(e => { const widgets = this._borrowableEditors.get(group.id); if (!widgets || e.kind !== GroupChangeKind.EDITOR_CLOSE || !(e.editor instanceof NotebookEditorInput)) { return; @@ -52,17 +52,22 @@ export class NotebookEditorWidgetService implements INotebookEditorService { value.token = undefined; this._disposeWidget(value.widget); widgets.delete(e.editor.resource); - }); - groupListener.set(id, listener); + })); + listeners.push(group.onWillMoveEditor(e => { + if (e.editor instanceof NotebookEditorInput) { + this._freeWidget(e.editor, e.groupId, e.target); + } + })); + groupListener.set(id, listeners); }; this._disposables.add(editorGroupService.onDidAddGroup(onNewGroup)); - editorGroupService.groups.forEach(onNewGroup); + editorGroupService.whenReady.then(() => editorGroupService.groups.forEach(onNewGroup)); // group removed -> clean up listeners, clean up widgets this._disposables.add(editorGroupService.onDidRemoveGroup(group => { - const listener = groupListener.get(group.id); - if (listener) { - listener.dispose(); + const listeners = groupListener.get(group.id); + if (listeners) { + listeners.forEach(listener => listener.dispose()); groupListener.delete(group.id); } const widgets = this._borrowableEditors.get(group.id); @@ -74,20 +79,6 @@ export class NotebookEditorWidgetService implements INotebookEditorService { } } })); - - // HACK - // we use the open override to spy on tab movements because that's the only - // way to do that... - this._disposables.add(editorService.overrideOpenEditor({ - open: (input, _options, group, context) => { - if (input instanceof NotebookEditorInput && context === OpenEditorContext.MOVE_EDITOR) { - // when moving a notebook editor we release it from its current tab and we - // "place" it into its future slot so that the editor can pick it up from there - this._freeWidget(input, editorGroupService.activeGroup, group); - } - return undefined; - } - })); } dispose() { @@ -105,24 +96,24 @@ export class NotebookEditorWidgetService implements INotebookEditorService { domNode.remove(); } - private _freeWidget(input: NotebookEditorInput, source: IEditorGroup, target: IEditorGroup): void { - const targetWidget = this._borrowableEditors.get(target.id)?.get(input.resource); + private _freeWidget(input: NotebookEditorInput, sourceID: GroupIdentifier, targetID: GroupIdentifier): void { + const targetWidget = this._borrowableEditors.get(targetID)?.get(input.resource); if (targetWidget) { // not needed return; } - const widget = this._borrowableEditors.get(source.id)?.get(input.resource); + const widget = this._borrowableEditors.get(sourceID)?.get(input.resource); if (!widget) { throw new Error('no widget at source group'); } - this._borrowableEditors.get(source.id)?.delete(input.resource); + this._borrowableEditors.get(sourceID)?.delete(input.resource); widget.token = undefined; - let targetMap = this._borrowableEditors.get(target.id); + let targetMap = this._borrowableEditors.get(targetID); if (!targetMap) { targetMap = new ResourceMap(); - this._borrowableEditors.set(target.id, targetMap); + this._borrowableEditors.set(targetID, targetMap); } targetMap.set(input.resource, widget); } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 1117b83e..c63737e0 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -10,18 +10,17 @@ import { IMouseWheelEvent, StandardMouseEvent } from 'vs/base/browser/mouseEvent import { IListContextMenuEvent } from 'vs/base/browser/ui/list/list'; import { IAction } from 'vs/base/common/actions'; import { SequencerByKey } from 'vs/base/common/async'; -import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { Color, RGBA } from 'vs/base/common/color'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { combinedDisposable, Disposable, DisposableStore, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { extname } from 'vs/base/common/resources'; +import { extname, isEqual } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import 'vs/css!./media/notebook'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; -import { BareFontInfo } from 'vs/editor/common/config/fontInfo'; +import { BareFontInfo, FontInfo } from 'vs/editor/common/config/fontInfo'; import { Range } from 'vs/editor/common/core/range'; import { IEditor } from 'vs/editor/common/editorCommon'; import { IModeService } from 'vs/editor/common/services/modeService'; @@ -37,22 +36,21 @@ import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { contrastBorder, diffInserted, diffRemoved, editorBackground, errorForeground, focusBorder, foreground, listInactiveSelectionBackground, registerColor, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, textBlockQuoteBackground, textBlockQuoteBorder, textLinkActiveForeground, textLinkForeground, textPreformatForeground, transparent } from 'vs/platform/theme/common/colorRegistry'; -import { IThemeService, registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService'; +import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { EditorMemento } from 'vs/workbench/browser/parts/editor/editorPane'; import { IEditorMemento } from 'vs/workbench/common/editor'; import { Memento, MementoObject } from 'vs/workbench/common/memento'; import { PANEL_BORDER } from 'vs/workbench/common/theme'; import { debugIconStartForeground } from 'vs/workbench/contrib/debug/browser/debugColors'; -import { BOTTOM_CELL_TOOLBAR_GAP, BOTTOM_CELL_TOOLBAR_HEIGHT, CELL_BOTTOM_MARGIN, CELL_MARGIN, CELL_OUTPUT_PADDING, CELL_RUN_GUTTER, CELL_TOP_MARGIN, CODE_CELL_LEFT_MARGIN, COLLAPSED_INDICATOR_HEIGHT, MARKDOWN_CELL_BOTTOM_MARGIN, MARKDOWN_CELL_TOP_MARGIN, MARKDOWN_PREVIEW_PADDING, SCROLLABLE_ELEMENT_PADDING_TOP } from 'vs/workbench/contrib/notebook/browser/constants'; +import { BOTTOM_CELL_TOOLBAR_GAP, BOTTOM_CELL_TOOLBAR_HEIGHT, CELL_BOTTOM_MARGIN, CELL_OUTPUT_PADDING, CELL_RIGHT_MARGIN, CELL_RUN_GUTTER, CELL_TOP_MARGIN, CODE_CELL_LEFT_MARGIN, COLLAPSED_INDICATOR_HEIGHT, MARKDOWN_CELL_BOTTOM_MARGIN, MARKDOWN_CELL_TOP_MARGIN, MARKDOWN_PREVIEW_PADDING, SCROLLABLE_ELEMENT_PADDING_TOP } from 'vs/workbench/contrib/notebook/browser/constants'; import { CellEditState, CellFocusMode, IActiveNotebookEditor, ICellOutputViewModel, ICellViewModel, ICommonCellInfo, IDisplayOutputLayoutUpdateRequest, IFocusNotebookCellOptions, IGenericCellViewModel, IInsetRenderOutput, INotebookCellList, INotebookCellOutputLayoutInfo, INotebookDeltaDecoration, INotebookEditor, INotebookEditorContribution, INotebookEditorContributionDescription, INotebookEditorCreationOptions, INotebookEditorMouseEvent, NotebookEditorOptions, NotebookLayoutInfo, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_ID, NOTEBOOK_OUTPUT_FOCUSED } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookDecorationCSSRules, NotebookRefCountedStyleSheet } from 'vs/workbench/contrib/notebook/browser/notebookEditorDecorations'; import { NotebookEditorExtensionsRegistry } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions'; -import { IKernelManagerDelegate, NotebookEditorKernelManager } from 'vs/workbench/contrib/notebook/browser/notebookEditorKernelManager'; +import { NotebookEditorKernelManager } from 'vs/workbench/contrib/notebook/browser/notebookEditorKernelManager'; import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/notebookEditorService'; -import { errorStateIcon, successStateIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons'; import { NotebookCellList } from 'vs/workbench/contrib/notebook/browser/view/notebookCellList'; import { OutputRenderer } from 'vs/workbench/contrib/notebook/browser/view/output/outputRenderer'; -import { BackLayerWebView } from 'vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView'; +import { BackLayerWebView, INotebookWebviewMessage } from 'vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView'; import { CellContextKeyManager } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellContextKeys'; import { CellDragAndDropController } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellDnd'; import { CodeCellRenderer, ListTopCellToolbar, MarkdownCellRenderer, NotebookCellListDelegate } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer'; @@ -61,9 +59,8 @@ import { NotebookEventDispatcher, NotebookLayoutChangedEvent } from 'vs/workbenc import { MarkdownCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel'; import { CellViewModel, IModelDecorationsChangeAccessor, INotebookEditorViewState, NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; -import { CellKind, CellToolbarLocKey, ICellRange, INotebookKernel, SelectionStateType, ShowCellStatusBarKey } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookProvider'; -import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; +import { CellKind, CellToolbarLocKey, ExperimentalUseMarkdownRenderer, SelectionStateType, ShowCellStatusBarKey } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; 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'; @@ -73,6 +70,11 @@ import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ITASExperimentService } from 'vs/workbench/services/experiment/common/experimentService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { isWeb } from 'vs/base/common/platform'; +import { mark } from 'vs/workbench/contrib/notebook/common/notebookPerformance'; +import { readFontInfo } from 'vs/editor/browser/config/configuration'; +import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { NotebookEditorContextKeys } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidgetContextKeys'; const $ = DOM.$; @@ -135,6 +137,14 @@ export class ListViewInfoAccessor extends Disposable { return this.list.getViewIndex(cell) ?? -1; } + getViewHeight(cell: ICellViewModel): number { + if (!this.list.viewModel) { + return -1; + } + + return this.list.elementHeight(cell); + } + getCellRangeFromViewRange(startIndex: number, endIndex: number): ICellRange | undefined { if (!this.list.viewModel) { return undefined; @@ -158,7 +168,7 @@ export class ListViewInfoAccessor extends Disposable { } } - getCellsFromViewRange(startIndex: number, endIndex: number): ICellViewModel[] { + getCellsFromViewRange(startIndex: number, endIndex: number): ReadonlyArray { if (!this.list.viewModel) { return []; } @@ -168,7 +178,7 @@ export class ListViewInfoAccessor extends Disposable { return []; } - return this.list.viewModel.viewCells.slice(range.start, range.end); + return this.list.viewModel.getCells(range); } setCellEditorSelection(cell: ICellViewModel, range: Range): void { @@ -205,7 +215,8 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor private _eventDispatcher: NotebookEventDispatcher | undefined; private _notebookViewModel: NotebookViewModel | undefined; private _localStore: DisposableStore = this._register(new DisposableStore()); - private _fontInfo: BareFontInfo | undefined; + private _localCellStateListeners: DisposableStore[] = []; + private _fontInfo: FontInfo | undefined; private _dimension: DOM.Dimension | null = null; private _shadowElementViewInfo: { height: number, width: number, top: number; left: number; } | null = null; @@ -255,31 +266,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor return this._notebookViewModel?.notebookDocument; } - get onDidChangeKernel(): Event { - return this._kernelManger.onDidChangeKernel; - } - get onDidChangeAvailableKernels(): Event { - return this._kernelManger.onDidChangeAvailableKernels; - } - - get activeKernel() { - return this._kernelManger.activeKernel; - } - - set activeKernel(kernel: INotebookKernel | undefined) { - this._kernelManger.activeKernel = kernel; - } - - private _currentKernelTokenSource: CancellationTokenSource | undefined = undefined; - - get multipleKernelsAvailable() { - return this._kernelManger.multipleKernelsAvailable; - } - - set multipleKernelsAvailable(state: boolean) { - this._kernelManger.multipleKernelsAvailable = state; - } - private readonly _onDidChangeActiveEditor = this._register(new Emitter()); readonly onDidChangeActiveEditor: Event = this._onDidChangeActiveEditor.event; @@ -325,8 +311,8 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor @IInstantiationService instantiationService: IInstantiationService, @IStorageService storageService: IStorageService, @IAccessibilityService accessibilityService: IAccessibilityService, - @INotebookService private notebookService: INotebookService, @INotebookEditorService private readonly notebookEditorService: INotebookEditorService, + @INotebookKernelService notebookKernelService: INotebookKernelService, @IEditorService private readonly editorService: IEditorService, @IConfigurationService private readonly configurationService: IConfigurationService, @IContextKeyService contextKeyService: IContextKeyService, @@ -342,28 +328,20 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor super(); this.isEmbedded = creationOptions.isEmbedded || false; - this.useRenderer = (this.configurationService.getValue('notebook.experimental.useMarkdownRenderer') ?? false /*!isWeb*/) && !accessibilityService.isScreenReaderOptimized(); + this.useRenderer = !isWeb && !!this.configurationService.getValue(ExperimentalUseMarkdownRenderer) && !accessibilityService.isScreenReaderOptimized(); this._overlayContainer = document.createElement('div'); this.scopedContextKeyService = contextKeyService.createScoped(this._overlayContainer); this.instantiationService = instantiationService.createChild(new ServiceCollection([IContextKeyService, this.scopedContextKeyService])); - const that = this; - this._kernelManger = instantiationService.createInstance(NotebookEditorKernelManager, { - getId() { return that.getId(); }, - loadKernelPreloads: that._loadKernelPreloads.bind(that), - onDidChangeViewModel: that.onDidChangeModel, - get viewModel() { return that.viewModel; }, - getContributedNotebookProviders(resource?: URI) { - return that.notebookService.getContributedNotebookProviders(resource); - }, - getContributedNotebookProvider(viewType: string): NotebookProviderInfo | undefined { - return that.notebookService.getContributedNotebookProvider(viewType); - }, - getNotebookKernels(viewType: string, resource: URI, token: CancellationToken): Promise { - return that.notebookService.getNotebookKernels(viewType, resource, token); + this._register(instantiationService.createInstance(NotebookEditorContextKeys, this)); + + this._kernelManger = instantiationService.createInstance(NotebookEditorKernelManager); + this._register(notebookKernelService.onDidChangeNotebookKernelBinding(e => { + if (isEqual(e.notebook, this.viewModel?.uri)) { + this._loadKernelPreloads(); } - }); + })); this._memento = new Memento(NOTEBOOK_EDITOR_ID, storageService); @@ -428,7 +406,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor this._frameId++; this._domFrameLog(); - }); + }, 1000000); } private _debug(...args: any[]) { @@ -501,13 +479,37 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor return; } - const cellToolbarLocation = this.configurationService.getValue(CellToolbarLocKey); + const cellToolbarLocation = this.configurationService.getValue(CellToolbarLocKey); this._overlayContainer.classList.remove('cell-title-toolbar-left'); this._overlayContainer.classList.remove('cell-title-toolbar-right'); this._overlayContainer.classList.remove('cell-title-toolbar-hidden'); - if (cellToolbarLocation === 'left' || cellToolbarLocation === 'right' || cellToolbarLocation === 'hidden') { - this._overlayContainer.classList.add(`cell-title-toolbar-${cellToolbarLocation}`); + if (typeof cellToolbarLocation === 'string') { + if (cellToolbarLocation === 'left' || cellToolbarLocation === 'right' || cellToolbarLocation === 'hidden') { + this._overlayContainer.classList.add(`cell-title-toolbar-${cellToolbarLocation}`); + } + } else { + if (this.viewModel) { + const notebookSpecificSetting = cellToolbarLocation[this.viewModel.viewType] ?? cellToolbarLocation['default']; + let cellToolbarLocationForCurrentView = 'right'; + + switch (notebookSpecificSetting) { + case 'left': + cellToolbarLocationForCurrentView = 'left'; + break; + case 'right': + cellToolbarLocationForCurrentView = 'right'; + case 'hidden': + cellToolbarLocationForCurrentView = 'hidden'; + default: + cellToolbarLocationForCurrentView = 'right'; + break; + } + + this._overlayContainer.classList.add(`cell-title-toolbar-${cellToolbarLocationForCurrentView}`); + } else { + this._overlayContainer.classList.add(`cell-title-toolbar-right`); + } } const showCellStatusBar = this.configurationService.getValue(ShowCellStatusBarKey); @@ -516,7 +518,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor private _generateFontInfo(): void { const editorOptions = this.configurationService.getValue('editor'); - this._fontInfo = BareFontInfo.createFromRawSettings(editorOptions, getZoomLevel(), getPixelRatio()); + this._fontInfo = readFontInfo(BareFontInfo.createFromRawSettings(editorOptions, getZoomLevel(), getPixelRatio())); } private _createBody(parent: HTMLElement): void { @@ -827,7 +829,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor applyFocusChange(); const newFocusedCell = this._list.getFocusedElements()[0]; - if (newFocusedCell.cellKind === CellKind.Code || newFocusedCell.editState === CellEditState.Editing) { + if (newFocusedCell.cellKind === CellKind.Code || newFocusedCell.getEditState() === CellEditState.Editing) { this.focusNotebookCell(newFocusedCell, 'editor'); } else { // Reset to "Editor", the state has not been consumed @@ -880,24 +882,12 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor this.restoreListViewState(viewState); } + // load preloads for matching kernel + this._loadKernelPreloads(); + // clear state this._dndController?.clearGlobalDragState(); - this._currentKernelTokenSource = new CancellationTokenSource(); - this._localStore.add(this._currentKernelTokenSource); - // we don't await for it, otherwise it will slow down the file opening - this._setKernels(this._currentKernelTokenSource); - - this._localStore.add(this.notebookService.onDidChangeKernels(async (e) => { - if (e && e.toString() !== this.textModel?.uri.toString()) { - // kernel update is not for current document. - return; - } - this._currentKernelTokenSource?.cancel(); - this._currentKernelTokenSource = new CancellationTokenSource(); - await this._setKernels(this._currentKernelTokenSource); - })); - this._localStore.add(this._list.onDidChangeFocus(() => { this.updateContextKeysOnFocusChange(); })); @@ -913,7 +903,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor const focused = this._list.getFocusedElements()[0]; if (focused) { if (!this._cellContextKeyManager) { - this._cellContextKeyManager = this._localStore.add(new CellContextKeyManager(this.scopedContextKeyService, this, this.viewModel.notebookDocument, focused as CellViewModel)); + this._cellContextKeyManager = this._localStore.add(new CellContextKeyManager(this.scopedContextKeyService, this, focused as CellViewModel)); } this._cellContextKeyManager.updateForElement(focused as CellViewModel); @@ -921,8 +911,16 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor } async setOptions(options: NotebookEditorOptions | undefined) { + if (!this.hasModel()) { + return; + } + + if (options?.isReadOnly !== undefined) { + this.viewModel.updateOptions({ isReadOnly: options.isReadOnly }); + } + // reveal cell if editor options tell to do so - if (options?.cellOptions && this.viewModel) { + if (options?.cellOptions) { const cellOptions = options.cellOptions; const cell = this.viewModel.viewCells.find(cell => cell.uri.toString() === cellOptions.resource.toString()); if (cell) { @@ -953,15 +951,25 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor // select cells if options tell to do so // todo@rebornix https://github.com/microsoft/vscode/issues/118108 support selections not just focus // todo@rebornix support multipe selections - if (options?.cellSelections && this.viewModel) { - const focusedCell = this.viewModel.viewCells[options.cellSelections[0].start]; - this.revealInCenterIfOutsideViewport(focusedCell); - this.focusElement(focusedCell); + if (options?.cellSelections) { + const focusCellIndex = options.cellSelections[0].start; + const focusedCell = this.viewModel.cellAt(focusCellIndex); + if (focusedCell) { + this.viewModel.updateSelectionsState({ + kind: SelectionStateType.Index, + focus: { start: focusCellIndex, end: focusCellIndex + 1 }, + selections: options.cellSelections + }); + this.revealInCenterIfOutsideViewport(focusedCell); + } } + + this._updateForOptions(); } private _detachModel() { this._localStore.clear(); + dispose(this._localCellStateListeners); this._list.detachViewModel(); this.viewModel?.dispose(); // avoid event @@ -972,37 +980,15 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor this._list.clear(); } - async beginComputeContributedKernels() { - return this._kernelManger.beginComputeContributedKernels(); - } - private async _setKernels(tokenSource: CancellationTokenSource) { - if (!this.viewModel) { + private _updateForOptions(): void { + if (!this.hasModel()) { return; } - this._kernelManger.setKernels(tokenSource); - } - - private async _loadKernelPreloads(extensionLocation: URI, kernel: INotebookKernel) { - if (kernel.preloads && kernel.preloads.length) { - if (!this._webview?.isResolved()) { - await this._resolveWebview(); - } - - this._webview?.updateKernelPreloads([extensionLocation], kernel.preloads.map(preload => URI.revive(preload))); - } - } - - private _updateForMetadata(): void { - if (!this.viewModel) { - return; - } - - const notebookMetadata = this.viewModel.metadata; - this._editorEditable.set(!!notebookMetadata?.editable); - this._overflowContainer.classList.toggle('notebook-editor-editable', !!notebookMetadata?.editable); - this.getDomNode().classList.toggle('notebook-editor-editable', !!notebookMetadata?.editable); + this._editorEditable.set(!this.viewModel.options.isReadOnly); + this._overflowContainer.classList.toggle('notebook-editor-editable', !this.viewModel.options.isReadOnly); + this.getDomNode().classList.toggle('notebook-editor-editable', !this.viewModel.options.isReadOnly); } private async _resolveWebview(): Promise | null> { @@ -1046,10 +1032,8 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor } }); - this._localStore.add(this._webview.onMessage(({ message, forRenderer }) => { - if (this.viewModel) { - this.notebookService.onDidReceiveMessage(this.viewModel.viewType, this.getId(), forRenderer, message); - } + this._localStore.add(this._webview.onMessage(e => { + this._onDidReceiveMessage.fire(e); })); resolve(this._webview); @@ -1064,7 +1048,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor outputNodeLeftPadding: CELL_OUTPUT_PADDING, previewNodePadding: MARKDOWN_PREVIEW_PADDING, leftMargin: CODE_CELL_LEFT_MARGIN, - cellMargin: CELL_MARGIN, + rightMargin: CELL_RIGHT_MARGIN, runGutter: CELL_RUN_GUTTER, }); this._webview.element.style.width = '100%'; @@ -1080,10 +1064,8 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor this.viewModel = this.instantiationService.createInstance(NotebookViewModel, textModel.viewType, textModel, this._eventDispatcher, this.getLayoutInfo()); this._eventDispatcher.emit([new NotebookLayoutChangedEvent({ width: true, fontInfo: true }, this.getLayoutInfo())]); - this._updateForMetadata(); - this._localStore.add(this._eventDispatcher.onDidChangeMetadata(() => { - this._updateForMetadata(); - })); + this._updateForOptions(); + this._updateForNotebookConfiguration(); // restore view states, including contributions @@ -1103,109 +1085,53 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor this._localStore.add(this.viewModel.onDidChangeSelection(() => { this._onDidChangeSelection.fire(); + this.updateSelectedMarkdownPreviews(); })); this._localStore.add(this._list.onWillScroll(e => { if (this._webview?.isResolved()) { - this._webview.updateViewScrollTop(-e.scrollTop, true, []); this._webviewTransparentCover!.style.top = `${e.scrollTop}px`; } })); + let hasPendingChangeContentHeight = false; this._localStore.add(this._list.onDidChangeContentHeight(() => { + if (hasPendingChangeContentHeight) { + return; + } + hasPendingChangeContentHeight = true; + DOM.scheduleAtNextAnimationFrame(() => { - if (this._isDisposed) { - return; - } + hasPendingChangeContentHeight = false; + this.updateScrollHeight(); + }, 100); + })); - const scrollTop = this._list.scrollTop; - const scrollHeight = this._list.scrollHeight; + this._localStore.add(this._list.onDidRemoveOutputs(outputs => { + outputs.forEach(output => this.removeInset(output)); + })); + this._localStore.add(this._list.onDidHideOutputs(outputs => { + outputs.forEach(output => this.hideInset(output)); + })); + this._localStore.add(this._list.onDidRemoveCellsFromView(cells => { + const hiddenCells: MarkdownCellViewModel[] = []; + const deletedCells: MarkdownCellViewModel[] = []; - if (!this._webview?.isResolved()) { - return; - } - - this._webview!.element.style.height = `${scrollHeight}px`; - - if (this._webview?.insetMapping) { - const updateItems: IDisplayOutputLayoutUpdateRequest[] = []; - const removedItems: ICellOutputViewModel[] = []; - this._webview?.insetMapping.forEach((value, key) => { - const cell = this.viewModel?.getCellByHandle(value.cellInfo.cellHandle); - if (!cell || !(cell instanceof CodeCellViewModel)) { - return; - } - - this.viewModel?.viewCells.find(cell => cell.handle === value.cellInfo.cellHandle); - const viewIndex = this._list.getViewIndex(cell); - - if (viewIndex === undefined) { - return; - } - - if (cell.outputsViewModels.indexOf(key) < 0) { - // output is already gone - removedItems.push(key); - } - - const cellTop = this._list.getAbsoluteTopOfElement(cell); - if (this._webview!.shouldUpdateInset(cell, key, cellTop)) { - const outputIndex = cell.outputsViewModels.indexOf(key); - - const outputOffset = cellTop + cell.getOutputOffset(outputIndex); - - updateItems.push({ - output: key, - cellTop: cellTop, - outputOffset - }); - } - }); - - removedItems.forEach(output => this._webview?.removeInset(output)); - - if (updateItems.length) { - this._debug('_list.onDidChangeContentHeight/outputs', updateItems); - this._webview?.updateViewScrollTop(-scrollTop, false, updateItems); + for (const cell of cells) { + if (cell.cellKind === CellKind.Markdown) { + const mdCell = cell as MarkdownCellViewModel; + if (this.viewModel?.viewCells.find(cell => cell.handle === mdCell.handle)) { + // Cell has been folded but is still in model + hiddenCells.push(mdCell); + } else { + // Cell was deleted + deletedCells.push(mdCell); } } - - if (this._webview?.markdownPreviewMapping) { - const updateItems: { id: string, top: number }[] = []; - this._webview.markdownPreviewMapping.forEach((_, cellId) => { - const cell = this.viewModel?.viewCells.find(cell => cell.id === cellId); - if (cell) { - const cellTop = this._list.getAbsoluteTopOfElement(cell); - updateItems.push({ id: cellId, top: cellTop }); - } - }); - - if (updateItems.length) { - this._debug('_list.onDidChangeContentHeight/markdown', updateItems); - this._webview?.updateMarkdownScrollTop(updateItems); - } - - } - }); - })); - - this._localStore.add(this._list.onDidRemoveOutput(output => { - this.removeInset(output); - })); - this._localStore.add(this._list.onDidHideOutput(output => { - this.hideInset(output); - })); - this._localStore.add(this._list.onDidRemoveCellFromView(cell => { - if (cell.cellKind === CellKind.Markdown) { - const mdCell = cell as MarkdownCellViewModel; - if (this.viewModel?.viewCells.find(cell => cell.handle === mdCell.handle)) { - // Cell has been folded but is still in model - this.hideMarkdownPreview(mdCell); - } else { - // Cell was deleted - this.removeMarkdownPreview(mdCell); - } } + + this.hideMarkdownPreviews(hiddenCells); + this.deleteMarkdownPreviews(deletedCells); })); // init rendering @@ -1215,6 +1141,25 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor this._list.attachViewModel(this.viewModel); } + mark(textModel.uri, 'customMarkdownLoaded'); + + // model attached + this._localCellStateListeners = this.viewModel.viewCells.map(cell => this._bindCellListener(cell)); + + this._localStore.add(this.viewModel.onDidChangeViewCells((e) => { + if (this._isDisposed) { + return; + } + + // update resize listener + e.splices.reverse().forEach(splice => { + const [start, deleted, newCells] = splice; + const deletedCells = this._localCellStateListeners.splice(start, deleted, ...newCells.map(cell => this._bindCellListener(cell))); + + dispose(deletedCells); + }); + })); + if (this._dimension) { this._list.layout(this._dimension.height - SCROLLABLE_ELEMENT_PADDING_TOP, this._dimension.width); } else { @@ -1227,6 +1172,34 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor this.restoreListViewState(viewState); } + private _bindCellListener(cell: ICellViewModel) { + const store = new DisposableStore(); + + store.add(cell.onDidChangeLayout(e => { + if (e.totalHeight !== undefined || e.outerWidth) { + this.layoutNotebookCell(cell, cell.layoutInfo.totalHeight); + } + })); + + if (cell.cellKind === CellKind.Code) { + store.add((cell as CodeCellViewModel).onDidRemoveOutputs((outputs) => { + outputs.forEach(output => this.removeInset(output)); + })); + + store.add((cell as CodeCellViewModel).onDidHideOutputs((outputs) => { + outputs.forEach(output => this.hideInset(output)); + })); + } + + if (cell.cellKind === CellKind.Markdown) { + store.add((cell as MarkdownCellViewModel).onDidHideInput(() => { + this.hideMarkdownPreviews([(cell as MarkdownCellViewModel)]); + })); + } + + return store; + } + private async _warmupWithMarkdownRenderer(viewModel: NotebookViewModel, viewState: INotebookEditorViewState | undefined) { await this._resolveWebview(); @@ -1265,8 +1238,8 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor let offset = 0; let requests: [ICellViewModel, number][] = []; - for (let i = 0; i < viewModel.viewCells.length; i++) { - const cell = viewModel.viewCells[i]; + for (let i = 0; i < viewModel.length; i++) { + const cell = viewModel.cellAt(i)!; if (offset + (totalHeightCache[i] ?? 0) < scrollTop) { offset += (totalHeightCache ? totalHeightCache[i] : 0); @@ -1293,10 +1266,9 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor // no cached view state so we are rendering the first viewport // after above async call, we already get init height for markdown cells, we can update their offset let offset = 0; - let offsetUpdateRequests: { id: string, top: number }[] = []; + const offsetUpdateRequests: { id: string, top: number }[] = []; const scrollBottom = Math.max(this._dimension?.height ?? 0, 1080); - for (let i = 0; i < viewModel.viewCells.length; i++) { - const cell = viewModel.viewCells[i]; + for (const cell of viewModel.viewCells) { if (cell.cellKind === CellKind.Markdown) { offsetUpdateRequests.push({ id: cell.id, top: offset }); } @@ -1308,7 +1280,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor } } - this._webview?.updateMarkdownScrollTop(offsetUpdateRequests); + this._webview?.updateScrollTops([], offsetUpdateRequests); } } @@ -1340,7 +1312,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor } if (viewState?.editorFocused) { - const cell = this.viewModel?.viewCells[focusIdx]; + const cell = this.viewModel?.cellAt(focusIdx); if (cell) { cell.focusMode = CellFocusMode.Editor; } @@ -1360,7 +1332,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor state.scrollPosition = { left: this._list.scrollLeft, top: this._list.scrollTop }; const cellHeights: { [key: number]: number } = {}; for (let i = 0; i < this.viewModel!.length; i++) { - const elm = this.viewModel!.viewCells[i] as CellViewModel; + const elm = this.viewModel!.cellAt(i) as CellViewModel; if (elm.cellKind === CellKind.Code) { cellHeights[i] = elm.layoutInfo.totalHeight; } else { @@ -1370,15 +1342,15 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor state.cellTotalHeights = cellHeights; - const focus = this._list.getFocus()[0]; - if (typeof focus === 'number' && this.viewModel) { - const element = this.viewModel.viewCells[focus]; + if (this.viewModel) { + const focusRange = this.viewModel.getFocus(); + const element = this.viewModel.cellAt(focusRange.start); if (element) { const itemDOM = this._list.domElementOfElement(element); - const editorFocused = element.editState === CellEditState.Editing && !!(document.activeElement && itemDOM && itemDOM.contains(document.activeElement)); + const editorFocused = element.getEditState() === CellEditState.Editing && !!(document.activeElement && itemDOM && itemDOM.contains(document.activeElement)); state.editorFocused = editorFocused; - state.focus = focus; + state.focus = focusRange.start; } } } @@ -1414,8 +1386,15 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor this._dimension = new DOM.Dimension(dimension.width, dimension.height); DOM.size(this._body, dimension.width, dimension.height - (this._useGlobalToolbar ? /** Toolbar height */ 26 : 0)); - this._list.updateOptions({ additionalScrollHeight: this._scrollBeyondLastLine ? dimension.height - SCROLLABLE_ELEMENT_PADDING_TOP : SCROLLABLE_ELEMENT_PADDING_TOP }); - this._list.layout(dimension.height - SCROLLABLE_ELEMENT_PADDING_TOP, dimension.width); + if (this._list.getRenderHeight() < dimension.height - SCROLLABLE_ELEMENT_PADDING_TOP) { + // the new dimension is larger than the list viewport, update its additional height first, otherwise the list view will move down a bit (as the `scrollBottom` will move down) + this._list.updateOptions({ additionalScrollHeight: this._scrollBeyondLastLine ? Math.max(0, (dimension.height - SCROLLABLE_ELEMENT_PADDING_TOP - 50)) : SCROLLABLE_ELEMENT_PADDING_TOP }); + this._list.layout(dimension.height - SCROLLABLE_ELEMENT_PADDING_TOP, dimension.width); + } else { + // the new dimension is smaller than the list viewport, if we update the additional height, the `scrollBottom` will move up, which moves the whole list view upwards a bit. So we run a layout first. + this._list.layout(dimension.height - SCROLLABLE_ELEMENT_PADDING_TOP, dimension.width); + this._list.updateOptions({ additionalScrollHeight: this._scrollBeyondLastLine ? Math.max(0, (dimension.height - SCROLLABLE_ELEMENT_PADDING_TOP - 50)) : SCROLLABLE_ELEMENT_PADDING_TOP }); + } this._overlayContainer.style.visibility = 'visible'; this._overlayContainer.style.display = 'block'; @@ -1444,18 +1423,18 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor if (this._webiewFocused) { this._webview?.focusWebview(); } else { - const focus = this._list.getFocus()[0]; - if (typeof focus === 'number' && this.viewModel) { - const element = this.viewModel.viewCells[focus]; + if (this.viewModel) { + const focusRange = this.viewModel.getFocus(); + const element = this.viewModel.cellAt(focusRange.start); - if (element.focusMode === CellFocusMode.Editor) { - element.editState = CellEditState.Editing; + if (element && element.focusMode === CellFocusMode.Editor) { + element.updateEditState(CellEditState.Editing, 'editorWidget.focus'); element.focusMode = CellFocusMode.Editor; this._onDidFocusEditorWidget.fire(); return; } - } + this._list.domFocus(); } @@ -1591,11 +1570,19 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor return this._listViewInfoAccessor.getViewIndex(cell); } + getViewHeight(cell: ICellViewModel): number { + if (!this._listViewInfoAccessor) { + return -1; + } + + return this._listViewInfoAccessor.getViewHeight(cell); + } + getCellRangeFromViewRange(startIndex: number, endIndex: number): ICellRange | undefined { return this._listViewInfoAccessor.getCellRangeFromViewRange(startIndex, endIndex); } - getCellsFromViewRange(startIndex: number, endIndex: number): ICellViewModel[] { + getCellsFromViewRange(startIndex: number, endIndex: number): ReadonlyArray { return this._listViewInfoAccessor.getCellsFromViewRange(startIndex, endIndex); } @@ -1657,7 +1644,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor } const existingDecorations = this._decortionKeyToIds.get(key) || []; - const newDecorations = this.viewModel.viewCells.slice(range.start, range.end).map(cell => ({ + const newDecorations = this.viewModel.getCells(range).map(cell => ({ handle: cell.handle, options: { className: decorationRule.className, outputClassName: decorationRule.className, topClassName: decorationRule.topClassName } })); @@ -1674,7 +1661,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor this.deltaCellDecorations(cellDecorations || [], []); } - deltaCellDecorations(oldDecorations: string[], newDecorations: INotebookDeltaDecoration[]): string[] { return this.viewModel?.deltaCellDecorations(oldDecorations, newDecorations) || []; } @@ -1699,30 +1685,46 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor //#endregion //#region Kernel/Execution - async cancelNotebookExecution(): Promise { - return this._kernelManger.cancelNotebookExecution(); - } - async executeNotebook(): Promise { - return this._kernelManger.executeNotebook(); - } - - async cancelNotebookCellExecution(cell: ICellViewModel): Promise { - return this._kernelManger.cancelNotebookCellExecution(cell); - } - - async executeNotebookCell(cell: ICellViewModel): Promise { - if (!this.viewModel) { + private async _loadKernelPreloads() { + const kernel = this.activeKernel; + if (!kernel) { + return; + } + const preloadUris = kernel.preloadUris; + if (!preloadUris.length) { return; } - // TODO@roblourens, don't use the "execute" command for this - if (cell.cellKind === CellKind.Markdown) { - this.focusNotebookCell(cell, 'container'); - return; + if (!this._webview?.isResolved()) { + await this._resolveWebview(); } - return this._kernelManger.executeNotebookCell(cell); + this._webview?.updateKernelPreloads([kernel.localResourceRoot], kernel.preloadUris); + } + + get activeKernel() { + return this.viewModel && this._kernelManger.getSelectedOrSuggestedKernel(this.viewModel.notebookDocument); + } + + async cancelNotebookCells(cells?: Iterable): Promise { + if (!this.hasModel()) { + return; + } + if (!cells) { + cells = this.viewModel.viewCells; + } + return this._kernelManger.cancelNotebookCells(this.viewModel.notebookDocument, cells); + } + + async executeNotebookCells(cells?: Iterable): Promise { + if (!this.hasModel()) { + return; + } + if (!cells) { + cells = this.viewModel.viewCells; + } + return this._kernelManger.executeNotebookCells(this.viewModel.notebookDocument, cells); } //#endregion @@ -1730,6 +1732,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor //#region Cell operations/layout API private _pendingLayouts = new WeakMap(); async layoutNotebookCell(cell: ICellViewModel, height: number): Promise { + this._debug('layout cell', cell.handle, height); const viewIndex = this._list.getViewIndex(cell); if (viewIndex === undefined) { // the cell is hidden @@ -1772,21 +1775,12 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor return new Promise(resolve => { r = resolve; }); } - private _nearestCodeCellIndex(index: number /* exclusive */, direction: 'above' | 'below') { + private _nearestCodeCellIndex(index: number /* exclusive */) { if (!this.viewModel) { return -1; } - const nearest = this.viewModel.viewCells.slice(0, index).reverse().findIndex(cell => cell.cellKind === CellKind.Code); - if (nearest > -1) { - return index - nearest - 1; - } else { - const nearestCellTheOtherDirection = this.viewModel.viewCells.slice(index + 1).findIndex(cell => cell.cellKind === CellKind.Code); - if (nearestCellTheOtherDirection > -1) { - return index + nearestCellTheOtherDirection; - } - return -1; - } + return this.viewModel.nearestCodeCellIndex(index); } insertNotebookCell(cell: ICellViewModel | undefined, type: CellKind, direction: 'above' | 'below' = 'above', initialText: string = '', ui: boolean = false): CellViewModel | null { @@ -1794,7 +1788,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor return null; } - if (!this.viewModel.metadata.editable) { + if (this.viewModel.options.isReadOnly) { return null; } @@ -1802,14 +1796,14 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor const nextIndex = ui ? this.viewModel.getNextVisibleCellIndex(index) : index + 1; let language; if (type === CellKind.Code) { - const supportedLanguages = this._kernelManger.activeKernel?.supportedLanguages ?? this.modeService.getRegisteredModes(); + const supportedLanguages = this.activeKernel?.supportedLanguages ?? this.modeService.getRegisteredModes(); const defaultLanguage = supportedLanguages[0] || 'plaintext'; if (cell?.cellKind === CellKind.Code) { language = cell.language; } else if (cell?.cellKind === CellKind.Markdown) { - const nearestCodeCellIndex = this._nearestCodeCellIndex(index, direction); + const nearestCodeCellIndex = this._nearestCodeCellIndex(index); if (nearestCodeCellIndex > -1) { - language = this.viewModel.viewCells[nearestCodeCellIndex].language; + language = this.viewModel.cellAt(nearestCodeCellIndex)!.language; } else { language = defaultLanguage; } @@ -1843,7 +1837,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor return null; } - if (!this.viewModel.metadata.editable) { + if (this.viewModel.options.isReadOnly) { return null; } @@ -1857,7 +1851,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor return false; } - if (!this.viewModel.metadata.editable) { + if (this.viewModel.options.isReadOnly) { return false; } @@ -1875,7 +1869,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor return null; } - if (!this.viewModel.metadata.editable) { + if (this.viewModel.options.isReadOnly) { return null; } @@ -1893,7 +1887,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor return null; } - if (!this.viewModel.metadata.editable) { + if (this.viewModel.options.isReadOnly) { return null; } @@ -1911,7 +1905,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor return null; } - if (!this.viewModel.metadata.editable) { + if (this.viewModel.options.isReadOnly) { return null; } @@ -1955,9 +1949,13 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor return; } - const viewCell = this.viewModel.viewCells[desiredIndex]; - this._list.revealElementInView(viewCell); - r(viewCell); + const viewCell = this.viewModel.cellAt(desiredIndex); + if (viewCell) { + this._list.revealElementInView(viewCell); + r(viewCell); + } else { + r(null); + } }); return new Promise(resolve => { r = resolve; }); @@ -1994,6 +1992,20 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor aria.alert(`Cell ${this._notebookViewModel?.getCellIndex(cell)}, ${position} `); } } + + toggleNotebookCellSelection(cell: ICellViewModel): void { + const currentSelections = this._list.getSelectedElements(); + + const isSelected = currentSelections.includes(cell); + if (isSelected) { + // Deselect + this._list.selectElements(currentSelections.filter(current => current !== cell)); + } else { + // Add to selection + this._list.selectElements([...currentSelections, cell]); + } + } + focusNotebookCell(cell: ICellViewModel, focusItem: 'editor' | 'container' | 'output', options?: IFocusNotebookCellOptions) { if (this._isDisposed) { return; @@ -2004,7 +2016,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor this._cellFocusAria(cell, focusItem); this._list.focusView(); - cell.editState = CellEditState.Editing; + cell.updateEditState(CellEditState.Editing, 'focusNotebookCell'); cell.focusMode = CellFocusMode.Editor; if (!options?.skipReveal) { this.revealInCenterIfOutsideViewport(cell); @@ -2019,7 +2031,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor } this._webview.focusOutput(cell.id); - cell.editState = CellEditState.Preview; + cell.updateEditState(CellEditState.Preview, 'focusNotebookCell'); cell.focusMode = CellFocusMode.Container; if (!options?.skipReveal) { this.revealInCenterIfOutsideViewport(cell); @@ -2030,7 +2042,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor (document.activeElement as HTMLElement).blur(); } - cell.editState = CellEditState.Preview; + cell.updateEditState(CellEditState.Preview, 'focusNotebookCell'); cell.focusMode = CellFocusMode.Container; this.focusElement(cell); @@ -2048,7 +2060,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor return; } - const newCell = this.viewModel?.viewCells[idx + 1]; + const newCell = this.viewModel?.cellAt(idx + 1); if (!newCell) { return; } @@ -2103,10 +2115,10 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor } const cellTop = this._list.getAbsoluteTopOfElement(cell); - await this._webview.showMarkdownPreview(cell.id, cell.handle, cell.getText(), cellTop, cell.version); + await this._webview.showMarkdownPreview(cell.id, cell.handle, cell.getText(), cellTop, cell.contentHash); } - async unhideMarkdownPreview(cell: MarkdownCellViewModel) { + async unhideMarkdownPreviews(cells: readonly MarkdownCellViewModel[]) { if (!this.useRenderer) { // TODO: handle case where custom renderer is disabled? return; @@ -2120,10 +2132,27 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor await this._resolveWebview(); } - await this._webview?.unhideMarkdownPreview(cell.id); + await this._webview?.unhideMarkdownPreviews(cells.map(cell => cell.id)); } - async hideMarkdownPreview(cell: MarkdownCellViewModel) { + async hideMarkdownPreviews(cells: readonly MarkdownCellViewModel[]) { + if (!this.useRenderer) { + // TODO: handle case where custom renderer is disabled? + return; + } + + if (!this._webview || !cells.length) { + return; + } + + if (!this._webview.isResolved()) { + await this._resolveWebview(); + } + + await this._webview?.hideMarkdownPreviews(cells.map(cell => cell.id)); + } + + async deleteMarkdownPreviews(cells: readonly MarkdownCellViewModel[]) { if (!this.useRenderer) { // TODO: handle case where custom renderer is disabled? return; @@ -2137,16 +2166,11 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor await this._resolveWebview(); } - await this._webview?.hideMarkdownPreview(cell.id); + await this._webview?.deleteMarkdownPreviews(cells.map(cell => cell.id)); } - async removeMarkdownPreview(cell: MarkdownCellViewModel) { - if (!this.useRenderer) { - // TODO: handle case where custom renderer is disabled? - return; - } - - if (!this._webview) { + private async updateSelectedMarkdownPreviews(): Promise { + if (!this.useRenderer || !this._webview) { return; } @@ -2154,24 +2178,10 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor await this._resolveWebview(); } - await this._webview?.removeMarkdownPreview(cell.id); - } + const selectedCells = this.getSelectionViewModels().map(cell => cell.id); - async updateMarkdownPreviewSelectionState(cell: ICellViewModel, isSelected: boolean): Promise { - if (!this.useRenderer) { - // TODO: handle case where custom renderer is disabled? - return; - } - - if (!this._webview) { - return; - } - - if (!this._webview.isResolved()) { - await this._resolveWebview(); - } - - await this._webview?.updateMarkdownPreviewSelectionState(cell.id, isSelected); + // Only show selection when there is more than 1 cell selected + await this._webview?.updateMarkdownPreviewSelections(selectedCells.length > 1 ? selectedCells : []); } async createOutput(cell: CodeCellViewModel, output: IInsetRenderOutput, offset: number): Promise { @@ -2184,16 +2194,23 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor await this._resolveWebview(); } - if (!this._webview!.insetMapping.has(output.source)) { - const cellTop = this._list.getAbsoluteTopOfElement(cell); - await this._webview!.createOutput({ cellId: cell.id, cellHandle: cell.handle, cellUri: cell.uri }, output, cellTop, offset); - } else { - const cellTop = this._list.getAbsoluteTopOfElement(cell); - const scrollTop = this._list.scrollTop; - const outputIndex = cell.outputsViewModels.indexOf(output.source); - const outputOffset = cellTop + cell.getOutputOffset(outputIndex); + if (!this._webview) { + return; + } - this._webview!.updateViewScrollTop(-scrollTop, true, [{ output: output.source, cellTop, outputOffset }]); + const cellTop = this._list.getAbsoluteTopOfElement(cell); + if (!this._webview.insetMapping.has(output.source)) { + await this._webview.createOutput({ cellId: cell.id, cellHandle: cell.handle, cellUri: cell.uri }, output, cellTop, offset); + } else { + const outputIndex = cell.outputsViewModels.indexOf(output.source); + const outputOffset = cell.getOutputOffset(outputIndex); + this._webview.updateScrollTops([{ + cell, + output: output.source, + cellTop, + outputOffset, + forceDisplay: !cell.metadata.outputCollapsed, + }], []); } }); } @@ -2201,7 +2218,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor removeInset(output: ICellOutputViewModel) { this._insetModifyQueueByOutputId.queue(output.model.outputId, async () => { if (this._webview?.isResolved()) { - this._webview.removeInset(output); + this._webview.removeInsets([output]); } }); } @@ -2218,6 +2235,12 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor return this._outputRenderer; } + //#region --- webview IPC ---- + + private readonly _onDidReceiveMessage = new Emitter(); + + readonly onDidReceiveMessage: Event = this._onDidReceiveMessage.event; + postMessage(forRendererId: string | undefined, message: any) { if (this._webview?.isResolved()) { if (forRendererId === undefined) { @@ -2228,6 +2251,8 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor } } + //#endregion + addClassName(className: string) { this._overlayContainer.classList.add(className); } @@ -2245,15 +2270,85 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor return this.viewModel?.viewCells.find(vc => vc.id === cellId); } - updateOutputHeight(cellInfo: ICommonCellInfo, output: ICellOutputViewModel, outputHeight: number, isInit: boolean): void { + updateOutputHeight(cellInfo: ICommonCellInfo, output: ICellOutputViewModel, outputHeight: number, isInit: boolean, source?: string): void { const cell = this.viewModel?.viewCells.find(vc => vc.handle === cellInfo.cellHandle); if (cell && cell instanceof CodeCellViewModel) { const outputIndex = cell.outputsViewModels.indexOf(output); - cell.updateOutputHeight(outputIndex, outputHeight); + if (isInit && outputHeight !== 0) { + cell.updateOutputMinHeight(0); + } + this._debug('update cell output', cell.handle, outputHeight); + cell.updateOutputHeight(outputIndex, outputHeight, source); this.layoutNotebookCell(cell, cell.layoutInfo.totalHeight); } } + updateScrollHeight() { + if (this._isDisposed || !this._webview?.isResolved()) { + return; + } + + const scrollHeight = this._list.scrollHeight; + this._webview!.element.style.height = `${scrollHeight}px`; + + const updateItems: IDisplayOutputLayoutUpdateRequest[] = []; + const removedItems: ICellOutputViewModel[] = []; + this._webview?.insetMapping.forEach((value, key) => { + const cell = this.viewModel?.getCellByHandle(value.cellInfo.cellHandle); + if (!cell || !(cell instanceof CodeCellViewModel)) { + return; + } + + this.viewModel?.viewCells.find(cell => cell.handle === value.cellInfo.cellHandle); + const viewIndex = this._list.getViewIndex(cell); + + if (viewIndex === undefined) { + return; + } + + if (cell.outputsViewModels.indexOf(key) < 0) { + // output is already gone + removedItems.push(key); + } + + const cellTop = this._list.getAbsoluteTopOfElement(cell); + const outputIndex = cell.outputsViewModels.indexOf(key); + const outputOffset = cell.getOutputOffset(outputIndex); + updateItems.push({ + cell, + output: key, + cellTop, + outputOffset, + forceDisplay: false, + }); + }); + + this._webview.removeInsets(removedItems); + + const markdownUpdateItems: { id: string, top: number }[] = []; + for (const cellId of this._webview.markdownPreviewMapping.keys()) { + const cell = this.viewModel?.viewCells.find(cell => cell.id === cellId); + if (cell) { + const cellTop = this._list.getAbsoluteTopOfElement(cell); + markdownUpdateItems.push({ id: cellId, top: cellTop }); + } + } + + if (markdownUpdateItems.length || updateItems.length) { + this._debug('_list.onDidChangeContentHeight/markdown', markdownUpdateItems); + this._webview?.updateScrollTops(updateItems, markdownUpdateItems); + } + } + + scheduleOutputHeightAck(cellInfo: ICommonCellInfo, outputId: string, height: number) { + DOM.scheduleAtNextAnimationFrame(() => { + this.updateScrollHeight(); + + this._debug('ack height', height); + this._webview?.ackHeight(cellInfo.cellId, outputId, height); + }, 10); + } + updateMarkdownCellHeight(cellId: string, height: number, isInit: boolean) { const cell = this.getCellById(cellId); if (cell && cell instanceof MarkdownCellViewModel) { @@ -2267,7 +2362,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor setMarkdownCellEditState(cellId: string, editState: CellEditState): void { const cell = this.getCellById(cellId); if (cell instanceof MarkdownCellViewModel) { - cell.editState = editState; + cell.updateEditState(editState, 'setMarkdownCellEditState'); } } @@ -2308,7 +2403,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor //#endregion - dispose() { + override dispose() { this._isDisposed = true; // dispose webview first this._webview?.dispose(); @@ -2319,6 +2414,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor this._contributions.clear(); this._localStore.clear(); + dispose(this._localCellStateListeners); this._list.dispose(); this._listTopCellToolbar?.dispose(); @@ -2405,7 +2501,7 @@ export const selectedCellBorder = registerColor('notebook.selectedCellBorder', { export const inactiveSelectedCellBorder = registerColor('notebook.inactiveSelectedCellBorder', { dark: null, light: null, - hc: null + hc: focusBorder }, nls.localize('notebook.inactiveSelectedCellBorder', "The color of the cell's borders when multiple cells are selected.")); export const focusedCellBorder = registerColor('notebook.focusedCellBorder', { @@ -2603,21 +2699,6 @@ registerThemingParticipant((theme, collector) => { collector.addRule(`.notebookOverlay .monaco-list-row .cell-editor-part:before { outline: solid 1px ${cellBorderColor}; }`); } - const cellStatusSuccessIcon = theme.getColor(cellStatusIconSuccess); - if (cellStatusSuccessIcon) { - collector.addRule(`.monaco-workbench .notebookOverlay .cell-statusbar-container .cell-run-status ${ThemeIcon.asCSSSelector(successStateIcon)} { color: ${cellStatusSuccessIcon} }`); - } - - const cellStatusErrorIcon = theme.getColor(cellStatusIconError); - if (cellStatusErrorIcon) { - collector.addRule(`.monaco-workbench .notebookOverlay .cell-statusbar-container .cell-run-status ${ThemeIcon.asCSSSelector(errorStateIcon)} { color: ${cellStatusErrorIcon} }`); - } - - const cellStatusRunningIcon = theme.getColor(cellStatusIconRunning); - if (cellStatusRunningIcon) { - collector.addRule(`.monaco-workbench .notebookOverlay .cell-statusbar-container .cell-run-status .codicon-sync { color: ${cellStatusRunningIcon} }`); - } - const cellStatusBarHoverBg = theme.getColor(cellStatusBarItemHover); if (cellStatusBarHoverBg) { collector.addRule(`.monaco-workbench .notebookOverlay .cell-statusbar-container .cell-language-picker:hover, @@ -2688,26 +2769,27 @@ registerThemingParticipant((theme, collector) => { } // Cell Margin - 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 > .markdown-cell-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 > .code-cell-row div.cell.code { margin-left: ${CODE_CELL_LEFT_MARGIN + CELL_RUN_GUTTER}px; }`); + collector.addRule(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row div.cell { margin-right: ${CELL_RIGHT_MARGIN}px; }`); collector.addRule(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row > .cell-inner-container { padding-top: ${CELL_TOP_MARGIN}px; }`); collector.addRule(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .markdown-cell-row > .cell-inner-container { padding-bottom: ${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.markdown-cell-edit-mode .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); }`); + collector.addRule(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .markdown-cell-row > .webview-backed-markdown-cell.markdown-cell-edit-mode .cell.code { padding-bottom: ${MARKDOWN_CELL_BOTTOM_MARGIN}px; padding-top: ${MARKDOWN_CELL_TOP_MARGIN}px; }`); + collector.addRule(`.notebookOverlay .output { margin: 0px ${CELL_RIGHT_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_RIGHT_MARGIN}px); }`); - collector.addRule(`.notebookOverlay .output-show-more-container { margin: 0px ${CELL_MARGIN}px 0px ${CODE_CELL_LEFT_MARGIN + CELL_RUN_GUTTER}px; }`); - collector.addRule(`.notebookOverlay .output-show-more-container { width: calc(100% - ${CODE_CELL_LEFT_MARGIN + CELL_RUN_GUTTER + (CELL_MARGIN * 2)}px); }`); + collector.addRule(`.notebookOverlay .output-show-more-container { margin: 0px ${CELL_RIGHT_MARGIN}px 0px ${CODE_CELL_LEFT_MARGIN + CELL_RUN_GUTTER}px; }`); + collector.addRule(`.notebookOverlay .output-show-more-container { width: calc(100% - ${CODE_CELL_LEFT_MARGIN + CELL_RUN_GUTTER + CELL_RIGHT_MARGIN}px); }`); collector.addRule(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row div.cell.markdown { padding-left: ${CELL_RUN_GUTTER}px; }`); - collector.addRule(`.notebookOverlay .cell .run-button-container { width: 20px; margin: 0px ${Math.floor(CELL_RUN_GUTTER - 20) / 2}px; }`); + collector.addRule(`.notebookOverlay .cell .run-button-container { width: 20px; left: ${CODE_CELL_LEFT_MARGIN + Math.floor(CELL_RUN_GUTTER - 20) / 2}px }`); collector.addRule(`.notebookOverlay .monaco-list .monaco-list-row :not(.webview-backed-markdown-cell) .cell-focus-indicator-top { height: ${CELL_TOP_MARGIN}px; }`); collector.addRule(`.notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator-side { bottom: ${BOTTOM_CELL_TOOLBAR_GAP}px; }`); collector.addRule(`.notebookOverlay .monaco-list .monaco-list-row.code-cell-row .cell-focus-indicator-left, .notebookOverlay .monaco-list .monaco-list-row.code-cell-row .cell-drag-handle { width: ${CODE_CELL_LEFT_MARGIN + CELL_RUN_GUTTER}px; }`); collector.addRule(`.notebookOverlay .monaco-list .monaco-list-row.markdown-cell-row .cell-focus-indicator-left { width: ${CODE_CELL_LEFT_MARGIN}px; }`); - collector.addRule(`.notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator.cell-focus-indicator-right { width: ${CELL_MARGIN * 2}px; }`); + collector.addRule(`.notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator.cell-focus-indicator-right { width: ${CELL_RIGHT_MARGIN}px; }`); collector.addRule(`.notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator-bottom { height: ${CELL_BOTTOM_MARGIN}px; }`); collector.addRule(`.notebookOverlay .monaco-list .monaco-list-row .cell-shadow-container-bottom { top: ${CELL_BOTTOM_MARGIN}px; }`); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidgetContextKeys.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidgetContextKeys.ts new file mode 100644 index 00000000..ba83b219 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidgetContextKeys.ts @@ -0,0 +1,98 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { ICellViewModel, NOTEBOOK_HAS_RUNNING_CELL, NOTEBOOK_INTERRUPTIBLE_KERNEL, NOTEBOOK_KERNEL_COUNT, INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; +import { NotebookCellExecutionState } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; + +export class NotebookEditorContextKeys { + + private readonly _notebookKernelCount: IContextKey; + private readonly _interruptibleKernel: IContextKey; + private readonly _someCellRunning: IContextKey; + + private readonly _disposables = new DisposableStore(); + private readonly _viewModelDisposables = new DisposableStore(); + private readonly _cellStateListeners: IDisposable[] = []; + + constructor( + private readonly _editor: INotebookEditor, + @INotebookKernelService private readonly _notebookKernelService: INotebookKernelService, + @IContextKeyService contextKeyService: IContextKeyService, + ) { + this._notebookKernelCount = NOTEBOOK_KERNEL_COUNT.bindTo(contextKeyService); + this._interruptibleKernel = NOTEBOOK_INTERRUPTIBLE_KERNEL.bindTo(contextKeyService); + this._someCellRunning = NOTEBOOK_HAS_RUNNING_CELL.bindTo(contextKeyService); + + this._disposables.add(_editor.onDidChangeModel(this._handleDidChangeModel, this)); + this._disposables.add(_notebookKernelService.onDidAddKernel(this._updateKernelContext, this)); + this._disposables.add(_notebookKernelService.onDidChangeNotebookKernelBinding(this._updateKernelContext, this)); + this._handleDidChangeModel(); + } + + dispose(): void { + this._disposables.dispose(); + this._viewModelDisposables.dispose(); + this._notebookKernelCount.reset(); + this._interruptibleKernel.reset(); + this._someCellRunning.reset(); + } + + private _handleDidChangeModel(): void { + + this._updateKernelContext(); + + this._viewModelDisposables.clear(); + dispose(this._cellStateListeners); + this._cellStateListeners.length = 0; + + if (!this._editor.hasModel()) { + return; + } + + let executionCount = 0; + + const addCellStateListener = (c: ICellViewModel) => { + return (c as CellViewModel).onDidChangeState(e => { + if (!e.runStateChanged) { + return; + } + if (c.metadata?.runState === NotebookCellExecutionState.Pending) { + executionCount++; + } else if (c.metadata?.runState === NotebookCellExecutionState.Idle) { + executionCount--; + } + this._someCellRunning.set(executionCount > 0); + }); + }; + + for (const cell of this._editor.viewModel.viewCells) { + this._cellStateListeners.push(addCellStateListener(cell)); + } + + this._viewModelDisposables.add(this._editor.viewModel.onDidChangeViewCells(e => { + e.splices.reverse().forEach(splice => { + const [start, deleted, newCells] = splice; + const deletedCells = this._cellStateListeners.splice(start, deleted, ...newCells.map(addCellStateListener)); + dispose(deletedCells); + }); + })); + } + + private _updateKernelContext(): void { + if (!this._editor.hasModel()) { + this._notebookKernelCount.reset(); + this._interruptibleKernel.reset(); + return; + } + + const { selected, all } = this._notebookKernelService.getMatchingKernel(this._editor.viewModel.notebookDocument); + this._notebookKernelCount.set(all.length); + this._interruptibleKernel.set(selected?.implementsInterrupt ?? false); + } +} diff --git a/src/vs/workbench/contrib/notebook/browser/notebookIcons.ts b/src/vs/workbench/contrib/notebook/browser/notebookIcons.ts index 22adce5f..edeff4ff 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookIcons.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookIcons.ts @@ -24,6 +24,8 @@ export const unfoldIcon = registerIcon('notebook-unfold', Codicon.unfold, locali export const successStateIcon = registerIcon('notebook-state-success', Codicon.check, localize('successStateIcon', 'Icon to indicate a success state in notebook editors.')); export const errorStateIcon = registerIcon('notebook-state-error', Codicon.error, localize('errorStateIcon', 'Icon to indicate an error state in notebook editors.')); +export const pendingStateIcon = registerIcon('notebook-state-pending', Codicon.clock, localize('pendingStateIcon', 'Icon to indicate a pending state in notebook editors.')); +export const executingStateIcon = registerIcon('notebook-state-executing', Codicon.sync, localize('executingStateIcon', 'Icon to indicate an executing state in notebook editors.')); export const collapsedIcon = registerIcon('notebook-collapsed', Codicon.chevronRight, localize('collapsedIcon', 'Icon to annotate a collapsed section in notebook editors.')); export const expandedIcon = registerIcon('notebook-expanded', Codicon.chevronDown, localize('expandedIcon', 'Icon to annotate an expanded section in notebook editors.')); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookKernelAssociation.ts b/src/vs/workbench/contrib/notebook/browser/notebookKernelAssociation.ts deleted file mode 100644 index c98c4911..00000000 --- a/src/vs/workbench/contrib/notebook/browser/notebookKernelAssociation.ts +++ /dev/null @@ -1,76 +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 nls from 'vs/nls'; -import { IJSONSchema } from 'vs/base/common/jsonSchema'; -import { IConfigurationNode, IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry'; -import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration'; -import { Registry } from 'vs/platform/registry/common/platform'; - -export class NotebookKernelProviderAssociationRegistry { - static extensionIds: (string | null)[] = []; - static extensionDescriptions: string[] = []; -} - -export class NotebookViewTypesExtensionRegistry { - static viewTypes: string[] = []; - static viewTypeDescriptions: string[] = []; -} - -export type NotebookKernelProviderAssociation = { - readonly viewType: string; - readonly kernelProvider?: string; -}; - -export type NotebookKernelProviderAssociations = readonly NotebookKernelProviderAssociation[]; - - -export const notebookKernelProviderAssociationsSettingId = 'notebook.kernelProviderAssociations'; - -export const viewTypeSchamaAddition: IJSONSchema = { - type: 'string', - enum: [] -}; - -export const notebookKernelProviderAssociationsConfigurationNode: IConfigurationNode = { - ...workbenchConfigurationNodeBase, - properties: { - [notebookKernelProviderAssociationsSettingId]: { - type: 'array', - markdownDescription: nls.localize('notebook.kernelProviderAssociations', "Defines a default kernel provider which takes precedence over all other kernel providers settings. Must be the identifier of an extension contributing a kernel provider."), - items: { - type: 'object', - defaultSnippets: [{ - body: { - 'viewType': '$1', - 'kernelProvider': '$2' - } - }], - properties: { - 'viewType': { - type: ['string', 'null'], - default: null, - enum: NotebookViewTypesExtensionRegistry.viewTypes, - markdownEnumDescriptions: NotebookViewTypesExtensionRegistry.viewTypeDescriptions - }, - 'kernelProvider': { - type: ['string', 'null'], - default: null, - enum: NotebookKernelProviderAssociationRegistry.extensionIds, - markdownEnumDescriptions: NotebookKernelProviderAssociationRegistry.extensionDescriptions - } - } - } - } - } -}; - -export function updateNotebookKernelProvideAssociationSchema(): void { - Registry.as(Extensions.Configuration) - .notifyConfigurationSchemaUpdated(notebookKernelProviderAssociationsConfigurationNode); -} - -Registry.as(Extensions.Configuration) - .registerConfiguration(notebookKernelProviderAssociationsConfigurationNode); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookKernelServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookKernelServiceImpl.ts new file mode 100644 index 00000000..52cdfead --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/notebookKernelServiceImpl.ts @@ -0,0 +1,235 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Event, Emitter } from 'vs/base/common/event'; +import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { INotebookKernel, INotebookTextModel } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookKernelBindEvent, INotebookKernelMatchResult, INotebookKernelService, INotebookTextModelLike } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { LRUCache, ResourceMap } from 'vs/base/common/map'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; +import { URI } from 'vs/base/common/uri'; +import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; +import { runWhenIdle } from 'vs/base/common/async'; + +class KernelInfo { + + private static _logicClock = 0; + + readonly kernel: INotebookKernel; + public score: number; + readonly time: number; + + readonly notebookPriorities = new ResourceMap(); + + constructor(kernel: INotebookKernel) { + this.kernel = kernel; + this.score = -1; + this.time = KernelInfo._logicClock++; + } +} + +class NotebookTextModelLikeId { + static str(k: INotebookTextModelLike): string { + return `${k.viewType}/${k.uri.toString()}`; + } + static obj(s: string): INotebookTextModelLike { + const idx = s.indexOf('/'); + return { + viewType: s.substr(0, idx), + uri: URI.parse(s.substr(idx + 1)) + }; + } +} + +export class NotebookKernelService implements INotebookKernelService { + + declare _serviceBrand: undefined; + + private readonly _disposables = new DisposableStore(); + private readonly _kernels = new Map(); + + private readonly _typeBindings = new LRUCache(100, 0.7); + private readonly _notebookBindings = new LRUCache(1000, 0.7); + + private readonly _onDidChangeNotebookKernelBinding = new Emitter(); + private readonly _onDidAddKernel = new Emitter(); + private readonly _onDidRemoveKernel = new Emitter(); + private readonly _onDidChangeNotebookAffinity = new Emitter(); + + readonly onDidChangeNotebookKernelBinding: Event = this._onDidChangeNotebookKernelBinding.event; + readonly onDidAddKernel: Event = this._onDidAddKernel.event; + readonly onDidRemoveKernel: Event = this._onDidRemoveKernel.event; + readonly onDidChangeNotebookAffinity: Event = this._onDidChangeNotebookAffinity.event; + + private static _storageNotebookBinding = 'notebook.controller2NotebookBindings'; + private static _storageTypeBinding = 'notebook.controller2TypeBindings'; + + constructor( + @INotebookService private readonly _notebookService: INotebookService, + @IStorageService private readonly _storageService: IStorageService, + ) { + + // auto associate kernels to new notebook documents, also emit event when + // a notebook has been closed (but don't update the memento) + this._disposables.add(_notebookService.onDidAddNotebookDocument(this._tryAutoBindNotebook, this)); + this._disposables.add(_notebookService.onDidRemoveNotebookDocument(notebook => { + const kernelId = this._notebookBindings.get(NotebookTextModelLikeId.str(notebook)); + if (kernelId) { + this._onDidChangeNotebookKernelBinding.fire({ notebook: notebook.uri, oldKernel: kernelId, newKernel: undefined }); + } + })); + + // restore from storage + try { + const data = JSON.parse(this._storageService.get(NotebookKernelService._storageNotebookBinding, StorageScope.WORKSPACE, '[]')); + this._notebookBindings.fromJSON(data); + } catch { + // ignore + } + try { + const data = JSON.parse(this._storageService.get(NotebookKernelService._storageTypeBinding, StorageScope.GLOBAL, '[]')); + this._typeBindings.fromJSON(data); + } catch { + // ignore + } + } + + dispose() { + this._disposables.dispose(); + this._onDidChangeNotebookKernelBinding.dispose(); + this._onDidAddKernel.dispose(); + this._onDidRemoveKernel.dispose(); + this._kernels.clear(); + } + + private _persistSoonHandle?: IDisposable; + + private _persistMementos(): void { + this._persistSoonHandle?.dispose(); + this._persistSoonHandle = runWhenIdle(() => { + this._storageService.store(NotebookKernelService._storageNotebookBinding, JSON.stringify(this._notebookBindings), StorageScope.WORKSPACE, StorageTarget.MACHINE); + this._storageService.store(NotebookKernelService._storageTypeBinding, JSON.stringify(this._typeBindings), StorageScope.GLOBAL, StorageTarget.USER); + }, 100); + } + + private static _score(kernel: INotebookKernel, notebook: INotebookTextModelLike): number { + if (kernel.viewType === '*') { + return 5; + } else if (kernel.viewType === notebook.viewType) { + return 10; + } else { + return 0; + } + } + + private _tryAutoBindNotebook(notebook: INotebookTextModel, onlyThisKernel?: INotebookKernel): void { + + const id = this._notebookBindings.get(NotebookTextModelLikeId.str(notebook)); + if (!id) { + // no kernel associated + return; + } + const existingKernel = this._kernels.get(id); + if (!existingKernel || !NotebookKernelService._score(existingKernel.kernel, notebook)) { + // associated kernel not known, not matching + return; + } + if (!onlyThisKernel || existingKernel.kernel === onlyThisKernel) { + this._onDidChangeNotebookKernelBinding.fire({ notebook: notebook.uri, oldKernel: undefined, newKernel: existingKernel.kernel.id }); + } + } + + registerKernel(kernel: INotebookKernel): IDisposable { + if (this._kernels.has(kernel.id)) { + throw new Error(`NOTEBOOK CONTROLLER with id '${kernel.id}' already exists`); + } + + this._kernels.set(kernel.id, new KernelInfo(kernel)); + this._onDidAddKernel.fire(kernel); + + // auto associate the new kernel to existing notebooks it was + // associated to in the past. + for (const notebook of this._notebookService.getNotebookTextModels()) { + this._tryAutoBindNotebook(notebook, kernel); + } + + return toDisposable(() => { + if (this._kernels.delete(kernel.id)) { + this._onDidRemoveKernel.fire(kernel); + } + for (const [key, candidate] of Array.from(this._notebookBindings)) { + if (candidate === kernel.id) { + this._onDidChangeNotebookKernelBinding.fire({ notebook: NotebookTextModelLikeId.obj(key).uri, oldKernel: kernel.id, newKernel: undefined }); + } + } + }); + } + + getMatchingKernel(notebook: INotebookTextModelLike): INotebookKernelMatchResult { + + // all applicable kernels + const kernels: { kernel: INotebookKernel, instanceAffinity: number, typeAffinity: number, score: number }[] = []; + for (const info of this._kernels.values()) { + const score = NotebookKernelService._score(info.kernel, notebook); + if (score) { + kernels.push({ + score, + kernel: info.kernel, + instanceAffinity: info.notebookPriorities.get(notebook.uri) ?? 1 /* vscode.NotebookControllerPriority.Default */, + typeAffinity: this._typeBindings.get(info.kernel.viewType) === info.kernel.id ? 1 : 0 + }); + } + } + + const all = kernels + .sort((a, b) => b.instanceAffinity - a.instanceAffinity || b.typeAffinity - a.typeAffinity || a.score - b.score || a.kernel.label.localeCompare(b.kernel.label)) + .map(obj => obj.kernel); + + // bound kernel + const selectedId = this._notebookBindings.get(NotebookTextModelLikeId.str(notebook)); + const selected = selectedId ? this._kernels.get(selectedId)?.kernel : undefined; + + return { all, selected }; + } + + // default kernel for notebookType + selectKernelForNotebookType(kernel: INotebookKernel, typeId: string): void { + const existing = this._typeBindings.get(typeId); + if (existing !== kernel.id) { + this._typeBindings.set(typeId, kernel.id); + this._persistMementos(); + this._onDidChangeNotebookAffinity.fire(); + } + } + + // a notebook has one kernel, a kernel has N notebooks + // notebook <-1----N-> kernel + selectKernelForNotebook(kernel: INotebookKernel, notebook: INotebookTextModelLike): void { + const key = NotebookTextModelLikeId.str(notebook); + const oldKernel = this._notebookBindings.get(key); + if (oldKernel !== kernel?.id) { + if (kernel) { + this._notebookBindings.set(key, kernel.id); + } else { + this._notebookBindings.delete(key); + } + this._onDidChangeNotebookKernelBinding.fire({ notebook: notebook.uri, oldKernel, newKernel: kernel.id }); + this._persistMementos(); + } + } + + updateKernelNotebookAffinity(kernel: INotebookKernel, notebook: URI, preference: number | undefined): void { + const info = this._kernels.get(kernel.id); + if (!info) { + throw new Error(`UNKNOWN kernel '${kernel.id}'`); + } + if (preference === undefined) { + info.notebookPriorities.delete(notebook); + } else { + info.notebookPriorities.set(notebook, preference); + } + this._onDidChangeNotebookAffinity.fire(); + } +} diff --git a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts index 71795c89..8a8acf00 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts @@ -3,10 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as glob from 'vs/base/common/glob'; import { localize } from 'vs/nls'; import { getPixelRatio, getZoomLevel } from 'vs/base/browser/browser'; -import { flatten } from 'vs/base/common/arrays'; -import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; import { Iterable } from 'vs/base/common/iterator'; import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; @@ -21,70 +20,46 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { NotebookExtensionDescription } from 'vs/workbench/api/common/extHost.protocol'; import { Memento } from 'vs/workbench/common/memento'; -import { INotebookEditorContribution, notebookMarkdownRendererExtensionPoint, notebookProviderExtensionPoint, notebookRendererExtensionPoint } from 'vs/workbench/contrib/notebook/browser/extensionPoint'; -import { updateEditorTopPadding } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; -import { NotebookKernelProviderAssociationRegistry, NotebookViewTypesExtensionRegistry, updateNotebookKernelProvideAssociationSchema } from 'vs/workbench/contrib/notebook/browser/notebookKernelAssociation'; +import { INotebookEditorContribution, notebookMarkupRendererExtensionPoint, notebookProviderExtensionPoint, notebookRendererExtensionPoint } from 'vs/workbench/contrib/notebook/browser/extensionPoint'; +import { NotebookEditorOptions, updateEditorTopPadding } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; -import { ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, BUILTIN_RENDERER_ID, DisplayOrderKey, INotebookKernel, INotebookKernelProvider, INotebookMarkdownRendererInfo, INotebookRendererInfo, INotebookTextModel, IOrderedMimeType, IOutputDto, mimeTypeIsAlwaysSecure, mimeTypeSupportedByCore, NotebookDataDto, notebookDocumentFilterMatch, NotebookEditorPriority, RENDERER_NOT_AVAILABLE, sortMimeTypes, TransientOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { NotebookMarkdownRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookMarkdownRenderer'; +import { ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, BUILTIN_RENDERER_ID, CellUri, DisplayOrderKey, INotebookExclusiveDocumentFilter, INotebookMarkupRendererInfo, INotebookRendererInfo, INotebookTextModel, IOrderedMimeType, IOutputDto, mimeTypeIsAlwaysSecure, mimeTypeSupportedByCore, NotebookDataDto, NotebookEditorPriority, NotebookRendererMatch, NotebookTextDiffEditorPreview, RENDERER_NOT_AVAILABLE, sortMimeTypes, TransientOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { NotebookMarkupRendererInfo as NotebookMarkupRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookMarkdownRenderer'; import { NotebookOutputRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookOutputRenderer'; import { NotebookEditorDescriptor, NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookProvider'; -import { ComplexNotebookProviderInfo, IMainNotebookController, INotebookSerializer, INotebookService, SimpleNotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookService'; +import { ComplexNotebookProviderInfo, INotebookContentProvider, INotebookSerializer, INotebookService, SimpleNotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; -import { Extensions as EditorExtensions, IEditorTypesHandler, IEditorType, IEditorAssociationsRegistry } from 'vs/workbench/browser/editor'; import { Registry } from 'vs/platform/registry/common/platform'; import { Schemas } from 'vs/base/common/network'; - -export class NotebookKernelProviderInfoStore { - private readonly _notebookKernelProviders: INotebookKernelProvider[] = []; - - add(provider: INotebookKernelProvider) { - this._notebookKernelProviders.push(provider); - this._updateProviderExtensionsInfo(); - - return toDisposable(() => { - const idx = this._notebookKernelProviders.indexOf(provider); - if (idx >= 0) { - this._notebookKernelProviders.splice(idx, 1); - } - - this._updateProviderExtensionsInfo(); - }); - } - - get(viewType: string, resource: URI) { - return this._notebookKernelProviders.filter(provider => notebookDocumentFilterMatch(provider.selector, viewType, resource)); - } - - getContributedKernelProviders() { - return [...this._notebookKernelProviders]; - } - - private _updateProviderExtensionsInfo() { - NotebookKernelProviderAssociationRegistry.extensionIds.length = 0; - NotebookKernelProviderAssociationRegistry.extensionDescriptions.length = 0; - - this._notebookKernelProviders.forEach(provider => { - NotebookKernelProviderAssociationRegistry.extensionIds.push(provider.providerExtensionId); - NotebookKernelProviderAssociationRegistry.extensionDescriptions.push(provider.providerDescription || ''); - }); - - updateNotebookKernelProvideAssociationSchema(); - } -} +import { Lazy } from 'vs/base/common/lazy'; +import { IResourceEditorInput } from 'vs/platform/editor/common/editor'; +import { NotebookDiffEditorInput } from 'vs/workbench/contrib/notebook/browser/notebookDiffEditorInput'; +import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput'; +import { ContributedEditorPriority, IEditorAssociationsRegistry, IEditorOverrideService, IEditorType, IEditorTypesHandler } from 'vs/workbench/services/editor/common/editorOverrideService'; +import { EditorExtensions } from 'vs/workbench/common/editor'; +import { IFileService } from 'vs/platform/files/common/files'; export class NotebookProviderInfoStore extends Disposable { + private static readonly CUSTOM_EDITORS_STORAGE_ID = 'notebookEditors'; private static readonly CUSTOM_EDITORS_ENTRY_ID = 'editors'; private readonly _memento: Memento; private _handled: boolean = false; - constructor( - storageService: IStorageService, - extensionService: IExtensionService + private readonly _contributedEditors = new Map(); + private readonly _contributedEditorDisposables = new DisposableStore(); + + constructor( + @IStorageService storageService: IStorageService, + @IExtensionService extensionService: IExtensionService, + @IEditorOverrideService private readonly _editorOverrideService: IEditorOverrideService, + @IConfigurationService private readonly _configurationService: IConfigurationService, + @IAccessibilityService private readonly _accessibilityService: IAccessibilityService, + @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IFileService private readonly _fileService: IFileService, ) { super(); this._memento = new Memento(NotebookProviderInfoStore.CUSTOM_EDITORS_STORAGE_ID, storageService); @@ -94,24 +69,27 @@ export class NotebookProviderInfoStore extends Disposable { this.add(new NotebookProviderInfo(info)); } - this._updateProviderExtensionsInfo(); - this._register(extensionService.onDidRegisterExtensions(() => { if (!this._handled) { // there is no extension point registered for notebook content provider // clear the memento and cache - this.clear(); + this._clear(); mementoObject[NotebookProviderInfoStore.CUSTOM_EDITORS_ENTRY_ID] = []; this._memento.saveMemento(); - - this._updateProviderExtensionsInfo(); } })); + + notebookProviderExtensionPoint.setHandler(extensions => this._setupHandler(extensions)); } - setupHandler(extensions: readonly IExtensionPointUser[]) { + override dispose(): void { + this._clear(); + super.dispose(); + } + + private _setupHandler(extensions: readonly IExtensionPointUser[]) { this._handled = true; - this.clear(); + this._clear(); for (const extension of extensions) { for (const notebookContribution of extension.value) { @@ -133,41 +111,65 @@ export class NotebookProviderInfoStore extends Disposable { const mementoObject = this._memento.getMemento(StorageScope.GLOBAL, StorageTarget.MACHINE); mementoObject[NotebookProviderInfoStore.CUSTOM_EDITORS_ENTRY_ID] = Array.from(this._contributedEditors.values()); this._memento.saveMemento(); - - this._updateProviderExtensionsInfo(); - } - - private _updateProviderExtensionsInfo() { - NotebookViewTypesExtensionRegistry.viewTypes.length = 0; - NotebookViewTypesExtensionRegistry.viewTypeDescriptions.length = 0; - - for (const contribute of this._contributedEditors) { - if (contribute[1].providerExtensionId) { - NotebookViewTypesExtensionRegistry.viewTypes.push(contribute[1].id); - NotebookViewTypesExtensionRegistry.viewTypeDescriptions.push(`${contribute[1].displayName}`); - } - } - - updateNotebookKernelProvideAssociationSchema(); } private _convertPriority(priority?: string) { if (!priority) { - return NotebookEditorPriority.default; + return ContributedEditorPriority.default; } if (priority === NotebookEditorPriority.default) { - return NotebookEditorPriority.default; + return ContributedEditorPriority.default; } - return NotebookEditorPriority.option; + return ContributedEditorPriority.option; } - private readonly _contributedEditors = new Map(); + private _registerContributionPoint(notebookProviderInfo: NotebookProviderInfo): void { + for (const selector of notebookProviderInfo.selectors) { + const globPattern = (selector as INotebookExclusiveDocumentFilter).include || selector as glob.IRelativePattern | string; + this._contributedEditorDisposables.add(this._editorOverrideService.registerContributionPoint( + globPattern, + { + id: notebookProviderInfo.id, + label: notebookProviderInfo.displayName, + detail: notebookProviderInfo.providerDisplayName, + describes: (currentEditor) => currentEditor instanceof NotebookEditorInput && currentEditor.viewType === notebookProviderInfo.id, + priority: notebookProviderInfo.exclusive ? ContributedEditorPriority.exclusive : notebookProviderInfo.priority, + }, + { + canHandleDiff: () => !!this._configurationService.getValue(NotebookTextDiffEditorPreview) && !this._accessibilityService.isScreenReaderOptimized(), + canSupportResource: resource => resource.scheme === Schemas.untitled || resource.scheme === Schemas.vscodeNotebookCell || this._fileService.canHandleResource(resource) + }, + (resource, options, group) => { + const data = CellUri.parse(resource); + let notebookUri: URI = resource; + let cellOptions: IResourceEditorInput | undefined; - clear() { + if (data) { + notebookUri = data.notebook; + cellOptions = { resource: resource }; + } + + const notebookOptions = new NotebookEditorOptions({ ...options, cellOptions }); + return { editor: NotebookEditorInput.create(this._instantiationService, notebookUri, notebookProviderInfo.id), options: notebookOptions }; + }, + (diffEditorInput, group) => { + const modifiedInput = diffEditorInput.modifiedInput; + const originalInput = diffEditorInput.originalInput; + const notebookUri = modifiedInput.resource!; + const originalNotebookUri = originalInput.resource!; + return { editor: NotebookDiffEditorInput.create(this._instantiationService, notebookUri, modifiedInput.getName(), originalNotebookUri, originalInput.getName(), diffEditorInput.getName(), notebookProviderInfo.id) }; + } + )); + } + } + + + private _clear(): void { this._contributedEditors.clear(); + this._contributedEditorDisposables.clear(); } get(viewType: string): NotebookProviderInfo | undefined { @@ -179,6 +181,7 @@ export class NotebookProviderInfoStore extends Disposable { return; } this._contributedEditors.set(info.id, info); + this._registerContributionPoint(info); const mementoObject = this._memento.getMemento(StorageScope.GLOBAL, StorageTarget.MACHINE); mementoObject[NotebookProviderInfoStore.CUSTOM_EDITORS_ENTRY_ID] = Array.from(this._contributedEditors.values()); @@ -206,13 +209,19 @@ export class NotebookProviderInfoStore extends Disposable { export class NotebookOutputRendererInfoStore { private readonly contributedRenderers = new Map(); + private readonly preferredMimetypeMemento: Memento; + private readonly preferredMimetype = new Lazy(() => this.preferredMimetypeMemento.getMemento(StorageScope.WORKSPACE, StorageTarget.USER)); + + constructor(@IStorageService storageService: IStorageService) { + this.preferredMimetypeMemento = new Memento('workbench.editor.notebook.preferredRenderer', storageService); + } clear() { this.contributedRenderers.clear(); } - get(viewType: string): NotebookOutputRendererInfo | undefined { - return this.contributedRenderers.get(viewType); + get(rendererId: string): NotebookOutputRendererInfo | undefined { + return this.contributedRenderers.get(rendererId); } add(info: NotebookOutputRendererInfo): void { @@ -222,9 +231,26 @@ export class NotebookOutputRendererInfoStore { this.contributedRenderers.set(info.id, info); } - getContributedRenderer(mimeType: string): readonly NotebookOutputRendererInfo[] { - return Array.from(this.contributedRenderers.values()).filter(customEditor => - customEditor.matches(mimeType)); + /** Update and remember the preferred renderer for the given mimetype in this workspace */ + setPreferred(mimeType: string, rendererId: string) { + this.preferredMimetype.getValue()[mimeType] = rendererId; + this.preferredMimetypeMemento.saveMemento(); + } + + getContributedRenderer(mimeType: string, kernelProvides: readonly string[] | undefined): NotebookOutputRendererInfo[] { + const preferred = this.preferredMimetype.getValue()[mimeType]; + const possible = Array.from(this.contributedRenderers.values()) + .map(renderer => ({ + renderer, + score: kernelProvides === undefined + ? renderer.matchesWithoutKernel(mimeType) + : renderer.matches(mimeType, kernelProvides), + })) + .sort((a, b) => a.score - b.score) + .filter(r => r.score !== NotebookRendererMatch.Never) + .map(r => r.renderer); + + return preferred ? possible.sort((a, b) => (a.id === preferred ? -1 : 0) + (b.id === preferred ? 1 : 0)) : possible; } } @@ -243,31 +269,27 @@ class ModelData implements IDisposable { } } - - export class NotebookService extends Disposable implements INotebookService, IEditorTypesHandler { declare readonly _serviceBrand: undefined; private readonly _notebookProviders = new Map(); private readonly _notebookProviderInfoStore: NotebookProviderInfoStore; - private readonly _notebookRenderersInfoStore: NotebookOutputRendererInfoStore = new NotebookOutputRendererInfoStore(); - private readonly _markdownRenderersInfos = new Set(); - private readonly _notebookKernelProviderInfoStore: NotebookKernelProviderInfoStore = new NotebookKernelProviderInfoStore(); + private readonly _notebookRenderersInfoStore = this._instantiationService.createInstance(NotebookOutputRendererInfoStore); + private readonly _markdownRenderersInfos = new Set(); private readonly _models = new ResourceMap(); + private readonly _onDidCreateNotebookDocument = this._register(new Emitter()); private readonly _onDidAddNotebookDocument = this._register(new Emitter()); - private readonly _onDidRemoveNotebookDocument = this._register(new Emitter()); + private readonly _onDidRemoveNotebookDocument = this._register(new Emitter()); + + readonly onDidCreateNotebookDocument = this._onDidCreateNotebookDocument.event; readonly onDidAddNotebookDocument = this._onDidAddNotebookDocument.event; readonly onDidRemoveNotebookDocument = this._onDidRemoveNotebookDocument.event; private readonly _onDidChangeEditorTypes = this._register(new Emitter()); onDidChangeEditorTypes: Event = this._onDidChangeEditorTypes.event; - private readonly _onDidChangeKernels = this._register(new Emitter()); - onDidChangeKernels: Event = this._onDidChangeKernels.event; - private readonly _onDidChangeNotebookActiveKernel = this._register(new Emitter<{ uri: URI, providerHandle: number | undefined, kernelFriendlyId: string | undefined; }>()); - onDidChangeNotebookActiveKernel: Event<{ uri: URI, providerHandle: number | undefined, kernelFriendlyId: string | undefined; }> = this._onDidChangeNotebookActiveKernel.event; private _cutItems: NotebookCellTextModel[] | undefined; private _lastClipboardIsCopy: boolean = true; @@ -277,19 +299,15 @@ export class NotebookService extends Disposable implements INotebookService, IEd @IExtensionService private readonly _extensionService: IExtensionService, @IConfigurationService private readonly _configurationService: IConfigurationService, @IAccessibilityService private readonly _accessibilityService: IAccessibilityService, - @IStorageService private readonly _storageService: IStorageService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @ICodeEditorService private readonly _codeEditorService: ICodeEditorService, - @IConfigurationService private readonly configurationService: IConfigurationService + @IConfigurationService private readonly configurationService: IConfigurationService, ) { super(); - this._notebookProviderInfoStore = new NotebookProviderInfoStore(this._storageService, this._extensionService); + this._notebookProviderInfoStore = _instantiationService.createInstance(NotebookProviderInfoStore); this._register(this._notebookProviderInfoStore); - notebookProviderExtensionPoint.setHandler((extensions) => { - this._notebookProviderInfoStore.setupHandler(extensions); - }); notebookRendererExtensionPoint.setHandler((renderers) => { this._notebookRenderersInfoStore.clear(); @@ -313,12 +331,13 @@ export class NotebookService extends Disposable implements INotebookService, IEd entrypoint: notebookContribution.entrypoint, displayName: notebookContribution.displayName, mimeTypes: notebookContribution.mimeTypes || [], + dependencies: notebookContribution.dependencies, + optionalDependencies: notebookContribution.optionalDependencies, })); } } }); - - notebookMarkdownRendererExtensionPoint.setHandler((renderers) => { + notebookMarkupRendererExtensionPoint.setHandler((renderers) => { this._markdownRenderersInfos.clear(); for (const extension of renderers) { @@ -339,11 +358,13 @@ export class NotebookService extends Disposable implements INotebookService, IEd continue; } - this._markdownRenderersInfos.add(new NotebookMarkdownRendererInfo({ + this._markdownRenderersInfos.add(new NotebookMarkupRendererInfo({ id, extension: extension.description, entrypoint: notebookContribution.entrypoint, displayName: notebookContribution.displayName, + mimeTypes: notebookContribution.mimeTypes, + dependsOn: notebookContribution.dependsOn, })); } } @@ -441,15 +462,14 @@ export class NotebookService extends Disposable implements INotebookService, IEd this._notebookProviders.set(viewType, data); } - registerNotebookController(viewType: string, extensionData: NotebookExtensionDescription, controller: IMainNotebookController): IDisposable { + registerNotebookController(viewType: string, extensionData: NotebookExtensionDescription, controller: INotebookContentProvider): IDisposable { this._registerProviderData(viewType, new ComplexNotebookProviderInfo(viewType, controller, extensionData)); - if (controller.viewOptions && !this._notebookProviderInfoStore.get(viewType)) { // register this content provider to the static contribution, if it does not exist const info = new NotebookProviderInfo({ displayName: controller.viewOptions.displayName, id: viewType, - priority: NotebookEditorPriority.default, + priority: ContributedEditorPriority.default, selectors: [], providerExtensionId: extensionData.id.value, providerDescription: extensionData.description, @@ -482,11 +502,8 @@ export class NotebookService extends Disposable implements INotebookService, IEd async withNotebookDataProvider(resource: URI, viewType?: string): Promise { const providers = this._notebookProviderInfoStore.getContributedNotebook(resource); - let selected = providers[0]; // If we have a viewtype specified we want that data provider, as the resource won't always map correctly - if (viewType) { - selected = providers.filter(provider => provider.id === viewType)[0]; - } + const selected = viewType ? providers.find(p => p.id === viewType) : providers[0]; if (!selected) { throw new Error(`NO contribution for resource: '${resource.toString()}'`); } @@ -498,41 +515,15 @@ export class NotebookService extends Disposable implements INotebookService, IEd return result; } - registerNotebookKernelProvider(provider: INotebookKernelProvider): IDisposable { - const d = this._notebookKernelProviderInfoStore.add(provider); - const kernelChangeEventListener = provider.onDidChangeKernels((e) => { - this._onDidChangeKernels.fire(e); - }); - - this._onDidChangeKernels.fire(undefined); - return toDisposable(() => { - kernelChangeEventListener.dispose(); - d.dispose(); - this._onDidChangeKernels.fire(undefined); - }); + getRendererInfo(rendererId: string): INotebookRendererInfo | undefined { + return this._notebookRenderersInfoStore.get(rendererId); } - async getNotebookKernels(viewType: string, resource: URI, token: CancellationToken): Promise { - const filteredProvider = this._notebookKernelProviderInfoStore.get(viewType, resource); - const result = new Array(filteredProvider.length); - const promises = filteredProvider.map(async (provider, index) => { - const data = await provider.provideKernels(resource, token); - result[index] = data; - }); - await Promise.all(promises); - return flatten(result); + updateMimePreferredRenderer(mimeType: string, rendererId: string): void { + this._notebookRenderersInfoStore.setPreferred(mimeType, rendererId); } - async getContributedNotebookKernelProviders(): Promise { - const kernelProviders = this._notebookKernelProviderInfoStore.getContributedKernelProviders(); - return kernelProviders; - } - - getRendererInfo(id: string): INotebookRendererInfo | undefined { - return this._notebookRenderersInfoStore.get(id); - } - - getMarkdownRendererInfo(): INotebookMarkdownRendererInfo[] { + getMarkupRendererInfo(): INotebookMarkupRendererInfo[] { return Array.from(this._markdownRenderersInfos); } @@ -544,6 +535,7 @@ export class NotebookService extends Disposable implements INotebookService, IEd } const notebookModel = this._instantiationService.createInstance(NotebookTextModel, viewType, uri, data.cells, data.metadata, transientOptions); this._models.set(uri, new ModelData(notebookModel, this._onWillDisposeDocument.bind(this))); + this._onDidCreateNotebookDocument.fire(notebookModel); this._onDidAddNotebookDocument.fire(notebookModel); return notebookModel; } @@ -565,11 +557,11 @@ export class NotebookService extends Disposable implements INotebookService, IEd if (modelData) { this._models.delete(model.uri); modelData.dispose(); - this._onDidRemoveNotebookDocument.fire(modelData.model.uri); + this._onDidRemoveNotebookDocument.fire(modelData.model); } } - getMimeTypeInfo(textModel: NotebookTextModel, output: IOutputDto): readonly IOrderedMimeType[] { + getMimeTypeInfo(textModel: NotebookTextModel, kernelProvides: readonly string[] | undefined, output: IOutputDto): readonly IOrderedMimeType[] { const mimeTypeSet = new Set(); let mimeTypes: string[] = []; @@ -585,7 +577,7 @@ export class NotebookService extends Disposable implements INotebookService, IEd const orderMimeTypes: IOrderedMimeType[] = []; sorted.forEach(mimeType => { - const handlers = this._findBestMatchedRenderer(mimeType); + const handlers = this._findBestMatchedRenderer(mimeType, kernelProvides); if (handlers.length) { const handler = handlers[0]; @@ -631,8 +623,8 @@ export class NotebookService extends Disposable implements INotebookService, IEd return orderMimeTypes; } - private _findBestMatchedRenderer(mimeType: string): readonly NotebookOutputRendererInfo[] { - return this._notebookRenderersInfoStore.getContributedRenderer(mimeType); + private _findBestMatchedRenderer(mimeType: string, kernelProvides: readonly string[] | undefined): readonly NotebookOutputRendererInfo[] { + return this._notebookRenderersInfoStore.getContributedRenderer(mimeType, kernelProvides); } getContributedNotebookProviders(resource?: URI): readonly NotebookProviderInfo[] { @@ -647,10 +639,6 @@ export class NotebookService extends Disposable implements INotebookService, IEd return this._notebookProviderInfoStore.get(viewType); } - getContributedNotebookOutputRenderers(viewType: string): NotebookOutputRendererInfo | undefined { - return this._notebookRenderersInfoStore.get(viewType); - } - getNotebookProviderResourceRoots(): URI[] { const ret: URI[] = []; this._notebookProviders.forEach(val => { @@ -660,22 +648,6 @@ export class NotebookService extends Disposable implements INotebookService, IEd return ret; } - // --- data provider IPC (deprecated) - - async resolveNotebookEditor(viewType: string, uri: URI, editorId: string): Promise { - const entry = this._notebookProviders.get(viewType); - if (entry instanceof ComplexNotebookProviderInfo) { - entry.controller.resolveNotebookEditor(viewType, uri, editorId); - } - } - - onDidReceiveMessage(viewType: string, editorId: string, rendererType: string | undefined, message: any): void { - const provider = this._notebookProviders.get(viewType); - if (provider instanceof ComplexNotebookProviderInfo) { - return provider.controller.onDidReceiveMessage(editorId, rendererType, message); - } - } - // --- copy & paste setToCopy(items: NotebookCellTextModel[], isCopy: boolean) { diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts index 1e8740af..f10680f3 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts @@ -21,7 +21,8 @@ import { IListService, IWorkbenchListOptions, WorkbenchList } from 'vs/platform/ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { CellRevealPosition, CellRevealType, CursorAtBoundary, getVisibleCells, ICellViewModel, INotebookCellList, reduceCellRanges, CellEditState, CellFocusMode, BaseCellRenderTemplate, NOTEBOOK_CELL_LIST_FOCUSED, cellRangesEqual, ICellOutputViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellViewModel, NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; -import { diff, NOTEBOOK_EDITOR_CURSOR_BOUNDARY, CellKind, ICellRange, NOTEBOOK_EDITOR_CURSOR_BEGIN_END, cellRangesToIndexes, SelectionStateType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { diff, NOTEBOOK_EDITOR_CURSOR_BOUNDARY, CellKind, SelectionStateType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { ICellRange, cellRangesToIndexes } from 'vs/workbench/contrib/notebook/common/notebookRange'; import { clamp } from 'vs/base/common/numbers'; import { SCROLLABLE_ELEMENT_PADDING_TOP } from 'vs/workbench/contrib/notebook/browser/constants'; import { ISplice } from 'vs/base/common/sequence'; @@ -46,12 +47,15 @@ export class NotebookCellList extends WorkbenchList implements ID private _viewModelStore = new DisposableStore(); private styleElement?: HTMLStyleElement; - private readonly _onDidRemoveOutput = new Emitter(); - readonly onDidRemoveOutput: Event = this._onDidRemoveOutput.event; - private readonly _onDidHideOutput = new Emitter(); - readonly onDidHideOutput: Event = this._onDidHideOutput.event; - private readonly _onDidRemoveCellFromView = new Emitter(); - readonly onDidRemoveCellFromView: Event = this._onDidRemoveCellFromView.event; + private readonly _onDidRemoveOutputs = new Emitter(); + readonly onDidRemoveOutputs = this._onDidRemoveOutputs.event; + + private readonly _onDidHideOutputs = new Emitter(); + readonly onDidHideOutputs = this._onDidHideOutputs.event; + + private readonly _onDidRemoveCellsFromView = new Emitter(); + readonly onDidRemoveCellsFromView = this._onDidRemoveCellsFromView.event; + private _viewModel: NotebookViewModel | null = null; get viewModel(): NotebookViewModel | null { return this._viewModel; @@ -120,9 +124,6 @@ export class NotebookCellList extends WorkbenchList implements ID const notebookEditorCursorAtBoundaryContext = NOTEBOOK_EDITOR_CURSOR_BOUNDARY.bindTo(contextKeyService); notebookEditorCursorAtBoundaryContext.set('none'); - const notebookEditorCursorAtBeginEndContext = NOTEBOOK_EDITOR_CURSOR_BEGIN_END.bindTo(contextKeyService); - notebookEditorCursorAtBeginEndContext.set(false); - let cursorSelectionListener: IDisposable | null = null; let textEditorAttachListener: IDisposable | null = null; @@ -142,12 +143,6 @@ export class NotebookCellList extends WorkbenchList implements ID break; } - if (element.cursorAtBeginEnd()) { - notebookEditorCursorAtBeginEndContext.set(true); - } else { - notebookEditorCursorAtBeginEndContext.set(false); - } - return; }; @@ -183,7 +178,7 @@ export class NotebookCellList extends WorkbenchList implements ID const focus = this.getFocusedElements()[0]; if (focus && focus.cellKind === CellKind.Markdown && !focus.metadata?.inputCollapsed) { - focus.editState = CellEditState.Editing; + focus.updateEditState(CellEditState.Editing, 'dbclick'); focus.focusMode = CellFocusMode.Editor; } })); @@ -298,9 +293,9 @@ 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!)); + const viewSelections = cellRangesToIndexes(model.getSelections()).map(index => model.cellAt(index)).filter(cell => !!cell).map(cell => this._getViewIndexUpperBound(cell!)); this.setSelection(viewSelections, undefined, true); - const primary = cellRangesToIndexes([model.getFocus()]).map(index => model.getCellByIndex(index)).filter(cell => !!cell).map(cell => this._getViewIndexUpperBound(cell!)); + const primary = cellRangesToIndexes([model.getFocus()]).map(index => model.cellAt(index)).filter(cell => !!cell).map(cell => this._getViewIndexUpperBound(cell!)); if (primary.length) { this.setFocus(primary, undefined, true); @@ -313,9 +308,7 @@ export class NotebookCellList extends WorkbenchList implements ID const viewCells = model.viewCells.slice(0) as CellViewModel[]; newRanges.reverse().forEach(range => { const removedCells = viewCells.splice(range.start, range.end - range.start + 1); - removedCells.forEach(cell => { - this._onDidRemoveCellFromView.fire(cell); - }); + this._onDidRemoveCellsFromView.fire(removedCells); }); this.splice2(0, 0, viewCells); @@ -323,7 +316,7 @@ export class NotebookCellList extends WorkbenchList implements ID private _updateElementsInWebview(viewDiffs: ISplice[]) { viewDiffs.reverse().forEach((diff) => { - const hideOutputs: ICellOutputViewModel[] = []; + const hiddenOutputs: ICellOutputViewModel[] = []; const deletedOutputs: ICellOutputViewModel[] = []; const removedMarkdownCells: ICellViewModel[] = []; @@ -331,7 +324,7 @@ export class NotebookCellList extends WorkbenchList implements ID const cell = this.element(i); if (cell.cellKind === CellKind.Code) { if (this._viewModel!.hasCell(cell.handle)) { - hideOutputs.push(...cell?.outputsViewModels); + hiddenOutputs.push(...cell?.outputsViewModels); } else { deletedOutputs.push(...cell?.outputsViewModels); } @@ -342,9 +335,9 @@ export class NotebookCellList extends WorkbenchList implements ID this.splice2(diff.start, diff.deleteCount, diff.toInsert); - hideOutputs.forEach(output => this._onDidHideOutput.fire(output)); - deletedOutputs.forEach(output => this._onDidRemoveOutput.fire(output)); - removedMarkdownCells.forEach(cell => this._onDidRemoveCellFromView.fire(cell)); + this._onDidHideOutputs.fire(hiddenOutputs); + this._onDidRemoveOutputs.fire(deletedOutputs); + this._onDidRemoveCellsFromView.fire(removedMarkdownCells); }); } @@ -619,26 +612,24 @@ export class NotebookCellList extends WorkbenchList implements ID } } - selectElement(cell: ICellViewModel) { - const index = this._getViewIndexUpperBound(cell); - if (index >= 0) { - this.setSelection([index]); - } + selectElements(elements: ICellViewModel[]) { + const indices = elements.map(cell => this._getViewIndexUpperBound(cell)).filter(index => index >= 0); + this.setSelection(indices); } - focusNext(n: number | undefined, loop: boolean | undefined, browserEvent?: UIEvent, filter?: (element: CellViewModel) => boolean): void { + override focusNext(n: number | undefined, loop: boolean | undefined, browserEvent?: UIEvent, filter?: (element: CellViewModel) => boolean): void { this._focusNextPreviousDelegate.onFocusNext(() => { super.focusNext(n, loop, browserEvent, filter); }); } - focusPrevious(n: number | undefined, loop: boolean | undefined, browserEvent?: UIEvent, filter?: (element: CellViewModel) => boolean): void { + override focusPrevious(n: number | undefined, loop: boolean | undefined, browserEvent?: UIEvent, filter?: (element: CellViewModel) => boolean): void { this._focusNextPreviousDelegate.onFocusPrevious(() => { super.focusPrevious(n, loop, browserEvent, filter); }); } - setFocus(indexes: number[], browserEvent?: UIEvent, ignoreTextModelUpdate?: boolean): void { + override setFocus(indexes: number[], browserEvent?: UIEvent, ignoreTextModelUpdate?: boolean): void { if (ignoreTextModelUpdate) { super.setFocus(indexes, browserEvent); return; @@ -666,7 +657,7 @@ export class NotebookCellList extends WorkbenchList implements ID super.setFocus(indexes, browserEvent); } - setSelection(indexes: number[], browserEvent?: UIEvent | undefined, ignoreTextModelUpdate?: boolean) { + override setSelection(indexes: number[], browserEvent?: UIEvent | undefined, ignoreTextModelUpdate?: boolean) { if (ignoreTextModelUpdate) { super.setSelection(indexes, browserEvent); return; @@ -864,11 +855,31 @@ export class NotebookCellList extends WorkbenchList implements ID } const focused = this.getFocus(); - this.view.updateElementHeight(index, size, focused.length ? focused[0] : null); + if (!focused.length) { + this.view.updateElementHeight(index, size, null); + return; + } + + const focus = focused[0]; + + if (focus <= index) { + this.view.updateElementHeight(index, size, focus); + return; + } + + // the `element` is in the viewport, it's very often that the height update is triggerred by user interaction (collapse, run cell) + // then we should make sure that the `element`'s visual view position doesn't change. + + if (this.view.elementTop(index) > this.view.scrollTop) { + this.view.updateElementHeight(index, size, index); + return; + } + + this.view.updateElementHeight(index, size, focus); } // override - domFocus() { + override domFocus() { const focused = this.getFocusedElements()[0]; const focusedDomElement = focused && this.domElementOfElement(focused); @@ -1046,7 +1057,7 @@ export class NotebookCellList extends WorkbenchList implements ID const element = this.view.element(viewIndex); // wait for the editor to be created only if the cell is in editing mode (meaning it has an editor and will focus the editor) - if (element.editState === CellEditState.Editing && !element.editorAttached) { + if (element.getEditState() === CellEditState.Editing && !element.editorAttached) { return getEditorAttachedPromise(element); } @@ -1129,7 +1140,7 @@ export class NotebookCellList extends WorkbenchList implements ID } - style(styles: IListStyles) { + override style(styles: IListStyles) { const selectorSuffix = this.view.domId; if (!this.styleElement) { this.styleElement = DOM.createStyleSheet(this.view.domNode); @@ -1248,7 +1259,11 @@ export class NotebookCellList extends WorkbenchList implements ID } } - layout(height?: number, width?: number): void { + getRenderHeight() { + return this.view.renderHeight; + } + + override layout(height?: number, width?: number): void { this._isInLayout = true; super.layout(height, width); if (this.renderHeight === 0) { @@ -1259,7 +1274,7 @@ export class NotebookCellList extends WorkbenchList implements ID this._isInLayout = false; } - dispose() { + override dispose() { this._isDisposed = true; this._viewModelStore.dispose(); this._localDisposableStore.dispose(); diff --git a/src/vs/workbench/contrib/notebook/browser/view/output/outputRenderer.ts b/src/vs/workbench/contrib/notebook/browser/view/output/outputRenderer.ts index 1ee7c554..c44bf609 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/output/outputRenderer.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/output/outputRenderer.ts @@ -36,6 +36,14 @@ export class OutputRenderer { } } + getContribution(preferredMimeType: string | undefined): IOutputTransformContribution | undefined { + if (preferredMimeType) { + return this._richMimeTypeRenderers.get(preferredMimeType); + } + + return undefined; + } + renderNoop(viewModel: ICellOutputViewModel, container: HTMLElement): IRenderOutput { const contentNode = document.createElement('p'); 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 86f089f9..4285b38c 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 @@ -29,6 +29,9 @@ function getStringValue(data: unknown): string { } class JSONRendererContrib extends Disposable implements IOutputRendererContribution { + getType() { + return RenderOutputType.Mainframe; + } getMimetypes() { return ['application/json']; @@ -73,11 +76,15 @@ class JSONRendererContrib extends Disposable implements IOutputRendererContribut container.style.height = `${height + 8}px`; - return { type: RenderOutputType.Mainframe }; + return { type: RenderOutputType.Mainframe, initHeight: height }; } } class JavaScriptRendererContrib extends Disposable implements IOutputRendererContribution { + getType() { + return RenderOutputType.Html; + } + getMimetypes() { return ['application/javascript']; } @@ -105,6 +112,9 @@ class JavaScriptRendererContrib extends Disposable implements IOutputRendererCon } class CodeRendererContrib extends Disposable implements IOutputRendererContribution { + getType() { + return RenderOutputType.Mainframe; + } getMimetypes() { return ['text/x-javascript']; @@ -152,6 +162,9 @@ class CodeRendererContrib extends Disposable implements IOutputRendererContribut } class StreamRendererContrib extends Disposable implements IOutputRendererContribution { + getType() { + return RenderOutputType.Mainframe; + } getMimetypes() { return ['application/x.notebook.stdout', 'application/x.notebook.stream']; @@ -182,12 +195,15 @@ class StreamRendererContrib extends Disposable implements IOutputRendererContrib } class StderrRendererContrib extends StreamRendererContrib { + override getType() { + return RenderOutputType.Mainframe; + } - getMimetypes() { + override getMimetypes() { return ['application/x.notebook.stderr']; } - render(output: ICellOutputViewModel, items: IOutputItemDto[], container: HTMLElement, notebookUri: URI | undefined): IRenderOutput { + override 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; @@ -195,6 +211,9 @@ class StderrRendererContrib extends StreamRendererContrib { } class ErrorRendererContrib extends Disposable implements IOutputRendererContribution { + getType() { + return RenderOutputType.Mainframe; + } getMimetypes() { return ['application/x.notebook.error-traceback']; @@ -242,6 +261,9 @@ class ErrorRendererContrib extends Disposable implements IOutputRendererContribu } class PlainTextRendererContrib extends Disposable implements IOutputRendererContribution { + getType() { + return RenderOutputType.Mainframe; + } getMimetypes() { return ['text/plain']; @@ -270,6 +292,9 @@ class PlainTextRendererContrib extends Disposable implements IOutputRendererCont } class HTMLRendererContrib extends Disposable implements IOutputRendererContribution { + getType() { + return RenderOutputType.Html; + } getMimetypes() { return ['text/html']; @@ -294,6 +319,9 @@ class HTMLRendererContrib extends Disposable implements IOutputRendererContribut } class SVGRendererContrib extends Disposable implements IOutputRendererContribution { + getType() { + return RenderOutputType.Html; + } getMimetypes() { return ['image/svg+xml']; @@ -316,6 +344,9 @@ class SVGRendererContrib extends Disposable implements IOutputRendererContributi } class MdRendererContrib extends Disposable implements IOutputRendererContribution { + getType() { + return RenderOutputType.Mainframe; + } getMimetypes() { return ['text/markdown']; @@ -343,6 +374,9 @@ class MdRendererContrib extends Disposable implements IOutputRendererContributio } class PNGRendererContrib extends Disposable implements IOutputRendererContribution { + getType() { + return RenderOutputType.Mainframe; + } getMimetypes() { return ['image/png']; @@ -369,6 +403,9 @@ class PNGRendererContrib extends Disposable implements IOutputRendererContributi } class JPEGRendererContrib extends Disposable implements IOutputRendererContribution { + getType() { + return RenderOutputType.Mainframe; + } getMimetypes() { return ['image/jpeg']; 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 a1613cc4..3cc75f4b 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts @@ -3,18 +3,26 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { IAction } from 'vs/base/common/actions'; +import { coalesce } from 'vs/base/common/arrays'; import { VSBuffer } from 'vs/base/common/buffer'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { getExtensionForMimeType } from 'vs/base/common/mime'; import { FileAccess, Schemas } from 'vs/base/common/network'; -import { isWeb } from 'vs/base/common/platform'; +import { isMacintosh, isWeb } from 'vs/base/common/platform'; import { dirname, joinPath } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import * as UUID from 'vs/base/common/uuid'; +import * as nls from 'vs/nls'; +import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IFileService } from 'vs/platform/files/common/files'; import { IOpenerService, matchesScheme } from 'vs/platform/opener/common/opener'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { CellEditState, ICellOutputViewModel, ICommonCellInfo, ICommonNotebookEditor, IDisplayOutputLayoutUpdateRequest, IDisplayOutputViewModel, IGenericCellViewModel, IInsetRenderOutput, RenderOutputType } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { preloadsScriptStr } from 'vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads'; @@ -34,14 +42,18 @@ export interface WebviewIntialized extends BaseToWebviewMessage { type: 'initialized'; } -export interface IDimensionMessage extends BaseToWebviewMessage { - type: 'dimension'; +export interface DimensionUpdate { id: string; init?: boolean; - data: { height: number }; + height: number; isOutput?: boolean; } +export interface IDimensionMessage extends BaseToWebviewMessage { + type: 'dimension'; + updates: readonly DimensionUpdate[]; +} + export interface IMouseEnterMessage extends BaseToWebviewMessage { type: 'mouseenter'; id: string; @@ -52,6 +64,16 @@ export interface IMouseLeaveMessage extends BaseToWebviewMessage { id: string; } +export interface IOutputFocusMessage extends BaseToWebviewMessage { + type: 'outputFocus'; + id: string; +} + +export interface IOutputBlurMessage extends BaseToWebviewMessage { + type: 'outputBlur'; + id: string; +} + export interface IWheelMessage extends BaseToWebviewMessage { type: 'did-scroll-wheel'; payload: any; @@ -84,6 +106,13 @@ export interface IClickMarkdownPreviewMessage extends BaseToWebviewMessage { readonly shiftKey: boolean; } +export interface IContextMenuMarkdownPreviewMessage extends BaseToWebviewMessage { + readonly type: 'contextMenuMarkdownPreview'; + readonly cellId: string; + readonly clientX: number; + readonly clientY: number; +} + export interface IMouseEnterMarkdownPreviewMessage extends BaseToWebviewMessage { type: 'mouseEnterMarkdownPreview'; cellId: string; @@ -134,6 +163,15 @@ export interface IInitializedMarkdownPreviewMessage extends BaseToWebviewMessage readonly type: 'initializedMarkdownPreview'; } +export interface ITelemetryFoundRenderedMarkdownMath extends BaseToWebviewMessage { + readonly type: 'telemetryFoundRenderedMarkdownMath'; +} + +export interface ITelemetryFoundUnrenderedMarkdownMath extends BaseToWebviewMessage { + readonly type: 'telemetryFoundUnrenderedMarkdownMath'; + readonly latexDirective: string; +} + export interface IClearMessage { type: 'clear'; } @@ -162,30 +200,25 @@ export interface ICreationRequestMessage { | { type: RenderOutputType.Extension; outputId: string; value: unknown; metadata: unknown; mimeType: string }; cellId: string; outputId: string; - top: number; + cellTop: number; + outputOffset: number; left: number; requiredPreloads: ReadonlyArray; - initiallyHidden?: boolean; + readonly initiallyHidden?: boolean; apiNamespace?: string | undefined; } export interface IContentWidgetTopRequest { - id: string; - top: number; - left: number; + outputId: string; + cellTop: number; + outputOffset: number; + forceDisplay: boolean; } export interface IViewScrollTopRequestMessage { type: 'view-scroll'; - top?: number; - forceDisplay: boolean; widgets: IContentWidgetTopRequest[]; - version: number; -} - -export interface IViewScrollMarkdownRequestMessage { - type: 'view-scroll-markdown'; - cells: { id: string; top: number }[]; + markdownPreviews: { id: string; top: number }[]; } export interface IScrollRequestMessage { @@ -214,7 +247,8 @@ export interface IShowOutputMessage { type: 'showOutput'; cellId: string; outputId: string; - top: number; + cellTop: number; + outputOffset: number; } export interface IFocusOutputMessage { @@ -222,6 +256,13 @@ export interface IFocusOutputMessage { cellId: string; } +export interface IAckOutputHeightMessage { + type: 'ack-dimension', + cellId: string; + outputId: string; + height: number; +} + export interface IPreloadResource { originalUri: string; uri: string; @@ -253,19 +294,19 @@ export interface ICreateMarkdownMessage { content: string; top: number; } -export interface IRemoveMarkdownMessage { - type: 'removeMarkdownPreview', - id: string; +export interface IDeleteMarkdownMessage { + type: 'deleteMarkdownPreview', + ids: readonly string[]; } export interface IHideMarkdownMessage { - type: 'hideMarkdownPreview'; - id: string; + type: 'hideMarkdownPreviews'; + ids: readonly string[]; } export interface IUnhideMarkdownMessage { - type: 'unhideMarkdownPreview'; - id: string; + type: 'unhideMarkdownPreviews'; + ids: readonly string[]; } export interface IShowMarkdownMessage { @@ -276,10 +317,9 @@ export interface IShowMarkdownMessage { top: number; } -export interface IUpdateMarkdownPreviewSelectionState { - readonly type: 'updateMarkdownPreviewSelectionState', - readonly id: string; - readonly isSelected: boolean; +export interface IUpdateSelectedMarkdownPreviews { + readonly type: 'updateSelectedMarkdownPreviews', + readonly selectedCellIds: readonly string[] } export interface IInitializeMarkdownMessage { @@ -292,12 +332,15 @@ export type FromWebviewMessage = | IDimensionMessage | IMouseEnterMessage | IMouseLeaveMessage + | IOutputFocusMessage + | IOutputBlurMessage | IWheelMessage | IScrollAckMessage | IBlurOutputMessage | ICustomRendererMessage | IClickedDataUrlMessage | IClickMarkdownPreviewMessage + | IContextMenuMarkdownPreviewMessage | IMouseEnterMarkdownPreviewMessage | IMouseLeaveMarkdownPreviewMessage | IToggleMarkdownPreviewMessage @@ -306,10 +349,14 @@ export type FromWebviewMessage = | ICellDropMessage | ICellDragEndMessage | IInitializedMarkdownPreviewMessage + | ITelemetryFoundRenderedMarkdownMath + | ITelemetryFoundUnrenderedMarkdownMath ; + export type ToWebviewMessage = | IClearMessage | IFocusOutputMessage + | IAckOutputHeightMessage | ICreationRequestMessage | IViewScrollTopRequestMessage | IScrollRequestMessage @@ -320,13 +367,12 @@ export type ToWebviewMessage = | IUpdateDecorationsMessage | ICustomRendererMessage | ICreateMarkdownMessage - | IRemoveMarkdownMessage + | IDeleteMarkdownMessage | IShowMarkdownMessage | IHideMarkdownMessage | IUnhideMarkdownMessage - | IUpdateMarkdownPreviewSelectionState - | IInitializeMarkdownMessage - | IViewScrollMarkdownRequestMessage; + | IUpdateSelectedMarkdownPreviews + | IInitializeMarkdownMessage; export type AnyMessage = FromWebviewMessage | ToWebviewMessage; @@ -354,12 +400,11 @@ export interface IResolvedBackLayerWebview { webview: WebviewElement; } -let version = 0; export class BackLayerWebView extends Disposable { element: HTMLElement; webview: WebviewElement | undefined = undefined; insetMapping: Map> = new Map(); - markdownPreviewMapping = new Map(); + readonly markdownPreviewMapping = new Map(); hiddenInsetMapping: Set = new Set(); reversedInsetMapping: Map = new Map(); localResourceRootsCache: URI[] | undefined = undefined; @@ -381,7 +426,7 @@ export class BackLayerWebView extends Disposable { outputNodeLeftPadding: number, previewNodePadding: number, leftMargin: number, - cellMargin: number, + rightMargin: number, runGutter: number, }, @IWebviewService readonly webviewService: IWebviewService, @@ -391,6 +436,10 @@ export class BackLayerWebView extends Disposable { @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @IFileDialogService private readonly fileDialogService: IFileDialogService, @IFileService private readonly fileService: IFileService, + @IContextMenuService private readonly contextMenuService: IContextMenuService, + @IMenuService private readonly menuService: IMenuService, + @IContextKeyService private readonly contextKeyService: IContextKeyService, + @ITelemetryService private readonly telemetryService: ITelemetryService, ) { super(); @@ -400,23 +449,181 @@ export class BackLayerWebView extends Disposable { this.element.style.position = 'absolute'; } private generateContent(coreDependencies: string, baseUrl: string) { - const markdownRenderersSrc = this.getMarkdownRendererScripts(); + const markupRenderer = this.getMarkdownRenderer(); + const outputWidth = `calc(100% - ${this.options.leftMargin + this.options.rightMargin + this.options.runGutter}px)`; + const outputMarginLeft = `${this.options.leftMargin + this.options.runGutter}px`; return html` + + + diff --git a/src/vs/workbench/contrib/update/browser/update.ts b/src/vs/workbench/contrib/update/browser/update.ts index 2315cdb2..a1838b38 100644 --- a/src/vs/workbench/contrib/update/browser/update.ts +++ b/src/vs/workbench/contrib/update/browser/update.ts @@ -54,7 +54,7 @@ export class OpenLatestReleaseNotesInBrowserAction extends Action { super('update.openLatestReleaseNotes', nls.localize('releaseNotes', "Release Notes"), undefined, true); } - async run(): Promise { + override async run(): Promise { if (this.productService.releaseNotesUrl) { const uri = URI.parse(this.productService.releaseNotesUrl); await this.openerService.open(uri); @@ -75,7 +75,7 @@ export abstract class AbstractShowReleaseNotesAction extends Action { super(id, label, undefined, true); } - async run(): Promise { + override async run(): Promise { if (!this.enabled) { return; } @@ -212,7 +212,7 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu @IActivityService private readonly activityService: IActivityService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @IProductService private readonly productService: IProductService, - @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, + @IHostService private readonly hostService: IHostService ) { super(); this.state = updateService.state; @@ -241,14 +241,14 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu this.registerGlobalActivityActions(); } - private onUpdateStateChange(state: UpdateState): void { + private async onUpdateStateChange(state: UpdateState): Promise { this.updateStateContextKey.set(state.type); switch (state.type) { case StateType.Idle: if (state.error) { this.onError(state.error); - } else if (this.state.type === StateType.CheckingForUpdates && this.state.context === this.environmentService.sessionId) { + } else if (this.state.type === StateType.CheckingForUpdates && this.state.explicit && await this.hostService.hadLastFocus()) { this.onUpdateNotAvailable(); } break; @@ -437,7 +437,7 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu } private registerGlobalActivityActions(): void { - CommandsRegistry.registerCommand('update.check', () => this.updateService.checkForUpdates(this.environmentService.sessionId)); + CommandsRegistry.registerCommand('update.check', () => this.updateService.checkForUpdates(true)); MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { group: '7_update', command: { @@ -611,7 +611,7 @@ export class SwitchProductQualityContribution extends Disposable implements IWor nls.localize('cancel', "Cancel"), ], { - detail: nls.localize('selectSyncService.detail', "Insiders version of VSCode will synchronize your settings, keybindings, extensions, snippets and UI State using separate insiders settings sync service by default."), + detail: nls.localize('selectSyncService.detail', "The Insiders version of VS Code will synchronize your settings, keybindings, extensions, snippets and UI State using separate insiders settings sync service by default."), cancelId: 2 } ); @@ -630,14 +630,12 @@ export class CheckForVSCodeUpdateAction extends Action { constructor( id: string, label: string, - @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @IUpdateService private readonly updateService: IUpdateService, ) { super(id, label, undefined, true); } - run(): Promise { - return this.updateService.checkForUpdates(this.environmentService.sessionId); + override run(): Promise { + return this.updateService.checkForUpdates(true); } } - diff --git a/src/vs/workbench/contrib/url/browser/trustedDomains.ts b/src/vs/workbench/contrib/url/browser/trustedDomains.ts index 145afdf0..1e6f5743 100644 --- a/src/vs/workbench/contrib/url/browser/trustedDomains.ts +++ b/src/vs/workbench/contrib/url/browser/trustedDomains.ts @@ -15,8 +15,6 @@ import { IAuthenticationService } from 'vs/workbench/services/authentication/bro import { IFileService } from 'vs/platform/files/common/files'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; -import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; const TRUSTED_DOMAINS_URI = URI.parse('trustedDomains:/Trusted Domains'); @@ -50,8 +48,6 @@ export async function configureOpenerTrustedDomainsHandler( storageService: IStorageService, editorService: IEditorService, telemetryService: ITelemetryService, - notificationService: INotificationService, - clipboardService: IClipboardService, ) { const parsedDomainToConfigure = URI.parse(domainToConfigure); const toplevelDomainSegements = parsedDomainToConfigure.authority.split('.'); @@ -116,12 +112,10 @@ export async function configureOpenerTrustedDomainsHandler( switch (pickedResult.id) { case 'manage': await editorService.openEditor({ - resource: TRUSTED_DOMAINS_URI, + resource: TRUSTED_DOMAINS_URI.with({ fragment: resource.toString() }), mode: 'jsonc', options: { pinned: true } }); - notificationService.prompt(Severity.Info, localize('configuringURL', "Configuring trust for: {0}", resource.toString()), - [{ label: 'Copy', run: () => clipboardService.writeText(resource.toString()) }]); return trustedDomains; case 'trust': const itemToTrust = pickedResult.toTrust; diff --git a/src/vs/workbench/contrib/url/browser/trustedDomainsFileSystemProvider.ts b/src/vs/workbench/contrib/url/browser/trustedDomainsFileSystemProvider.ts index 988007b3..175ec1bd 100644 --- a/src/vs/workbench/contrib/url/browser/trustedDomainsFileSystemProvider.ts +++ b/src/vs/workbench/contrib/url/browser/trustedDomainsFileSystemProvider.ts @@ -49,7 +49,7 @@ const CONFIG_PLACEHOLDER_TEXT = `[ // "https://microsoft.com" ]`; -function computeTrustedDomainContent(defaultTrustedDomains: string[], trustedDomains: string[], userTrustedDomains: string[], workspaceTrustedDomains: string[]) { +function computeTrustedDomainContent(defaultTrustedDomains: string[], trustedDomains: string[], userTrustedDomains: string[], workspaceTrustedDomains: string[], configuring?: string) { let content = CONFIG_HELP_TEXT_PRE; if (defaultTrustedDomains.length > 0) { @@ -77,6 +77,8 @@ function computeTrustedDomainContent(defaultTrustedDomains: string[], trustedDom content += CONFIG_HELP_TEXT_AFTER; + content += configuring ? `\n// Currently configuring trust for ${configuring}\n` : ''; + if (trustedDomains.length === 0) { content += CONFIG_PLACEHOLDER_TEXT; } else { @@ -110,14 +112,17 @@ export class TrustedDomainsFileSystemProvider implements IFileSystemProviderWith StorageScope.GLOBAL ); + const configuring: string | undefined = resource.fragment; + const { defaultTrustedDomains, trustedDomains, userDomains, workspaceDomains } = await this.instantiationService.invokeFunction(readTrustedDomains); if ( !trustedDomainsContent || trustedDomainsContent.indexOf(CONFIG_HELP_TEXT_PRE) === -1 || trustedDomainsContent.indexOf(CONFIG_HELP_TEXT_AFTER) === -1 || + trustedDomainsContent.indexOf(configuring ?? '') === -1 || [...defaultTrustedDomains, ...trustedDomains, ...userDomains, ...workspaceDomains].some(d => !assertIsDefined(trustedDomainsContent).includes(d)) ) { - trustedDomainsContent = computeTrustedDomainContent(defaultTrustedDomains, trustedDomains, userDomains, workspaceDomains); + trustedDomainsContent = computeTrustedDomainContent(defaultTrustedDomains, trustedDomains, userDomains, workspaceDomains, configuring); } const buffer = VSBuffer.fromString(trustedDomainsContent).buffer; diff --git a/src/vs/workbench/contrib/url/browser/trustedDomainsValidator.ts b/src/vs/workbench/contrib/url/browser/trustedDomainsValidator.ts index 092f5c5d..414c0799 100644 --- a/src/vs/workbench/contrib/url/browser/trustedDomainsValidator.ts +++ b/src/vs/workbench/contrib/url/browser/trustedDomainsValidator.ts @@ -18,7 +18,6 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { INotificationService } from 'vs/platform/notification/common/notification'; import { IdleValue } from 'vs/base/common/async'; import { IAuthenticationService } from 'vs/workbench/services/authentication/browser/authenticationService'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; @@ -43,7 +42,6 @@ export class OpenerValidatorContributions implements IWorkbenchContribution { @IClipboardService private readonly _clipboardService: IClipboardService, @ITelemetryService private readonly _telemetryService: ITelemetryService, @IInstantiationService private readonly _instantiationService: IInstantiationService, - @INotificationService private readonly _notificationService: INotificationService, @IAuthenticationService private readonly _authenticationService: IAuthenticationService, @IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService, ) { @@ -151,8 +149,6 @@ export class OpenerValidatorContributions implements IWorkbenchContribution { this._storageService, this._editorService, this._telemetryService, - this._notificationService, - this._clipboardService, ); // Trust all domains if (pickedDomains.indexOf('*') !== -1) { diff --git a/src/vs/workbench/contrib/url/test/browser/trustedDomains.test.ts b/src/vs/workbench/contrib/url/test/browser/trustedDomains.test.ts index 77144c98..9ff8c657 100644 --- a/src/vs/workbench/contrib/url/test/browser/trustedDomains.test.ts +++ b/src/vs/workbench/contrib/url/test/browser/trustedDomains.test.ts @@ -18,7 +18,7 @@ function linkNotAllowedByRules(link: string, rules: string[]) { suite('GitHub remote extraction', () => { test('All known formats', () => { - assert.deepEqual( + assert.deepStrictEqual( extractGitHubRemotesFromGitConfig( ` [remote "1"] diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index 2524044c..9e443a31 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Action } from 'vs/base/common/actions'; -import { isPromiseCanceledError } from 'vs/base/common/errors'; +import { getErrorMessage, isPromiseCanceledError } from 'vs/base/common/errors'; import { Event } from 'vs/base/common/event'; import { Disposable, DisposableStore, dispose, MutableDisposable, toDisposable, IDisposable } from 'vs/base/common/lifecycle'; import { isEqual, basename } from 'vs/base/common/resources'; @@ -510,8 +510,10 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo this.notificationService.error(localize('auth failed', "Error while turning on Settings Sync: Authentication failed.")); return; } + this.notificationService.error(localize('turn on failed with user data sync error', "Error while turning on Settings Sync. Please check [logs]({0}) for more details.", `command:${SHOW_SYNC_LOG_COMMAND_ID}`)); + } else { + this.notificationService.error(localize({ key: 'turn on failed', comment: ['Substitution is for error reason'] }, "Error while turning on Settings Sync. {0}", getErrorMessage(e))); } - this.notificationService.error(localize('turn on failed', "Error while turning on Settings Sync. Please check [logs]({0}) for more details.", `command:${SHOW_SYNC_LOG_COMMAND_ID}`)); } } @@ -1331,7 +1333,7 @@ class AcceptChangesContribution extends Disposable implements IEditorContributio this.acceptChangesButton = undefined; } - dispose(): void { + override dispose(): void { this.disposeAcceptChangesWidgetRenderer(); super.dispose(); } diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncMergesView.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncMergesView.ts index cbfc0489..cb0c35e2 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncMergesView.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncMergesView.ts @@ -77,7 +77,7 @@ export class UserDataSyncMergesViewPane extends TreeViewPane { this.registerActions(); } - protected renderTreeView(container: HTMLElement): void { + protected override renderTreeView(container: HTMLElement): void { super.renderTreeView(DOM.append(container, DOM.$(''))); this.createButtons(container); @@ -101,7 +101,7 @@ export class UserDataSyncMergesViewPane extends TreeViewPane { this._register(this.cancelButton.onDidClick(() => this.cancel())); } - protected layoutTreeView(height: number, width: number): void { + protected override layoutTreeView(height: number, width: number): void { const buttonContainerHeight = 78; this.buttonsContainer.style.height = `${buttonContainerHeight}px`; this.buttonsContainer.style.width = `${width}px`; @@ -499,7 +499,7 @@ class AcceptChangesContribution extends Disposable implements IEditorContributio this.acceptChangesButton = undefined; } - dispose(): void { + override dispose(): void { this.disposeAcceptChangesWidgetRenderer(); super.dispose(); } diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncViews.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncViews.ts index d66cdd43..4470fe0c 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncViews.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncViews.ts @@ -339,7 +339,7 @@ class RemoteUserDataSyncActivityViewDataProvider extends UserDataSyncActivityVie super(userDataSyncService, userDataAutoSyncService, userDataSyncWorkbenchService, notificationService); } - async getChildren(element?: ITreeItem): Promise { + override async getChildren(element?: ITreeItem): Promise { if (!element) { this.machinesPromise = undefined; } @@ -357,7 +357,7 @@ class RemoteUserDataSyncActivityViewDataProvider extends UserDataSyncActivityVie return this.userDataSyncService.getRemoteSyncResourceHandles(syncResource); } - protected async getChildrenForSyncResourceTreeItem(element: SyncResourceHandleTreeItem): Promise { + protected override async getChildrenForSyncResourceTreeItem(element: SyncResourceHandleTreeItem): Promise { const children = await super.getChildrenForSyncResourceTreeItem(element); if (children.length) { const machineId = await this.userDataSyncService.getMachineId(element.syncResourceHandle.syncResource, element.syncResourceHandle); diff --git a/src/vs/workbench/contrib/watermark/browser/watermark.ts b/src/vs/workbench/contrib/watermark/browser/watermark.ts index 3d079753..d6840773 100644 --- a/src/vs/workbench/contrib/watermark/browser/watermark.ts +++ b/src/vs/workbench/contrib/watermark/browser/watermark.ts @@ -5,7 +5,7 @@ import 'vs/css!./watermark'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { isMacintosh, OS } from 'vs/base/common/platform'; +import { isMacintosh, isWeb, OS } from 'vs/base/common/platform'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import * as nls from 'vs/nls'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -27,6 +27,8 @@ import { assertIsDefined } from 'vs/base/common/types'; import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration'; import { NEW_UNTITLED_FILE_COMMAND_ID } from 'vs/workbench/contrib/files/browser/fileCommands'; import { DEBUG_START_COMMAND_ID } from 'vs/workbench/contrib/debug/browser/debugCommands'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { attachKeybindingLabelStyler } from 'vs/platform/theme/common/styler'; const $ = dom.$; @@ -79,7 +81,8 @@ export class WatermarkContribution extends Disposable implements IWorkbenchContr @IKeybindingService private readonly keybindingService: IKeybindingService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IConfigurationService private readonly configurationService: IConfigurationService, - @IEditorGroupsService private readonly editorGroupsService: IEditorGroupsService + @IEditorGroupsService private readonly editorGroupsService: IEditorGroupsService, + @IThemeService private readonly themeService: IThemeService ) { super(); @@ -94,7 +97,7 @@ export class WatermarkContribution extends Disposable implements IWorkbenchContr } private registerListeners(): void { - this.lifecycleService.onShutdown(() => this.dispose()); + this.lifecycleService.onDidShutdown(() => this.dispose()); this._register(this.configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration(WORKBENCH_TIPS_ENABLED_KEY)) { @@ -128,17 +131,21 @@ export class WatermarkContribution extends Disposable implements IWorkbenchContr const box = dom.append(this.watermark, $('.watermark-box')); const folder = this.workbenchState !== WorkbenchState.EMPTY; const selected = folder ? folderEntries : noFolderEntries - .filter(entry => !('mac' in entry) || entry.mac === isMacintosh) + .filter(entry => !('mac' in entry) || entry.mac === (isMacintosh && !isWeb)) .filter(entry => !!CommandsRegistry.getCommand(entry.id)); + const keybindingLabelStylers = this.watermarkDisposable.add(new DisposableStore()); + const update = () => { dom.clearNode(box); + keybindingLabelStylers.clear(); selected.map(entry => { const dl = dom.append(box, $('dl')); const dt = dom.append(dl, $('dt')); dt.textContent = entry.text; const dd = dom.append(dl, $('dd')); const keybinding = new KeybindingLabel(dd, OS, { renderUnboundKeybindings: true }); + keybindingLabelStylers.add(attachKeybindingLabelStyler(keybinding, this.themeService)); keybinding.set(this.keybindingService.lookupKeybinding(entry.id)); }); }; diff --git a/src/vs/workbench/contrib/webview/browser/baseWebviewElement.ts b/src/vs/workbench/contrib/webview/browser/baseWebviewElement.ts index 2c3f6c87..eb1458e9 100644 --- a/src/vs/workbench/contrib/webview/browser/baseWebviewElement.ts +++ b/src/vs/workbench/contrib/webview/browser/baseWebviewElement.ts @@ -4,16 +4,25 @@ *--------------------------------------------------------------------------------------------*/ import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; +import { ThrottledDelayer } from 'vs/base/common/async'; +import { streamToBuffer } from 'vs/base/common/buffer'; +import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { Emitter } from 'vs/base/common/event'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { IFileService } from 'vs/platform/files/common/files'; import { ILogService } from 'vs/platform/log/common/log'; import { INotificationService } from 'vs/platform/notification/common/notification'; +import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver'; +import { ITunnelService } from 'vs/platform/remote/common/tunnel'; +import { IRequestService } from 'vs/platform/request/common/request'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { WebviewPortMappingManager } from 'vs/platform/webview/common/webviewPortMapping'; +import { loadLocalResource, WebviewResourceResponse } from 'vs/workbench/contrib/webview/browser/resourceLoading'; import { WebviewThemeDataProvider } from 'vs/workbench/contrib/webview/browser/themeing'; -import { areWebviewContentOptionsEqual, WebviewContentOptions, WebviewExtensionDescription, WebviewOptions } from 'vs/workbench/contrib/webview/browser/webview'; +import { areWebviewContentOptionsEqual, WebviewContentOptions, WebviewExtensionDescription, WebviewMessageReceivedEvent, WebviewOptions } from 'vs/workbench/contrib/webview/browser/webview'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; export const enum WebviewMessageChannels { @@ -78,25 +87,59 @@ export abstract class BaseWebview extends Disposable { protected content: WebviewContent; + private readonly _portMappingManager: WebviewPortMappingManager; + + private readonly _resourceLoadingCts = this._register(new CancellationTokenSource()); + + private readonly _fileService: IFileService; + private readonly _logService: ILogService; + private readonly _remoteAuthorityResolverService: IRemoteAuthorityResolverService; + private readonly _requestService: IRequestService; + private readonly _telemetryService: ITelemetryService; + private readonly _tunnelService: ITunnelService; + protected readonly _environmentService: IWorkbenchEnvironmentService; + + private readonly _focusDelayer = this._register(new ThrottledDelayer(10)); + constructor( public readonly id: string, private readonly options: WebviewOptions, contentOptions: WebviewContentOptions, public extension: WebviewExtensionDescription | undefined, private readonly webviewThemeDataProvider: WebviewThemeDataProvider, - @INotificationService notificationService: INotificationService, - @ILogService private readonly _logService: ILogService, - @ITelemetryService private readonly _telemetryService: ITelemetryService, - @IWorkbenchEnvironmentService protected readonly environmentService: IWorkbenchEnvironmentService + services: { + environmentService: IWorkbenchEnvironmentService, + fileService: IFileService, + logService: ILogService, + notificationService: INotificationService, + remoteAuthorityResolverService: IRemoteAuthorityResolverService, + requestService: IRequestService, + telemetryService: ITelemetryService, + tunnelService: ITunnelService, + } ) { super(); + this._environmentService = services.environmentService; + this._fileService = services.fileService; + this._logService = services.logService; + this._remoteAuthorityResolverService = services.remoteAuthorityResolverService; + this._requestService = services.requestService; + this._telemetryService = services.telemetryService; + this._tunnelService = services.tunnelService; + this.content = { html: '', options: contentOptions, state: undefined }; + this._portMappingManager = this._register(new WebviewPortMappingManager( + () => this.extension?.location, + () => this.content.options.portMapping || [], + this._tunnelService + )); + this._element = this.createElement(options, contentOptions); const subscription = this._register(this.on(WebviewMessageChannels.webviewReady, () => { @@ -120,8 +163,11 @@ export abstract class BaseWebview extends Disposable { this._onDidClickLink.fire(uri); })); - this._register(this.on(WebviewMessageChannels.onmessage, (data: any) => { - this._onMessage.fire(data); + this._register(this.on(WebviewMessageChannels.onmessage, (data: { message: any, transfer?: ArrayBuffer[] }) => { + this._onMessage.fire({ + message: data.message, + transfer: data.transfer, + }); })); this._register(this.on(WebviewMessageChannels.didScroll, (scrollYPercentage: number) => { @@ -150,7 +196,7 @@ export abstract class BaseWebview extends Disposable { })); this._register(this.on<{ message: string }>(WebviewMessageChannels.fatalError, (e) => { - notificationService.error(localize('fatalErrorMessage', "Error loading webview: {0}", e.message)); + services.notificationService.error(localize('fatalErrorMessage', "Error loading webview: {0}", e.message)); })); this._register(this.on('did-keydown', (data: KeyboardEvent) => { @@ -164,11 +210,30 @@ export abstract class BaseWebview extends Disposable { this.handleKeyEvent('keyup', data); })); + this._register(this.on(WebviewMessageChannels.loadResource, (entry: { id: number, path: string, query: string, ifNoneMatch?: string }) => { + const rawPath = entry.path; + const uri = URI.parse(rawPath.replace(/^\/([\w\-]+)(\/{1,2})/, (_: string, scheme: string, sep: string) => { + if (sep.length === 1) { + return `${scheme}:///`; // Add empty authority. + } else { + return `${scheme}://`; // Url has own authority. + } + })).with({ + query: decodeURIComponent(entry.query), + }); + + this.loadResource(entry.id, rawPath, uri, entry.ifNoneMatch); + })); + + this._register(this.on(WebviewMessageChannels.loadLocalhost, (entry: any) => { + this.localLocalhost(entry.id, entry.origin); + })); + this.style(); this._register(webviewThemeDataProvider.onThemeDataChanged(this.style, this)); } - dispose(): void { + override dispose(): void { if (this.element) { this.element.remove(); } @@ -176,6 +241,8 @@ export abstract class BaseWebview extends Disposable { this._onDidDispose.fire(); + this._resourceLoadingCts.dispose(true); + super.dispose(); } @@ -188,7 +255,7 @@ export abstract class BaseWebview extends Disposable { private readonly _onDidReload = this._register(new Emitter()); public readonly onDidReload = this._onDidReload.event; - private readonly _onMessage = this._register(new Emitter()); + private readonly _onMessage = this._register(new Emitter()); public readonly onMessage = this._onMessage.event; private readonly _onDidScroll = this._register(new Emitter<{ readonly scrollYPercentage: number; }>()); @@ -209,8 +276,8 @@ export abstract class BaseWebview extends Disposable { private readonly _onDidDispose = this._register(new Emitter()); public readonly onDidDispose = this._onDidDispose.event; - public postMessage(data: any): void { - this._send('message', data); + public postMessage(message: any, transfer?: ArrayBuffer[]): void { + this._send('message', { message, transfer }); } protected _send(channel: string, data?: any): void { @@ -237,7 +304,7 @@ export abstract class BaseWebview extends Disposable { this._hasAlertedAboutMissingCsp = true; if (this.extension && this.extension.id) { - if (this.environmentService.isExtensionDevelopment) { + if (this._environmentService.isExtensionDevelopment) { this._onMissingCsp.fire(this.extension.id); } @@ -264,13 +331,32 @@ export abstract class BaseWebview extends Disposable { } public set html(value: string) { + const rewrittenHtml = this.rewriteVsCodeResourceUrls(value); this.doUpdateContent({ - html: value, + html: rewrittenHtml, options: this.content.options, state: this.content.state, }); } + protected abstract get webviewResourceEndpoint(): string; + + private rewriteVsCodeResourceUrls(value: string): string { + return value + .replace(/(["'])(?:vscode-resource):(\/\/([^\s\/'"]+?)(?=\/))?([^\s'"]+?)(["'])/gi, (match, startQuote, _1, scheme, path, endQuote) => { + if (scheme) { + return `${startQuote}${this.webviewResourceEndpoint}/vscode-resource/${scheme}${path}${endQuote}`; + } + return `${startQuote}${this.webviewResourceEndpoint}/vscode-resource/file${path}${endQuote}`; + }) + .replace(/(["'])(?:vscode-webview-resource):(\/\/[^\s\/'"]+\/([^\s\/'"]+?)(?=\/))?([^\s'"]+?)(["'])/gi, (match, startQuote, _1, scheme, path, endQuote) => { + if (scheme) { + return `${startQuote}${this.webviewResourceEndpoint}/vscode-resource/${scheme}${path}${endQuote}`; + } + return `${startQuote}${this.webviewResourceEndpoint}/vscode-resource/file${path}${endQuote}`; + }); + } + public set contentOptions(options: WebviewContentOptions) { this._logService.debug(`Webview(${this.id}): will update content options`); @@ -311,6 +397,7 @@ export abstract class BaseWebview extends Disposable { contents: this.content.html, options: this.content.options, state: this.content.state, + resourceEndpoint: this.webviewResourceEndpoint, ...this.extraContentOptions }); } @@ -388,4 +475,118 @@ export abstract class BaseWebview extends Disposable { this._send('execCommand', command); } } + + private async loadResource(id: number, requestPath: string, uri: URI, ifNoneMatch: string | undefined) { + try { + const remoteAuthority = this._environmentService.remoteAuthority; + const remoteConnectionData = remoteAuthority ? this._remoteAuthorityResolverService.getConnectionData(remoteAuthority) : null; + + const result = await loadLocalResource(uri, ifNoneMatch, { + extensionLocation: this.extension?.location, + roots: this.content.options.localResourceRoots || [], + remoteConnectionData, + useRootAuthority: this.content.options.useRootAuthority + }, this._fileService, this._requestService, this._logService, this._resourceLoadingCts.token); + + switch (result.type) { + case WebviewResourceResponse.Type.Success: + { + const { buffer } = await streamToBuffer(result.stream); + return this._send('did-load-resource', { + id, + status: 200, + path: requestPath, + mime: result.mimeType, + data: buffer, + etag: result.etag, + }); + } + case WebviewResourceResponse.Type.NotModified: + { + return this._send('did-load-resource', { + id, + status: 304, // not modified + path: requestPath, + mime: result.mimeType, + }); + } + case WebviewResourceResponse.Type.AccessDenied: + { + return this._send('did-load-resource', { + id, + status: 401, // unauthorized + path: requestPath, + }); + } + } + } catch { + // noop + } + + return this._send('did-load-resource', { + id, + status: 404, + path: requestPath + }); + } + + private async localLocalhost(id: string, origin: string) { + const authority = this._environmentService.remoteAuthority; + const resolveAuthority = authority ? await this._remoteAuthorityResolverService.resolveAuthority(authority) : undefined; + const redirect = resolveAuthority ? await this._portMappingManager.getRedirect(resolveAuthority.authority, origin) : undefined; + return this._send('did-load-localhost', { + id, + origin, + location: redirect + }); + } + + public focus(): void { + this.doFocus(); + + // Handle focus change programmatically (do not rely on event from ) + this.handleFocusChange(true); + } + + protected doFocus() { + if (!this.element) { + return; + } + + // Clear the existing focus first if not already on the webview. + // This is required because the next part where we set the focus is async. + if (document.activeElement && document.activeElement instanceof HTMLElement && document.activeElement !== this.element) { + // Don't blur if on the webview because this will also happen async and may unset the focus + // after the focus trigger fires below. + document.activeElement.blur(); + } + + // Workaround for https://github.com/microsoft/vscode/issues/75209 + // Electron's webview.focus is async so for a sequence of actions such as: + // + // 1. Open webview + // 1. Show quick pick from command palette + // + // We end up focusing the webview after showing the quick pick, which causes + // the quick pick to instantly dismiss. + // + // Workaround this by debouncing the focus and making sure we are not focused on an input + // when we try to re-focus. + this._focusDelayer.trigger(async () => { + if (!this.isFocused || !this.element) { + return; + } + if (document.activeElement && document.activeElement?.tagName !== 'BODY') { + return; + } + try { + this.elementFocusImpl(); + } catch { + // noop + } + this._send('focus'); + }); + } + + protected abstract elementFocusImpl(): void; } diff --git a/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts b/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts index 102f7132..3dae6689 100644 --- a/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts +++ b/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts @@ -7,12 +7,12 @@ import { Dimension } from 'vs/base/browser/dom'; import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; import { memoize } from 'vs/base/common/decorators'; import { Emitter, Event } from 'vs/base/common/event'; -import { URI } from 'vs/base/common/uri'; import { Disposable, DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; +import { URI } from 'vs/base/common/uri'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; -import { IWebviewService, KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_ENABLED, KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE, Webview, WebviewContentOptions, WebviewElement, WebviewExtensionDescription, WebviewOptions, WebviewOverlay } from 'vs/workbench/contrib/webview/browser/webview'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; +import { IWebviewService, KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_ENABLED, KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE, Webview, WebviewContentOptions, WebviewElement, WebviewExtensionDescription, WebviewMessageReceivedEvent, WebviewOptions, WebviewOverlay } from 'vs/workbench/contrib/webview/browser/webview'; /** * Webview editor overlay that creates and destroys the underlying webview as needed. @@ -22,7 +22,7 @@ export class DynamicWebviewEditorOverlay extends Disposable implements WebviewOv private readonly _onDidWheel = this._register(new Emitter()); public readonly onDidWheel = this._onDidWheel.event; - private readonly _pendingMessages = new Set(); + private readonly _pendingMessages = new Set<{ readonly message: any, readonly transfer?: readonly ArrayBuffer[] }>(); private readonly _webview = this._register(new MutableDisposable()); private readonly _webviewEvents = this._register(new DisposableStore()); @@ -65,7 +65,7 @@ export class DynamicWebviewEditorOverlay extends Disposable implements WebviewOv private readonly _onDidDispose = this._register(new Emitter()); public onDidDispose = this._onDidDispose.event; - dispose() { + override dispose() { this._isDisposed = true; this.container.remove(); this._onDidDispose.fire(); @@ -182,7 +182,7 @@ export class DynamicWebviewEditorOverlay extends Disposable implements WebviewOv this._onDidUpdateState.fire(state); })); - this._pendingMessages.forEach(msg => webview.postMessage(msg)); + this._pendingMessages.forEach(msg => webview.postMessage(msg.message, msg.transfer)); this._pendingMessages.clear(); } @@ -244,28 +244,28 @@ export class DynamicWebviewEditorOverlay extends Disposable implements WebviewOv private readonly _onDidUpdateState = this._register(new Emitter()); public readonly onDidUpdateState: Event = this._onDidUpdateState.event; - private readonly _onMessage = this._register(new Emitter()); - public readonly onMessage: Event = this._onMessage.event; + private readonly _onMessage = this._register(new Emitter()); + public readonly onMessage = this._onMessage.event; private readonly _onMissingCsp = this._register(new Emitter()); public readonly onMissingCsp: Event = this._onMissingCsp.event; - postMessage(data: any): void { + public postMessage(message: any, transfer?: readonly ArrayBuffer[]): void { if (this._webview.value) { - this._webview.value.postMessage(data); + this._webview.value.postMessage(message, transfer); } else { - this._pendingMessages.add(data); + this._pendingMessages.add({ message, transfer }); } } - focus(): void { this.withWebview(webview => webview.focus()); } - reload(): void { this.withWebview(webview => webview.reload()); } - selectAll(): void { this.withWebview(webview => webview.selectAll()); } - copy(): void { this.withWebview(webview => webview.copy()); } - paste(): void { this.withWebview(webview => webview.paste()); } - cut(): void { this.withWebview(webview => webview.cut()); } - undo(): void { this.withWebview(webview => webview.undo()); } - redo(): void { this.withWebview(webview => webview.redo()); } + focus(): void { this._webview.value?.focus(); } + reload(): void { this._webview.value?.reload(); } + selectAll(): void { this._webview.value?.selectAll(); } + copy(): void { this._webview.value?.copy(); } + paste(): void { this._webview.value?.paste(); } + cut(): void { this._webview.value?.cut(); } + undo(): void { this._webview.value?.undo(); } + redo(): void { this._webview.value?.redo(); } showFind() { if (this._webview.value) { @@ -279,11 +279,7 @@ export class DynamicWebviewEditorOverlay extends Disposable implements WebviewOv this._webview.value?.hideFind(); } - runFindAction(previous: boolean): void { this.withWebview(webview => webview.runFindAction(previous)); } - - public getInnerWebview() { - return this._webview.value; - } + runFindAction(previous: boolean): void { this._webview.value?.runFindAction(previous); } private withWebview(f: (webview: Webview) => void): void { if (this._webview.value) { @@ -292,10 +288,10 @@ export class DynamicWebviewEditorOverlay extends Disposable implements WebviewOv } windowDidDragStart() { - this.withWebview(webview => webview.windowDidDragStart()); + this._webview.value?.windowDidDragStart(); } windowDidDragEnd() { - this.withWebview(webview => webview.windowDidDragEnd()); + this._webview.value?.windowDidDragEnd(); } } diff --git a/src/vs/workbench/contrib/webview/browser/pre/host.js b/src/vs/workbench/contrib/webview/browser/pre/host.js index 65fcf9bc..8310f417 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/host.js +++ b/src/vs/workbench/contrib/webview/browser/pre/host.js @@ -3,194 +3,112 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // @ts-check -(function () { - const id = document.location.search.match(/\bid=([\w-]+)/)[1]; - const onElectron = /platform=electron/.test(document.location.search); - const hostMessaging = new class HostMessaging { - constructor() { - /** @type {Map void>>} */ - this.handlers = new Map(); +import { createWebviewManager } from './main.js'; - window.addEventListener('message', (e) => { - const channel = e.data.channel; - const handlers = this.handlers.get(channel); - if (handlers) { - for (const handler of handlers) { - handler(e, e.data.args); - } - } else { - console.log('no handler for ', e); +const id = document.location.search.match(/\bid=([\w-]+)/)[1]; +const onElectron = /platform=electron/.test(document.location.search); + +const hostMessaging = new class HostMessaging { + constructor() { + /** @type {Map void>>} */ + this.handlers = new Map(); + + window.addEventListener('message', (e) => { + const channel = e.data.channel; + const handlers = this.handlers.get(channel); + if (handlers) { + for (const handler of handlers) { + handler(e, e.data.args); } - }); - } - - /** - * @param {string} channel - * @param {any} data - */ - postMessage(channel, data) { - window.parent.postMessage({ target: id, channel, data }, '*'); - } - - /** - * @param {string} channel - * @param {(event: MessageEvent, data: any) => void} handler - */ - onMessage(channel, handler) { - let handlers = this.handlers.get(channel); - if (!handlers) { - handlers = []; - this.handlers.set(channel, handlers); - } - handlers.push(handler); - } - }(); - - function fatalError(/** @type {string} */ message) { - console.error(`Webview fatal error: ${message}`); - hostMessaging.postMessage('fatal-error', { message }); - } - - /** @type {Promise} */ - const workerReady = new Promise(async (resolveWorkerReady) => { - if (onElectron) { - return resolveWorkerReady(); - } - - if (!areServiceWorkersEnabled()) { - fatalError('Service Workers are not enabled in browser. Webviews will not work.'); - return resolveWorkerReady(); - } - - const expectedWorkerVersion = 1; - - navigator.serviceWorker.register('service-worker.js').then( - async registration => { - await navigator.serviceWorker.ready; - - const versionHandler = (event) => { - if (event.data.channel !== 'version') { - return; - } - - navigator.serviceWorker.removeEventListener('message', versionHandler); - if (event.data.version === expectedWorkerVersion) { - return resolveWorkerReady(); - } else { - // If we have the wrong version, try once to unregister and re-register - return registration.update() - .then(() => navigator.serviceWorker.ready) - .finally(resolveWorkerReady); - } - }; - navigator.serviceWorker.addEventListener('message', versionHandler); - registration.active.postMessage({ channel: 'version' }); - }, - error => { - fatalError(`Could not register service workers: ${error}.`); - resolveWorkerReady(); - }); - - const forwardFromHostToWorker = (channel) => { - hostMessaging.onMessage(channel, event => { - navigator.serviceWorker.ready.then(registration => { - registration.active.postMessage({ channel: channel, data: event.data.args }); - }); - }); - }; - forwardFromHostToWorker('did-load-resource'); - forwardFromHostToWorker('did-load-localhost'); - - navigator.serviceWorker.addEventListener('message', event => { - if (['load-resource', 'load-localhost'].includes(event.data.channel)) { - hostMessaging.postMessage(event.data.channel, event.data); + } else { + console.log('no handler for ', e); } }); - }); - - function areServiceWorkersEnabled() { - try { - return !!navigator.serviceWorker; - } catch (e) { - return false; - } } - const unloadMonitor = new class { + /** + * @param {string} channel + * @param {any} data + */ + postMessage(channel, data) { + window.parent.postMessage({ target: id, channel, data }, '*'); + } - constructor() { - this.confirmBeforeClose = 'keyboardOnly'; - this.isModifierKeyDown = false; + /** + * @param {string} channel + * @param {(event: MessageEvent, data: any) => void} handler + */ + onMessage(channel, handler) { + let handlers = this.handlers.get(channel); + if (!handlers) { + handlers = []; + this.handlers.set(channel, handlers); + } + handlers.push(handler); + } +}(); - hostMessaging.onMessage('set-confirm-before-close', (_e, /** @type {string} */ data) => { - this.confirmBeforeClose = data; - }); +const unloadMonitor = new class { - hostMessaging.onMessage('content', (_e, /** @type {any} */ data) => { - this.confirmBeforeClose = data.confirmBeforeClose; - }); + constructor() { + this.confirmBeforeClose = 'keyboardOnly'; + this.isModifierKeyDown = false; - window.addEventListener('beforeunload', (event) => { - if (onElectron) { - return; - } + hostMessaging.onMessage('set-confirm-before-close', (_e, /** @type {string} */ data) => { + this.confirmBeforeClose = data; + }); - switch (this.confirmBeforeClose) { - case 'always': - { - event.preventDefault(); - event.returnValue = ''; - return ''; - } - case 'never': - { - break; - } - case 'keyboardOnly': - default: { - if (this.isModifierKeyDown) { - event.preventDefault(); - event.returnValue = ''; - return ''; - } + hostMessaging.onMessage('content', (_e, /** @type {any} */ data) => { + this.confirmBeforeClose = data.confirmBeforeClose; + }); + + window.addEventListener('beforeunload', (event) => { + if (onElectron) { + return; + } + + switch (this.confirmBeforeClose) { + case 'always': + { + event.preventDefault(); + event.returnValue = ''; + return ''; + } + case 'never': + { break; } + case 'keyboardOnly': + default: { + if (this.isModifierKeyDown) { + event.preventDefault(); + event.returnValue = ''; + return ''; + } + break; } - }); - } - - onIframeLoaded(/** @type {HTMLIFrameElement} */frame) { - frame.contentWindow.addEventListener('keydown', e => { - this.isModifierKeyDown = e.metaKey || e.ctrlKey || e.altKey; - }); - - frame.contentWindow.addEventListener('keyup', () => { - this.isModifierKeyDown = false; - }); - } - }; - - /** @type {import('./main').WebviewHost} */ - const host = { - postMessage: hostMessaging.postMessage.bind(hostMessaging), - onMessage: hostMessaging.onMessage.bind(hostMessaging), - ready: workerReady, - fakeLoad: !onElectron, - onElectron: onElectron, - useParentPostMessage: false, - onIframeLoaded: (/** @type {HTMLIFrameElement} */ frame) => { - unloadMonitor.onIframeLoaded(frame); - }, - rewriteCSP: onElectron - ? (csp) => { - return csp.replace(/vscode-resource:(?=(\s|;|$))/g, 'vscode-webview-resource:'); } - : (csp, endpoint) => { - const endpointUrl = new URL(endpoint); - return csp.replace(/(vscode-webview-resource|vscode-resource):(?=(\s|;|$))/g, endpointUrl.origin); - } - }; + }); + } - (/** @type {any} */ (window)).createWebviewManager(host); -}()); + onIframeLoaded(/** @type {HTMLIFrameElement} */frame) { + frame.contentWindow.addEventListener('keydown', e => { + this.isModifierKeyDown = e.metaKey || e.ctrlKey || e.altKey; + }); + + frame.contentWindow.addEventListener('keyup', () => { + this.isModifierKeyDown = false; + }); + } +}; + +createWebviewManager({ + postMessage: hostMessaging.postMessage.bind(hostMessaging), + onMessage: hostMessaging.onMessage.bind(hostMessaging), + onElectron: onElectron, + useParentPostMessage: false, + onIframeLoaded: (frame) => { + unloadMonitor.onIframeLoaded(frame); + } +}); diff --git a/src/vs/workbench/contrib/webview/browser/pre/index.html b/src/vs/workbench/contrib/webview/browser/pre/index.html index 36d86f6a..98288317 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/index.html +++ b/src/vs/workbench/contrib/webview/browser/pre/index.html @@ -13,8 +13,7 @@ - - + diff --git a/src/vs/workbench/contrib/webview/browser/pre/main.js b/src/vs/workbench/contrib/webview/browser/pre/main.js index e7fb4afe..e9a1697f 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/main.js +++ b/src/vs/workbench/contrib/webview/browser/pre/main.js @@ -11,56 +11,56 @@ * focusIframeOnCreate?: boolean, * ready?: Promise, * onIframeLoaded?: (iframe: HTMLIFrameElement) => void, - * fakeLoad?: boolean, - * rewriteCSP: (existingCSP: string, endpoint?: string) => string, * onElectron?: boolean, * useParentPostMessage: boolean, * }} WebviewHost */ -(function () { - 'use strict'; +const isSafari = navigator.vendor && navigator.vendor.indexOf('Apple') > -1 && + navigator.userAgent && + navigator.userAgent.indexOf('CriOS') === -1 && + navigator.userAgent.indexOf('FxiOS') === -1; - const isSafari = navigator.vendor && navigator.vendor.indexOf('Apple') > -1 && - navigator.userAgent && - navigator.userAgent.indexOf('CriOS') === -1 && - navigator.userAgent.indexOf('FxiOS') === -1; +const searchParams = new URL(location.toString()).searchParams; +const ID = searchParams.get('id'); - /** - * Use polling to track focus of main webview and iframes within the webview - * - * @param {Object} handlers - * @param {() => void} handlers.onFocus - * @param {() => void} handlers.onBlur - */ - const trackFocus = ({ onFocus, onBlur }) => { - const interval = 50; - let isFocused = document.hasFocus(); - setInterval(() => { - const isCurrentlyFocused = document.hasFocus(); - if (isCurrentlyFocused === isFocused) { - return; - } - isFocused = isCurrentlyFocused; - if (isCurrentlyFocused) { - onFocus(); - } else { - onBlur(); - } - }, interval); - }; +/** + * Use polling to track focus of main webview and iframes within the webview + * + * @param {Object} handlers + * @param {() => void} handlers.onFocus + * @param {() => void} handlers.onBlur + */ +const trackFocus = ({ onFocus, onBlur }) => { + const interval = 50; + let isFocused = document.hasFocus(); + setInterval(() => { + const isCurrentlyFocused = document.hasFocus(); + if (isCurrentlyFocused === isFocused) { + return; + } + isFocused = isCurrentlyFocused; + if (isCurrentlyFocused) { + onFocus(); + } else { + onBlur(); + } + }, interval); +}; - const getActiveFrame = () => { - return /** @type {HTMLIFrameElement} */ (document.getElementById('active-frame')); - }; +const getActiveFrame = () => { + return /** @type {HTMLIFrameElement} */ (document.getElementById('active-frame')); +}; - const getPendingFrame = () => { - return /** @type {HTMLIFrameElement} */ (document.getElementById('pending-frame')); - }; +const getPendingFrame = () => { + return /** @type {HTMLIFrameElement} */ (document.getElementById('pending-frame')); +}; - const vscodePostMessageFuncName = '__vscode_post_message__'; +const vscodePostMessageFuncName = '__vscode_post_message__'; - const defaultCssRules = ` +const defaultStyles = document.createElement('style'); +defaultStyles.id = '_defaultStyles'; +defaultStyles.textContent = ` html { scrollbar-color: var(--vscode-scrollbarSlider-background) var(--vscode-editor-background); } @@ -142,22 +142,22 @@ background-color: var(--vscode-scrollbarSlider-activeBackground); }`; - /** - * @param {boolean} allowMultipleAPIAcquire - * @param {boolean} useParentPostMessage - * @param {*} [state] - * @return {string} - */ - function getVsCodeApiScript(allowMultipleAPIAcquire, useParentPostMessage, state) { - const encodedState = state ? encodeURIComponent(state) : undefined; - return ` +/** + * @param {boolean} allowMultipleAPIAcquire + * @param {boolean} useParentPostMessage + * @param {*} [state] + * @return {string} + */ +function getVsCodeApiScript(allowMultipleAPIAcquire, useParentPostMessage, state) { + const encodedState = state ? encodeURIComponent(state) : undefined; + return /* js */` globalThis.acquireVsCodeApi = (function() { const originalPostMessage = window.parent['${useParentPostMessage ? 'postMessage' : vscodePostMessageFuncName}'].bind(window.parent); - const doPostMessage = (channel, data) => { + const doPostMessage = (channel, data, transfer) => { ${useParentPostMessage - ? `originalPostMessage({ command: channel, data: data }, '*');` - : `originalPostMessage(channel, data);` - } + ? `originalPostMessage({ command: channel, data: data }, '*', transfer);` + : `originalPostMessage(channel, data, transfer);` + } }; let acquired = false; @@ -170,8 +170,8 @@ } acquired = true; return Object.freeze({ - postMessage: function(msg) { - doPostMessage('onmessage', msg); + postMessage: function(message, transfer) { + doPostMessage('onmessage', { message, transfer }, transfer); }, setState: function(newState) { state = newState; @@ -188,542 +188,616 @@ delete window.top; delete window.frameElement; `; +} + +/** @type {Promise} */ +const workerReady = new Promise(async (resolve, reject) => { + if (!areServiceWorkersEnabled()) { + return reject(new Error('Service Workers are not enabled in browser. Webviews will not work.')); } - /** - * @param {WebviewHost} host - */ - function createWebviewManager(host) { - // state - let firstLoad = true; - let loadTimeout; - let pendingMessages = []; + const expectedWorkerVersion = 1; - const initData = { - initialScrollProgress: undefined, - }; + navigator.serviceWorker.register(`service-worker.js${self.location.search}`).then( + async registration => { + await navigator.serviceWorker.ready; - /** - * @param {HTMLDocument?} document - * @param {HTMLElement?} body - */ - const applyStyles = (document, body) => { - if (!document) { - return; - } - - if (body) { - body.classList.remove('vscode-light', 'vscode-dark', 'vscode-high-contrast'); - body.classList.add(initData.activeTheme); - - body.dataset.vscodeThemeKind = initData.activeTheme; - body.dataset.vscodeThemeName = initData.themeName || ''; - } - - if (initData.styles) { - const documentStyle = document.documentElement.style; - - // Remove stale properties - for (let i = documentStyle.length - 1; i >= 0; i--) { - const property = documentStyle[i]; - - // Don't remove properties that the webview might have added separately - if (property && property.startsWith('--vscode-')) { - documentStyle.removeProperty(property); - } - } - - // Re-add new properties - for (const variable of Object.keys(initData.styles)) { - documentStyle.setProperty(`--${variable}`, initData.styles[variable]); - } - } - }; - - /** - * @param {MouseEvent} event - */ - const handleInnerClick = (event) => { - if (!event || !event.view || !event.view.document) { - return; - } - - let baseElement = event.view.document.getElementsByTagName('base')[0]; - /** @type {any} */ - let node = event.target; - while (node) { - if (node.tagName && node.tagName.toLowerCase() === 'a' && node.href) { - if (node.getAttribute('href') === '#') { - event.view.scrollTo(0, 0); - } else if (node.hash && (node.getAttribute('href') === node.hash || (baseElement && node.href.indexOf(baseElement.href) >= 0))) { - let scrollTarget = event.view.document.getElementById(node.hash.substr(1, node.hash.length - 1)); - if (scrollTarget) { - scrollTarget.scrollIntoView(); - } - } else { - host.postMessage('did-click-link', node.href.baseVal || node.href); - } - event.preventDefault(); - break; - } - node = node.parentNode; - } - }; - - /** - * @param {MouseEvent} event - */ - const handleAuxClick = - (event) => { - // Prevent middle clicks opening a broken link in the browser - if (!event.view || !event.view.document) { + const versionHandler = (event) => { + if (event.data.channel !== 'version') { return; } - if (event.button === 1) { - let node = /** @type {any} */ (event.target); - while (node) { - if (node.tagName && node.tagName.toLowerCase() === 'a' && node.href) { - event.preventDefault(); - break; - } - node = node.parentNode; - } + navigator.serviceWorker.removeEventListener('message', versionHandler); + if (event.data.version === expectedWorkerVersion) { + return resolve(); + } else { + // If we have the wrong version, try once to unregister and re-register + return registration.update() + .then(() => navigator.serviceWorker.ready) + .finally(resolve); } }; + navigator.serviceWorker.addEventListener('message', versionHandler); + registration.active.postMessage({ channel: 'version' }); + }, + error => { + reject(new Error(`Could not register service workers: ${error}.`)); + }); +}); - /** - * @param {KeyboardEvent} e - */ - const handleInnerKeydown = (e) => { - // If the keypress would trigger a browser event, such as copy or paste, - // make sure we block the browser from dispatching it. Instead VS Code - // handles these events and will dispatch a copy/paste back to the webview - // if needed - if (isUndoRedo(e)) { - e.preventDefault(); - } else if (isCopyPasteOrCut(e)) { - if (host.onElectron) { - e.preventDefault(); - } else { - return; // let the browser handle this - } - } +/** + * @param {WebviewHost} host + */ +export async function createWebviewManager(host) { + // state + let firstLoad = true; + let loadTimeout; + let styleVersion = 0; - host.postMessage('did-keydown', { - key: e.key, - keyCode: e.keyCode, - code: e.code, - shiftKey: e.shiftKey, - altKey: e.altKey, - ctrlKey: e.ctrlKey, - metaKey: e.metaKey, - repeat: e.repeat - }); - }; - /** - * @param {KeyboardEvent} e - */ - const handleInnerUp = (e) => { - host.postMessage('did-keyup', { - key: e.key, - keyCode: e.keyCode, - code: e.code, - shiftKey: e.shiftKey, - altKey: e.altKey, - ctrlKey: e.ctrlKey, - metaKey: e.metaKey, - repeat: e.repeat - }); - }; + /** @type {Array<{ readonly message: any, transfer?: ArrayBuffer[] }>} */ + let pendingMessages = []; - /** - * @param {KeyboardEvent} e - * @return {boolean} - */ - function isCopyPasteOrCut(e) { - const hasMeta = e.ctrlKey || e.metaKey; - const shiftInsert = e.shiftKey && e.key.toLowerCase() === 'insert'; - return (hasMeta && ['c', 'v', 'x'].includes(e.key.toLowerCase())) || shiftInsert; + const initData = { + /** @type {number | undefined} */ + initialScrollProgress: undefined, + + /** @type {{ [key: string]: string }} */ + styles: undefined, + + /** @type {string | undefined} */ + activeTheme: undefined, + + /** @type {string | undefined} */ + themeName: undefined, + }; + + host.onMessage('did-load-resource', (_event, data) => { + navigator.serviceWorker.ready.then(registration => { + registration.active.postMessage({ channel: 'did-load-resource', data }, data.data?.buffer ? [data.data.buffer] : []); + }); + }); + + host.onMessage('did-load-localhost', (_event, data) => { + navigator.serviceWorker.ready.then(registration => { + registration.active.postMessage({ channel: 'did-load-localhost', data }); + }); + }); + + navigator.serviceWorker.addEventListener('message', event => { + switch (event.data.channel) { + case 'load-resource': + case 'load-localhost': + host.postMessage(event.data.channel, event.data); + return; + } + }); + /** + * @param {HTMLDocument?} document + * @param {HTMLElement?} body + */ + const applyStyles = (document, body) => { + if (!document) { + return; } - /** - * @param {KeyboardEvent} e - * @return {boolean} - */ - function isUndoRedo(e) { - const hasMeta = e.ctrlKey || e.metaKey; - return hasMeta && ['z', 'y'].includes(e.key.toLowerCase()); + if (body) { + body.classList.remove('vscode-light', 'vscode-dark', 'vscode-high-contrast'); + body.classList.add(initData.activeTheme); + + body.dataset.vscodeThemeKind = initData.activeTheme; + body.dataset.vscodeThemeName = initData.themeName || ''; } - let isHandlingScroll = false; + if (initData.styles) { + const documentStyle = document.documentElement.style; - const handleWheel = (event) => { - if (isHandlingScroll) { - return; - } + // Remove stale properties + for (let i = documentStyle.length - 1; i >= 0; i--) { + const property = documentStyle[i]; - host.postMessage('did-scroll-wheel', { - deltaMode: event.deltaMode, - deltaX: event.deltaX, - deltaY: event.deltaY, - deltaZ: event.deltaZ, - detail: event.detail, - type: event.type - }); - }; - - const handleInnerScroll = (event) => { - if (!event.target || !event.target.body) { - return; - } - if (isHandlingScroll) { - return; - } - - const progress = event.currentTarget.scrollY / event.target.body.clientHeight; - if (isNaN(progress)) { - return; - } - - isHandlingScroll = true; - window.requestAnimationFrame(() => { - try { - host.postMessage('did-scroll', progress); - } catch (e) { - // noop - } - isHandlingScroll = false; - }); - }; - - /** - * @return {string} - */ - function toContentHtml(data) { - const options = data.options; - const text = data.contents; - const newDocument = new DOMParser().parseFromString(text, 'text/html'); - - newDocument.querySelectorAll('a').forEach(a => { - if (!a.title) { - a.title = a.getAttribute('href'); - } - }); - - // apply default script - if (options.allowScripts) { - const defaultScript = newDocument.createElement('script'); - defaultScript.id = '_vscodeApiScript'; - defaultScript.textContent = getVsCodeApiScript(options.allowMultipleAPIAcquire, host.useParentPostMessage, data.state); - newDocument.head.prepend(defaultScript); - } - - // apply default styles - const defaultStyles = newDocument.createElement('style'); - defaultStyles.id = '_defaultStyles'; - defaultStyles.textContent = defaultCssRules; - newDocument.head.prepend(defaultStyles); - - applyStyles(newDocument, newDocument.body); - - // Check for CSP - const csp = newDocument.querySelector('meta[http-equiv="Content-Security-Policy"]'); - if (!csp) { - host.postMessage('no-csp-found'); - } else { - try { - csp.setAttribute('content', host.rewriteCSP(csp.getAttribute('content'), data.endpoint)); - } catch (e) { - console.error(`Could not rewrite csp: ${e}`); + // Don't remove properties that the webview might have added separately + if (property && property.startsWith('--vscode-')) { + documentStyle.removeProperty(property); } } - // set DOCTYPE for newDocument explicitly as DOMParser.parseFromString strips it off - // and DOCTYPE is needed in the iframe to ensure that the user agent stylesheet is correctly overridden - return '\n' + newDocument.documentElement.outerHTML; + // Re-add new properties + for (const variable of Object.keys(initData.styles)) { + documentStyle.setProperty(`--${variable}`, initData.styles[variable]); + } + } + }; + + /** + * @param {MouseEvent} event + */ + const handleInnerClick = (event) => { + if (!event || !event.view || !event.view.document) { + return; } - document.addEventListener('DOMContentLoaded', () => { - const idMatch = document.location.search.match(/\bid=([\w-]+)/); - const ID = idMatch ? idMatch[1] : undefined; - if (!document.body) { + const baseElement = event.view.document.getElementsByTagName('base')[0]; + + for (const pathElement of event.composedPath()) { + /** @type {any} */ + const node = pathElement; + if (node.tagName && node.tagName.toLowerCase() === 'a' && node.href) { + if (node.getAttribute('href') === '#') { + event.view.scrollTo(0, 0); + } else if (node.hash && (node.getAttribute('href') === node.hash || (baseElement && node.href === baseElement.href + node.hash))) { + const scrollTarget = event.view.document.getElementById(node.hash.substr(1, node.hash.length - 1)); + if (scrollTarget) { + scrollTarget.scrollIntoView(); + } + } else { + host.postMessage('did-click-link', node.href.baseVal || node.href); + } + event.preventDefault(); + return; + } + } + }; + + /** + * @param {MouseEvent} event + */ + const handleAuxClick = + (event) => { + // Prevent middle clicks opening a broken link in the browser + if (!event.view || !event.view.document) { return; } - host.onMessage('styles', (_event, data) => { - initData.styles = data.styles; - initData.activeTheme = data.activeTheme; - initData.themeName = data.themeName; - - const target = getActiveFrame(); - if (!target) { - return; - } - - if (target.contentDocument) { - applyStyles(target.contentDocument, target.contentDocument.body); - } - }); - - // propagate focus - host.onMessage('focus', () => { - const activeFrame = getActiveFrame(); - if (!activeFrame || !activeFrame.contentWindow) { - // Focus the top level webview instead - window.focus(); - return; - } - - if (document.activeElement === activeFrame) { - // We are already focused on the iframe (or one of its children) so no need - // to refocus. - return; - } - - activeFrame.contentWindow.focus(); - }); - - // update iframe-contents - let updateId = 0; - host.onMessage('content', async (_event, data) => { - const currentUpdateId = ++updateId; - await host.ready; - if (currentUpdateId !== updateId) { - return; - } - - const options = data.options; - const newDocument = toContentHtml(data); - - const frame = getActiveFrame(); - const wasFirstLoad = firstLoad; - // keep current scrollY around and use later - let setInitialScrollPosition; - if (firstLoad) { - firstLoad = false; - setInitialScrollPosition = (body, window) => { - if (!isNaN(initData.initialScrollProgress)) { - if (window.scrollY === 0) { - window.scroll(0, body.clientHeight * initData.initialScrollProgress); - } - } - }; - } else { - const scrollY = frame && frame.contentDocument && frame.contentDocument.body ? frame.contentWindow.scrollY : 0; - setInitialScrollPosition = (body, window) => { - if (window.scrollY === 0) { - window.scroll(0, scrollY); - } - }; - } - - // Clean up old pending frames and set current one as new one - const previousPendingFrame = getPendingFrame(); - if (previousPendingFrame) { - previousPendingFrame.setAttribute('id', ''); - document.body.removeChild(previousPendingFrame); - } - if (!wasFirstLoad) { - pendingMessages = []; - } - - const newFrame = document.createElement('iframe'); - newFrame.setAttribute('id', 'pending-frame'); - newFrame.setAttribute('frameborder', '0'); - newFrame.setAttribute('sandbox', options.allowScripts ? 'allow-scripts allow-forms allow-same-origin allow-pointer-lock allow-downloads' : 'allow-same-origin allow-pointer-lock'); - newFrame.setAttribute('allow', options.allowScripts ? 'clipboard-read; clipboard-write;' : ''); - if (host.fakeLoad) { - // We should just be able to use srcdoc, but I wasn't - // seeing the service worker applying properly. - // Fake load an empty on the correct origin and then write real html - // into it to get around this. - newFrame.src = `./fake.html?id=${ID}`; - } else { - newFrame.src = `about:blank?webviewFrame`; - } - newFrame.style.cssText = 'display: block; margin: 0; overflow: hidden; position: absolute; width: 100%; height: 100%; visibility: hidden'; - document.body.appendChild(newFrame); - - if (!host.fakeLoad) { - // write new content onto iframe - newFrame.contentDocument.open(); - } - - /** - * @param {Document} contentDocument - */ - function onFrameLoaded(contentDocument) { - // Workaround for https://bugs.chromium.org/p/chromium/issues/detail?id=978325 - setTimeout(() => { - if (host.fakeLoad) { - contentDocument.open(); - contentDocument.write(newDocument); - contentDocument.close(); - hookupOnLoadHandlers(newFrame); - } - if (contentDocument) { - applyStyles(contentDocument, contentDocument.body); - } - }, 0); - } - - if (host.fakeLoad && !options.allowScripts && isSafari) { - // On Safari for iframes with scripts disabled, the `DOMContentLoaded` never seems to be fired. - // Use polling instead. - const interval = setInterval(() => { - // If the frame is no longer mounted, loading has stopped - if (!newFrame.parentElement) { - clearInterval(interval); - return; - } - - if (newFrame.contentDocument.readyState !== 'loading') { - clearInterval(interval); - onFrameLoaded(newFrame.contentDocument); - } - }, 10); - } else { - newFrame.contentWindow.addEventListener('DOMContentLoaded', e => { - const contentDocument = e.target ? (/** @type {HTMLDocument} */ (e.target)) : undefined; - onFrameLoaded(contentDocument); - }); - } - - /** - * @param {Document} contentDocument - * @param {Window} contentWindow - */ - const onLoad = (contentDocument, contentWindow) => { - if (contentDocument && contentDocument.body) { - // Workaround for https://github.com/microsoft/vscode/issues/12865 - // check new scrollY and reset if necessary - setInitialScrollPosition(contentDocument.body, contentWindow); - } - - const newFrame = getPendingFrame(); - if (newFrame && newFrame.contentDocument && newFrame.contentDocument === contentDocument) { - const oldActiveFrame = getActiveFrame(); - if (oldActiveFrame) { - document.body.removeChild(oldActiveFrame); - } - // Styles may have changed since we created the element. Make sure we re-style - applyStyles(newFrame.contentDocument, newFrame.contentDocument.body); - newFrame.setAttribute('id', 'active-frame'); - newFrame.style.visibility = 'visible'; - if (host.focusIframeOnCreate) { - newFrame.contentWindow.focus(); - } - - contentWindow.addEventListener('scroll', handleInnerScroll); - contentWindow.addEventListener('wheel', handleWheel); - - if (document.hasFocus()) { - contentWindow.focus(); - } - - pendingMessages.forEach((data) => { - contentWindow.postMessage(data, '*'); - }); - pendingMessages = []; - } - - host.postMessage('did-load'); - }; - - /** - * @param {HTMLIFrameElement} newFrame - */ - function hookupOnLoadHandlers(newFrame) { - clearTimeout(loadTimeout); - loadTimeout = undefined; - loadTimeout = setTimeout(() => { - clearTimeout(loadTimeout); - loadTimeout = undefined; - onLoad(newFrame.contentDocument, newFrame.contentWindow); - }, 200); - - newFrame.contentWindow.addEventListener('load', function (e) { - const contentDocument = /** @type {Document} */ (e.target); - - if (loadTimeout) { - clearTimeout(loadTimeout); - loadTimeout = undefined; - onLoad(contentDocument, this); - } - }); - - // Bubble out various events - newFrame.contentWindow.addEventListener('click', handleInnerClick); - newFrame.contentWindow.addEventListener('auxclick', handleAuxClick); - newFrame.contentWindow.addEventListener('keydown', handleInnerKeydown); - newFrame.contentWindow.addEventListener('keyup', handleInnerUp); - newFrame.contentWindow.addEventListener('contextmenu', e => e.preventDefault()); - - if (host.onIframeLoaded) { - host.onIframeLoaded(newFrame); - } - } - - if (!host.fakeLoad) { - hookupOnLoadHandlers(newFrame); - } - - if (!host.fakeLoad) { - newFrame.contentDocument.write(newDocument); - newFrame.contentDocument.close(); - } - - host.postMessage('did-set-content', undefined); - }); - - // Forward message to the embedded iframe - host.onMessage('message', (_event, data) => { - const pending = getPendingFrame(); - if (!pending) { - const target = getActiveFrame(); - if (target) { - target.contentWindow.postMessage(data, '*'); + if (event.button === 1) { + for (const pathElement of event.composedPath()) { + /** @type {any} */ + const node = pathElement; + if (node.tagName && node.tagName.toLowerCase() === 'a' && node.href) { + event.preventDefault(); return; } } - pendingMessages.push(data); - }); + } + }; - host.onMessage('initial-scroll-position', (_event, progress) => { - initData.initialScrollProgress = progress; - }); + /** + * @param {KeyboardEvent} e + */ + const handleInnerKeydown = (e) => { + // If the keypress would trigger a browser event, such as copy or paste, + // make sure we block the browser from dispatching it. Instead VS Code + // handles these events and will dispatch a copy/paste back to the webview + // if needed + if (isUndoRedo(e)) { + e.preventDefault(); + } else if (isCopyPasteOrCut(e)) { + if (host.onElectron) { + e.preventDefault(); + } else { + return; // let the browser handle this + } + } - host.onMessage('execCommand', (_event, data) => { - const target = getActiveFrame(); - if (!target) { - return; + host.postMessage('did-keydown', { + key: e.key, + keyCode: e.keyCode, + code: e.code, + shiftKey: e.shiftKey, + altKey: e.altKey, + ctrlKey: e.ctrlKey, + metaKey: e.metaKey, + repeat: e.repeat + }); + }; + /** + * @param {KeyboardEvent} e + */ + const handleInnerUp = (e) => { + host.postMessage('did-keyup', { + key: e.key, + keyCode: e.keyCode, + code: e.code, + shiftKey: e.shiftKey, + altKey: e.altKey, + ctrlKey: e.ctrlKey, + metaKey: e.metaKey, + repeat: e.repeat + }); + }; + + /** + * @param {KeyboardEvent} e + * @return {boolean} + */ + function isCopyPasteOrCut(e) { + const hasMeta = e.ctrlKey || e.metaKey; + const shiftInsert = e.shiftKey && e.key.toLowerCase() === 'insert'; + return (hasMeta && ['c', 'v', 'x'].includes(e.key.toLowerCase())) || shiftInsert; + } + + /** + * @param {KeyboardEvent} e + * @return {boolean} + */ + function isUndoRedo(e) { + const hasMeta = e.ctrlKey || e.metaKey; + return hasMeta && ['z', 'y'].includes(e.key.toLowerCase()); + } + + let isHandlingScroll = false; + + const handleWheel = (event) => { + if (isHandlingScroll) { + return; + } + + host.postMessage('did-scroll-wheel', { + deltaMode: event.deltaMode, + deltaX: event.deltaX, + deltaY: event.deltaY, + deltaZ: event.deltaZ, + detail: event.detail, + type: event.type + }); + }; + + const handleInnerScroll = (event) => { + if (!event.target || !event.target.body) { + return; + } + if (isHandlingScroll) { + return; + } + + const progress = event.currentTarget.scrollY / event.target.body.clientHeight; + if (isNaN(progress)) { + return; + } + + isHandlingScroll = true; + window.requestAnimationFrame(() => { + try { + host.postMessage('did-scroll', progress); + } catch (e) { + // noop + } + isHandlingScroll = false; + }); + }; + + /** + * @return {string} + */ + function toContentHtml(data) { + const options = data.options; + const text = data.contents; + const newDocument = new DOMParser().parseFromString(text, 'text/html'); + + newDocument.querySelectorAll('a').forEach(a => { + if (!a.title) { + a.title = a.getAttribute('href'); + } + }); + + // Inject default script + if (options.allowScripts) { + const defaultScript = newDocument.createElement('script'); + defaultScript.id = '_vscodeApiScript'; + defaultScript.textContent = getVsCodeApiScript(options.allowMultipleAPIAcquire, host.useParentPostMessage, data.state); + newDocument.head.prepend(defaultScript); + } + + // Inject default styles + newDocument.head.prepend(defaultStyles.cloneNode(true)); + + applyStyles(newDocument, newDocument.body); + + // Check for CSP + const csp = newDocument.querySelector('meta[http-equiv="Content-Security-Policy"]'); + if (!csp) { + host.postMessage('no-csp-found'); + } else { + try { + // Attempt to rewrite CSPs that hardcode old-style resource endpoint + const endpointUrl = new URL(data.resourceEndpoint); + const newCsp = csp.getAttribute('content').replace(/(vscode-webview-resource|vscode-resource):(?=(\s|;|$))/g, endpointUrl.origin); + csp.setAttribute('content', newCsp); + } catch (e) { + console.error(`Could not rewrite csp: ${e}`); + } + } + + // set DOCTYPE for newDocument explicitly as DOMParser.parseFromString strips it off + // and DOCTYPE is needed in the iframe to ensure that the user agent stylesheet is correctly overridden + return '\n' + newDocument.documentElement.outerHTML; + } + + onDomReady(() => { + if (!document.body) { + return; + } + + host.onMessage('styles', (_event, data) => { + ++styleVersion; + + initData.styles = data.styles; + initData.activeTheme = data.activeTheme; + initData.themeName = data.themeName; + + const target = getActiveFrame(); + if (!target) { + return; + } + + if (target.contentDocument) { + applyStyles(target.contentDocument, target.contentDocument.body); + } + }); + + // propagate focus + host.onMessage('focus', () => { + const activeFrame = getActiveFrame(); + if (!activeFrame || !activeFrame.contentWindow) { + // Focus the top level webview instead + window.focus(); + return; + } + + if (document.activeElement === activeFrame) { + // We are already focused on the iframe (or one of its children) so no need + // to refocus. + return; + } + + activeFrame.contentWindow.focus(); + }); + + // update iframe-contents + let updateId = 0; + host.onMessage('content', async (_event, data) => { + const currentUpdateId = ++updateId; + + try { + await workerReady; + } catch (e) { + console.error(`Webview fatal error: ${e}`); + host.postMessage('fatal-error', { message: e + '' }); + return; + } + + if (currentUpdateId !== updateId) { + return; + } + + const options = data.options; + const newDocument = toContentHtml(data); + + const initialStyleVersion = styleVersion; + + const frame = getActiveFrame(); + const wasFirstLoad = firstLoad; + // keep current scrollY around and use later + let setInitialScrollPosition; + if (firstLoad) { + firstLoad = false; + setInitialScrollPosition = (body, window) => { + if (!isNaN(initData.initialScrollProgress)) { + if (window.scrollY === 0) { + window.scroll(0, body.clientHeight * initData.initialScrollProgress); + } + } + }; + } else { + const scrollY = frame && frame.contentDocument && frame.contentDocument.body ? frame.contentWindow.scrollY : 0; + setInitialScrollPosition = (body, window) => { + if (window.scrollY === 0) { + window.scroll(0, scrollY); + } + }; + } + + // Clean up old pending frames and set current one as new one + const previousPendingFrame = getPendingFrame(); + if (previousPendingFrame) { + previousPendingFrame.setAttribute('id', ''); + document.body.removeChild(previousPendingFrame); + } + if (!wasFirstLoad) { + pendingMessages = []; + } + + const newFrame = document.createElement('iframe'); + newFrame.setAttribute('id', 'pending-frame'); + newFrame.setAttribute('frameborder', '0'); + newFrame.setAttribute('sandbox', options.allowScripts ? 'allow-scripts allow-forms allow-same-origin allow-pointer-lock allow-downloads' : 'allow-same-origin allow-pointer-lock'); + newFrame.setAttribute('allow', options.allowScripts ? 'clipboard-read; clipboard-write;' : ''); + // We should just be able to use srcdoc, but I wasn't + // seeing the service worker applying properly. + // Fake load an empty on the correct origin and then write real html + // into it to get around this. + newFrame.src = `./fake.html?id=${ID}`; + + newFrame.style.cssText = 'display: block; margin: 0; overflow: hidden; position: absolute; width: 100%; height: 100%; visibility: hidden'; + document.body.appendChild(newFrame); + + /** + * @param {Document} contentDocument + */ + function onFrameLoaded(contentDocument) { + // Workaround for https://bugs.chromium.org/p/chromium/issues/detail?id=978325 + setTimeout(() => { + contentDocument.open(); + contentDocument.write(newDocument); + contentDocument.close(); + hookupOnLoadHandlers(newFrame); + + if (initialStyleVersion !== styleVersion) { + applyStyles(contentDocument, contentDocument.body); + } + }, 0); + } + + if (!options.allowScripts && isSafari) { + // On Safari for iframes with scripts disabled, the `DOMContentLoaded` never seems to be fired. + // Use polling instead. + const interval = setInterval(() => { + // If the frame is no longer mounted, loading has stopped + if (!newFrame.parentElement) { + clearInterval(interval); + return; + } + + if (newFrame.contentDocument.readyState !== 'loading') { + clearInterval(interval); + onFrameLoaded(newFrame.contentDocument); + } + }, 10); + } else { + newFrame.contentWindow.addEventListener('DOMContentLoaded', e => { + const contentDocument = e.target ? (/** @type {HTMLDocument} */ (e.target)) : undefined; + onFrameLoaded(contentDocument); + }); + } + + /** + * @param {Document} contentDocument + * @param {Window} contentWindow + */ + const onLoad = (contentDocument, contentWindow) => { + if (contentDocument && contentDocument.body) { + // Workaround for https://github.com/microsoft/vscode/issues/12865 + // check new scrollY and reset if necessary + setInitialScrollPosition(contentDocument.body, contentWindow); } - target.contentDocument.execCommand(data); - }); - trackFocus({ - onFocus: () => host.postMessage('did-focus'), - onBlur: () => host.postMessage('did-blur') - }); + const newFrame = getPendingFrame(); + if (newFrame && newFrame.contentDocument && newFrame.contentDocument === contentDocument) { + const oldActiveFrame = getActiveFrame(); + if (oldActiveFrame) { + document.body.removeChild(oldActiveFrame); + } + // Styles may have changed since we created the element. Make sure we re-style + if (initialStyleVersion !== styleVersion) { + applyStyles(newFrame.contentDocument, newFrame.contentDocument.body); + } + newFrame.setAttribute('id', 'active-frame'); + newFrame.style.visibility = 'visible'; + if (host.focusIframeOnCreate) { + newFrame.contentWindow.focus(); + } - (/** @type {any} */ (window))[vscodePostMessageFuncName] = (command, data) => { - switch (command) { - case 'onmessage': - case 'do-update-state': - host.postMessage(command, data); - break; + contentWindow.addEventListener('scroll', handleInnerScroll); + contentWindow.addEventListener('wheel', handleWheel); + + if (document.hasFocus()) { + contentWindow.focus(); + } + + pendingMessages.forEach((message) => { + contentWindow.postMessage(message.message, '*', message.transfer); + }); + pendingMessages = []; } + + host.postMessage('did-load'); }; - // signal ready - host.postMessage('webview-ready', {}); - }); - } + /** + * @param {HTMLIFrameElement} newFrame + */ + function hookupOnLoadHandlers(newFrame) { + clearTimeout(loadTimeout); + loadTimeout = undefined; + loadTimeout = setTimeout(() => { + clearTimeout(loadTimeout); + loadTimeout = undefined; + onLoad(newFrame.contentDocument, newFrame.contentWindow); + }, 200); - if (typeof module !== 'undefined') { - module.exports = createWebviewManager; + newFrame.contentWindow.addEventListener('load', function (e) { + const contentDocument = /** @type {Document} */ (e.target); + + if (loadTimeout) { + clearTimeout(loadTimeout); + loadTimeout = undefined; + onLoad(contentDocument, this); + } + }); + + // Bubble out various events + newFrame.contentWindow.addEventListener('click', handleInnerClick); + newFrame.contentWindow.addEventListener('auxclick', handleAuxClick); + newFrame.contentWindow.addEventListener('keydown', handleInnerKeydown); + newFrame.contentWindow.addEventListener('keyup', handleInnerUp); + newFrame.contentWindow.addEventListener('contextmenu', e => e.preventDefault()); + + if (host.onIframeLoaded) { + host.onIframeLoaded(newFrame); + } + } + + host.postMessage('did-set-content', undefined); + }); + + // Forward message to the embedded iframe + host.onMessage('message', (_event, /** @type {{message: any, transfer?: ArrayBuffer[] }} */ data) => { + const pending = getPendingFrame(); + if (!pending) { + const target = getActiveFrame(); + if (target) { + target.contentWindow.postMessage(data.message, '*', data.transfer); + return; + } + } + pendingMessages.push(data); + }); + + host.onMessage('initial-scroll-position', (_event, progress) => { + initData.initialScrollProgress = progress; + }); + + host.onMessage('execCommand', (_event, data) => { + const target = getActiveFrame(); + if (!target) { + return; + } + target.contentDocument.execCommand(data); + }); + + trackFocus({ + onFocus: () => host.postMessage('did-focus'), + onBlur: () => host.postMessage('did-blur') + }); + + (/** @type {any} */ (window))[vscodePostMessageFuncName] = (command, data, transfer) => { + switch (command) { + case 'onmessage': + case 'do-update-state': + host.postMessage(command, data); + break; + } + }; + + // signal ready + host.postMessage('webview-ready', {}); + }); +} + +/** + * @param {() => void} callback + */ +function onDomReady(callback) { + if (document.readyState === 'interactive' || document.readyState === 'complete') { + callback(); } else { - (/** @type {any} */ (window)).createWebviewManager = createWebviewManager; + document.addEventListener('DOMContentLoaded', callback); } -}()); +} + +function areServiceWorkersEnabled() { + try { + return !!navigator.serviceWorker; + } catch (e) { + return false; + } +} diff --git a/src/vs/workbench/contrib/webview/browser/pre/service-worker.js b/src/vs/workbench/contrib/webview/browser/pre/service-worker.js index f6c93682..26813dc9 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/service-worker.js +++ b/src/vs/workbench/contrib/webview/browser/pre/service-worker.js @@ -15,11 +15,20 @@ const resourceCacheName = `vscode-resource-cache-${VERSION}`; const rootPath = sw.location.pathname.replace(/\/service-worker.js$/, ''); + +const searchParams = new URL(location.toString()).searchParams; +/** + * Origin used for resources + */ +const resourceOrigin = searchParams.get('vscode-resource-origin') ?? sw.origin; + /** * Root path for resources */ const resourceRoot = rootPath + '/vscode-resource'; +const serviceWorkerFetchIgnoreSubdomain = searchParams.get('serviceWorkerFetchIgnoreSubdomain') ?? false; + const resolveTimeout = 30000; /** @@ -162,13 +171,21 @@ sw.addEventListener('message', async (event) => { sw.addEventListener('fetch', (event) => { const requestUrl = new URL(event.request.url); - // See if it's a resource request - if (requestUrl.origin === sw.origin && requestUrl.pathname.startsWith(resourceRoot + '/')) { + if (serviceWorkerFetchIgnoreSubdomain && requestUrl.pathname.startsWith(resourceRoot + '/')) { + // #121981 + const ignoreFirstSubdomainRegex = /(.*):\/\/.*?\.(.*)/; + const match1 = resourceOrigin.match(ignoreFirstSubdomainRegex); + const match2 = requestUrl.origin.match(ignoreFirstSubdomainRegex); + if (match1 && match2 && match1[1] === match2[1] && match1[2] === match2[2]) { + return event.respondWith(processResourceRequest(event, requestUrl)); + } + } else if (requestUrl.origin === resourceOrigin && requestUrl.pathname.startsWith(resourceRoot + '/')) { + // See if it's a resource request return event.respondWith(processResourceRequest(event, requestUrl)); } // See if it's a localhost request - if (requestUrl.origin !== sw.origin && requestUrl.host.match(/^localhost:(\d+)$/)) { + if (requestUrl.origin !== sw.origin && requestUrl.host.match(/^(localhost|127.0.0.1|0.0.0.0):(\d+)$/)) { return event.respondWith(processLocalhostRequest(event, requestUrl)); } }); @@ -247,6 +264,7 @@ async function processResourceRequest(event, requestUrl) { channel: 'load-resource', id: requestId, path: resourcePath, + query: requestUrl.search.replace(/^\?/, ''), ifNoneMatch: cached?.headers.get('ETag'), }); @@ -308,6 +326,7 @@ async function getOuterIframeClient(webviewId) { const allClients = await sw.clients.matchAll({ includeUncontrolled: true }); return allClients.find(client => { const clientUrl = new URL(client.url); - return (clientUrl.pathname === `${rootPath}/` || clientUrl.pathname === `${rootPath}/index.html`) && clientUrl.search.match(new RegExp('\\bid=' + webviewId)); + const hasExpectedPathName = (clientUrl.pathname === `${rootPath}/` || clientUrl.pathname === `${rootPath}/index.html` || clientUrl.pathname === `${rootPath}/electron-browser-index.html`); + return hasExpectedPathName && clientUrl.search.match(new RegExp('\\bid=' + webviewId)); }); } diff --git a/src/vs/platform/webview/common/resourceLoader.ts b/src/vs/workbench/contrib/webview/browser/resourceLoading.ts similarity index 58% rename from src/vs/platform/webview/common/resourceLoader.ts rename to src/vs/workbench/contrib/webview/browser/resourceLoading.ts index 06f243e4..f26f012b 100644 --- a/src/vs/platform/webview/common/resourceLoader.ts +++ b/src/vs/workbench/contrib/webview/browser/resourceLoading.ts @@ -16,9 +16,6 @@ import { IRemoteConnectionData } from 'vs/platform/remote/common/remoteAuthority import { IRequestService } from 'vs/platform/request/common/request'; import { getWebviewContentMimeType } from 'vs/platform/webview/common/mimeTypes'; - -export const webviewPartitionId = 'webview'; - export namespace WebviewResourceResponse { export enum Type { Success, Failed, AccessDenied, NotModified } @@ -46,53 +43,6 @@ export namespace WebviewResourceResponse { export type StreamResponse = StreamSuccess | typeof Failed | typeof AccessDenied | NotModified; } -export namespace WebviewFileReadResponse { - export enum Type { Success, NotModified } - - export class StreamSuccess { - readonly type = Type.Success; - - constructor( - public readonly stream: VSBufferReadableStream, - public readonly etag: string | undefined - ) { } - } - - export const NotModified = { type: Type.NotModified } as const; - - export type Response = StreamSuccess | typeof NotModified; -} - -/** - * Wraps a call to `IFileService.readFileStream` and converts the result to a `WebviewFileReadResponse.Response` - */ -export async function readFileStream( - fileService: IFileService, - resource: URI, - etag: string | undefined, -): Promise { - try { - const result = await fileService.readFileStream(resource, { etag }); - return new WebviewFileReadResponse.StreamSuccess(result.value, result.etag); - } catch (e) { - if (e instanceof FileOperationError) { - const result = e.fileOperationResult; - - // NotModified status is expected and can be handled gracefully - if (result === FileOperationResult.FILE_NOT_MODIFIED_SINCE) { - return WebviewFileReadResponse.NotModified; - } - } - - // Otherwise the error is unexpected. Re-throw and let caller handle it - throw e; - } -} - -export interface WebviewResourceFileReader { - readFileStream(resource: URI, etag: string | undefined): Promise; -} - export async function loadLocalResource( requestUri: URI, ifNoneMatch: string | undefined, @@ -100,16 +50,16 @@ export async function loadLocalResource( extensionLocation: URI | undefined; roots: ReadonlyArray; remoteConnectionData?: IRemoteConnectionData | null; - rewriteUri?: (uri: URI) => URI, + useRootAuthority?: boolean; }, - fileReader: WebviewResourceFileReader, + fileService: IFileService, requestService: IRequestService, logService: ILogService, token: CancellationToken, ): Promise { logService.debug(`loadLocalResource - being. requestUri=${requestUri}`); - let resourceToLoad = getResourceToLoad(requestUri, options.roots); + const resourceToLoad = getResourceToLoad(requestUri, options.roots, options.extensionLocation, options.useRootAuthority); logService.debug(`loadLocalResource - found resource to load. requestUri=${requestUri}, resourceToLoad=${resourceToLoad}`); @@ -119,11 +69,6 @@ export async function loadLocalResource( const mime = getWebviewContentMimeType(requestUri); // Use the original path for the mime - // Perform extra normalization if needed - if (options.rewriteUri) { - resourceToLoad = options.rewriteUri(resourceToLoad); - } - if (resourceToLoad.scheme === Schemas.http || resourceToLoad.scheme === Schemas.https) { const headers: IHeaders = {}; if (ifNoneMatch) { @@ -150,21 +95,19 @@ export async function loadLocalResource( } try { - const contents = await fileReader.readFileStream(resourceToLoad, ifNoneMatch); - logService.debug(`loadLocalResource - Loaded using fileReader. requestUri=${requestUri}`); - - switch (contents.type) { - case WebviewFileReadResponse.Type.Success: - return new WebviewResourceResponse.StreamSuccess(contents.stream, contents.etag, mime); - - case WebviewFileReadResponse.Type.NotModified: - return new WebviewResourceResponse.NotModified(mime); - - default: - logService.error(`loadLocalResource - Unknown file read response`); - return WebviewResourceResponse.Failed; - } + const result = await fileService.readFileStream(resourceToLoad, { etag: ifNoneMatch }); + return new WebviewResourceResponse.StreamSuccess(result.value, result.etag, mime); } catch (err) { + if (err instanceof FileOperationError) { + const result = err.fileOperationResult; + + // NotModified status is expected and can be handled gracefully + if (result === FileOperationResult.FILE_NOT_MODIFIED_SINCE) { + return new WebviewResourceResponse.NotModified(mime); + } + } + + // Otherwise the error is unexpected. logService.debug(`loadLocalResource - Error using fileReader. requestUri=${requestUri}`); console.log(err); @@ -174,41 +117,32 @@ export async function loadLocalResource( function getResourceToLoad( requestUri: URI, - roots: ReadonlyArray + roots: ReadonlyArray, + extensionLocation: URI | undefined, + useRootAuthority: boolean | undefined ): URI | undefined { - const normalizedPath = normalizeRequestPath(requestUri); - for (const root of roots) { - if (containsResource(root, normalizedPath)) { - return normalizedPath; + if (containsResource(root, requestUri)) { + return normalizeResourcePath(requestUri, extensionLocation, useRootAuthority ? root.authority : undefined); } } return undefined; } -function normalizeRequestPath(requestUri: URI) { - if (requestUri.scheme === Schemas.vscodeWebviewResource) { - // The `vscode-webview-resource` scheme has the following format: - // - // vscode-webview-resource://id/scheme//authority?/path - // - - // Encode requestUri.path so that URI.parse can properly parse special characters like '#', '?', etc. - const resourceUri = URI.parse(encodeURIComponent(requestUri.path).replace(/%2F/gi, '/').replace(/^\/([a-z0-9\-]+)(\/{1,2})/i, (_: string, scheme: string, sep: string) => { - if (sep.length === 1) { - return `${scheme}:///`; // Add empty authority. - } else { - return `${scheme}://`; // Url has own authority. - } - })); - return resourceUri.with({ - query: requestUri.query, - fragment: requestUri.fragment +function normalizeResourcePath(resource: URI, extensionLocation: URI | undefined, useRemoteAuthority: string | undefined): URI { + // If we are loading a file resource from a webview created by a remote extension, rewrite the uri to go remote + if (useRemoteAuthority || (resource.scheme === Schemas.file && extensionLocation?.scheme === Schemas.vscodeRemote)) { + return URI.from({ + scheme: Schemas.vscodeRemote, + authority: useRemoteAuthority ?? extensionLocation!.authority, + path: '/vscode-resource', + query: JSON.stringify({ + requestResourcePath: resource.path + }) }); - } else { - return requestUri; } + return resource; } function containsResource(root: URI, resource: URI): boolean { diff --git a/src/vs/workbench/contrib/webview/browser/webview.ts b/src/vs/workbench/contrib/webview/browser/webview.ts index 63b8303a..45dd8a4d 100644 --- a/src/vs/workbench/contrib/webview/browser/webview.ts +++ b/src/vs/workbench/contrib/webview/browser/webview.ts @@ -32,6 +32,11 @@ export interface IWebviewService { */ readonly activeWebview: Webview | undefined; + /** + * Fired when the currently focused webview changes. + */ + readonly onDidChangeActiveWebview: Event; + /** * Create a basic webview dom element. */ @@ -70,10 +75,12 @@ export interface WebviewOptions { readonly enableFindWidget?: boolean; readonly tryRestoreScrollPosition?: boolean; readonly retainContextWhenHidden?: boolean; + readonly serviceWorkerFetchIgnoreSubdomain?: boolean; transformCssVariables?(styles: Readonly): Readonly; } export interface WebviewContentOptions { + readonly useRootAuthority?: boolean; readonly allowMultipleAPIAcquire?: boolean; readonly allowScripts?: boolean; readonly localResourceRoots?: ReadonlyArray; @@ -101,6 +108,11 @@ export interface IDataLinkClickEvent { downloadName?: string; } +export interface WebviewMessageReceivedEvent { + readonly message: any; + readonly transfer?: readonly ArrayBuffer[]; +} + export interface Webview extends IDisposable { readonly id: string; @@ -123,10 +135,10 @@ export interface Webview extends IDisposable { readonly onDidWheel: Event; readonly onDidUpdateState: Event; readonly onDidReload: Event; - readonly onMessage: Event; + readonly onMessage: Event; readonly onMissingCsp: Event; - postMessage(data: any): void; + postMessage(message: any, transfer?: readonly ArrayBuffer[]): void; focus(): void; reload(): void; @@ -163,7 +175,5 @@ export interface WebviewOverlay extends Webview { claim(owner: any, scopedContextKeyService: IContextKeyService | undefined): void; release(owner: any): void; - getInnerWebview(): Webview | undefined; - layoutWebviewOverElement(element: HTMLElement, dimension?: Dimension): void; } diff --git a/src/vs/workbench/contrib/webview/browser/webviewElement.ts b/src/vs/workbench/contrib/webview/browser/webviewElement.ts index f228247c..5c447557 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewElement.ts @@ -4,12 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { addDisposableListener } from 'vs/base/browser/dom'; -import { ThrottledDelayer } from 'vs/base/common/async'; -import { streamToBuffer } from 'vs/base/common/buffer'; -import { CancellationToken } from 'vs/base/common/cancellation'; import { IDisposable } from 'vs/base/common/lifecycle'; -import { Schemas } from 'vs/base/common/network'; -import { URI } from 'vs/base/common/uri'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IFileService } from 'vs/platform/files/common/files'; import { ILogService } from 'vs/platform/log/common/log'; @@ -18,8 +13,6 @@ import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remot import { ITunnelService } from 'vs/platform/remote/common/tunnel'; import { IRequestService } from 'vs/platform/request/common/request'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { loadLocalResource, readFileStream, WebviewResourceResponse } from 'vs/platform/webview/common/resourceLoader'; -import { WebviewPortMappingManager } from 'vs/platform/webview/common/webviewPortMapping'; import { BaseWebview, WebviewMessageChannels } from 'vs/workbench/contrib/webview/browser/baseWebviewElement'; import { WebviewThemeDataProvider } from 'vs/workbench/contrib/webview/browser/themeing'; import { Webview, WebviewContentOptions, WebviewExtensionDescription, WebviewOptions } from 'vs/workbench/contrib/webview/browser/webview'; @@ -27,29 +20,34 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ export class IFrameWebview extends BaseWebview implements Webview { - private readonly _portMappingManager: WebviewPortMappingManager; private _confirmBeforeClose: string; - private readonly _focusDelayer = this._register(new ThrottledDelayer(10)); - private _elementFocusImpl!: (options?: FocusOptions | undefined) => void; - constructor( id: string, options: WebviewOptions, contentOptions: WebviewContentOptions, extension: WebviewExtensionDescription | undefined, webviewThemeDataProvider: WebviewThemeDataProvider, + @IConfigurationService configurationService: IConfigurationService, + @IFileService fileService: IFileService, + @ILogService logService: ILogService, @INotificationService notificationService: INotificationService, - @ITunnelService tunnelService: ITunnelService, - @IFileService private readonly fileService: IFileService, - @IRequestService private readonly requestService: IRequestService, + @IRemoteAuthorityResolverService remoteAuthorityResolverService: IRemoteAuthorityResolverService, + @IRequestService requestService: IRequestService, @ITelemetryService telemetryService: ITelemetryService, + @ITunnelService tunnelService: ITunnelService, @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, - @IConfigurationService private readonly _configurationService: IConfigurationService, - @IRemoteAuthorityResolverService private readonly _remoteAuthorityResolverService: IRemoteAuthorityResolverService, - @ILogService private readonly logService: ILogService, ) { - super(id, options, contentOptions, extension, webviewThemeDataProvider, notificationService, logService, telemetryService, environmentService); + super(id, options, contentOptions, extension, webviewThemeDataProvider, { + notificationService, + logService, + telemetryService, + environmentService, + requestService, + fileService, + tunnelService, + remoteAuthorityResolverService + }); /* __GDPR__ "webview.createWebview" : { @@ -62,28 +60,11 @@ export class IFrameWebview extends BaseWebview implements Web webviewElementType: 'iframe', }); - this._portMappingManager = this._register(new WebviewPortMappingManager( - () => this.extension?.location, - () => this.content.options.portMapping || [], - tunnelService - )); + this._confirmBeforeClose = configurationService.getValue('window.confirmBeforeClose'); - this._register(this.on(WebviewMessageChannels.loadResource, (entry: any) => { - const rawPath = entry.path; - const normalizedPath = decodeURIComponent(rawPath); - const uri = URI.parse(normalizedPath.replace(/^\/([\w\-]+)\/(.+)$/, (_, scheme, path) => scheme + ':/' + path)); - this.loadResource(entry.id, rawPath, uri, entry.ifNoneMatch); - })); - - this._register(this.on(WebviewMessageChannels.loadLocalhost, (entry: any) => { - this.localLocalhost(entry.id, entry.origin); - })); - - this._confirmBeforeClose = this._configurationService.getValue('window.confirmBeforeClose'); - - this._register(this._configurationService.onDidChangeConfiguration(e => { + this._register(configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration('window.confirmBeforeClose')) { - this._confirmBeforeClose = this._configurationService.getValue('window.confirmBeforeClose'); + this._confirmBeforeClose = configurationService.getValue('window.confirmBeforeClose'); this._send(WebviewMessageChannels.setConfirmBeforeClose, this._confirmBeforeClose); } })); @@ -102,7 +83,6 @@ export class IFrameWebview extends BaseWebview implements Web element.style.width = '100%'; element.style.height = '100%'; - this._elementFocusImpl = () => element.contentWindow?.focus(); element.focus = () => { this.doFocus(); }; @@ -110,59 +90,46 @@ export class IFrameWebview extends BaseWebview implements Web return element; } - protected initElement(extension: WebviewExtensionDescription | undefined, options: WebviewOptions) { + protected elementFocusImpl() { + this.element?.contentWindow?.focus(); + } + + protected initElement(extension: WebviewExtensionDescription | undefined, options: WebviewOptions, extraParams?: object) { const params = { id: this.id, - - // The extensionId and purpose in the URL are used for filtering in js-debug: - extensionId: extension?.id.value ?? '', + extensionId: extension?.id.value ?? '', // The extensionId and purpose in the URL are used for filtering in js-debug: purpose: options.purpose, + serviceWorkerFetchIgnoreSubdomain: options.serviceWorkerFetchIgnoreSubdomain, + ...extraParams } as const; const queryString = (Object.keys(params) as Array) - .map((key) => `${key}=${params[key]}`) + .map((key) => `${key}=${encodeURIComponent(params[key]!)}`) .join('&'); - this.element!.setAttribute('src', `${this.externalEndpoint}/index.html?${queryString}`); + this.element!.setAttribute('src', `${this.webviewContentEndpoint}/index.html?${queryString}`); } - private get externalEndpoint(): string { - const endpoint = this.environmentService.webviewExternalEndpoint!.replace('{{uuid}}', this.id); + protected get webviewContentEndpoint(): string { + const endpoint = this._environmentService.webviewExternalEndpoint!.replace('{{uuid}}', this.id); if (endpoint[endpoint.length - 1] === '/') { return endpoint.slice(0, endpoint.length - 1); } return endpoint; } + protected get webviewResourceEndpoint(): string { + return this.webviewContentEndpoint; + } + public mountTo(parent: HTMLElement) { if (this.element) { parent.appendChild(this.element); } } - public set html(value: string) { - super.html = this.preprocessHtml(value); - } - - protected preprocessHtml(value: string): string { - return value - .replace(/(["'])(?:vscode-resource):(\/\/([^\s\/'"]+?)(?=\/))?([^\s'"]+?)(["'])/gi, (match, startQuote, _1, scheme, path, endQuote) => { - if (scheme) { - return `${startQuote}${this.externalEndpoint}/vscode-resource/${scheme}${path}${endQuote}`; - } - return `${startQuote}${this.externalEndpoint}/vscode-resource/file${path}${endQuote}`; - }) - .replace(/(["'])(?:vscode-webview-resource):(\/\/[^\s\/'"]+\/([^\s\/'"]+?)(?=\/))?([^\s'"]+?)(["'])/gi, (match, startQuote, _1, scheme, path, endQuote) => { - if (scheme) { - return `${startQuote}${this.externalEndpoint}/vscode-resource/${scheme}${path}${endQuote}`; - } - return `${startQuote}${this.externalEndpoint}/vscode-resource/file${path}${endQuote}`; - }); - } - protected get extraContentOptions(): any { return { - endpoint: this.externalEndpoint, confirmBeforeClose: this._confirmBeforeClose, }; } @@ -179,92 +146,6 @@ export class IFrameWebview extends BaseWebview implements Web throw new Error('Method not implemented.'); } - private async loadResource(id: number, requestPath: string, uri: URI, ifNoneMatch: string | undefined) { - try { - const remoteAuthority = this.environmentService.remoteAuthority; - const remoteConnectionData = remoteAuthority ? this._remoteAuthorityResolverService.getConnectionData(remoteAuthority) : null; - const extensionLocation = this.extension?.location; - - // If we are loading a file resource from a remote extension, rewrite the uri to go remote - let rewriteUri: undefined | ((uri: URI) => URI); - if (extensionLocation?.scheme === Schemas.vscodeRemote) { - rewriteUri = (uri) => { - if (uri.scheme === Schemas.file && extensionLocation?.scheme === Schemas.vscodeRemote) { - return URI.from({ - scheme: Schemas.vscodeRemote, - authority: extensionLocation.authority, - path: '/vscode-resource', - query: JSON.stringify({ - requestResourcePath: uri.path - }) - }); - } - return uri; - }; - } - - const result = await loadLocalResource(uri, ifNoneMatch, { - extensionLocation: extensionLocation, - roots: this.content.options.localResourceRoots || [], - remoteConnectionData, - rewriteUri, - }, { - readFileStream: (resource, etag) => readFileStream(this.fileService, resource, etag), - }, this.requestService, this.logService, CancellationToken.None); - - switch (result.type) { - case WebviewResourceResponse.Type.Success: - { - const { buffer } = await streamToBuffer(result.stream); - return this._send('did-load-resource', { - id, - status: 200, - path: requestPath, - mime: result.mimeType, - data: buffer, - etag: result.etag, - }); - } - case WebviewResourceResponse.Type.NotModified: - { - return this._send('did-load-resource', { - id, - status: 304, // not modified - path: requestPath, - mime: result.mimeType, - }); - } - case WebviewResourceResponse.Type.AccessDenied: - { - return this._send('did-load-resource', { - id, - status: 401, // unauthorized - path: requestPath, - }); - } - } - } catch { - // noop - } - - return this._send('did-load-resource', { - id, - status: 404, - path: requestPath - }); - } - - private async localLocalhost(id: string, origin: string) { - const authority = this.environmentService.remoteAuthority; - const resolveAuthority = authority ? await this._remoteAuthorityResolverService.resolveAuthority(authority) : undefined; - const redirect = resolveAuthority ? await this._portMappingManager.getRedirect(resolveAuthority.authority, origin) : undefined; - return this._send('did-load-localhost', { - id, - origin, - location: redirect - }); - } - protected doPostMessage(channel: string, data?: any): void { if (this.element) { this.element.contentWindow!.postMessage({ channel, args: data }, '*'); @@ -281,51 +162,4 @@ export class IFrameWebview extends BaseWebview implements Web } }); } - - public focus(): void { - this.doFocus(); - - // Handle focus change programmatically (do not rely on event from ) - this.handleFocusChange(true); - } - - private doFocus() { - if (!this.element) { - return; - } - - // Clear the existing focus first if not already on the webview. - // This is required because the next part where we set the focus is async. - if (document.activeElement && document.activeElement instanceof HTMLElement && document.activeElement !== this.element) { - // Don't blur if on the webview because this will also happen async and may unset the focus - // after the focus trigger fires below. - document.activeElement.blur(); - } - - // Workaround for https://github.com/microsoft/vscode/issues/75209 - // Electron's webview.focus is async so for a sequence of actions such as: - // - // 1. Open webview - // 1. Show quick pick from command palette - // - // We end up focusing the webview after showing the quick pick, which causes - // the quick pick to instantly dismiss. - // - // Workaround this by debouncing the focus and making sure we are not focused on an input - // when we try to re-focus. - this._focusDelayer.trigger(async () => { - if (!this.isFocused || !this.element) { - return; - } - if (document.activeElement && document.activeElement?.tagName !== 'BODY') { - return; - } - try { - this._elementFocusImpl(); - } catch { - // noop - } - this._send('focus'); - }); - } } diff --git a/src/vs/workbench/contrib/webview/browser/webviewFindWidget.ts b/src/vs/workbench/contrib/webview/browser/webviewFindWidget.ts index ed69cb1a..109d1e54 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewFindWidget.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewFindWidget.ts @@ -40,7 +40,7 @@ export class WebviewFindWidget extends SimpleFindWidget { } } - public hide() { + public override hide() { super.hide(); this._delegate.stopFind(true); this._delegate.focus(); diff --git a/src/vs/workbench/contrib/webview/browser/webviewService.ts b/src/vs/workbench/contrib/webview/browser/webviewService.ts index 12695664..25e922e2 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewService.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewService.ts @@ -3,13 +3,15 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { Emitter } from 'vs/base/common/event'; +import { Disposable } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { WebviewThemeDataProvider } from 'vs/workbench/contrib/webview/browser/themeing'; import { IWebviewService, Webview, WebviewContentOptions, WebviewElement, WebviewExtensionDescription, WebviewOptions, WebviewOverlay } from 'vs/workbench/contrib/webview/browser/webview'; import { IFrameWebview } from 'vs/workbench/contrib/webview/browser/webviewElement'; import { DynamicWebviewEditorOverlay } from './dynamicWebviewEditorOverlay'; -export class WebviewService implements IWebviewService { +export class WebviewService extends Disposable implements IWebviewService { declare readonly _serviceBrand: undefined; protected readonly _webviewThemeDataProvider: WebviewThemeDataProvider; @@ -17,12 +19,24 @@ export class WebviewService implements IWebviewService { constructor( @IInstantiationService protected readonly _instantiationService: IInstantiationService, ) { + super(); this._webviewThemeDataProvider = this._instantiationService.createInstance(WebviewThemeDataProvider); } private _activeWebview?: Webview; + public get activeWebview() { return this._activeWebview; } + private updateActiveWebview(value: Webview | undefined) { + if (value !== this._activeWebview) { + this._activeWebview = value; + this._onDidChangeActiveWebview.fire(value); + } + } + + private readonly _onDidChangeActiveWebview = this._register(new Emitter()); + public readonly onDidChangeActiveWebview = this._onDidChangeActiveWebview.event; + createWebviewElement( id: string, options: WebviewOptions, @@ -47,12 +61,12 @@ export class WebviewService implements IWebviewService { protected addWebviewListeners(webview: Webview) { webview.onDidFocus(() => { - this._activeWebview = webview; + this.updateActiveWebview(webview); }); const onBlur = () => { if (this._activeWebview === webview) { - this._activeWebview = undefined; + this.updateActiveWebview(undefined); } }; diff --git a/src/vs/workbench/contrib/webview/electron-browser/pre/electron-index.js b/src/vs/workbench/contrib/webview/electron-browser/pre/electron-index.js index bac642ca..56cf7e50 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/pre/electron-index.js +++ b/src/vs/workbench/contrib/webview/electron-browser/pre/electron-index.js @@ -6,12 +6,10 @@ (function () { 'use strict'; - const ipcRenderer = require('electron').ipcRenderer; - - let isInDevelopmentMode = false; + const { ipcRenderer, contextBridge } = require('electron'); /** - * @type {import('../../browser/pre/main').WebviewHost} + * @type {import('../../browser/pre/main').WebviewHost & {isInDevelopmentMode: boolean}} */ const host = { onElectron: true, @@ -23,52 +21,19 @@ ipcRenderer.on(channel, handler); }, focusIframeOnCreate: true, - onIframeLoaded: (newFrame) => { - newFrame.contentWindow.onbeforeunload = () => { - if (isInDevelopmentMode) { // Allow reloads while developing a webview - host.postMessage('do-reload'); - return false; - } - // Block navigation when not in development mode - console.log('prevented webview navigation'); - return false; - }; - - // Electron 4 eats mouseup events from inside webviews - // https://github.com/microsoft/vscode/issues/75090 - // Try to fix this by rebroadcasting mouse moves and mouseups so that we can - // emulate these on the main window - let isMouseDown = false; - newFrame.contentWindow.addEventListener('mousedown', () => { - isMouseDown = true; - }); - - const tryDispatchSyntheticMouseEvent = (e) => { - if (!isMouseDown) { - host.postMessage('synthetic-mouse-event', { type: e.type, screenX: e.screenX, screenY: e.screenY, clientX: e.clientX, clientY: e.clientY }); - } - }; - newFrame.contentWindow.addEventListener('mouseup', e => { - tryDispatchSyntheticMouseEvent(e); - isMouseDown = false; - }); - newFrame.contentWindow.addEventListener('mousemove', tryDispatchSyntheticMouseEvent); - }, - rewriteCSP: (csp) => { - return csp.replace(/vscode-resource:(?=(\s|;|$))/g, 'vscode-webview-resource:'); - }, + isInDevelopmentMode: false }; host.onMessage('devtools-opened', () => { - isInDevelopmentMode = true; + host.isInDevelopmentMode = true; }); document.addEventListener('DOMContentLoaded', e => { // Forward messages from the embedded iframe - window.onmessage = (message) => { - ipcRenderer.sendToHost(message.data.command, message.data.data); + window.onmessage = (/** @type {MessageEvent} */ event) => { + ipcRenderer.sendToHost(event.data.command, event.data.data); }; }); - require('../../browser/pre/main')(host); + contextBridge.exposeInMainWorld('vscodeHost', host); }()); diff --git a/src/vs/workbench/contrib/webview/electron-browser/pre/index.html b/src/vs/workbench/contrib/webview/electron-browser/pre/index.html index 591caaf7..5281fd3a 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/pre/index.html +++ b/src/vs/workbench/contrib/webview/electron-browser/pre/index.html @@ -1,8 +1,49 @@ + -Virtual Document + Virtual Document + + + diff --git a/src/vs/workbench/contrib/webview/electron-browser/webviewCommands.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewCommands.ts index bef48981..6cc97f83 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webviewCommands.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewCommands.ts @@ -4,9 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import { WebviewTag } from 'electron'; -import { Action2 } from 'vs/platform/actions/common/actions'; import * as nls from 'vs/nls'; +import { Action2 } from 'vs/platform/actions/common/actions'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; import { CATEGORIES } from 'vs/workbench/common/actions'; export class OpenWebviewDeveloperToolsAction extends Action2 { @@ -21,13 +22,21 @@ export class OpenWebviewDeveloperToolsAction extends Action2 { } async run(accessor: ServicesAccessor): Promise { - const elements = document.querySelectorAll('webview.ready'); - for (let i = 0; i < elements.length; i++) { + const nativeHostService = accessor.get(INativeHostService); + + const webviewElements = document.querySelectorAll('webview.ready'); + for (const element of webviewElements) { try { - (elements.item(i) as WebviewTag).openDevTools(); + (element as WebviewTag).openDevTools(); } catch (e) { console.error(e); } } + + const iframeWebviewElements = document.querySelectorAll('iframe.webview.ready'); + if (iframeWebviewElements.length) { + console.info(nls.localize('iframeWebviewAlert', "Using standard dev tools to debug iframe based webview")); + nativeHostService.openDevTools(); + } } } diff --git a/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts index 88c35cb0..bb1ec9cf 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts @@ -5,25 +5,26 @@ import { FindInPageOptions, WebviewTag } from 'electron'; import { addDisposableListener } from 'vs/base/browser/dom'; -import { ThrottledDelayer } from 'vs/base/common/async'; import { Emitter, Event } from 'vs/base/common/event'; import { once } from 'vs/base/common/functional'; import { IDisposable } from 'vs/base/common/lifecycle'; import { FileAccess, Schemas } from 'vs/base/common/network'; -import { URI } from 'vs/base/common/uri'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IFileService } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/services'; import { ILogService } from 'vs/platform/log/common/log'; import { INotificationService } from 'vs/platform/notification/common/notification'; +import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver'; +import { ITunnelService } from 'vs/platform/remote/common/tunnel'; +import { IRequestService } from 'vs/platform/request/common/request'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { webviewPartitionId } from 'vs/platform/webview/common/resourceLoader'; +import { webviewPartitionId } from 'vs/platform/webview/common/webviewManagerService'; import { BaseWebview, WebviewMessageChannels } from 'vs/workbench/contrib/webview/browser/baseWebviewElement'; import { WebviewThemeDataProvider } from 'vs/workbench/contrib/webview/browser/themeing'; import { Webview, WebviewContentOptions, WebviewExtensionDescription, WebviewOptions } from 'vs/workbench/contrib/webview/browser/webview'; import { WebviewFindDelegate, WebviewFindWidget } from 'vs/workbench/contrib/webview/browser/webviewFindWidget'; import { WebviewIgnoreMenuShortcutsManager } from 'vs/workbench/contrib/webview/electron-browser/webviewIgnoreMenuShortcutsManager'; -import { rewriteVsCodeResourceUrls, WebviewResourceRequestManager } from 'vs/workbench/contrib/webview/electron-sandbox/resourceLoading'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; export class ElectronWebviewBasedWebview extends BaseWebview implements Webview, WebviewFindDelegate { @@ -43,14 +44,6 @@ export class ElectronWebviewBasedWebview extends BaseWebview impleme private _webviewFindWidget: WebviewFindWidget | undefined; private _findStarted: boolean = false; - private readonly _resourceRequestManager: WebviewResourceRequestManager; - - private readonly _focusDelayer = this._register(new ThrottledDelayer(10)); - private _elementFocusImpl!: (options?: FocusOptions | undefined) => void; - - private _isWebviewReadyForMessages = false; - private readonly _pendingMessages: Array<{ channel: string, data: any }> = []; - constructor( id: string, options: WebviewOptions, @@ -64,8 +57,21 @@ export class ElectronWebviewBasedWebview extends BaseWebview impleme @IConfigurationService configurationService: IConfigurationService, @IMainProcessService mainProcessService: IMainProcessService, @INotificationService notificationService: INotificationService, + @IFileService fileService: IFileService, + @IRequestService requestService: IRequestService, + @ITunnelService tunnelService: ITunnelService, + @IRemoteAuthorityResolverService remoteAuthorityResolverService: IRemoteAuthorityResolverService, ) { - super(id, options, contentOptions, extension, _webviewThemeDataProvider, notificationService, _myLogService, telemetryService, environmentService); + super(id, options, contentOptions, extension, _webviewThemeDataProvider, { + notificationService, + logService: _myLogService, + telemetryService, + environmentService, + fileService, + requestService, + tunnelService, + remoteAuthorityResolverService + }); /* __GDPR__ "webview.createWebview" : { @@ -82,18 +88,6 @@ export class ElectronWebviewBasedWebview extends BaseWebview impleme this._myLogService.debug(`Webview(${this.id}): init`); - this._resourceRequestManager = this._register(instantiationService.createInstance(WebviewResourceRequestManager, id, extension, this.content.options)); - this._resourceRequestManager.ensureReady() - .then(() => { - this._isWebviewReadyForMessages = true; - - while (this._pendingMessages.length) { - const { channel, data } = this._pendingMessages.shift()!; - this._myLogService.debug(`Webview(${this.id}): did post message on '${channel}'`); - this.element?.send(channel, data); - } - }); - this._register(addDisposableListener(this.element!, 'dom-ready', once(() => { this._register(ElectronWebviewBasedWebview.getWebviewKeyboardHandler(configurationService, mainProcessService).add(this.element!)); }))); @@ -162,7 +156,7 @@ export class ElectronWebviewBasedWebview extends BaseWebview impleme // and not the `vscode-file` URI because preload scripts are loaded // via node.js from the main side and only allow `file:` protocol this.element!.preload = FileAccess.asFileUri('./pre/electron-index.js', require).toString(true); - this.element!.src = `${Schemas.vscodeWebview}://${this.id}/electron-browser/index.html?platform=electron`; + this.element!.src = `${Schemas.vscodeWebview}://${this.id}/electron-browser-index.html?platform=electron&id=${this.id}&vscode-resource-origin=${encodeURIComponent(this.webviewResourceEndpoint)}`; } protected createElement(options: WebviewOptions) { @@ -170,7 +164,6 @@ export class ElectronWebviewBasedWebview extends BaseWebview impleme // Wait the end of the ctor when all listeners have been hooked up. const element = document.createElement('webview'); - this._elementFocusImpl = element.focus.bind(element); element.focus = () => { this.doFocus(); }; @@ -187,28 +180,21 @@ export class ElectronWebviewBasedWebview extends BaseWebview impleme return element; } - public set contentOptions(options: WebviewContentOptions) { + protected elementFocusImpl() { + this.element?.focus(); + } + + public override set contentOptions(options: WebviewContentOptions) { this._myLogService.debug(`Webview(${this.id}): will set content options`); - this._resourceRequestManager.update(options); super.contentOptions = options; } - public set localResourcesRoot(resources: URI[]) { - this._resourceRequestManager.update({ - ...this.contentOptions, - localResourceRoots: resources, - }); - super.localResourcesRoot = resources; + protected override get webviewResourceEndpoint(): string { + return `https://${this.id}.vscode-webview-test.com`; } protected readonly extraContentOptions = {}; - public set html(value: string) { - this._myLogService.debug(`Webview(${this.id}): will set html`); - - super.html = rewriteVsCodeResourceUrls(this.id, value); - } - public mountTo(parent: HTMLElement) { if (!this.element) { return; @@ -221,64 +207,11 @@ export class ElectronWebviewBasedWebview extends BaseWebview impleme } protected async doPostMessage(channel: string, data?: any): Promise { - this._myLogService.debug(`Webview(${this.id}): will post message on '${channel}'`); - if (!this._isWebviewReadyForMessages) { - this._pendingMessages.push({ channel, data }); - return; - } - this._myLogService.debug(`Webview(${this.id}): did post message on '${channel}'`); this.element?.send(channel, data); } - public focus(): void { - this.doFocus(); - - // Handle focus change programmatically (do not rely on event from ) - this.handleFocusChange(true); - } - - private doFocus() { - if (!this.element) { - return; - } - - // Clear the existing focus first if not already on the webview. - // This is required because the next part where we set the focus is async. - if (document.activeElement && document.activeElement instanceof HTMLElement && document.activeElement !== this.element) { - // Don't blur if on the webview because this will also happen async and may unset the focus - // after the focus trigger fires below. - document.activeElement.blur(); - } - - // Workaround for https://github.com/microsoft/vscode/issues/75209 - // Electron's webview.focus is async so for a sequence of actions such as: - // - // 1. Open webview - // 1. Show quick pick from command palette - // - // We end up focusing the webview after showing the quick pick, which causes - // the quick pick to instantly dismiss. - // - // Workaround this by debouncing the focus and making sure we are not focused on an input - // when we try to re-focus. - this._focusDelayer.trigger(async () => { - if (!this.isFocused || !this.element) { - return; - } - if (document.activeElement && document.activeElement?.tagName !== 'BODY') { - return; - } - try { - this._elementFocusImpl(); - } catch { - // noop - } - this._send('focus'); - }); - } - - protected style(): void { + protected override style(): void { super.style(); this.styledFindWidget(); } @@ -301,9 +234,8 @@ export class ElectronWebviewBasedWebview extends BaseWebview impleme // FindNext must be false for a first request const findOptions: FindInPageOptions = { forward: options.forward, - findNext: false, - matchCase: options.matchCase, - medialCapitalAsWordStart: options.medialCapitalAsWordStart + findNext: true, + matchCase: options.matchCase }; this._findStarted = true; @@ -327,7 +259,7 @@ export class ElectronWebviewBasedWebview extends BaseWebview impleme return; } - const options = { findNext: true, forward: !previous }; + const options = { findNext: false, forward: !previous }; if (!this._findStarted) { this.startFind(value, options); return; @@ -357,31 +289,31 @@ export class ElectronWebviewBasedWebview extends BaseWebview impleme this._webviewFindWidget?.find(previous); } - public selectAll() { + public override selectAll() { this.element?.selectAll(); } - public copy() { + public override copy() { this.element?.copy(); } - public paste() { + public override paste() { this.element?.paste(); } - public cut() { + public override cut() { this.element?.cut(); } - public undo() { + public override undo() { this.element?.undo(); } - public redo() { + public override redo() { this.element?.redo(); } - protected on(channel: WebviewMessageChannels | string, handler: (data: T) => void): IDisposable { + protected override on(channel: WebviewMessageChannels | string, handler: (data: T) => void): IDisposable { if (!this.element) { throw new Error('Cannot add event listener. No webview element found.'); } diff --git a/src/vs/workbench/contrib/webview/electron-browser/webviewService.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewService.ts index 4c2a4403..9d839838 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webviewService.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewService.ts @@ -12,7 +12,6 @@ import { ElectronIframeWebview } from 'vs/workbench/contrib/webview/electron-san import { ElectronWebviewBasedWebview } from 'vs/workbench/contrib/webview/electron-browser/webviewElement'; export class ElectronWebviewService extends WebviewService { - declare readonly _serviceBrand: undefined; constructor( @IInstantiationService instantiationService: IInstantiationService, @@ -21,19 +20,19 @@ export class ElectronWebviewService extends WebviewService { super(instantiationService); } - createWebviewElement( + override createWebviewElement( id: string, options: WebviewOptions, contentOptions: WebviewContentOptions, extension: WebviewExtensionDescription | undefined, ): WebviewElement { - const useIframes = this._configService.getValue('webview.experimental.useIframes'); + const useIframes = this._configService.getValue('webview.experimental.useIframes') ?? !options.enableFindWidget; const webview = this._instantiationService.createInstance(useIframes ? ElectronIframeWebview : ElectronWebviewBasedWebview, id, options, contentOptions, extension, this._webviewThemeDataProvider); this.addWebviewListeners(webview); return webview; } - createWebviewOverlay( + override createWebviewOverlay( id: string, options: WebviewOptions, contentOptions: WebviewContentOptions, diff --git a/src/vs/workbench/contrib/webview/electron-sandbox/iframeWebviewElement.ts b/src/vs/workbench/contrib/webview/electron-sandbox/iframeWebviewElement.ts index 835db2a3..42496232 100644 --- a/src/vs/workbench/contrib/webview/electron-sandbox/iframeWebviewElement.ts +++ b/src/vs/workbench/contrib/webview/electron-sandbox/iframeWebviewElement.ts @@ -3,11 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Schemas } from 'vs/base/common/network'; -import { URI } from 'vs/base/common/uri'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IFileService } from 'vs/platform/files/common/files'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/services'; import { ILogService } from 'vs/platform/log/common/log'; import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; @@ -20,7 +17,6 @@ import { WebviewMessageChannels } from 'vs/workbench/contrib/webview/browser/bas import { WebviewThemeDataProvider } from 'vs/workbench/contrib/webview/browser/themeing'; import { WebviewContentOptions, WebviewExtensionDescription, WebviewOptions } from 'vs/workbench/contrib/webview/browser/webview'; import { IFrameWebview } from 'vs/workbench/contrib/webview/browser/webviewElement'; -import { rewriteVsCodeResourceUrls, WebviewResourceRequestManager } from 'vs/workbench/contrib/webview/electron-sandbox/resourceLoading'; import { WindowIgnoreMenuShortcutsManager } from 'vs/workbench/contrib/webview/electron-sandbox/windowIgnoreMenuShortcutsManager'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -29,11 +25,6 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ */ export class ElectronIframeWebview extends IFrameWebview { - private readonly _resourceRequestManager: WebviewResourceRequestManager; - - private _isWebviewReadyForMessages = false; - private readonly _pendingMessages: Array<{ channel: string, data: any }> = []; - private readonly _webviewKeyboardHandler: WindowIgnoreMenuShortcutsManager; constructor( @@ -49,25 +40,13 @@ export class ElectronIframeWebview extends IFrameWebview { @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, @IRemoteAuthorityResolverService _remoteAuthorityResolverService: IRemoteAuthorityResolverService, @ILogService logService: ILogService, - @IInstantiationService instantiationService: IInstantiationService, @IConfigurationService configurationService: IConfigurationService, @IMainProcessService mainProcessService: IMainProcessService, @INotificationService noficationService: INotificationService, @INativeHostService nativeHostService: INativeHostService, ) { super(id, options, contentOptions, extension, webviewThemeDataProvider, - noficationService, tunnelService, fileService, requestService, telemetryService, environmentService, configurationService, _remoteAuthorityResolverService, logService); - - this._resourceRequestManager = this._register(instantiationService.createInstance(WebviewResourceRequestManager, id, extension, this.content.options)); - this._resourceRequestManager.ensureReady() - .then(() => { - this._isWebviewReadyForMessages = true; - - while (this._pendingMessages.length) { - const { channel, data } = this._pendingMessages.shift()!; - this.element?.contentWindow!.postMessage({ channel, args: data }, '*'); - } - }); + configurationService, fileService, logService, noficationService, _remoteAuthorityResolverService, requestService, telemetryService, tunnelService, environmentService); this._webviewKeyboardHandler = new WindowIgnoreMenuShortcutsManager(configurationService, mainProcessService, nativeHostService); @@ -80,38 +59,27 @@ export class ElectronIframeWebview extends IFrameWebview { })); } - protected initElement(extension: WebviewExtensionDescription | undefined, options: WebviewOptions) { - // The extensionId and purpose in the URL are used for filtering in js-debug: - this.element!.setAttribute('src', `${Schemas.vscodeWebview}://${this.id}/index.html?id=${this.id}&platform=electron&extensionId=${extension?.id.value ?? ''}&purpose=${options.purpose}`); - } - - public set contentOptions(options: WebviewContentOptions) { - this._resourceRequestManager.update(options); - super.contentOptions = options; - } - - public set localResourcesRoot(resources: URI[]) { - this._resourceRequestManager.update({ - ...this.contentOptions, - localResourceRoots: resources, + protected override initElement(extension: WebviewExtensionDescription | undefined, options: WebviewOptions) { + super.initElement(extension, options, { + platform: 'electron', + 'vscode-resource-origin': this.webviewResourceEndpoint, }); - super.localResourcesRoot = resources; } - protected get extraContentOptions() { - return {}; - } - - protected async doPostMessage(channel: string, data?: any): Promise { - if (!this._isWebviewReadyForMessages) { - this._pendingMessages.push({ channel, data }); - return; + protected override get webviewContentEndpoint(): string { + const endpoint = this._environmentService.webviewExternalEndpoint!.replace('{{uuid}}', this.id); + if (endpoint[endpoint.length - 1] === '/') { + return endpoint.slice(0, endpoint.length - 1); } + return endpoint; + } + protected override get webviewResourceEndpoint(): string { + return `https://${this.id}.vscode-webview-test.com`; + } + + protected override async doPostMessage(channel: string, data?: any): Promise { this.element?.contentWindow!.postMessage({ channel, args: data }, '*'); } - protected preprocessHtml(value: string): string { - return rewriteVsCodeResourceUrls(this.id, value); - } } diff --git a/src/vs/workbench/contrib/webview/electron-sandbox/resourceLoading.ts b/src/vs/workbench/contrib/webview/electron-sandbox/resourceLoading.ts deleted file mode 100644 index 1c7d7155..00000000 --- a/src/vs/workbench/contrib/webview/electron-sandbox/resourceLoading.ts +++ /dev/null @@ -1,178 +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 { equals } from 'vs/base/common/arrays'; -import { streamToBuffer } from 'vs/base/common/buffer'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; -import { Schemas } from 'vs/base/common/network'; -import { URI, UriComponents } from 'vs/base/common/uri'; -import { ProxyChannel } from 'vs/base/parts/ipc/common/ipc'; -import { ipcRenderer } from 'vs/base/parts/sandbox/electron-sandbox/globals'; -import { IFileService } from 'vs/platform/files/common/files'; -import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/services'; -import { ILogService } from 'vs/platform/log/common/log'; -import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; -import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver'; -import { IRequestService } from 'vs/platform/request/common/request'; -import { loadLocalResource, readFileStream, WebviewResourceResponse } from 'vs/platform/webview/common/resourceLoader'; -import { IWebviewManagerService } from 'vs/platform/webview/common/webviewManagerService'; -import { IWebviewPortMapping } from 'vs/platform/webview/common/webviewPortMapping'; -import { WebviewContentOptions, WebviewExtensionDescription } from 'vs/workbench/contrib/webview/browser/webview'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; - -/** - * Try to rewrite `vscode-resource:` urls in html - */ -export function rewriteVsCodeResourceUrls( - id: string, - html: string, -): string { - return html - .replace(/(["'])vscode-resource:(\/\/([^\s\/'"]+?)(?=\/))?([^\s'"]+?)(["'])/gi, (_match, startQuote, _1, scheme, path, endQuote) => { - if (scheme) { - return `${startQuote}${Schemas.vscodeWebviewResource}://${id}/${scheme}${path}${endQuote}`; - } - if (!path.startsWith('//')) { - // Add an empty authority if we don't already have one - path = '//' + path; - } - return `${startQuote}${Schemas.vscodeWebviewResource}://${id}/file${path}${endQuote}`; - }); -} - -/** - * Manages the loading of resources inside of a webview. - */ -export class WebviewResourceRequestManager extends Disposable { - - private readonly _webviewManagerService: IWebviewManagerService; - - private _localResourceRoots: ReadonlyArray; - private _portMappings: ReadonlyArray; - - private _ready: Promise; - - constructor( - private readonly id: string, - private readonly extension: WebviewExtensionDescription | undefined, - initialContentOptions: WebviewContentOptions, - @ILogService private readonly _logService: ILogService, - @IRemoteAuthorityResolverService remoteAuthorityResolverService: IRemoteAuthorityResolverService, - @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, - @IMainProcessService mainProcessService: IMainProcessService, - @INativeHostService nativeHostService: INativeHostService, - @IFileService fileService: IFileService, - @IRequestService requestService: IRequestService, - ) { - super(); - - this._logService.debug(`WebviewResourceRequestManager(${this.id}): init`); - - this._webviewManagerService = ProxyChannel.toService(mainProcessService.getChannel('webview')); - - this._localResourceRoots = initialContentOptions.localResourceRoots || []; - this._portMappings = initialContentOptions.portMapping || []; - - const remoteAuthority = environmentService.remoteAuthority; - const remoteConnectionData = remoteAuthority ? remoteAuthorityResolverService.getConnectionData(remoteAuthority) : null; - - this._logService.debug(`WebviewResourceRequestManager(${this.id}): did-start-loading`); - - this._ready = this._webviewManagerService.registerWebview(this.id, nativeHostService.windowId, { - extensionLocation: this.extension?.location.toJSON(), - localResourceRoots: this._localResourceRoots.map(x => x.toJSON()), - remoteConnectionData: remoteConnectionData, - portMappings: this._portMappings, - }).then(() => { - this._logService.debug(`WebviewResourceRequestManager(${this.id}): did register`); - }); - - if (remoteAuthority) { - this._register(remoteAuthorityResolverService.onDidChangeConnectionData(() => { - const update = this._webviewManagerService.updateWebviewMetadata(this.id, { - remoteConnectionData: remoteAuthority ? remoteAuthorityResolverService.getConnectionData(remoteAuthority) : null, - }); - this._ready = this._ready.then(() => update); - })); - } - - this._register(toDisposable(() => this._webviewManagerService.unregisterWebview(this.id))); - - const loadResourceChannel = `vscode:loadWebviewResource-${id}`; - const loadResourceListener = async (_event: any, requestId: number, resource: UriComponents, ifNoneMatch: string | undefined) => { - const uri = URI.revive(resource); - try { - this._logService.debug(`WebviewResourceRequestManager(${this.id}): starting resource load. uri: ${uri}`); - - const response = await loadLocalResource(uri, ifNoneMatch, { - extensionLocation: this.extension?.location, - roots: this._localResourceRoots, - remoteConnectionData: remoteConnectionData, - }, { - readFileStream: (resource, etag) => readFileStream(fileService, resource, etag), - }, requestService, this._logService, CancellationToken.None); - - this._logService.debug(`WebviewResourceRequestManager(${this.id}): finished resource load. uri: ${uri}, type=${response.type}`); - - switch (response.type) { - case WebviewResourceResponse.Type.Success: - { - const buffer = await streamToBuffer(response.stream); - return this._webviewManagerService.didLoadResource(requestId, buffer, { etag: response.etag }); - } - case WebviewResourceResponse.Type.NotModified: - return this._webviewManagerService.didLoadResource(requestId, 'not-modified'); - - case WebviewResourceResponse.Type.AccessDenied: - return this._webviewManagerService.didLoadResource(requestId, 'access-denied'); - } - } catch { - // Noop - } - this._webviewManagerService.didLoadResource(requestId, 'not-found'); - }; - - ipcRenderer.on(loadResourceChannel, loadResourceListener); - this._register(toDisposable(() => ipcRenderer.removeListener(loadResourceChannel, loadResourceListener))); - } - - public update(options: WebviewContentOptions) { - const localResourceRoots = options.localResourceRoots || []; - const portMappings = options.portMapping || []; - - if (!this.needsUpdate(localResourceRoots, portMappings)) { - return; - } - - this._localResourceRoots = localResourceRoots; - this._portMappings = portMappings; - - this._logService.debug(`WebviewResourceRequestManager(${this.id}): will update`); - - const update = this._webviewManagerService.updateWebviewMetadata(this.id, { - localResourceRoots: localResourceRoots.map(x => x.toJSON()), - portMappings: portMappings, - }).then(() => { - this._logService.debug(`WebviewResourceRequestManager(${this.id}): did update`); - }); - - this._ready = this._ready.then(() => update); - } - - private needsUpdate( - localResourceRoots: readonly URI[], - portMappings: readonly IWebviewPortMapping[], - ): boolean { - return !( - equals(this._localResourceRoots, localResourceRoots, (a, b) => a.toString() === b.toString()) - && equals(this._portMappings, portMappings, (a, b) => a.extensionHostPort === b.extensionHostPort && a.webviewPort === b.webviewPort) - ); - } - - public ensureReady(): Promise { - return this._ready; - } -} diff --git a/src/vs/workbench/contrib/webviewPanel/browser/webviewEditor.ts b/src/vs/workbench/contrib/webviewPanel/browser/webviewEditor.ts index f4b7e44c..af616fb2 100644 --- a/src/vs/workbench/contrib/webviewPanel/browser/webviewEditor.ts +++ b/src/vs/workbench/contrib/webviewPanel/browser/webviewEditor.ts @@ -37,7 +37,7 @@ export class WebviewEditor extends EditorPane { private readonly _onFocusWindowHandler = this._register(new MutableDisposable()); private readonly _onDidFocusWebview = this._register(new Emitter()); - public get onDidFocus(): Event { return this._onDidFocusWebview.event; } + public override get onDidFocus(): Event { return this._onDidFocusWebview.event; } private readonly _scopedContextKeyService = this._register(new MutableDisposable()); @@ -58,7 +58,7 @@ export class WebviewEditor extends EditorPane { return this.input instanceof WebviewInput ? this.input.webview : undefined; } - get scopedContextKeyService(): IContextKeyService | undefined { + override get scopedContextKeyService(): IContextKeyService | undefined { return this._scopedContextKeyService.value; } @@ -71,7 +71,7 @@ export class WebviewEditor extends EditorPane { this._scopedContextKeyService.value = this._contextKeyService.createScoped(element); } - public dispose(): void { + public override dispose(): void { this._isDisposed = true; this._element?.remove(); @@ -87,7 +87,7 @@ export class WebviewEditor extends EditorPane { } } - public focus(): void { + public override focus(): void { super.focus(); if (!this._onFocusWindowHandler.value && !isWeb) { // Make sure we restore focus when switching back to a VS Code window @@ -100,7 +100,7 @@ export class WebviewEditor extends EditorPane { this.webview?.focus(); } - protected setEditorVisible(visible: boolean, group: IEditorGroup | undefined): void { + protected override setEditorVisible(visible: boolean, group: IEditorGroup | undefined): void { this._visible = visible; if (this.input instanceof WebviewInput && this.webview) { if (visible) { @@ -112,7 +112,7 @@ export class WebviewEditor extends EditorPane { super.setEditorVisible(visible, group); } - public clearInput() { + public override clearInput() { if (this.webview) { this.webview.release(this); this._webviewVisibleDisposables.clear(); @@ -121,7 +121,7 @@ export class WebviewEditor extends EditorPane { super.clearInput(); } - public async setInput(input: EditorInput, options: EditorOptions, context: IEditorOpenContext, token: CancellationToken): Promise { + public override async setInput(input: EditorInput, options: EditorOptions, context: IEditorOpenContext, token: CancellationToken): Promise { if (input.matches(this.input)) { return; } @@ -164,7 +164,7 @@ export class WebviewEditor extends EditorPane { // Webviews are not part of the normal editor dom, so we have to register our own drag and drop handler on them. this._webviewVisibleDisposables.add(this._editorDropService.createEditorDropTarget(input.webview.container, { - containsGroup: (group) => this.group?.id === group.group.id + containsGroup: (group) => this.group?.id === group.id })); this._webviewVisibleDisposables.add(new WebviewWindowDragMonitor(() => this.webview)); diff --git a/src/vs/workbench/contrib/webviewPanel/browser/webviewEditorInput.ts b/src/vs/workbench/contrib/webviewPanel/browser/webviewEditorInput.ts index ff6904c3..fe15fcef 100644 --- a/src/vs/workbench/contrib/webviewPanel/browser/webviewEditorInput.ts +++ b/src/vs/workbench/contrib/webviewPanel/browser/webviewEditorInput.ts @@ -13,6 +13,10 @@ export class WebviewInput extends EditorInput { public static typeId = 'workbench.editors.webviewInput'; + public override get typeId(): string { + return WebviewInput.typeId; + } + private _name: string; private _iconPath?: WebviewIcons; private _group?: GroupIdentifier; @@ -40,7 +44,7 @@ export class WebviewInput extends EditorInput { this._webview = webview; } - dispose() { + override dispose() { if (!this.isDisposed()) { if (!this._hasTransfered) { this._webview?.dispose(); @@ -49,19 +53,15 @@ export class WebviewInput extends EditorInput { super.dispose(); } - public getTypeId(): string { - return WebviewInput.typeId; - } - - public getName(): string { + public override getName(): string { return this._name; } - public getTitle(_verbosity?: Verbosity): string { + public override getTitle(_verbosity?: Verbosity): string { return this.getName(); } - public getDescription(): string | undefined { + public override getDescription(): string | undefined { return undefined; } @@ -87,7 +87,7 @@ export class WebviewInput extends EditorInput { this._iconManager.setIcons(this.id, value); } - public matches(other: IEditorInput): boolean { + public override matches(other: IEditorInput): boolean { return other === this; } @@ -99,7 +99,7 @@ export class WebviewInput extends EditorInput { this._group = group; } - public supportsSplitEditor() { + public override canSplit() { return false; } diff --git a/src/vs/workbench/contrib/webviewPanel/browser/webviewEditorInputFactory.ts b/src/vs/workbench/contrib/webviewPanel/browser/webviewEditorInputSerializer.ts similarity index 97% rename from src/vs/workbench/contrib/webviewPanel/browser/webviewEditorInputFactory.ts rename to src/vs/workbench/contrib/webviewPanel/browser/webviewEditorInputSerializer.ts index 91a9fb96..67f22698 100644 --- a/src/vs/workbench/contrib/webviewPanel/browser/webviewEditorInputFactory.ts +++ b/src/vs/workbench/contrib/webviewPanel/browser/webviewEditorInputSerializer.ts @@ -6,7 +6,7 @@ import { URI, UriComponents } from 'vs/base/common/uri'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IEditorInputFactory } from 'vs/workbench/common/editor'; +import { IEditorInputSerializer } from 'vs/workbench/common/editor'; import { WebviewContentOptions, WebviewExtensionDescription, WebviewOptions } from 'vs/workbench/contrib/webview/browser/webview'; import { WebviewIcons } from 'vs/workbench/contrib/webviewPanel/browser/webviewIconManager'; import { WebviewInput } from './webviewEditorInput'; @@ -43,7 +43,7 @@ export interface DeserializedWebview { readonly group?: number; } -export class WebviewEditorInputFactory implements IEditorInputFactory { +export class WebviewEditorInputSerializer implements IEditorInputSerializer { public static readonly ID = WebviewInput.typeId; diff --git a/src/vs/workbench/contrib/webviewPanel/browser/webviewPanel.contribution.ts b/src/vs/workbench/contrib/webviewPanel/browser/webviewPanel.contribution.ts index 81a8d4e5..3340d423 100644 --- a/src/vs/workbench/contrib/webviewPanel/browser/webviewPanel.contribution.ts +++ b/src/vs/workbench/contrib/webviewPanel/browser/webviewPanel.contribution.ts @@ -9,16 +9,16 @@ import { EditorOverride, ITextEditorOptions } from 'vs/platform/editor/common/ed import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { Registry } from 'vs/platform/registry/common/platform'; -import { EditorDescriptor, Extensions as EditorExtensions, IEditorRegistry } from 'vs/workbench/browser/editor'; +import { EditorDescriptor, IEditorRegistry } from 'vs/workbench/browser/editor'; import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; -import { Extensions as EditorInputExtensions, IEditorInput, IEditorInputFactoryRegistry } from 'vs/workbench/common/editor'; +import { EditorExtensions, IEditorInput, IEditorInputFactoryRegistry } from 'vs/workbench/common/editor'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService, IOpenEditorOverride } from 'vs/workbench/services/editor/common/editorService'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { HideWebViewEditorFindCommand, ReloadWebviewAction, ShowWebViewEditorFindWidgetAction, WebViewEditorFindNextCommand, WebViewEditorFindPreviousCommand } from './webviewCommands'; import { WebviewEditor } from './webviewEditor'; import { WebviewInput } from './webviewEditorInput'; -import { WebviewEditorInputFactory } from './webviewEditorInputFactory'; +import { WebviewEditorInputSerializer } from './webviewEditorInputSerializer'; import { IWebviewWorkbenchService, WebviewEditorService } from './webviewWorkbenchService'; (Registry.as(EditorExtensions.Editors)).registerEditor(EditorDescriptor.create( @@ -42,18 +42,18 @@ class WebviewPanelContribution implements IWorkbenchContribution { options: ITextEditorOptions | undefined, group: IEditorGroup ): IOpenEditorOverride | undefined { - if (!(editor instanceof WebviewInput) || editor.getTypeId() !== WebviewInput.typeId) { + if (!(editor instanceof WebviewInput) || editor.typeId !== WebviewInput.typeId) { return undefined; } - if (group.isOpened(editor)) { + if (group.contains(editor)) { return undefined; } let previousGroup: IEditorGroup | undefined; const groups = this.editorGroupService.groups; for (const group of groups) { - if (group.isOpened(editor)) { + if (group.contains(editor)) { previousGroup = group; break; } @@ -74,9 +74,9 @@ class WebviewPanelContribution implements IWorkbenchContribution { const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); workbenchContributionsRegistry.registerWorkbenchContribution(WebviewPanelContribution, LifecyclePhase.Starting); -Registry.as(EditorInputExtensions.EditorInputFactories).registerEditorInputFactory( - WebviewEditorInputFactory.ID, - WebviewEditorInputFactory); +Registry.as(EditorExtensions.EditorInputFactories).registerEditorInputSerializer( + WebviewEditorInputSerializer.ID, + WebviewEditorInputSerializer); registerSingleton(IWebviewWorkbenchService, WebviewEditorService, true); diff --git a/src/vs/workbench/contrib/webviewPanel/browser/webviewWorkbenchService.ts b/src/vs/workbench/contrib/webviewPanel/browser/webviewWorkbenchService.ts index 8e776fc3..2655d89c 100644 --- a/src/vs/workbench/contrib/webviewPanel/browser/webviewWorkbenchService.ts +++ b/src/vs/workbench/contrib/webviewPanel/browser/webviewWorkbenchService.ts @@ -7,11 +7,13 @@ import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { memoize } from 'vs/base/common/decorators'; import { isPromiseCanceledError } from 'vs/base/common/errors'; +import { Event, Emitter } from 'vs/base/common/event'; import { Iterable } from 'vs/base/common/iterator'; import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { EditorActivation } from 'vs/platform/editor/common/editor'; import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { GroupIdentifier } from 'vs/workbench/common/editor'; +import { GroupIdentifier, IEditorInput } from 'vs/workbench/common/editor'; +import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { IWebviewService, WebviewContentOptions, WebviewExtensionDescription, WebviewOptions, WebviewOverlay } from 'vs/workbench/contrib/webview/browser/webview'; import { WebviewIconManager, WebviewIcons } from 'vs/workbench/contrib/webviewPanel/browser/webviewIconManager'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; @@ -69,6 +71,8 @@ export interface IWebviewWorkbenchService { resolveWebview( webview: WebviewInput, ): CancelablePromise; + + readonly onDidChangeActiveWebviewEditor: Event; } export interface WebviewResolver { @@ -103,14 +107,14 @@ export class LazilyResolvedWebviewEditorInput extends WebviewInput { super(id, viewType, name, webview, _webviewWorkbenchService.iconManager); } - dispose() { + override dispose() { super.dispose(); this.#resolvePromise?.cancel(); this.#resolvePromise = undefined; } @memoize - public async resolve() { + public override async resolve() { if (!this.#resolved) { this.#resolved = true; this.#resolvePromise = this._webviewWorkbenchService.resolveWebview(this); @@ -125,7 +129,7 @@ export class LazilyResolvedWebviewEditorInput extends WebviewInput { return super.resolve(); } - protected transfer(other: LazilyResolvedWebviewEditorInput): WebviewInput | undefined { + protected override transfer(other: LazilyResolvedWebviewEditorInput): WebviewInput | undefined { if (!super.transfer(other)) { return; } @@ -171,12 +175,48 @@ export class WebviewEditorService extends Disposable implements IWebviewWorkbenc super(); this._iconManager = this._register(this._instantiationService.createInstance(WebviewIconManager)); + + this._register(_editorService.onDidActiveEditorChange(() => { + this.updateActiveWebview(); + })); + + // The user may have switched focus between two sides of a diff editor + this._register(_webviewService.onDidChangeActiveWebview(() => { + this.updateActiveWebview(); + })); + + this.updateActiveWebview(); } get iconManager() { return this._iconManager; } + private _activeWebview: WebviewInput | undefined; + + private readonly _onDidChangeActiveWebviewEditor = this._register(new Emitter()); + public readonly onDidChangeActiveWebviewEditor = this._onDidChangeActiveWebviewEditor.event; + + private updateActiveWebview() { + const activeInput = this._editorService.activeEditor; + + let newActiveWebview: WebviewInput | undefined; + if (activeInput instanceof WebviewInput) { + newActiveWebview = activeInput; + } else if (activeInput instanceof DiffEditorInput) { + if (activeInput.primary instanceof WebviewInput && activeInput.primary.webview === this._webviewService.activeWebview) { + newActiveWebview = activeInput.primary; + } else if (activeInput.secondary instanceof WebviewInput && activeInput.secondary.webview === this._webviewService.activeWebview) { + newActiveWebview = activeInput.secondary; + } + } + + if (newActiveWebview !== this._activeWebview) { + this._activeWebview = newActiveWebview; + this._onDidChangeActiveWebviewEditor.fire(newActiveWebview); + } + } + public createWebview( id: string, viewType: string, @@ -203,8 +243,13 @@ export class WebviewEditorService extends Disposable implements IWebviewWorkbenc group: IEditorGroup, preserveFocus: boolean ): void { + const topLevelEditor = this.findTopLevelEditorForWebview(webview); if (webview.group === group.id) { - this._editorService.openEditor(webview, { + if (this._editorService.activeEditor === topLevelEditor) { + return; + } + + this._editorService.openEditor(topLevelEditor, { preserveFocus, // preserve pre 1.38 behaviour to not make group active when preserveFocus: true // but make sure to restore the editor to fix https://github.com/microsoft/vscode/issues/79633 @@ -213,11 +258,25 @@ export class WebviewEditorService extends Disposable implements IWebviewWorkbenc } else { const groupView = this._editorGroupService.getGroup(webview.group!); if (groupView) { - groupView.moveEditor(webview, group, { preserveFocus }); + groupView.moveEditor(topLevelEditor, group, { preserveFocus }); } } } + private findTopLevelEditorForWebview(webview: WebviewInput): IEditorInput { + for (const editor of this._editorService.editors) { + if (editor === webview) { + return editor; + } + if (editor instanceof DiffEditorInput) { + if (webview === editor.primary || webview === editor.secondary) { + return editor; + } + } + } + return webview; + } + public reviveWebview(options: { id: string, viewType: string, diff --git a/src/vs/workbench/contrib/webviewView/browser/webviewViewPane.ts b/src/vs/workbench/contrib/webviewView/browser/webviewViewPane.ts index eed36d2b..f1291d26 100644 --- a/src/vs/workbench/contrib/webviewView/browser/webviewViewPane.ts +++ b/src/vs/workbench/contrib/webviewView/browser/webviewViewPane.ts @@ -91,18 +91,18 @@ export class WebviewViewPane extends ViewPane { private readonly _onDispose = this._register(new Emitter()); readonly onDispose = this._onDispose.event; - dispose() { + override dispose() { this._onDispose.fire(); super.dispose(); } - focus(): void { + override focus(): void { super.focus(); this._webview.value?.focus(); } - renderBody(container: HTMLElement): void { + override renderBody(container: HTMLElement): void { super.renderBody(container); this._container = container; @@ -123,7 +123,7 @@ export class WebviewViewPane extends ViewPane { } } - public saveState() { + public override saveState() { if (this._webview.value) { this.viewState[storageKeys.webviewState] = this._webview.value.state; } @@ -132,7 +132,7 @@ export class WebviewViewPane extends ViewPane { super.saveState(); } - protected layoutBody(height: number, width: number): void { + protected override layoutBody(height: number, width: number): void { super.layoutBody(height, width); if (!this._webview.value) { @@ -210,7 +210,7 @@ export class WebviewViewPane extends ViewPane { } } - protected updateTitle(value: string | undefined) { + protected override updateTitle(value: string | undefined) { this.setTitle = value; super.updateTitle(typeof value === 'string' ? value : this.defaultTitle); } diff --git a/src/vs/workbench/contrib/welcome/common/viewsWelcomeExtensionPoint.ts b/src/vs/workbench/contrib/welcome/common/viewsWelcomeExtensionPoint.ts index a90884eb..c6629c19 100644 --- a/src/vs/workbench/contrib/welcome/common/viewsWelcomeExtensionPoint.ts +++ b/src/vs/workbench/contrib/welcome/common/viewsWelcomeExtensionPoint.ts @@ -28,6 +28,7 @@ export const ViewIdentifierMap: { [key: string]: string } = { 'explorer': 'workbench.explorer.emptyView', 'debug': 'workbench.debug.welcome', 'scm': 'workbench.scm', + 'testing': 'workbench.view.testing' }; const viewsWelcomeExtensionPointSchema = Object.freeze({ 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 7bdcfcfd..01633f07 100644 --- a/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.contribution.ts +++ b/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.contribution.ts @@ -4,22 +4,25 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; -import { GettingStartedInputFactory, GettingStartedPage, inGettingStartedContext } from 'vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted'; +import { GettingStartedInputSerializer, GettingStartedPage, inGettingStartedContext } from 'vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted'; import { Registry } from 'vs/platform/registry/common/platform'; -import { Extensions as EditorInputExtensions, IEditorInputFactoryRegistry } from 'vs/workbench/common/editor'; +import { EditorExtensions, IEditorInputFactoryRegistry } from 'vs/workbench/common/editor'; import { MenuId, registerAction2, Action2 } from 'vs/platform/actions/common/actions'; -import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, 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 { EditorDescriptor, IEditorRegistry } 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/contrib/welcome/gettingStarted/browser/gettingStartedService'; import { GettingStartedInput } from 'vs/workbench/contrib/welcome/gettingStarted/browser/gettingStartedInput'; +import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; +import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { ConfigurationScope, Extensions as ConfigurationExtensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; +import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration'; +import product from 'vs/platform/product/common/product'; + export * as icons from 'vs/workbench/contrib/welcome/gettingStarted/browser/gettingStartedIcons'; @@ -43,7 +46,7 @@ registerAction2(class extends Action2 { } }); -Registry.as(EditorInputExtensions.EditorInputFactories).registerEditorInputFactory(GettingStartedInput.ID, GettingStartedInputFactory); +Registry.as(EditorExtensions.EditorInputFactories).registerEditorInputSerializer(GettingStartedInput.ID, GettingStartedInputSerializer); Registry.as(EditorExtensions.Editors).registerEditor( EditorDescriptor.create( GettingStartedPage, @@ -137,8 +140,8 @@ registerAction2(class extends Action2 { registerAction2(class extends Action2 { constructor() { super({ - id: 'gettingStarted.markTaskComplete', - title: localize('gettingStarted.markTaskComplete', "Mark Task Complete"), + id: 'gettingStarted.markStepComplete', + title: localize('gettingStarted.markStepComplete', "Mark Step Complete"), category, }); } @@ -146,15 +149,15 @@ registerAction2(class extends Action2 { run(accessor: ServicesAccessor, arg: string) { if (!arg) { return; } const gettingStartedService = accessor.get(IGettingStartedService); - gettingStartedService.progressTask(arg); + gettingStartedService.progressStep(arg); } }); registerAction2(class extends Action2 { constructor() { super({ - id: 'gettingStarted.markTaskIncomplete', - title: localize('gettingStarted.markTaskInomplete', "Mark Task Incomplete"), + id: 'gettingStarted.markStepIncomplete', + title: localize('gettingStarted.markStepInomplete', "Mark Step Incomplete"), category, }); } @@ -162,153 +165,34 @@ registerAction2(class extends Action2 { run(accessor: ServicesAccessor, arg: string) { if (!arg) { return; } const gettingStartedService = accessor.get(IGettingStartedService); - gettingStartedService.deprogressTask(arg); + gettingStartedService.deprogressStep(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.") - }, - } - }); +class WorkbenchConfigurationContribution { + constructor( + @IInstantiationService _instantiationService: IInstantiationService, + @IGettingStartedService _gettingStartedService: IGettingStartedService, + ) { + // Init the getting started service via DI. + } +} -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.") - }, - primary: { - type: 'boolean', - description: localize('walkthroughs.primary', "if this is a `primary` walkthrough, hinting if it should be opened on install of the extension. The first `primary` walkthough with a `when` condition matching the current context may be opened by core on install of the extension.") - }, - 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.") - } - } - } - } +Registry.as(WorkbenchExtensions.Workbench) + .registerWorkbenchContribution(WorkbenchConfigurationContribution, LifecyclePhase.Restored); + + +const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); +if (product.quality !== 'stable') { + configurationRegistry.registerConfiguration({ + ...workbenchConfigurationNodeBase, + properties: { + 'workbench.welcomePage.experimental.extensionContributions': { + scope: ConfigurationScope.APPLICATION, + type: 'boolean', + default: false, + description: localize('workbench.welcomePage.experimental.extensionContributions', "When enabled, allow extensions to contribute items to the \"Getting Started\" and \"Start\" sections of the welcome page. Experimental, subject to breakage as api changes.") } } - } -}); + }); +} diff --git a/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.css b/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.css index 5f2c5df2..28b04da5 100644 --- a/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.css +++ b/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.css @@ -10,7 +10,6 @@ .monaco-workbench .part.editor > .content .gettingStartedContainer { box-sizing: border-box; - padding: 10px 20px; line-height: 22px; position: relative; overflow: hidden; @@ -24,14 +23,20 @@ .monaco-workbench .part.editor > .content .gettingStartedContainer img { max-width: 100%; max-height: 100%; + object-fit: contain; + pointer-events: none; } .monaco-workbench .part.editor > .content .gettingStartedContainer { font-size: 13px; } +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStarted { + height: 100%; +} + .monaco-workbench .part.editor > .content .gettingStartedContainer h1 { - padding: 0; + padding: 5px 0 0; margin: 0; border: none; font-weight: normal; @@ -57,7 +62,7 @@ .monaco-workbench .part.editor > .content .gettingStartedContainer h2 { font-weight: 200; - margin-top: 17px; + margin-top: 0; margin-bottom: 5px; font-size: 1.5em; line-height: initial; @@ -71,143 +76,180 @@ .monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide { width: 100%; height: 100%; + padding: 0; position: absolute; box-sizing: border-box; left: 0; top: 0; } +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideCategories { + padding: 12px; +} + .monaco-workbench .part.editor > .content .gettingStartedContainer.animationReady .gettingStartedSlide { /* keep consistant with SLIDE_TRANSITION_TIME_MS in gettingStarted.ts */ transition: left 0.25s, opacity 0.25s; } -.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 .gettingStartedSlideCategories > .gettingStartedCategoriesContainer { + display: grid; + height: 100%; + max-width: 800px; + margin: 0 auto; + grid-template-rows: 25% minmax(min-content, auto) min-content; + grid-template-columns: 1fr 1fr; + grid-template-areas: + "header header" + "left-column right-column" + "footer footer" +; } -.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.categories .categories-slide-container { +.monaco-workbench .part.editor > .content .gettingStartedContainer.width-constrained .gettingStartedSlideCategories > .gettingStartedCategoriesContainer { + grid-template-rows: auto min-content minmax(min-content, auto) min-content; + grid-template-columns: 1fr; + grid-template-areas: + "header" + "left-column" + "right-column" + "footer" +; +} + +.monaco-workbench .part.editor > .content .gettingStartedContainer.height-constrained .gettingStartedSlideCategories > .gettingStartedCategoriesContainer { + grid-template-rows: auto minmax(min-content, auto) min-content; + grid-template-areas: + "header" + "left-column right-column" + "footer footer" +; +} + + +.monaco-workbench .part.editor > .content .gettingStartedContainer.height-constrained.width-constrained .gettingStartedSlideCategories > .gettingStartedCategoriesContainer { + grid-template-rows: min-content minmax(min-content, auto) min-content; + grid-template-columns: 1fr; + grid-template-areas: + "left-column" + "right-column" + "footer" +; +} + +.monaco-workbench .part.editor > .content .gettingStartedContainer.width-constrained .gettingStartedSlideCategories > .gettingStartedCategoriesContainer > .header, +.monaco-workbench .part.editor > .content .gettingStartedContainer.height-constrained .gettingStartedSlideCategories > .gettingStartedCategoriesContainer > .header { + display: none; +} + +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideCategories > .gettingStartedCategoriesContainer > * { + overflow: hidden; + text-overflow: ellipsis; +} + +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideCategories > .gettingStartedCategoriesContainer > .categories-column > div { + margin-bottom: 32px; +} + +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideCategories > .gettingStartedCategoriesContainer > .categories-column-left { + grid-area: left-column; +} + +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideCategories > .gettingStartedCategoriesContainer > .categories-column-right { + grid-area: right-column; +} + +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideCategories > .gettingStartedCategoriesContainer > .header { + grid-area: header; + align-self: end; +} + +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideCategories > .gettingStartedCategoriesContainer > .footer { + grid-area: footer; + justify-self: center; +} + +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideCategories .categories-slide-container { width: 90%; max-width: 1200px; } -.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.categories .gap { +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideCategories .gap { flex: 150px 0 1000 } -.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.categories .header { - display: flex; - 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 { +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideCategories .category-title { margin: 4px 0 4px; font-size: 14px; font-weight: 500; } -.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.categories .category-progress { +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideCategories .category-progress { position: absolute; bottom: 0; left: 0; width: 100%; } -.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.categories .categories-split-view { - display: flex; -} - -.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 1 0; - overflow: hidden; -} - -.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.categories .categories-split-view ul { +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideCategories ul { list-style: none; margin: 0; line-height: 24px; padding-left: 0; } -.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.categories .categories-split-view li { +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideCategories li { list-style: none; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } -.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.categories .categories-split-view .path { +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideCategories .path { padding-left: 1em; } -.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.categories .categories-split-view .codicon { + +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideCategories .codicon { padding-right: 8px; position: relative; top: 3px; } -.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.categories .categories-split-view .start-container .codicon { - left: 1px; +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideCategories .start-container img { + padding-right: 8px; + position: relative; + top: 3px; + max-width: 16px; + max-height: 16px; } -.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.categories .categories-split-view .keybinding-label { +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideCategories .keybinding-label { padding-left: 1em; } -.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.categories .progress-bar-outer { +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideCategories .progress-bar-outer { height: 4px; margin-top: 4px; } -.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.categories .progress-bar-inner { +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideCategories .progress-bar-inner { height: 100%; } -.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.categories .getting-started-categories-container { - justify-content: center; - max-width: 900px; - margin: 32px auto; -} - -.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.categories .getting-started-categories-container .no-categories { - display: none; -} - -.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.categories .getting-started-categories-container .no-categories:first-child { - display: block; - padding-left: 6px; -} - -.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.categories .getting-started-categories-scrolling-container { - overflow: scroll; - height: 100%; - width: 100%; -} - .monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide .getting-started-category { - width: 92%; + width: calc(100% - 16px); font-size: 13px; box-sizing: border-box; line-height: normal; - margin: 8px; + margin: 8px 8px 12px; padding: 6px 12px 6px; left: 1px; text-align: left; display: flex; } -.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.categories .getting-started-category { +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideCategories .getting-started-category { position: relative; border-radius: 6px; overflow: hidden; @@ -225,7 +267,7 @@ padding-right: 0; } -.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.categories .getting-started-category img.category-icon { +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideCategories .getting-started-category img.category-icon { padding-right: 8px; max-width: 20px; max-height: 20px; @@ -233,48 +275,47 @@ top: 2px; } -.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .getting-started-category img.category-icon { +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideDetails .getting-started-category img.category-icon { margin-right: 10px; margin-left: 10px; max-width: 32px; max-height: 32px; } -.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail { +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideDetails { display: flex; flex-direction: column; overflow: hidden; } -.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .gap { +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideDetails .gap { flex: 150px 0 1000 } -.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .getting-started-category { - width: 330px; +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideDetails .getting-started-category { display: flex; padding: 10px 0 20px 12px; margin: 0; min-height: auto; } -.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .getting-started-detail-columns .gap { +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideDetails .getting-started-detail-columns .gap { flex: 150px 1 1000 } -.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .getting-started-category .codicon { +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideDetails .getting-started-category .codicon { margin-right: 8px; font-size: 28px; } -.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .getting-started-detail-columns { +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideDetails .getting-started-detail-columns { display: flex; justify-content: flex-start; padding: 40px 40px 0; max-height: calc(100% - 40px); } -.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task { +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideDetails .getting-started-step { display: flex; width: 100%; margin: 4px 0; @@ -282,68 +323,80 @@ transition: height .1s linear; } -.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task:not(.expanded) { +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideDetails .getting-started-step.expanded { + cursor: default !important; +} + +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideDetails .getting-started-step.expanded > .codicon { + cursor: pointer !important; +} + +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideDetails .getting-started-step:not(.expanded) { height: 54px; background: none; opacity: 0.8; } -.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .getting-started-detail-columns .getting-started-detail-left > div { +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideDetails .getting-started-step:not(.expanded) .step-title { + white-space: nowrap; + text-overflow: ellipsis; + display: inline-block; + overflow: hidden; + width: inherit; +} + +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideDetails .getting-started-detail-columns .getting-started-detail-left > div { width: 100%; } -.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task:not(.expanded) .task-description, -.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task:not(.expanded) .image-description, -.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task:not(.expanded) .actions, -.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task:not(.expanded) button, -.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task:not(.expanded) a { +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideDetails .getting-started-step:not(.expanded) .step-description-container { visibility: hidden; } -.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task .task-description-container { +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideDetails .getting-started-step .step-container { width: 100%; } -.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task .task-description { +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideDetails .getting-started-step .step-description { padding-top: 8px; } -.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task .actions { +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideDetails .getting-started-step .actions { margin-top: 12px; display: flex; align-items: center; } -.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task .shortcut-message { +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideDetails .getting-started-step .shortcut-message { opacity: 0.8; font-size: 8pt; } -.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task .shortcut-message .keybinding { +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideDetails .getting-started-step .shortcut-message .keybinding { font-weight: bold; } -.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task .task-next { +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideDetails .getting-started-step .step-next { margin-left: auto; margin-right: 10px; padding: 6px 12px; } -.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task .codicon.hidden { +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideDetails .getting-started-step .codicon.hidden { display: none; } -.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task .codicon { +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideDetails .getting-started-step .codicon { margin-right: 8px; } -.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task-action { +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideDetails .getting-started-step-action { padding: 6px 12px; font-size: 13px; margin-bottom: 0; } -.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .getting-started-detail-left { +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideDetails .getting-started-detail-left { min-width: 330px; width: 40%; max-width: 400px; @@ -351,21 +404,78 @@ flex-direction: column; } -.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .full-height-scrollable { +.monaco-workbench .part.editor > .content .gettingStartedContainer .full-height-scrollable { height: 100%; } -.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .getting-started-detail-container { +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideDetails .getting-started-detail-container { height: 100%; } -.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .gettingStartedDetailsContent { +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideDetails .gettingStartedDetailsContent { height: 100%; + max-width: 1200px; + margin: 0 auto; + padding: 0 12px; + display: grid; + + grid-template-columns: minmax(auto, 400px) 1fr; + grid-template-rows: minmax(40px, 20%) auto max-content 1fr; + grid-column-gap: 20px; + grid-template-areas: + "back img" + "title img" + "steps img" + ". img" + ; +} + +.monaco-workbench .part.editor > .content .gettingStartedContainer.width-semi-constrained .gettingStartedSlideDetails .gettingStartedDetailsContent { + max-width: 500px; + grid-template-columns: auto; + grid-template-rows: 30px auto minmax(30%, 40%) 1fr; + grid-column-gap: 20px; + grid-template-areas: + "back" + "title" + "img" + "steps" + ; +} + +.monaco-workbench .part.editor > .content .gettingStartedContainer.width-semi-constrained.height-constrained .gettingStartedSlideDetails .gettingStartedDetailsContent { + grid-template-rows: 0 auto minmax(40%, 50%) 1fr; +} + +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideDetails .gettingStartedDetailsContent > .prev-button { + grid-area: back; +} + +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideDetails .gettingStartedDetailsContent > .getting-started-category { + grid-area: title; +} + +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideDetails .gettingStartedDetailsContent > .steps-container { + height: 100%; + grid-area: steps; +} + +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideDetails .gettingStartedDetailsContent > .getting-started-media { + grid-area: img; + align-self: center; +} +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideDetails .gettingStartedDetailsContent > .getting-started-media.markdown { + height: inherit; +} + +.monaco-workbench .part.editor > .content .gettingStartedContainer.width-semi-constrained .gettingStartedSlideDetails .gettingStartedDetailsContent > .getting-started-media.image { + height: inherit; + width: inherit; display: flex; - flex-direction: column; + justify-content: center; } -.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .getting-started-detail-right { +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideDetails .getting-started-detail-right { display: flex; align-items: flex-start; justify-content: center; @@ -376,12 +486,12 @@ max-width: 800px; } -.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .getting-started-detail-right img { +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideDetails .getting-started-detail-right img { object-fit: contain; cursor: unset; } -.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .getting-started-detail-right img.clickable { +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideDetails .getting-started-detail-right img.clickable { cursor: pointer; } @@ -410,6 +520,24 @@ z-index: 1; } +.monaco-workbench .part.editor > .content .gettingStartedContainer.width-semi-constrained .prev-button.button-link { + left: 0; + top: -10px; +} + +.monaco-workbench .part.editor > .content .gettingStartedContainer.height-constrained .prev-button.button-link { + left: 0; + top: -10px; +} + +.monaco-workbench .part.editor > .content .gettingStartedContainer.height-constrained .prev-button.button-link .codicon { + font-size: 20px; +} + +.monaco-workbench .part.editor > .content .gettingStartedContainer.height-constrained .prev-button.button-link .moreText { + display: none; +} + .monaco-workbench .part.editor > .content .gettingStartedContainer .prev-button:hover { cursor: pointer; } @@ -427,19 +555,19 @@ text-align: center; } -.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail h2 { +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideDetails h2 { font-weight: normal; line-height: 26px; margin: 0 0 4px 0; } -.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail h3 { +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideDetails h3 { font-size: 13px; font-weight: 700; margin: 0; } -.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .subtitle { +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideDetails .subtitle { font-size: 16px; margin: 0; padding: 0; @@ -450,11 +578,15 @@ opacity: 0; } -.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStarted.showDetails .gettingStartedSlideCategory { +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStarted.showDetails .gettingStartedSlideCategories { left: -100%; opacity: 0; } +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStarted.showDetails .categoriesScrollbar .scrollbar.vertical { + display: none; +} + .monaco-workbench .part.editor > .content .gettingStartedContainer .button-link { padding: 0; background: transparent; @@ -469,16 +601,25 @@ background: transparent; } +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide .configureVisibility > button, .monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide .showOnStartup { text-align: center; } -.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide .showOnStartup label { +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide .showOnStartup checkbox { position: relative; top: -2px; left: 5px; } +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideCategories > .gettingStartedCategoriesContainer > .footer > p { + margin: 0; +} + +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideCategories > .gettingStartedCategoriesContainer > .footer > button { + text-align: center; +} + .monaco-workbench .part.editor > .content .gettingStartedContainer .getting-started-category .codicon { top: 0; } @@ -490,3 +631,12 @@ .monaco-workbench .part.editor > .content .gettingStartedContainer .getting-started-category:hover .codicon-close { visibility: visible; } + +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideDetails .getting-started-step .step-description-container .monaco-button, +.monaco-workbench .part.editor > .content .gettingStartedContainer .max-lines-3 { + /* Supported everywhere: https://developer.mozilla.org/en-US/docs/Web/CSS/-webkit-line-clamp#browser_compatibility */ + -webkit-line-clamp: 3; + display: -webkit-box; + -webkit-box-orient: vertical; + overflow: hidden; +} diff --git a/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.ts b/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.ts index 06e01be2..53dbb428 100644 --- a/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.ts +++ b/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.ts @@ -6,13 +6,13 @@ import 'vs/css!./gettingStarted'; import { localize } from 'vs/nls'; import { IInstantiationService, optional } from 'vs/platform/instantiation/common/instantiation'; -import { EditorOptions, IEditorInputFactory, IEditorOpenContext } from 'vs/workbench/common/editor'; -import { DisposableStore } from 'vs/base/common/lifecycle'; +import { EditorOptions, IEditorInputSerializer, IEditorOpenContext } from 'vs/workbench/common/editor'; +import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { assertIsDefined } from 'vs/base/common/types'; -import { $, addDisposableListener, Dimension, reset } from 'vs/base/browser/dom'; +import { $, addDisposableListener, append, clearNode, Dimension, reset } from 'vs/base/browser/dom'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IProductService } from 'vs/platform/product/common/productService'; -import { IGettingStartedCategory, IGettingStartedCategoryDescriptor, IGettingStartedCategoryWithProgress, IGettingStartedService } from 'vs/workbench/contrib/welcome/gettingStarted/browser/gettingStartedService'; +import { IGettingStartedCategory, IGettingStartedCategoryWithProgress, IGettingStartedService } from 'vs/workbench/contrib/welcome/gettingStarted/browser/gettingStartedService'; import { IThemeService, registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { welcomePageBackground, welcomePageProgressBackground, welcomePageProgressForeground, welcomePageTileBackground, welcomePageTileHoverBackground, welcomePageTileShadow } from 'vs/workbench/contrib/welcome/page/browser/welcomePageColors'; import { activeContrastBorder, buttonBackground, buttonForeground, buttonHoverBackground, contrastBorder, descriptionForeground, focusBorder, foreground, textLinkActiveForeground, textLinkForeground } from 'vs/platform/theme/common/colorRegistry'; @@ -23,7 +23,7 @@ import { gettingStartedCheckedCodicon, gettingStartedUncheckedCodicon } from 'vs import { IOpenerService } from 'vs/platform/opener/common/opener'; import { URI } from 'vs/base/common/uri'; import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; @@ -35,10 +35,29 @@ import { ILabelService } from 'vs/platform/label/common/label'; import { IWindowOpenable } from 'vs/platform/windows/common/windows'; 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'; import { Throttler } from 'vs/base/common/async'; import { GettingStartedInput } from 'vs/workbench/contrib/welcome/gettingStarted/browser/gettingStartedInput'; +import { GroupDirection, GroupsOrder, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { Emitter, Event } from 'vs/base/common/event'; +import { LinkedText } from 'vs/base/common/linkedText'; +import { Button } from 'vs/base/browser/ui/button/button'; +import { attachButtonStyler, attachLinkStyler } from 'vs/platform/theme/common/styler'; +import { Link } from 'vs/platform/opener/browser/link'; +import { renderFormattedText } from 'vs/base/browser/formattedTextRenderer'; +import { IWebviewService } from 'vs/workbench/contrib/webview/browser/webview'; +import { DEFAULT_MARKDOWN_STYLES, renderMarkdownDocument } from 'vs/workbench/contrib/markdown/common/markdownDocumentRenderer'; +import { IModeService } from 'vs/editor/common/services/modeService'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { generateUuid } from 'vs/base/common/uuid'; +import { TokenizationRegistry } from 'vs/editor/common/modes'; +import { generateTokensCSSForColorMap } from 'vs/editor/common/modes/supports/tokenization'; +import { ResourceMap } from 'vs/base/common/map'; +import { IFileService } from 'vs/platform/files/common/files'; +import { joinPath } from 'vs/base/common/resources'; +import { asWebviewUri } from 'vs/workbench/contrib/webview/common/webviewUri'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; const SLIDE_TRANSITION_TIME_MS = 250; const configurationKey = 'workbench.startupEditor'; @@ -64,14 +83,17 @@ export class GettingStartedPage extends EditorPane { private inProgressScroll = Promise.resolve(); private dispatchListeners: DisposableStore = new DisposableStore(); - private taskDisposables: DisposableStore = new DisposableStore(); + private stepDisposables: DisposableStore = new DisposableStore(); + private detailsPageDisposables: DisposableStore = new DisposableStore(); private gettingStartedCategories: IGettingStartedCategoryWithProgress[]; private currentCategory: IGettingStartedCategoryWithProgress | undefined; - private categoriesScrollbar: DomScrollableElement | undefined; + private categoriesPageScrollbar: DomScrollableElement | undefined; + private detailsPageScrollbar: DomScrollableElement | undefined; + private detailsScrollbar: DomScrollableElement | undefined; - private detailImageScrollbar: DomScrollableElement | undefined; + private buildSlideThrottle: Throttler = new Throttler(); private container: HTMLElement; @@ -80,8 +102,18 @@ export class GettingStartedPage extends EditorPane { private tasExperimentService?: ITASExperimentService; private previousSelection?: string; private recentlyOpened: Promise; - private selectedTaskElement?: HTMLDivElement; + private selectedStepElement?: HTMLDivElement; private hasScrolledToFirstCategory = false; + private recentlyOpenedList?: GettingStartedIndexList; + private startList?: GettingStartedIndexList; + private gettingStartedList?: GettingStartedIndexList; + + private stepsSlide!: HTMLElement; + private categoriesSlide!: HTMLElement; + private stepsContent!: HTMLElement; + private stepMediaComponent!: HTMLElement; + + private webviewID = generateUuid(); constructor( @ICommandService private readonly commandService: ICommandService, @@ -90,13 +122,21 @@ export class GettingStartedPage extends EditorPane { @IGettingStartedService private readonly gettingStartedService: IGettingStartedService, @IConfigurationService private readonly configurationService: IConfigurationService, @ITelemetryService telemetryService: ITelemetryService, + @IModeService private readonly modeService: IModeService, + @IFileService private readonly fileService: IFileService, @IOpenerService private readonly openerService: IOpenerService, @IThemeService themeService: IThemeService, @IStorageService private storageService: IStorageService, + @IExtensionService private readonly extensionService: IExtensionService, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, + @IEditorGroupsService private readonly groupsService: IEditorGroupsService, @IContextKeyService contextService: IContextKeyService, + @IQuickInputService private quickInputService: IQuickInputService, @IWorkspacesService workspacesService: IWorkspacesService, @ILabelService private readonly labelService: ILabelService, @IHostService private readonly hostService: IHostService, + @IWebviewService private readonly webviewService: IWebviewService, @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, @optional(ITASExperimentService) tasExperimentService: ITASExperimentService, ) { @@ -109,6 +149,9 @@ export class GettingStartedPage extends EditorPane { tabindex: 0, 'aria-label': localize('gettingStartedLabel', "Getting Started. Overview of how to get up to speed with your editor.") }); + this.stepMediaComponent = $('.getting-started-media'); + this.stepMediaComponent.id = generateUuid(); + this.tasExperimentService = tasExperimentService; @@ -118,19 +161,26 @@ export class GettingStartedPage extends EditorPane { this.gettingStartedCategories = this.gettingStartedService.getCategories(); this._register(this.dispatchListeners); this.buildSlideThrottle = new Throttler(); - this._register(this.gettingStartedService.onDidAddTask(task => { + + const rerender = () => { this.gettingStartedCategories = this.gettingStartedService.getCategories(); this.buildSlideThrottle.queue(() => this.buildCategoriesSlide()); - })); + }; - this._register(this.gettingStartedService.onDidChangeTask(task => { - const ourCategory = this.gettingStartedCategories.find(c => c.id === task.category); + this._register(this.gettingStartedService.onDidAddCategory(rerender)); + this._register(this.gettingStartedService.onDidRemoveCategory(rerender)); + + this._register(this.gettingStartedService.onDidChangeStep(step => { + const ourCategory = this.gettingStartedCategories.find(c => c.id === step.category); if (!ourCategory || ourCategory.content.type === 'startEntry') { return; } - const ourTask = ourCategory.content.items.find(item => item.id === task.id); - if (!ourTask) { return; } - ourTask.title = task.title; - ourTask.description = task.description; - ourTask.media.path = task.media.path; + const ourStep = ourCategory.content.steps.find(step => step.id === step.id); + if (!ourStep) { return; } + ourStep.title = step.title; + ourStep.description = step.description; + ourStep.media.path = step.media.path; + + this.container.querySelectorAll(`[x-step-title-for="${step.id}"]`).forEach(element => (element as HTMLDivElement).innerText = step.title); + this.container.querySelectorAll(`[x-step-description-for="${step.id}"]`).forEach(element => this.buildStepMarkdownDescription((element), step.description)); })); this._register(this.gettingStartedService.onDidChangeCategory(category => { @@ -139,21 +189,24 @@ export class GettingStartedPage extends EditorPane { ourCategory.title = category.title; ourCategory.description = category.description; + + this.container.querySelectorAll(`[x-category-title-for="${category.id}"]`).forEach(step => (step as HTMLDivElement).innerText = ourCategory.title); + this.container.querySelectorAll(`[x-category-description-for="${category.id}"]`).forEach(step => (step as HTMLDivElement).innerText = ourCategory.description); })); - this._register(this.gettingStartedService.onDidProgressTask(task => { - const category = this.gettingStartedCategories.find(category => category.id === task.category); - if (!category) { throw Error('Could not find category with ID: ' + task.category); } - if (category.content.type !== 'items') { throw Error('internaal error: progressing task in a non-items category'); } - const ourTask = category.content.items.find(_task => _task.id === task.id); - if (!ourTask) { - throw Error('Could not find task with ID: ' + task.id); + this._register(this.gettingStartedService.onDidProgressStep(step => { + const category = this.gettingStartedCategories.find(category => category.id === step.category); + if (!category) { throw Error('Could not find category with ID: ' + step.category); } + if (category.content.type !== 'steps') { throw Error('internal error: progressing step in a non-steps category'); } + const ourStep = category.content.steps.find(_step => _step.id === step.id); + if (!ourStep) { + throw Error('Could not find step with ID: ' + step.id); } - ourTask.done = task.done; + ourStep.done = step.done; if (category.id === this.currentCategory?.id) { - const badgeelements = assertIsDefined(document.querySelectorAll(`[data-done-task-id="${task.id}"]`)); + const badgeelements = assertIsDefined(document.querySelectorAll(`[data-done-step-id="${step.id}"]`)); badgeelements.forEach(badgeelement => { - if (task.done) { + if (step.done) { badgeelement.parentElement?.setAttribute('aria-checked', 'true'); badgeelement.classList.remove(...ThemeIcon.asClassNameArray(gettingStartedUncheckedCodicon)); badgeelement.classList.add('complete', ...ThemeIcon.asClassNameArray(gettingStartedCheckedCodicon)); @@ -171,7 +224,7 @@ export class GettingStartedPage extends EditorPane { this.recentlyOpened = workspacesService.getRecentlyOpened(); } - async setInput(newInput: GettingStartedInput, options: EditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken) { + override async setInput(newInput: GettingStartedInput, options: EditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken) { this.container.classList.remove('animationReady'); this.editorInput = newInput; await super.setInput(newInput, options, context, token); @@ -185,8 +238,8 @@ export class GettingStartedPage extends EditorPane { if (!ourCategory) { throw Error('Could not find category with ID: ' + categoryID); } - if (ourCategory.content.type !== 'items') { - throw Error('internaal error: category is not items'); + if (ourCategory.content.type !== 'steps') { + throw Error('internaal error: category is not steps'); } this.scrollToCategory(categoryID); } @@ -201,225 +254,253 @@ export class GettingStartedPage extends EditorPane { this.commandService.executeCommand('workbench.action.keepEditor'); this.telemetryService.publicLog2('gettingStarted.ActionExecuted', { command, argument }); - - switch (command) { - case 'scrollPrev': { - this.scrollPrev(); - break; - } - case 'skip': { - this.runSkip(); - break; - } - case 'showMoreRecents': { - this.commandService.executeCommand('workbench.action.openRecent'); - break; - } - case 'configureVisibility': { - this.commandService.executeCommand('workbench.action.openSettings', hiddenEntriesConfigurationKey); - break; - } - case 'openFolder': { - this.commandService.executeCommand(isMacintosh ? 'workbench.action.files.openFileFolder' : 'workbench.action.files.openFolder'); - break; - } - case 'selectCategory': { - const selectedCategory = this.gettingStartedCategories.find(category => category.id === argument); - if (!selectedCategory) { throw Error('Could not find category with ID ' + argument); } - if (selectedCategory.content.type === 'startEntry') { - this.commandService.executeCommand(selectedCategory.content.command); - } else { - this.scrollToCategory(argument); + (async () => { + switch (command) { + case 'scrollPrev': { + this.scrollPrev(); + break; } - 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); - 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); + case 'skip': { + this.runSkip(); + break; } - - const taskToggle = assertIsDefined(this.currentCategory?.content.items.find(task => task.id === argument)); - if (taskToggle.done) { - this.gettingStartedService.deprogressTask(argument); - } else { - this.gettingStartedService.progressTask(argument); + case 'showMoreRecents': { + this.commandService.executeCommand('workbench.action.openRecent'); + break; } - break; - } - case 'runTaskAction': { - if (!this.currentCategory || this.currentCategory.content.type !== 'items') { - throw Error('cannot run task action for category of non items type' + this.currentCategory?.id); + case 'configureVisibility': { + await this.configureCategoryVisibility(); + break; } - const taskToRun = assertIsDefined(this.currentCategory?.content.items.find(task => task.id === argument)); - if (taskToRun.button.command) { - this.commandService.executeCommand(taskToRun.button.command); - } else if (taskToRun.button.link) { - this.openerService.open(taskToRun.button.link); - this.gettingStartedService.progressByEvent('linkOpened:' + taskToRun.button.link); - } else { - throw Error('Task ' + JSON.stringify(taskToRun) + ' does not have an associated action'); + case 'openFolder': { + this.commandService.executeCommand(isMacintosh ? 'workbench.action.files.openFileFolder' : 'workbench.action.files.openFolder'); + break; + } + case 'selectCategory': { + const selectedCategory = this.gettingStartedCategories.find(category => category.id === argument); + if (!selectedCategory) { throw Error('Could not find category with ID ' + argument); } + if (selectedCategory.content.type === 'startEntry') { + this.commandService.executeCommand(selectedCategory.content.command); + } else { + this.scrollToCategory(argument); + } + break; + } + case 'hideCategory': { + const selectedCategory = this.gettingStartedCategories.find(category => category.id === argument); + if (!selectedCategory) { throw Error('Could not find category with ID ' + argument); } + this.setHiddenCategories([...this.getHiddenCategories().add(argument)]); + this.gettingStartedList?.rerender(); + break; + } + // Use selectTask over selectStep to keep telemetry consistant:https://github.com/microsoft/vscode/issues/122256 + case 'selectTask': { + this.selectStep(argument); + break; + } + case 'toggleStepCompletion': { + this.toggleStepCompletion(argument); + break; + } + default: { + console.error('Dispatch to', command, argument, 'not defined'); + break; } - break; } - default: { - console.error('Dispatch to', command, argument, 'not defined'); - break; - } - } + })(); e.stopPropagation(); })); } }); } - private selectTask(id: string | undefined, contractIfAlreadySelected = true, delayFocus = true) { - const mediaElement = assertIsDefined(this.container.querySelector('.getting-started-media') as HTMLImageElement); - this.taskDisposables.clear(); + private toggleStepCompletion(argument: string) { + if (!this.currentCategory || this.currentCategory.content.type !== 'steps') { + throw Error('cannot run step action for category of non steps type' + this.currentCategory?.id); + } + + const stepToggle = assertIsDefined(this.currentCategory?.content.steps.find(step => step.id === argument)); + if (stepToggle.done) { + this.gettingStartedService.deprogressStep(argument); + } else { + this.gettingStartedService.progressStep(argument); + } + } + + private async configureCategoryVisibility() { + const hiddenCategories = this.getHiddenCategories(); + const allCategories = this.gettingStartedCategories.filter(x => x.content.type === 'steps'); + const visibleCategories = await this.quickInputService.pick(allCategories.map(x => ({ + picked: !hiddenCategories.has(x.id), + id: x.id, + label: x.title, + detail: x.description, + })), { canPickMany: true, title: localize('pickWalkthroughs', "Select Walkthroughs to Show") }); + if (visibleCategories) { + const visibleIDs = new Set(visibleCategories.map(c => c.id)); + this.setHiddenCategories(allCategories.map(c => c.id).filter(id => !visibleIDs.has(id))); + this.buildCategoriesSlide(); + } + } + + private mdCache = new ResourceMap>(); + private async readAndCacheStepMarkdown(path: URI): Promise { + if (!this.mdCache.has(path)) { + this.mdCache.set(path, (async () => { + const bytes = await this.fileService.readFile(path); + const markdown = bytes.value.toString(); + return renderMarkdownDocument(markdown, this.extensionService, this.modeService); + })()); + } + return assertIsDefined(this.mdCache.get(path)); + } + + private getHiddenCategories(): Set { + return new Set(JSON.parse(this.storageService.get(hiddenEntriesConfigurationKey, StorageScope.GLOBAL, '[]'))); + } + + private setHiddenCategories(hidden: string[]) { + this.storageService.store( + hiddenEntriesConfigurationKey, + JSON.stringify(hidden), + StorageScope.GLOBAL, + StorageTarget.USER); + } + + private async selectStep(id: string | undefined, toggleIfAlreadySelected = true, delayFocus = true) { + this.stepDisposables.clear(); + clearNode(this.stepMediaComponent); + if (id) { - const taskElement = assertIsDefined(this.container.querySelector(`[data-task-id="${id}"]`)); - taskElement.parentElement?.querySelectorAll('.expanded').forEach(node => { + const stepElement = assertIsDefined(this.container.querySelector(`[data-step-id="${id}"]`)); + stepElement.parentElement?.querySelectorAll('.expanded').forEach(node => { node.classList.remove('expanded'); node.style.height = ``; node.setAttribute('aria-expanded', 'false'); }); - setTimeout(() => (taskElement as HTMLElement).focus(), delayFocus ? SLIDE_TRANSITION_TIME_MS : 0); - if (this.editorInput.selectedTask === id && contractIfAlreadySelected) { - this.previousSelection = this.editorInput.selectedTask; - this.editorInput.selectedTask = undefined; - this.selectedTaskElement = undefined; - return; + setTimeout(() => (stepElement as HTMLElement).focus(), delayFocus ? SLIDE_TRANSITION_TIME_MS : 0); + if (this.editorInput.selectedStep === id && toggleIfAlreadySelected) { + this.telemetryService.publicLog2('gettingStarted.ActionExecuted', { command: 'toggleStepCompletion2', argument: id }); + this.toggleStepCompletion(id); } - taskElement.style.height = `${taskElement.scrollHeight}px`; - if (!this.currentCategory || this.currentCategory.content.type !== 'items') { - 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)); + stepElement.style.height = ``; + stepElement.style.height = `${stepElement.scrollHeight}px`; - mediaElement.setAttribute('alt', taskToExpand.media.altText); - this.updateMediaSourceForColorMode(mediaElement, taskToExpand.media.path); - this.taskDisposables.add(addDisposableListener(mediaElement, 'load', () => mediaElement.width = mediaElement.naturalWidth * 2 / 3)); - if (taskToExpand.button.link) { - this.taskDisposables.add(addDisposableListener(mediaElement, 'click', () => taskElement.querySelector('button')?.click())); - mediaElement.classList.add('clickable'); - } else { - mediaElement.classList.remove('clickable'); + if (!this.currentCategory || this.currentCategory.content.type !== 'steps') { + throw Error('cannot expand step for category of non steps type' + this.currentCategory?.id); } - this.taskDisposables.add(this.themeService.onDidColorThemeChange(() => this.updateMediaSourceForColorMode(mediaElement, taskToExpand.media.path))); - taskElement.classList.add('expanded'); - taskElement.setAttribute('aria-expanded', 'true'); + this.editorInput.selectedStep = id; + this.selectedStepElement = stepElement; + const stepToExpand = assertIsDefined(this.currentCategory.content.steps.find(step => step.id === id)); + if (stepToExpand.media.type === 'image') { + + this.stepMediaComponent.classList.add('image'); + this.stepMediaComponent.classList.remove('markdown'); + + const media = stepToExpand.media; + const mediaElement = $('img'); + this.stepMediaComponent.appendChild(mediaElement); + mediaElement.setAttribute('alt', media.altText); + this.updateMediaSourceForColorMode(mediaElement, media.path); + + this.stepDisposables.add(this.themeService.onDidColorThemeChange(() => this.updateMediaSourceForColorMode(mediaElement, media.path))); + + } else if (stepToExpand.media.type === 'markdown') { + + this.stepMediaComponent.classList.remove('image'); + this.stepMediaComponent.classList.add('markdown'); + + const media = stepToExpand.media; + + const webview = this.stepDisposables.add(this.webviewService.createWebviewElement(this.webviewID, {}, { localResourceRoots: [media.base] }, undefined)); + webview.mountTo(this.stepMediaComponent); + webview.html = await this.renderMarkdown(media.path, media.base); + + let isDisposed = false; + this.stepDisposables.add(toDisposable(() => { isDisposed = true; })); + + this.stepDisposables.add(this.themeService.onDidColorThemeChange(async () => { + // Render again since syntax highlighting of code blocks may have changed + const body = await this.renderMarkdown(media.path, media.base); + if (!isDisposed) { // Make sure we weren't disposed of in the meantime + webview.html = body; + } + })); + } + stepElement.classList.add('expanded'); + stepElement.setAttribute('aria-expanded', 'true'); } else { - this.editorInput.selectedTask = undefined; - mediaElement.setAttribute('src', ''); - mediaElement.setAttribute('alt', ''); + this.editorInput.selectedStep = undefined; } setTimeout(() => { // rescan after animation finishes + this.detailsPageScrollbar?.scanDomNode(); this.detailsScrollbar?.scanDomNode(); - this.detailImageScrollbar?.scanDomNode(); }, 100); + this.detailsPageScrollbar?.scanDomNode(); this.detailsScrollbar?.scanDomNode(); - this.detailImageScrollbar?.scanDomNode(); } private updateMediaSourceForColorMode(element: HTMLImageElement, sources: { hc: URI, dark: URI, light: URI }) { const themeType = this.themeService.getColorTheme().type; - element.src = sources[themeType].toString(true); + element.srcset = sources[themeType].toString().replace(/ /g, '%20') + ' 1.5x'; + } + + private async renderMarkdown(path: URI, base: URI): Promise { + const content = await this.readAndCacheStepMarkdown(path); + const nonce = generateUuid(); + const colorMap = TokenizationRegistry.getColorMap(); + + const uriTranformedContent = content.replace(/src="([^"]*)"/g, (_, src) => { + const path = joinPath(base, src); + const transformed = asWebviewUri(this.environmentService, this.webviewID, path).toString(); + return `src="${transformed}"`; + }); + + const css = colorMap ? generateTokensCSSForColorMap(colorMap) : ''; + return ` + + + + + + + + ${uriTranformedContent} + + `; } createEditor(parent: HTMLElement) { - const tasksContent = - $('.gettingStartedDetailsContent', {}, - $('.gap'), - $('.getting-started-detail-columns', {}, - $('.gap'), - $('.getting-started-detail-left', {}, - $('.getting-started-detail-title')), - $('.getting-started-detail-right', {}, - $('img.getting-started-media')), - $('.gap'), - ), - $('.gap') - ); + if (this.detailsPageScrollbar) { this.detailsPageScrollbar.dispose(); } + if (this.categoriesPageScrollbar) { this.categoriesPageScrollbar.dispose(); } - const tasksSlide = - $('.gettingStartedSlideDetails.gettingStartedSlide.detail', {}, - $('button.prev-button.button-link', { 'x-dispatch': 'scrollPrev' }, $('span.scroll-button.codicon.codicon-chevron-left'), localize('more', "More")), - tasksContent - ); + this.categoriesSlide = $('.gettingStartedSlideCategories.gettingStartedSlide'); - const gettingStartedPage = - $('.gettingStarted', { - }, - $('.gettingStartedSlideCategory.gettingStartedSlide.categories'), - tasksSlide - ); + const prevButton = $('button.prev-button.button-link', { 'x-dispatch': 'scrollPrev' }, $('span.scroll-button.codicon.codicon-chevron-left'), $('span.moreText', {}, localize('more', "More"))); + this.stepsSlide = $('.gettingStartedSlideDetails.gettingStartedSlide', {}, prevButton); + this.stepsContent = $('.gettingStartedDetailsContent', {}); - if (this.detailImageScrollbar) { this.detailImageScrollbar.dispose(); } - this.detailImageScrollbar = this._register(new DomScrollableElement(tasksContent, { className: 'full-height-scrollable' })); - tasksSlide.appendChild(this.detailImageScrollbar.getDomNode()); - this.detailImageScrollbar.scanDomNode(); + this.detailsPageScrollbar = this._register(new DomScrollableElement(this.stepsContent, { className: 'full-height-scrollable' })); + this.categoriesPageScrollbar = this._register(new DomScrollableElement(this.categoriesSlide, { className: 'full-height-scrollable categoriesScrollbar' })); + this.stepsSlide.appendChild(this.detailsPageScrollbar.getDomNode()); + + const gettingStartedPage = $('.gettingStarted', {}, this.categoriesPageScrollbar.getDomNode(), this.stepsSlide); this.container.appendChild(gettingStartedPage); + + this.categoriesPageScrollbar.scanDomNode(); + this.detailsPageScrollbar.scanDomNode(); + + parent.appendChild(this.container); } private async buildCategoriesSlide() { - 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 => { - 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"), - }), - $('h3.category-title', {}, category.title), - $('.category-progress', { 'x-data-category-id': category.id, }, - $('.progress-bar-outer', { 'role': 'progressbar' }, - $('.progress-bar-inner')))); - }); - - const categoryScrollContainer = $('.getting-started-categories-scrolling-container'); - const categoriesContainer = $('ul.getting-started-categories-container', { 'role': 'list' }); - categoryElements.forEach(element => { - categoriesContainer.appendChild(element); - }); - - categoriesContainer.appendChild( - $('.no-categories', {}, - localize('no categories', "No remaining walkthroughs."), - $('button.button-link', { 'x-dispatch': 'configureVisibility' }, localize('configure visiblity', "Configure visibility?"))) - ); - - - categoryScrollContainer.appendChild(categoriesContainer); - - if (this.categoriesScrollbar) { this.categoriesScrollbar.dispose(); } - this.categoriesScrollbar = this._register(new DomScrollableElement(categoryScrollContainer, {})); - const showOnStartupCheckbox = $('input.checkbox', { id: 'showOnStartup', type: 'checkbox' }) as HTMLInputElement; showOnStartupCheckbox.checked = this.configurationService.getValue(configurationKey) === 'gettingStarted'; @@ -433,53 +514,61 @@ export class GettingStartedPage extends EditorPane { } })); - const categoriesSlide = assertIsDefined(this.container.querySelector('.gettingStartedSlideCategory') as HTMLElement); - reset(categoriesSlide, - $('.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-split-view', {}, - $('.categories-left', {}, - this.buildStartList(), - this.buildRecentlyOpenedList(), - ), - $('.categories-right', {}, - $('h2', {}, localize('gettingStarted', "Getting Started")), - this.categoriesScrollbar.getDomNode(), - ), - ) - ), - $('.footer', {}, - $('p.showOnStartup', {}, - showOnStartupCheckbox, - $('label.caption', { for: 'showOnStartup' }, localize('welcomePage.showOnStartup', "Show welcome page on startup"))) - ) + const header = $('.header', {}, + $('h1.product-name.caption', {}, this.productService.nameLong), + $('p.subtitle.description', {}, localize({ key: 'gettingStarted.editingEvolved', comment: ['Shown as subtitle on the Welcome page.'] }, "Editing evolved")) ); - this.categoriesScrollbar.scanDomNode(); + + const footer = $('.footer', {}, + $('p.showOnStartup', {}, showOnStartupCheckbox, $('label.caption', { for: 'showOnStartup' }, localize('welcomePage.showOnStartup', "Show welcome page on startup"))), + $('p.configureVisibility', {}, $('button.button-link', { 'x-dispatch': 'configureVisibility' }, localize('configureVisibility', "Configure Welcome Page Content"))) + ); + + const leftColumn = $('.categories-column.categories-column-left', {},); + const rightColumn = $('.categories-column.categories-column-right', {},); + + const startList = this.buildStartList(); + const recentList = this.buildRecentlyOpenedList(); + const gettingStartedList = this.buildGettingStartedWalkthroughsList(); + + const layoutLists = () => { + if (gettingStartedList.itemCount) { + reset(leftColumn, startList.getDomElement(), recentList.getDomElement()); + reset(rightColumn, gettingStartedList.getDomElement()); + recentList.setLimit(5); + } + else { + reset(leftColumn, startList.getDomElement()); + reset(rightColumn, recentList.getDomElement()); + recentList.setLimit(10); + } + setTimeout(() => this.categoriesPageScrollbar?.scanDomNode(), 50); + }; + + gettingStartedList.onDidChange(layoutLists); + layoutLists(); + + reset(this.categoriesSlide, $('.gettingStartedCategoriesContainer', {}, header, leftColumn, rightColumn, footer,)); + this.categoriesPageScrollbar?.scanDomNode(); this.updateCategoryProgress(); - - assertIsDefined(this.container.querySelector('.product-name')).textContent = this.productService.nameLong; this.registerDispatchListeners(); - if (this.editorInput.selectedCategory) { this.currentCategory = this.gettingStartedCategories.find(category => category.id === this.editorInput.selectedCategory); if (!this.currentCategory) { console.error('Could not restore to category ' + this.editorInput.selectedCategory + ' as it was not found'); this.editorInput.selectedCategory = undefined; - this.editorInput.selectedTask = undefined; + this.editorInput.selectedStep = undefined; } else { - this.buildCategorySlide(this.editorInput.selectedCategory, this.editorInput.selectedTask); + this.buildCategorySlide(this.editorInput.selectedCategory, this.editorInput.selectedStep); this.setSlide('details'); return; } } - const someItemsComplete = this.gettingStartedCategories.some(categry => categry.content.type === 'items' && categry.content.stepsComplete); - if (!someItemsComplete && !this.hasScrolledToFirstCategory) { + const someStepsComplete = this.gettingStartedCategories.some(categry => categry.content.type === 'steps' && categry.content.stepsComplete); + if (!someStepsComplete && !this.hasScrolledToFirstCategory) { const fistContentBehaviour = !this.storageService.get(lastSessionDateStorageKey, StorageScope.GLOBAL) // isNewUser ? @@ -489,12 +578,12 @@ export class GettingStartedPage extends EditorPane { new Promise<'index'>(resolve => setTimeout(() => resolve('index'), 1000)), ]); - if (this.gettingStartedCategories.some(category => category.content.type === 'items' && category.content.stepsComplete)) { + if (this.gettingStartedCategories.some(category => category.content.type === 'steps' && category.content.stepsComplete)) { this.setSlide('categories'); return; } else { if (fistContentBehaviour === 'openToFirstCategory') { - const first = this.gettingStartedCategories.find(category => category.content.type === 'items'); + const first = this.gettingStartedCategories.find(category => category.content.type === 'steps'); this.hasScrolledToFirstCategory = true; if (first) { this.currentCategory = first; @@ -510,7 +599,7 @@ export class GettingStartedPage extends EditorPane { this.setSlide('categories'); } - private buildRecentlyOpenedList(): HTMLElement { + private buildRecentlyOpenedList(): GettingStartedIndexList { const renderRecent = (recent: (IRecentFolder | IRecentWorkspace)) => { let fullPath: string; let windowOpenable: IWindowOpenable; @@ -531,6 +620,7 @@ export class GettingStartedPage extends EditorPane { link.title = fullPath; link.setAttribute('aria-label', localize('welcomePage.openFolderWithPath', "Open folder {0} with path {1}", name, parentPath)); link.addEventListener('click', e => { + this.telemetryService.publicLog2('gettingStarted.ActionExecuted', { command: 'openRecent', argument: undefined }); this.hostService.openWindow([windowOpenable], { forceNewWindow: e.ctrlKey || e.metaKey, remoteAuthority: recent.remoteAuthority }); e.preventDefault(); e.stopPropagation(); @@ -547,82 +637,125 @@ export class GettingStartedPage extends EditorPane { return li; }; - const container = $('.recently-opened-container', {}, - $('h2', {}, localize('recent', "Recent")), - $('ul.recent')); + if (this.recentlyOpenedList) { this.recentlyOpenedList.dispose(); } + + const recentlyOpenedList = this.recentlyOpenedList = new GettingStartedIndexList( + localize('recent', "Recent"), + 'recently-opened', + 5, + $('.empty-recent', {}, 'You have no recent folders,', $('button.button-link', { 'x-dispatch': 'openFolder' }, 'open a folder'), 'to start.'), + $('.more', {}, + $('button.button-link', + { + 'x-dispatch': 'showMoreRecents', + title: localize('show more recents', "Show All Recent Folders {0}", this.getKeybindingLabel('workbench.action.openRecent')) + }, 'More...')), + renderRecent); + + recentlyOpenedList.onDidChange(() => this.registerDispatchListeners()); this.recentlyOpened.then(({ workspaces }) => { // Filter out the current workspace workspaces = workspaces.filter(recent => !this.workspaceContextService.isCurrentWorkspace(isRecentWorkspace(recent) ? recent.workspace : recent.folderUri)); - const ul = container.querySelector('ul'); - if (!ul) { - return; - } - if (!workspaces.length) { - this.container.classList.add('emptyRecent'); - ul.append($('.empty-recent', {}, 'You have no recent folders,', $('button.button-link', { 'x-dispatch': 'openFolder' }, 'open a folder'), 'to start.')); - return; - } - - const workspacesToShow = workspaces.slice(0, 5); - const updateEntries = () => { - const listEntries = workspacesToShow.map(renderRecent); - while (ul.firstChild) { - ul.removeChild(ul.firstChild); - } - ul.append(...listEntries); - ul.append($('.more', {}, $('button.button-link', { 'x-dispatch': 'showMoreRecents' }, 'More...'), $('span.keybinding-label', {}, this.getKeybindingLabel('workbench.action.openRecent')))); - }; + const updateEntries = () => { recentlyOpenedList.setEntries(workspaces); }; updateEntries(); - this._register(this.labelService.onDidChangeFormatters(() => { - updateEntries(); - this.registerDispatchListeners(); - })); - }).then(() => this.registerDispatchListeners(), onUnexpectedError); + recentlyOpenedList.register(this.labelService.onDidChangeFormatters(() => updateEntries())); + }).catch(onUnexpectedError); - return container; + return recentlyOpenedList; } + private buildStartList(): GettingStartedIndexList { + const renderStartEntry = (entry: IGettingStartedCategory): HTMLElement | undefined => + entry.content.type === 'steps' + ? undefined + : $('li', + {}, + $('button.button-link', + { + 'x-dispatch': 'selectCategory:' + entry.id, + title: entry.description + this.getKeybindingLabel(entry.content.command), + }, + this.iconWidgetFor(entry), + $('span', {}, entry.title))); - private buildStartList(): HTMLElement { - const renderStartEntry = (entry: IGettingStartedCategory): HTMLElement | undefined => { + if (this.startList) { this.startList.dispose(); } - if (entry.content.type === 'items') { return undefined; } + const startList = this.startList = new GettingStartedIndexList( + localize('start', "Start"), + 'start-container', + 10, + undefined, + undefined, + renderStartEntry); - const li = $('li', {}); - const button = $('button.button-link', { 'x-dispatch': 'selectCategory:' + entry.id }, this.iconWidgetFor(entry), $('span', {}, entry.title)); - button.title = entry.description; + startList.setEntries(this.gettingStartedCategories); + startList.onDidChange(() => this.registerDispatchListeners()); + return startList; + } - li.appendChild(button); + private buildGettingStartedWalkthroughsList(): GettingStartedIndexList { - return li; - }; - const ul = $('ul.start'); - const container = $('.start-container', {}, $('h2', {}, localize('start', "Start")), ul); - const updateEntries = () => { - const listEntries = this.gettingStartedCategories.map(renderStartEntry); - while (ul.firstChild) { - ul.removeChild(ul.firstChild); + const renderGetttingStaredWalkthrough = (category: IGettingStartedCategory) => { + const hiddenCategories = this.getHiddenCategories(); + + if (category.content.type !== 'steps' || hiddenCategories.has(category.id)) { + return undefined; } - ul.append(...coalesce(listEntries)); + + 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"), + }), + $('h3.category-title.max-lines-3', { 'x-category-title-for': category.id }, category.title), + $('.category-progress', { 'x-data-category-id': category.id, }, + $('.progress-bar-outer', { 'role': 'progressbar' }, + $('.progress-bar-inner')))); }; - updateEntries(); - return container; + if (this.gettingStartedList) { this.gettingStartedList.dispose(); } + + const gettingStartedList = this.gettingStartedList = new GettingStartedIndexList( + localize('gettingStarted', "Getting Started"), + 'getting-started', + 10, + undefined, + undefined, + renderGetttingStaredWalkthrough); + + gettingStartedList.onDidChange(() => { + this.registerDispatchListeners(); + this.updateCategoryProgress(); + }); + gettingStartedList.setEntries(this.gettingStartedCategories); + + return gettingStartedList; } - - layout(size: Dimension) { - this.categoriesScrollbar?.scanDomNode(); this.detailsScrollbar?.scanDomNode(); - this.detailImageScrollbar?.scanDomNode(); - this.container.classList[size.height <= 685 ? 'add' : 'remove']('height-constrained'); + this.categoriesPageScrollbar?.scanDomNode(); + this.detailsPageScrollbar?.scanDomNode(); - 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`; + this.startList?.layout(size); + this.gettingStartedList?.layout(size); + this.recentlyOpenedList?.layout(size); + + this.container.classList[size.height <= 600 ? 'add' : 'remove']('height-constrained'); + this.container.classList[size.width <= 400 ? 'add' : 'remove']('width-constrained'); + this.container.classList[size.width <= 800 ? 'add' : 'remove']('width-semi-constrained'); + + if (this.selectedStepElement) { + this.selectedStepElement.style.height = ``; // unset or the scrollHeight will just be the old height + this.selectedStepElement.style.height = `${this.selectedStepElement.scrollHeight}px`; } } @@ -631,9 +764,9 @@ export class GettingStartedPage extends EditorPane { const categoryID = element.getAttribute('x-data-category-id'); const category = this.gettingStartedCategories.find(category => category.id === categoryID); if (!category) { throw Error('Could not find category with ID ' + categoryID); } - if (category.content.type !== 'items') { throw Error('Category with ID ' + categoryID + ' is not of items type'); } - const numDone = category.content.stepsComplete = category.content.items.filter(task => task.done).length; - const numTotal = category.content.stepsTotal = category.content.items.length; + if (category.content.type !== 'steps') { throw Error('Category with ID ' + categoryID + ' is not of steps type'); } + const numDone = category.content.stepsComplete = category.content.steps.filter(step => step.done).length; + const numTotal = category.content.stepsTotal = category.content.steps.length; const bar = assertIsDefined(element.querySelector('.progress-bar-inner')) as HTMLDivElement; bar.setAttribute('aria-valuemin', '0'); @@ -643,17 +776,17 @@ export class GettingStartedPage extends EditorPane { bar.style.width = `${progress}%`; if (numTotal === numDone) { - bar.title = `All items complete!`; + bar.title = `All steps complete!`; } else { - bar.title = `${numDone} of ${numTotal} items complete`; + bar.title = `${numDone} of ${numTotal} steps complete`; } }); } private async scrollToCategory(categoryID: string) { this.inProgressScroll = this.inProgressScroll.then(async () => { - this.clearDetialView(); + reset(this.stepsContent); this.editorInput.selectedCategory = categoryID; this.currentCategory = this.gettingStartedCategories.find(category => category.id === categoryID); this.buildCategorySlide(categoryID); @@ -661,112 +794,173 @@ export class GettingStartedPage extends EditorPane { }); } - private iconWidgetFor(category: IGettingStartedCategoryDescriptor) { + private iconWidgetFor(category: IGettingStartedCategory) { return category.icon.type === 'icon' ? $(ThemeIcon.asCSSSelector(category.icon.icon)) : $('img.category-icon', { src: category.icon.path }); } - private buildCategorySlide(categoryID: string, selectedItem?: string) { + private buildStepMarkdownDescription(container: HTMLElement, text: LinkedText[]) { + while (container.firstChild) { container.removeChild(container.firstChild); } + + for (const linkedText of text) { + if (linkedText.nodes.length === 1 && typeof linkedText.nodes[0] !== 'string') { + const node = linkedText.nodes[0]; + const buttonContainer = append(container, $('.button-container')); + const button = new Button(buttonContainer, { title: node.title, supportIcons: true }); + + const isCommand = node.href.startsWith('command:'); + const toSide = node.href.startsWith('command:toSide:'); + const command = node.href.replace(/command:(toSide:)?/, 'command:'); + + button.label = node.label; + button.onDidClick(async e => { + e.stopPropagation(); + e.preventDefault(); + + this.telemetryService.publicLog2('gettingStarted.ActionExecuted', { command: 'runStepAction', argument: node.href }); + + const fullSize = this.groupsService.contentDimension; + + if (toSide && fullSize.width > 700) { + if (this.groupsService.count === 1) { + this.groupsService.addGroup(this.groupsService.groups[0], GroupDirection.LEFT, { activate: true }); + + let gettingStartedSize: number; + if (fullSize.width > 1600) { + gettingStartedSize = 800; + } else if (fullSize.width > 800) { + gettingStartedSize = 400; + } else { + gettingStartedSize = 350; + } + + const gettingStartedGroup = this.groupsService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE).find(group => (group.activeEditor instanceof GettingStartedInput)); + this.groupsService.setSize(assertIsDefined(gettingStartedGroup), { width: gettingStartedSize, height: fullSize.height }); + } + + const nonGettingStartedGroup = this.groupsService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE).find(group => !(group.activeEditor instanceof GettingStartedInput)); + if (nonGettingStartedGroup) { + this.groupsService.activateGroup(nonGettingStartedGroup); + nonGettingStartedGroup.focus(); + } + } + this.openerService.open(command, { allowCommands: true }); + + }, null, this.detailsPageDisposables); + + if (isCommand) { + const keybindingLabel = this.getKeybindingLabel(command); + if (keybindingLabel) { + container.appendChild($('span.shortcut-message', {}, 'Tip: Use keyboard shortcut ', $('span.keybinding', {}, keybindingLabel))); + } + } + + this.detailsPageDisposables.add(button); + this.detailsPageDisposables.add(attachButtonStyler(button, this.themeService)); + } else { + const p = append(container, $('p')); + for (const node of linkedText.nodes) { + if (typeof node === 'string') { + append(p, renderFormattedText(node, { inline: true, renderCodeSegements: true })); + } else { + const link = this.instantiationService.createInstance(Link, node); + + append(p, link.el); + this.detailsPageDisposables.add(link); + this.detailsPageDisposables.add(attachLinkStyler(link, this.themeService)); + } + } + } + } + return container; + } + + override clearInput() { + this.stepDisposables.clear(); + super.clearInput(); + } + + private buildCategorySlide(categoryID: string, selectedStep?: string) { + if (this.detailsScrollbar) { this.detailsScrollbar.dispose(); } + + this.detailsPageDisposables.clear(); + const category = this.gettingStartedCategories.find(category => category.id === categoryID); - let foundNext = false; - const nextCategory = this.gettingStartedCategories.find(category => { - if (foundNext && category.content.type === 'items') { return true; } - if (category.id === categoryID) { foundNext = true; } - return false; - }); if (!category) { throw Error('could not find category with ID ' + categoryID); } - if (category.content.type !== 'items') { throw Error('category with ID ' + categoryID + ' is not of items type'); } + if (category.content.type !== 'steps') { throw Error('category with ID ' + categoryID + ' is not of steps type'); } - const leftColumn = assertIsDefined(this.container.querySelector('.getting-started-detail-left')); - const detailTitle = assertIsDefined(this.container.querySelector('.getting-started-detail-title')); - const oldTitle = detailTitle.querySelector('.getting-started-category'); - if (oldTitle) { detailTitle.removeChild(oldTitle); } - - detailTitle.appendChild( + const categoryDescriptorComponent = $('.getting-started-category', {}, this.iconWidgetFor(category), $('.category-description-container', {}, - $('h2.category-title', {}, category.title), - $('.category-description.description', {}, category.description)))); + $('h2.category-title.max-lines-3', { 'x-category-title-for': category.id }, category.title), + $('.category-description.description.max-lines-3', { 'x-category-description-for': category.id }, category.description))); - const categoryElements = category.content.items.map( - (task, i, arr) => { - - const codicon = $('.codicon' + (task.done ? '.complete' + ThemeIcon.asCSSSelector(gettingStartedCheckedCodicon) : ThemeIcon.asCSSSelector(gettingStartedUncheckedCodicon)), + const categoryElements = category.content.steps.map( + (step, i, arr) => { + const codicon = $('.codicon' + (step.done ? '.complete' + ThemeIcon.asCSSSelector(gettingStartedCheckedCodicon) : ThemeIcon.asCSSSelector(gettingStartedUncheckedCodicon)), { - 'data-done-task-id': task.id, - 'x-dispatch': 'toggleTaskCompletion:' + task.id, + 'data-done-step-id': step.id, + 'x-dispatch': 'toggleStepCompletion:' + step.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 container = $('.step-description-container', { 'x-step-description-for': step.id }); + this.buildStepMarkdownDescription(container, step.description); - - 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) }), - taskActions, + const stepDescription = $('.step-container', {}, + $('h3.step-title.max-lines-3', { 'x-step-title-for': step.id }, step.title), + container, ); - const keybindingLabel = (task.button.command && this.getKeybindingLabel(task.button.command)); - if (keybindingLabel) { - taskDescription.appendChild($('span.shortcut-message', {}, 'Tip: Use keyboard shortcut ', $('span.keybinding', {}, keybindingLabel))); + if (step.media.type === 'image') { + stepDescription.appendChild( + $('.image-description', { 'aria-label': localize('imageShowing', "Image showing {0}", step.media.altText) }), + ); } - return $('button.getting-started-task', + return $('button.getting-started-step', { - 'x-dispatch': 'selectTask:' + task.id, - 'data-task-id': task.id, + 'x-dispatch': 'selectTask:' + step.id, + 'data-step-id': step.id, 'aria-expanded': 'false', - 'aria-checked': '' + task.done, + 'aria-checked': '' + step.done, 'role': 'listitem', }, codicon, - taskDescription); + stepDescription); }); - const detailContainer = $('.getting-started-detail-container', { 'role': 'list' }); - if (this.detailsScrollbar) { this.detailsScrollbar.getDomNode().remove(); this.detailsScrollbar.dispose(); } - this.detailsScrollbar = this._register(new DomScrollableElement(detailContainer, { className: 'full-height-scrollable' })); - categoryElements.forEach(element => detailContainer.appendChild(element)); - leftColumn.appendChild(this.detailsScrollbar.getDomNode()); + const stepsContainer = $('.getting-started-detail-container', { 'role': 'list' }, ...categoryElements); + this.detailsScrollbar = this._register(new DomScrollableElement(stepsContainer, { className: 'steps-container' })); + const stepListComponent = this.detailsScrollbar.getDomNode(); + + reset(this.stepsContent, categoryDescriptorComponent, stepListComponent, this.stepMediaComponent); + + const toExpand = category.content.steps.find(step => !step.done) ?? category.content.steps[0]; + this.selectStep(selectedStep ?? toExpand.id, false); - const toExpand = category.content.items.find(item => !item.done) ?? category.content.items[0]; - this.selectTask(selectedItem ?? toExpand.id, false); this.detailsScrollbar.scanDomNode(); + this.detailsPageScrollbar?.scanDomNode(); + this.registerDispatchListeners(); } - private clearDetialView() { - const detailContainer = (this.container.querySelector('.getting-started-detail-container')); - detailContainer?.remove(); - const detailTitle = assertIsDefined(this.container.querySelector('.getting-started-detail-title')); - while (detailTitle.firstChild) { detailTitle.removeChild(detailTitle.firstChild); } - } - private getKeybindingLabel(command: string) { - const binding = this.keybindingService.lookupKeybinding(command); - if (!binding) { return ''; } - else { return binding.getLabel() ?? ''; } + command = command.replace(/^command:/, ''); + const label = this.keybindingService.lookupKeybinding(command)?.getLabel(); + if (!label) { return ''; } + else { + return `(${label})`; + } } private async scrollPrev() { this.inProgressScroll = this.inProgressScroll.then(async () => { this.currentCategory = undefined; this.editorInput.selectedCategory = undefined; - this.editorInput.selectedTask = undefined; - this.selectTask(undefined); + this.editorInput.selectedStep = undefined; + this.selectStep(undefined); this.setSlide('categories'); }); } @@ -785,11 +979,11 @@ export class GettingStartedPage extends EditorPane { focusNext() { if (this.editorInput.selectedCategory) { - const allTasks = this.currentCategory?.content.type === 'items' && this.currentCategory.content.items; - if (allTasks) { - const toFind = this.editorInput.selectedTask ?? this.previousSelection; - const selectedIndex = allTasks.findIndex(task => task.id === toFind); - if (allTasks[selectedIndex + 1]?.id) { this.selectTask(allTasks[selectedIndex + 1]?.id, true, false); } + const allSteps = this.currentCategory?.content.type === 'steps' && this.currentCategory.content.steps; + if (allSteps) { + const toFind = this.editorInput.selectedStep ?? this.previousSelection; + const selectedIndex = allSteps.findIndex(step => step.id === toFind); + if (allSteps[selectedIndex + 1]?.id) { this.selectStep(allSteps[selectedIndex + 1]?.id, true, false); } } } else { (document.activeElement?.nextElementSibling as HTMLElement)?.focus?.(); @@ -798,11 +992,11 @@ export class GettingStartedPage extends EditorPane { focusPrevious() { if (this.editorInput.selectedCategory) { - const allTasks = this.currentCategory?.content.type === 'items' && this.currentCategory.content.items; - if (allTasks) { - const toFind = this.editorInput.selectedTask ?? this.previousSelection; - const selectedIndex = allTasks.findIndex(task => task.id === toFind); - if (allTasks[selectedIndex - 1]?.id) { this.selectTask(allTasks[selectedIndex - 1]?.id, true, false); } + const allSteps = this.currentCategory?.content.type === 'steps' && this.currentCategory.content.steps; + if (allSteps) { + const toFind = this.editorInput.selectedStep ?? this.previousSelection; + const selectedIndex = allSteps.findIndex(step => step.id === toFind); + if (allSteps[selectedIndex - 1]?.id) { this.selectStep(allSteps[selectedIndex - 1]?.id, true, false); } } } else { (document.activeElement?.previousElementSibling as HTMLElement)?.focus?.(); @@ -815,42 +1009,126 @@ export class GettingStartedPage extends EditorPane { slideManager.classList.remove('showDetails'); 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.container.querySelector('.gettingStartedSlideCategory')!.querySelectorAll('input').forEach(button => button.disabled = false); + this.container.querySelector('.gettingStartedSlideCategories')!.querySelectorAll('button').forEach(button => button.disabled = false); + this.container.querySelector('.gettingStartedSlideCategories')!.querySelectorAll('input').forEach(button => button.disabled = false); this.container.focus(); } else { slideManager.classList.add('showDetails'); slideManager.classList.remove('showCategories'); this.container.querySelector('.gettingStartedSlideDetails')!.querySelectorAll('button').forEach(button => button.disabled = false); - this.container.querySelector('.gettingStartedSlideCategory')!.querySelectorAll('button').forEach(button => button.disabled = true); - this.container.querySelector('.gettingStartedSlideCategory')!.querySelectorAll('input').forEach(button => button.disabled = true); + this.container.querySelector('.gettingStartedSlideCategories')!.querySelectorAll('button').forEach(button => button.disabled = true); + this.container.querySelector('.gettingStartedSlideCategories')!.querySelectorAll('input').forEach(button => button.disabled = true); } } } -export class GettingStartedInputFactory implements IEditorInputFactory { +export class GettingStartedInputSerializer implements IEditorInputSerializer { public canSerialize(editorInput: GettingStartedInput): boolean { return true; } public serialize(editorInput: GettingStartedInput): string { - return JSON.stringify({ selectedCategory: editorInput.selectedCategory, selectedTask: editorInput.selectedTask }); + return JSON.stringify({ selectedCategory: editorInput.selectedCategory, selectedStep: editorInput.selectedStep }); } public deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): GettingStartedInput { try { - const { selectedCategory, selectedTask } = JSON.parse(serializedEditorInput); - return new GettingStartedInput({ selectedCategory, selectedTask }); + const { selectedCategory, selectedStep } = JSON.parse(serializedEditorInput); + return new GettingStartedInput({ selectedCategory, selectedStep }); } catch { } return new GettingStartedInput({}); } } +class GettingStartedIndexList extends Disposable { + private readonly _onDidChangeEntries = new Emitter(); + private readonly onDidChangeEntries: Event = this._onDidChangeEntries.event; + + private domElement: HTMLElement; + private list: HTMLUListElement; + private scrollbar: DomScrollableElement; + + private entries: T[]; + + public itemCount: number; + + constructor( + title: string, + klass: string, + private limit: number, + private empty: HTMLElement | undefined, + private more: HTMLElement | undefined, + private renderElement: (item: T) => HTMLElement | undefined, + ) { + super(); + this.entries = []; + this.itemCount = 0; + this.list = $('ul'); + this.scrollbar = this._register(new DomScrollableElement(this.list, {})); + this._register(this.onDidChangeEntries(() => this.scrollbar.scanDomNode())); + this.domElement = $('.index-list.' + klass, {}, + $('h2', {}, title), + this.scrollbar.getDomNode()); + } + + getDomElement() { + return this.domElement; + } + + layout(size: Dimension) { + this.scrollbar.scanDomNode(); + } + + onDidChange(listener: () => void) { + this._register(this.onDidChangeEntries(listener)); + } + + register(d: IDisposable) { this._register(d); } + + setLimit(limit: number) { + this.limit = limit; + this.setEntries(this.entries); + } + + rerender() { + this.setEntries(this.entries); + } + + setEntries(entries: T[]) { + this.itemCount = 0; + this.entries = entries; + while (this.list.firstChild) { + this.list.removeChild(this.list.firstChild); + } + + + for (const entry of entries) { + const rendered = this.renderElement(entry); + if (!rendered) { continue; } + this.itemCount++; + if (this.itemCount > this.limit) { + if (this.more) { + this.list.appendChild(this.more); + } + break; + } else { + this.list.appendChild(rendered); + } + } + + if (this.itemCount === 0 && this.empty) { + this.list.appendChild(this.empty); + } + + this._onDidChangeEntries.fire(); + } +} + registerThemingParticipant((theme, collector) => { const backgroundColor = theme.getColor(welcomePageBackground); if (backgroundColor) { - collector.addRule(`.monaco-workbench .part.editor > .content .welcomePageContainer { background-color: ${backgroundColor}; }`); + collector.addRule(`.monaco-workbench .part.editor > .content .gettingStartedContainer { background-color: ${backgroundColor}; }`); } const foregroundColor = theme.getColor(foreground); @@ -867,8 +1145,8 @@ registerThemingParticipant((theme, collector) => { const iconColor = theme.getColor(textLinkForeground); if (iconColor) { collector.addRule(`.monaco-workbench .part.editor > .content .gettingStartedContainer .getting-started-category .codicon:not(.codicon-close) { color: ${iconColor} }`); - collector.addRule(`.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task .codicon.complete { color: ${iconColor} } `); - collector.addRule(`.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task.expanded .codicon { color: ${iconColor} } `); + collector.addRule(`.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideDetails .getting-started-step .codicon.complete { color: ${iconColor} } `); + collector.addRule(`.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideDetails .getting-started-step.expanded .codicon { color: ${iconColor} } `); } const buttonColor = theme.getColor(welcomePageTileBackground); @@ -878,7 +1156,7 @@ registerThemingParticipant((theme, collector) => { const shadowColor = theme.getColor(welcomePageTileShadow); if (shadowColor) { - collector.addRule(`.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.categories .getting-started-category { filter: drop-shadow(2px 2px 2px ${buttonColor}); }`); + collector.addRule(`.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideCategories .getting-started-category { filter: drop-shadow(2px 2px 2px ${buttonColor}); }`); } const buttonHoverColor = theme.getColor(welcomePageTileHoverBackground); @@ -899,9 +1177,9 @@ registerThemingParticipant((theme, collector) => { collector.addRule(`.monaco-workbench .part.editor > .content .gettingStartedContainer button.emphasis { background: ${emphasisButtonBackground}; }`); } - const pendingItemColor = theme.getColor(descriptionForeground); - if (pendingItemColor) { - collector.addRule(`.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task .codicon { color: ${pendingItemColor} } `); + const pendingStepColor = theme.getColor(descriptionForeground); + if (pendingStepColor) { + collector.addRule(`.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideDetails .getting-started-step .codicon { color: ${pendingStepColor} } `); } const emphasisButtonHoverBackground = theme.getColor(buttonHoverBackground); @@ -936,10 +1214,10 @@ registerThemingParticipant((theme, collector) => { const progressBackground = theme.getColor(welcomePageProgressBackground); if (progressBackground) { - collector.addRule(`.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.categories .progress-bar-outer { background-color: ${progressBackground}; }`); + collector.addRule(`.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideCategories .progress-bar-outer { background-color: ${progressBackground}; }`); } const progressForeground = theme.getColor(welcomePageProgressForeground); if (progressForeground) { - collector.addRule(`.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.categories .progress-bar-inner { background-color: ${progressForeground}; }`); + collector.addRule(`.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideCategories .progress-bar-inner { background-color: ${progressForeground}; }`); } }); diff --git a/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStartedExtensionPoint.ts b/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStartedExtensionPoint.ts new file mode 100644 index 00000000..ae51f02c --- /dev/null +++ b/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStartedExtensionPoint.ts @@ -0,0 +1,184 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { localize } from 'vs/nls'; +import { IStartEntry, IWalkthrough } from 'vs/platform/extensions/common/extensions'; +import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; + +export const walkthroughsExtensionPoint = ExtensionsRegistry.registerExtensionPoint({ + extensionPoint: 'walkthroughs', + jsonSchema: { + doNotSuggest: true, + description: localize('walkthroughs', "Contribute collections of steps to help users with your extension. Experimental, available in VS Code Insiders only."), + type: 'array', + items: { + type: 'object', + required: ['id', 'title', 'description', 'steps'], + defaultSnippets: [{ body: { 'id': '$1', 'title': '$2', 'description': '$3', 'steps': [] } }], + 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.") + }, + primary: { + type: 'boolean', + description: localize('walkthroughs.primary', "if this is a `primary` walkthrough, hinting if it should be opened on install of the extension. The first `primary` walkthough with a `when` condition matching the current context may be opened by core on install of the extension.") + }, + when: { + type: 'string', + description: localize('walkthroughs.when', "Context key expression to control the visibility of this walkthrough.") + }, + tasks: { + deprecationMessage: localize('usesteps', "Deprecated. Use `steps` instead") + }, + steps: { + type: 'array', + description: localize('walkthroughs.steps', "Steps to complete as part of this walkthrough."), + items: { + type: 'object', + required: ['id', 'title', 'description', 'media'], + defaultSnippets: [{ + body: { + 'id': '$1', 'title': '$2', 'description': '$3', + 'doneOn': { 'command': '$5' }, + 'media': { 'path': '$6', 'type': '$7' } + } + }], + properties: { + id: { + type: 'string', + description: localize('walkthroughs.steps.id', "Unique identifier for this step. This is used to keep track of which steps have been completed."), + }, + title: { + type: 'string', + description: localize('walkthroughs.steps.title', "Title of step.") + }, + description: { + type: 'string', + description: localize('walkthroughs.steps.description', "Description of step. Supports ``preformatted``, __italic__, and **bold** text. Use markdown-style links for commands or external links: [Title](command:myext.command), [Title](command:toSide:myext.command), or [Title](https://aka.ms). Links on their own line will be rendered as buttons.") + }, + button: { + deprecationMessage: localize('walkthroughs.steps.button.deprecated', "Deprecated. Use markdown links in the description instead, i.e. [Title](command:myext.command), [Title](command:toSide:myext.command), or [Title](https://aka.ms), "), + }, + media: { + type: 'object', + description: localize('walkthroughs.steps.media', "Media to show alongside this step, either an image or markdown content."), + defaultSnippets: [{ 'body': { 'type': '$1', 'path': '$2' } }], + oneOf: [ + { + required: ['path', 'altText'], + additionalProperties: false, + properties: { + path: { + description: localize('walkthroughs.steps.media.image.path.string', "Path to an image - or object consisting of paths to light, dark, and hc images - relative to extension directory. Depending on context, the image will be displayed from 400px to 800px wide, with similar bounds on height. To support HIDPI displays, the image will be rendered at 1.5x scaling, for example a 900 physical pixels wide image will be displayed as 600 logical pixels wide."), + oneOf: [ + { + type: 'string', + }, + { + type: 'object', + required: ['dark', 'light', 'hc'], + properties: { + dark: { + description: localize('walkthroughs.steps.media.image.path.dark.string', "Path to the image for dark themes, relative to extension directory."), + type: 'string', + }, + light: { + description: localize('walkthroughs.steps.media.image.path.light.string', "Path to the image for light themes, relative to extension directory."), + type: 'string', + }, + hc: { + description: localize('walkthroughs.steps.media.image.path.hc.string', "Path to the image for hc themes, relative to extension directory."), + type: 'string', + } + } + } + ] + }, + altText: { + type: 'string', + description: localize('walkthroughs.steps.media.altText', "Alternate text to display when the image cannot be loaded or in screen readers.") + } + } + }, { + required: ['path'], + additionalProperties: false, + properties: { + path: { + description: localize('walkthroughs.steps.media.markdown.path', "Path to the markdown document, relative to extension directory."), + type: 'string', + } + } + } + ] + }, + doneOn: { + description: localize('walkthroughs.steps.doneOn', "Signal to mark step as complete."), + type: 'object', + required: ['command'], + defaultSnippets: [{ 'body': { command: '$1' } }], + properties: { + 'command': { + description: localize('walkthroughs.steps.oneOn.command', "Mark step done when the specified command is executed."), + type: 'string' + } + }, + }, + when: { + type: 'string', + description: localize('walkthroughs.steps.when', "Context key expression to control the visibility of this step.") + } + } + } + } + } + } + } +}); + +export const startEntriesExtensionPoint = ExtensionsRegistry.registerExtensionPoint({ + extensionPoint: 'startEntries', + jsonSchema: { + doNotSuggest: true, + description: localize('startEntries', "Contribute commands to help users start using your extension. Experimental, available in VS Code Insiders only."), + type: 'array', + items: { + type: 'object', + required: ['id', 'title', 'description'], + defaultSnippets: [{ body: { 'id': '$1', 'title': '$2', 'description': '$3' } }], + properties: { + title: { + type: 'string', + description: localize('startEntries.title', "Title of start item.") + }, + command: { + type: 'string', + description: localize('startEntries.command', "Command to run.") + }, + description: { + type: 'string', + description: localize('startEntries.description', "Description of start item.") + }, + when: { + type: 'string', + description: localize('startEntries.when', "Context key expression to control the visibility of this start item.") + }, + type: { + type: 'string', + enum: ['sample-notebook', 'template-folder'], + description: localize('startEntries.type', "The type of start item this is, used for grouping. Supported values are `sample-notebook` or `template-folder`.") + } + } + } + } +}); diff --git a/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStartedIcons.ts b/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStartedIcons.ts index d745beed..1570f525 100644 --- a/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStartedIcons.ts +++ b/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStartedIcons.ts @@ -6,5 +6,5 @@ import { localize } from 'vs/nls'; import { Codicon } from 'vs/base/common/codicons'; import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; -export const gettingStartedUncheckedCodicon = registerIcon('getting-started-item-unchecked', Codicon.circleLargeOutline, localize('gettingStartedUnchecked', "Used to represent getting started items which have not been completed")); -export const gettingStartedCheckedCodicon = registerIcon('getting-started-item-checked', Codicon.passFilled, localize('gettingStartedChecked', "Used to represent getting started items which have been completed")); +export const gettingStartedUncheckedCodicon = registerIcon('getting-started-step-unchecked', Codicon.circleLargeOutline, localize('gettingStartedUnchecked', "Used to represent getting started steps which have not been completed")); +export const gettingStartedCheckedCodicon = registerIcon('getting-started-step-checked', Codicon.passFilled, localize('gettingStartedChecked', "Used to represent getting started steps which have been completed")); diff --git a/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStartedInput.ts b/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStartedInput.ts index 2bd489a8..a6639f10 100644 --- a/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStartedInput.ts +++ b/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStartedInput.ts @@ -12,17 +12,18 @@ import { Schemas } from 'vs/base/common/network'; export const gettingStartedInputTypeId = 'workbench.editors.gettingStartedInput'; export class GettingStartedInput extends EditorInput { + static readonly ID = gettingStartedInputTypeId; + override get typeId(): string { + return GettingStartedInput.ID; + } + get resource(): URI | undefined { return URI.from({ scheme: Schemas.walkThrough, authority: 'vscode_getting_started_page' }); } - getTypeId(): string { - return GettingStartedInput.ID; - } - - matches(other: unknown) { + override matches(other: unknown) { if (other instanceof GettingStartedInput) { return other.selectedCategory === this.selectedCategory; } @@ -30,17 +31,17 @@ export class GettingStartedInput extends EditorInput { } constructor( - options: { selectedCategory?: string, selectedTask?: string } + options: { selectedCategory?: string, selectedStep?: string } ) { super(); this.selectedCategory = options.selectedCategory; - this.selectedTask = options.selectedTask; + this.selectedStep = options.selectedStep; } - getName() { + override getName() { return localize('gettingStarted', "Getting Started"); } selectedCategory: string | undefined; - selectedTask: string | undefined; + selectedStep: string | undefined; } diff --git a/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStartedService.ts b/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStartedService.ts index 28dd19e5..3ed42ffa 100644 --- a/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStartedService.ts +++ b/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStartedService.ts @@ -5,7 +5,6 @@ import { createDecorator, IInstantiationService, optional, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { Emitter, Event } from 'vs/base/common/event'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { Memento } from 'vs/workbench/common/memento'; import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; @@ -13,15 +12,14 @@ import { ICommandService } from 'vs/platform/commands/common/commands'; import { ContextKeyExpr, ContextKeyExpression, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { Disposable } from 'vs/base/common/lifecycle'; import { IUserDataAutoSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; -import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { IExtensionDescription, IStartEntry } from 'vs/platform/extensions/common/extensions'; import { URI } from 'vs/base/common/uri'; import { joinPath } from 'vs/base/common/resources'; import { FileAccess } from 'vs/base/common/network'; import { DefaultIconPath, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IProductService } from 'vs/platform/product/common/productService'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; -import { BuiltinGettingStartedCategory, BuiltinGettingStartedItem, content } from 'vs/workbench/contrib/welcome/gettingStarted/common/gettingStartedContent'; +import { BuiltinGettingStartedCategory, BuiltinGettingStartedStep, BuiltinGettingStartedStartEntry, startEntries, walkthroughs } from 'vs/workbench/contrib/welcome/gettingStarted/common/gettingStartedContent'; import { ITASExperimentService } from 'vs/workbench/services/experiment/common/experimentService'; import { assertIsDefined } from 'vs/base/common/types'; import { IHostService } from 'vs/workbench/services/host/browser/host'; @@ -30,6 +28,10 @@ import { GettingStartedInput } from 'vs/workbench/contrib/welcome/gettingStarted import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { GettingStartedPage } from 'vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { LinkedText, parseLinkedText } from 'vs/base/common/linkedText'; +import { walkthroughsExtensionPoint } from 'vs/workbench/contrib/welcome/gettingStarted/browser/gettingStartedExtensionPoint'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { dirname } from 'vs/base/common/path'; export const IGettingStartedService = createDecorator('gettingStartedService'); @@ -39,21 +41,37 @@ export const enum GettingStartedCategory { Advanced = 'Advanced' } -export interface IGettingStartedTask { +type LegacyButtonConfig = + | { title: string, command?: never, link: string } + | { title: string, command: string, link?: never, sideBySide?: boolean }; + +export interface IGettingStartedStep { id: string title: string - description: string + description: LinkedText[] category: GettingStartedCategory | string when: ContextKeyExpression order: number - button: - | { title: string, command?: never, link: string } - | { title: string, command: string, link?: never }, doneOn: { commandExecuted: string, eventFired?: never } | { eventFired: string, commandExecuted?: never } - media: { type: 'image', path: { hc: URI, light: URI, dark: URI }, altText: string } + media: + | { type: 'image', path: { hc: URI, light: URI, dark: URI }, altText: string } + | { type: 'markdown', path: URI, base: URI, } } -export interface IGettingStartedCategoryDescriptor { +export interface IGettingStartedWalkthroughDescriptor { + id: GettingStartedCategory | string + title: string + description: string + order: number + icon: + | { type: 'icon', icon: ThemeIcon } + | { type: 'image', path: string } + when: ContextKeyExpression + content: + | { type: 'steps' } +} + +export interface IGettingStartedStartEntryDescriptor { id: GettingStartedCategory | string title: string description: string @@ -63,7 +81,6 @@ export interface IGettingStartedCategoryDescriptor { | { type: 'image', path: string } when: ContextKeyExpression content: - | { type: 'items' } | { type: 'startEntry', command: string } } @@ -77,18 +94,18 @@ export interface IGettingStartedCategory { | { type: 'image', path: string } when: ContextKeyExpression content: - | { type: 'items', items: IGettingStartedTask[] } + | { type: 'steps', steps: IGettingStartedStep[] } | { type: 'startEntry', command: string } } -type TaskProgress = { done?: boolean; }; -export interface IGettingStartedTaskWithProgress extends IGettingStartedTask, Required { } +type StepProgress = { done?: boolean; }; +export interface IGettingStartedStepWithProgress extends IGettingStartedStep, Required { } export interface IGettingStartedCategoryWithProgress extends Omit { content: | { - type: 'items', - items: IGettingStartedTaskWithProgress[], + type: 'steps', + steps: IGettingStartedStepWithProgress[], done: boolean; stepsComplete: number stepsTotal: number @@ -99,59 +116,55 @@ export interface IGettingStartedCategoryWithProgress extends Omit readonly onDidAddCategory: Event - readonly onDidChangeTask: Event + readonly onDidRemoveCategory: Event + readonly onDidChangeStep: Event readonly onDidChangeCategory: Event - readonly onDidProgressTask: Event + readonly onDidProgressStep: Event getCategories(): IGettingStartedCategoryWithProgress[] progressByEvent(eventName: string): void; - progressTask(id: string): void; - deprogressTask(id: string): void; + progressStep(id: string): void; + deprogressStep(id: string): void; } export class GettingStartedService extends Disposable implements IGettingStartedService { declare readonly _serviceBrand: undefined; - private readonly _onDidAddTask = new Emitter(); - onDidAddTask: Event = this._onDidAddTask.event; private readonly _onDidAddCategory = new Emitter(); onDidAddCategory: Event = this._onDidAddCategory.event; + private readonly _onDidRemoveCategory = new Emitter(); + onDidRemoveCategory: Event = this._onDidRemoveCategory.event; + private readonly _onDidChangeCategory = new Emitter(); onDidChangeCategory: Event = this._onDidChangeCategory.event; - private readonly _onDidChangeTask = new Emitter(); - onDidChangeTask: Event = this._onDidChangeTask.event; + private readonly _onDidChangeStep = new Emitter(); + onDidChangeStep: Event = this._onDidChangeStep.event; - private readonly _onDidProgressTask = new Emitter(); - onDidProgressTask: Event = this._onDidProgressTask.event; + private readonly _onDidProgressStep = new Emitter(); + onDidProgressStep: Event = this._onDidProgressStep.event; private memento: Memento; - private taskProgress: Record; + private stepProgress: Record; private commandListeners = new Map(); private eventListeners = new Map(); - private trackedExtensions = new Set(); - private gettingStartedContributions = new Map(); - private tasks = new Map(); + private steps = new Map(); private tasExperimentService?: ITASExperimentService; private sessionInstalledExtensions = new Set(); - private overrideShortcircuit: Promise; - constructor( @IStorageService private readonly storageService: IStorageService, @ICommandService private readonly commandService: ICommandService, @IContextKeyService private readonly contextService: IContextKeyService, @IUserDataAutoSyncEnablementService readonly userDataAutoSyncEnablementService: IUserDataAutoSyncEnablementService, - @IExtensionService private readonly extensionService: IExtensionService, @IProductService private readonly productService: IProductService, @IEditorService private readonly editorService: IEditorService, @IEditorGroupsService private readonly editorGroupsService: IEditorGroupsService, @@ -166,16 +179,11 @@ export class GettingStartedService extends Disposable implements IGettingStarted this.tasExperimentService = tasExperimentService; this.memento = new Memento('gettingStartedService', this.storageService); - this.taskProgress = this.memento.getMemento(StorageScope.GLOBAL, StorageTarget.USER); + this.stepProgress = this.memento.getMemento(StorageScope.GLOBAL, StorageTarget.USER); - this.extensionService.getExtensions().then(extensions => { - extensions.forEach(extension => this.registerExtensionContributions(extension)); - }); - - this.extensionService.onDidChangeExtensions(() => { - this.extensionService.getExtensions().then(extensions => { - extensions.forEach(extension => this.registerExtensionContributions(extension)); - }); + walkthroughsExtensionPoint.setHandler((_, { added, removed }) => { + added.forEach(e => this.registerExtensionContributions(e.description)); + removed.forEach(e => this.unregisterExtensionContributions(e.description)); }); this._register(this.commandService.onDidExecuteCommand(command => this.progressByCommand(command.commandId))); @@ -190,107 +198,84 @@ export class GettingStartedService extends Disposable implements IGettingStarted this._register(userDataAutoSyncEnablementService.onDidChangeEnablement(() => { if (userDataAutoSyncEnablementService.isEnabled()) { this.progressByEvent('sync-enabled'); } })); - this.overrideShortcircuit = new Promise(resolve => setTimeout(resolve, 300)); - content.forEach(async (category, index) => { - category = await this.getCategoryOverrides(category); - this.registerCategory({ + startEntries.forEach(async (entry, index) => { + this.getCategoryOverrides(entry); + this.registerStartEntry({ + ...entry, + icon: { type: 'icon', icon: entry.icon }, + order: index, + when: ContextKeyExpr.deserialize(entry.when) ?? ContextKeyExpr.true() + }); + }); + + walkthroughs.forEach(async (category, index) => { + this.getCategoryOverrides(category); + this.registerWalkthrough({ ...category, icon: { type: 'icon', icon: category.icon }, order: index, when: ContextKeyExpr.deserialize(category.when) ?? ContextKeyExpr.true() - }); - - if (category.content.type === 'items') { - category.content.items.forEach(async (item, index) => { - item = await this.getTaskOverrides(item, category.id); - this.registerTask({ - ...item, + }, + category.content.steps.map((step, index) => { + this.getStepOverrides(step, category.id); + return ({ + ...step, + description: parseDescription(step.description), category: category.id, order: index, - when: ContextKeyExpr.deserialize(item.when) ?? ContextKeyExpr.true(), - media: { - type: item.media.type, - altText: item.media.altText, - path: convertPaths(item.media.path) - } + when: ContextKeyExpr.deserialize(step.when) ?? ContextKeyExpr.true(), + media: step.media.type === 'image' + ? { type: 'image', altText: step.media.altText, path: convertInternalMediaPathsToBrowserURIs(step.media.path) } + : { type: 'markdown', path: convertInternalMediaPathToFileURI(step.media.path), base: FileAccess.asFileUri('vs/workbench/contrib/welcome/gettingStarted/common/media/', require) }, }); - }); - } + })); }); } - private async getCategoryOverrides(category: BuiltinGettingStartedCategory): Promise { - return new Promise(async (resolve) => { - if (!this.tasExperimentService) { resolve(category); return; } - let resolved = false; + private async getCategoryOverrides(category: BuiltinGettingStartedCategory | BuiltinGettingStartedStartEntry) { + if (!this.tasExperimentService) { return; } - this.overrideShortcircuit.then(() => { - resolve(category); - resolved = true; - }); + const [title, description] = await Promise.all([ + this.tasExperimentService.getTreatment(`gettingStarted.overrideCategory.${category.id}.title`), + this.tasExperimentService.getTreatment(`gettingStarted.overrideCategory.${category.id}.description`), + ]); - const [title, description] = await Promise.all([ - this.tasExperimentService.getTreatment(`gettingStarted.overrideCategory.${category.id}.title`), - this.tasExperimentService.getTreatment(`gettingStarted.overrideCategory.${category.id}.description`), - ]); + if (!(title || description)) { return; } - if (resolved) { - const existing = assertIsDefined(this.gettingStartedContributions.get(category.id)); - existing.title = title ?? existing.title; - existing.description = description ?? existing.description; - this._onDidChangeCategory.fire(this.getCategoryProgress(existing)); - } else { - resolve({ - ...category, - title: title ?? category.title, - description: description ?? category.description, - }); - } - }); + const existing = assertIsDefined(this.gettingStartedContributions.get(category.id)); + existing.title = title ?? existing.title; + existing.description = description ?? existing.description; + this._onDidChangeCategory.fire(this.getCategoryProgress(existing)); } - private async getTaskOverrides(item: BuiltinGettingStartedItem, categoryId: string): Promise { - return new Promise(async (resolve) => { - if (!this.tasExperimentService) { resolve(item); return; } - let resolved = false; + private async getStepOverrides(step: BuiltinGettingStartedStep, categoryId: string) { + if (!this.tasExperimentService) { return; } - this.overrideShortcircuit.then(() => { - resolve(item); - resolved = true; - }); + const [title, description, media] = await Promise.all([ + this.tasExperimentService.getTreatment(`gettingStarted.overrideStep.${step.id}.title`), + this.tasExperimentService.getTreatment(`gettingStarted.overrideStep.${step.id}.description`), + this.tasExperimentService.getTreatment(`gettingStarted.overrideStep.${step.id}.media`), + ]); - const [title, description, media] = await Promise.all([ - this.tasExperimentService.getTreatment(`gettingStarted.overrideTask.${item.id}.title`), - this.tasExperimentService.getTreatment(`gettingStarted.overrideTask.${item.id}.description`), - this.tasExperimentService.getTreatment(`gettingStarted.overrideTask.${item.id}.media`), - ]); + if (!(title || description || media)) { return; } - if (resolved) { - const existingCategory = assertIsDefined(this.gettingStartedContributions.get(categoryId)); - if (existingCategory.content.type === 'startEntry') { throw Error('Unexpected content type'); } - const existingItem = assertIsDefined(existingCategory.content.items.find(_item => _item.id === item.id)); - existingItem.title = title ?? existingItem.title; - existingItem.description = description ?? existingItem.description; - existingItem.media.path = media ? convertPaths(media) : existingItem.media.path; - this._onDidChangeTask.fire(this.getTaskProgress(existingItem)); - } else { - resolve({ - ...item, - title: title ?? item.title, - description: description ?? item.description, - media: { - altText: item.media.altText, - path: media ? media : item.media.path, - type: item.media.type, - }, - }); - } - }); + const existingCategory = assertIsDefined(this.gettingStartedContributions.get(categoryId)); + if (existingCategory.content.type === 'startEntry') { throw Error('Unexpected content type'); } + const existingStep = assertIsDefined(existingCategory.content.steps.find(_step => _step.id === step.id)); + + existingStep.title = title ?? existingStep.title; + existingStep.description = description ? parseDescription(description) : existingStep.description; + existingStep.media.path = media ? convertInternalMediaPathsToBrowserURIs(media) : existingStep.media.path; + this._onDidChangeStep.fire(this.getStepProgress(existingStep)); } private registerExtensionContributions(extension: IExtensionDescription) { - const convertPaths = (path: string | { hc: string, dark: string, light: string }): { hc: URI, dark: URI, light: URI } => { + const convertExtensionPathToFileURI = (path: string) => path.startsWith('https://') + ? URI.parse(path, true) + : FileAccess.asFileUri(joinPath(extension.extensionLocation, path)); + + const convertExtensionRelativePathsToBrowserURIs = (path: string | { hc: string, dark: string, light: string }): { hc: URI, dark: URI, light: URI } => { const convertPath = (path: string) => path.startsWith('https://') ? URI.parse(path, true) : FileAccess.asBrowserUri(joinPath(extension.extensionLocation, path)); @@ -309,68 +294,102 @@ export class GettingStartedService extends Disposable implements IGettingStarted let sectionToOpen: string | undefined; - if (!this.trackedExtensions.has(ExtensionIdentifier.toKey(extension.identifier))) { - this.trackedExtensions.add(ExtensionIdentifier.toKey(extension.identifier)); - if (!(extension.contributes?.walkthroughs?.length)) { - return; - } + if (!(extension.contributes?.walkthroughs?.length)) { + return; + } - if (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; - } + if (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; + } - if (!this.configurationService.getValue('workbench.welcomePage.experimental.extensionContributions')) { - console.warn('Extension', extension.identifier.value, 'contributes welcome page content but the welcome page extension contribution feature flag has not been set.'); - return; - } + if (!this.configurationService.getValue('workbench.welcomePage.experimental.extensionContributions')) { + console.warn('Extension', extension.identifier.value, 'contributes welcome page content but the welcome page extension contribution feature flag has not been set. Set `workbench.welcomePage.experimental.extensionContributions` to begin using this experimental feature.'); + return; + } - extension.contributes?.walkthroughs?.forEach(section => { - const categoryID = extension.identifier.value + '#' + section.id; - if ( - this.sessionInstalledExtensions.has(extension.identifier.value) - && section.primary - && this.contextService.contextMatchesRules(ContextKeyExpr.deserialize(section.when) ?? ContextKeyExpr.true()) - ) { - this.sessionInstalledExtensions.delete(extension.identifier.value); - sectionToOpen = categoryID; - } - this.registerCategory({ - content: { type: 'items' }, - description: section.description, - title: section.title, - id: categoryID, - order: Math.min(), - icon: { - type: 'image', - path: extension.icon - ? FileAccess.asBrowserUri(joinPath(extension.extensionLocation, extension.icon)).toString(true) - : DefaultIconPath - }, - when: ContextKeyExpr.deserialize(section.when) ?? ContextKeyExpr.true(), - }); - try { - section.tasks.forEach((task, index) => - this.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); + extension.contributes.startEntries?.forEach(entry => { + const entryID = extension.identifier.value + '#startEntry#' + idForStartEntry(entry); + this.registerStartEntry({ + content: { + type: 'startEntry', + command: entry.command, + }, + description: entry.description, + title: entry.title, + id: entryID, + order: 0, + when: ContextKeyExpr.deserialize(entry.when) ?? ContextKeyExpr.true(), + icon: { + type: 'image', + path: extension.icon + ? FileAccess.asBrowserUri(joinPath(extension.extensionLocation, extension.icon)).toString(true) + : DefaultIconPath } }); - } + }); + + + extension.contributes?.walkthroughs?.forEach(walkthrough => { + const categoryID = extension.identifier.value + '#walkthrough#' + walkthrough.id; + if ( + this.sessionInstalledExtensions.has(extension.identifier.value) + && walkthrough.primary + && this.contextService.contextMatchesRules(ContextKeyExpr.deserialize(walkthrough.when) ?? ContextKeyExpr.true()) + ) { + this.sessionInstalledExtensions.delete(extension.identifier.value); + sectionToOpen = categoryID; + } + this.registerWalkthrough({ + content: { type: 'steps' }, + description: walkthrough.description, + title: walkthrough.title, + id: categoryID, + order: Math.min(), + icon: { + type: 'image', + path: extension.icon + ? FileAccess.asBrowserUri(joinPath(extension.extensionLocation, extension.icon)).toString(true) + : DefaultIconPath + }, + when: ContextKeyExpr.deserialize(walkthrough.when) ?? ContextKeyExpr.true(), + }, + (walkthrough.steps ?? (walkthrough as any).tasks).map((step, index) => { + const description = parseDescription(step.description); + const buttonDescription = (step as any as { button: LegacyButtonConfig }).button; + if (buttonDescription) { + description.push({ nodes: [{ href: buttonDescription.link ?? `command:${buttonDescription.command}`, label: buttonDescription.title }] }); + } + const fullyQualifiedID = extension.identifier.value + '#' + walkthrough.id + '#' + step.id; + + let media: IGettingStartedStep['media']; + if (typeof step.media.path === 'string' && step.media.path.endsWith('.md')) { + media = { + type: 'markdown', + path: convertExtensionPathToFileURI(step.media.path), + base: convertExtensionPathToFileURI(dirname(step.media.path)) + }; + } else { + const altText = (step.media as any).altText; + if (!altText) { + console.error('Getting Started: item', fullyQualifiedID, 'is missing altText for its media element.'); + } + media = { type: 'image', altText, path: convertExtensionRelativePathsToBrowserURIs(step.media.path) }; + } + + return ({ + description, media, + doneOn: step.doneOn?.command + ? { commandExecuted: step.doneOn.command } + : { eventFired: 'markDone:' + fullyQualifiedID }, + id: fullyQualifiedID, + title: step.title, + when: ContextKeyExpr.deserialize(step.when) ?? ContextKeyExpr.true(), + category: categoryID, + order: index, + }); + })); + }); if (sectionToOpen) { for (const group of this.editorGroupsService.groups) { @@ -390,19 +409,41 @@ export class GettingStartedService extends Disposable implements IGettingStarted } } - private registerDoneListeners(task: IGettingStartedTask) { - if (task.doneOn.commandExecuted) { - const existing = this.commandListeners.get(task.doneOn.commandExecuted); - if (existing) { existing.push(task.id); } + private unregisterExtensionContributions(extension: IExtensionDescription) { + if (!(extension.contributes?.walkthroughs?.length)) { + return; + } + + extension.contributes?.startEntries?.forEach(section => { + const categoryID = extension.identifier.value + '#startEntry#' + idForStartEntry(section); + this.gettingStartedContributions.delete(categoryID); + this._onDidRemoveCategory.fire(categoryID); + }); + + extension.contributes?.walkthroughs?.forEach(section => { + const categoryID = extension.identifier.value + '#walkthrough#' + section.id; + section.steps.forEach(step => { + const fullyQualifiedID = extension.identifier.value + '#' + section.id + '#' + step.id; + this.steps.delete(fullyQualifiedID); + }); + this.gettingStartedContributions.delete(categoryID); + this._onDidRemoveCategory.fire(categoryID); + }); + } + + private registerDoneListeners(step: IGettingStartedStep) { + if (step.doneOn.commandExecuted) { + const existing = this.commandListeners.get(step.doneOn.commandExecuted); + if (existing) { existing.push(step.id); } else { - this.commandListeners.set(task.doneOn.commandExecuted, [task.id]); + this.commandListeners.set(step.doneOn.commandExecuted, [step.id]); } } - if (task.doneOn.eventFired) { - const existing = this.eventListeners.get(task.doneOn.eventFired); - if (existing) { existing.push(task.id); } + if (step.doneOn.eventFired) { + const existing = this.eventListeners.get(step.doneOn.eventFired); + if (existing) { existing.push(step.id); } else { - this.eventListeners.set(task.doneOn.eventFired, [task.id]); + this.eventListeners.set(step.doneOn.eventFired, [step.id]); } } } @@ -413,18 +454,18 @@ export class GettingStartedService extends Disposable implements IGettingStarted .sort((a, b) => a.order - b.order) .filter(category => this.contextService.contextMatchesRules(category.when)) .map(category => { - if (category.content.type === 'items') { + if (category.content.type === 'steps') { return { ...category, content: { - type: 'items' as const, - items: category.content.items.filter(item => this.contextService.contextMatchesRules(item.when)) + type: 'steps' as const, + steps: category.content.steps.filter(step => this.contextService.contextMatchesRules(step.when)) } }; } return category; }) - .filter(category => category.content.type !== 'items' || category.content.items.length) + .filter(category => category.content.type !== 'steps' || category.content.steps.length) .map(category => this.getCategoryProgress(category)); return categoriesWithCompletion; } @@ -434,108 +475,114 @@ export class GettingStartedService extends Disposable implements IGettingStarted return { ...category, content: category.content }; } - const tasksWithProgress = category.content.items.map(task => this.getTaskProgress(task)); - const tasksComplete = tasksWithProgress.filter(task => task.done); + const stepsWithProgress = category.content.steps.map(step => this.getStepProgress(step)); + const stepsComplete = stepsWithProgress.filter(step => step.done); return { ...category, content: { - type: 'items', - items: tasksWithProgress, - stepsComplete: tasksComplete.length, - stepsTotal: tasksWithProgress.length, - done: tasksComplete.length === tasksWithProgress.length, + type: 'steps', + steps: stepsWithProgress, + stepsComplete: stepsComplete.length, + stepsTotal: stepsWithProgress.length, + done: stepsComplete.length === stepsWithProgress.length, } }; } - private getTaskProgress(task: IGettingStartedTask): IGettingStartedTaskWithProgress { + private getStepProgress(step: IGettingStartedStep): IGettingStartedStepWithProgress { return { - ...task, + ...step, done: false, - ...this.taskProgress[task.id] + ...this.stepProgress[step.id] }; } - progressTask(id: string) { - const oldProgress = this.taskProgress[id]; + progressStep(id: string) { + const oldProgress = this.stepProgress[id]; if (!oldProgress || oldProgress.done !== true) { - this.taskProgress[id] = { done: true }; + this.stepProgress[id] = { done: true }; this.memento.saveMemento(); - const task = this.getTask(id); - this._onDidProgressTask.fire(this.getTaskProgress(task)); + const step = this.getStep(id); + this._onDidProgressStep.fire(this.getStepProgress(step)); } } - deprogressTask(id: string) { - delete this.taskProgress[id]; + deprogressStep(id: string) { + delete this.stepProgress[id]; this.memento.saveMemento(); - const task = this.getTask(id); - this._onDidProgressTask.fire(this.getTaskProgress(task)); + const step = this.getStep(id); + this._onDidProgressStep.fire(this.getStepProgress(step)); } private progressByCommand(command: string) { const listening = this.commandListeners.get(command) ?? []; - listening.forEach(id => this.progressTask(id)); + listening.forEach(id => this.progressStep(id)); } progressByEvent(event: string): void { const listening = this.eventListeners.get(event) ?? []; - listening.forEach(id => this.progressTask(id)); + listening.forEach(id => this.progressStep(id)); } - public registerTask(task: IGettingStartedTask): IGettingStartedTask { - const category = this.gettingStartedContributions.get(task.category); - if (!category) { throw Error('Registering getting started task to category that does not exist (' + task.category + ')'); } - if (category.content.type !== 'items') { throw Error('Registering getting started task to category that is not of `items` type (' + task.category + ')'); } - if (this.tasks.has(task.id)) { throw Error('Attempting to register task with id ' + task.id + ' twice. Second is dropped.'); } - this.tasks.set(task.id, task); - let insertIndex: number | undefined = category.content.items.findIndex(item => item.order > task.order); - if (insertIndex === -1) { insertIndex = undefined; } - insertIndex = insertIndex ?? category.content.items.length; - category.content.items.splice(insertIndex, 0, task); - this.registerDoneListeners(task); - this._onDidAddTask.fire(this.getTaskProgress(task)); - return task; - } - - public registerCategory(categoryDescriptor: IGettingStartedCategoryDescriptor): void { + private registerStartEntry(categoryDescriptor: IGettingStartedStartEntryDescriptor): void { const oldCategory = this.gettingStartedContributions.get(categoryDescriptor.id); if (oldCategory) { console.error(`Skipping attempt to overwrite getting started category. (${categoryDescriptor})`); return; } - const category: IGettingStartedCategory = { - ...categoryDescriptor, - content: categoryDescriptor.content.type === 'items' - ? { type: 'items', items: [] } - : categoryDescriptor.content - }; + const category: IGettingStartedCategory = { ...categoryDescriptor }; this.gettingStartedContributions.set(categoryDescriptor.id, category); this._onDidAddCategory.fire(this.getCategoryProgress(category)); } - private getTask(id: string): IGettingStartedTask { - const task = this.tasks.get(id); - if (!task) { throw Error('Attempting to access task which does not exist in registry ' + id); } - return task; + private registerWalkthrough(categoryDescriptor: IGettingStartedWalkthroughDescriptor, steps: IGettingStartedStep[]): void { + const oldCategory = this.gettingStartedContributions.get(categoryDescriptor.id); + if (oldCategory) { + console.error(`Skipping attempt to overwrite getting started category. (${categoryDescriptor.id})`); + return; + } + + const category: IGettingStartedCategory = { ...categoryDescriptor, content: { type: 'steps', steps } }; + this.gettingStartedContributions.set(categoryDescriptor.id, category); + steps.forEach(step => { + if (this.steps.has(step.id)) { throw Error('Attempting to register step with id ' + step.id + ' twice. Second is dropped.'); } + this.steps.set(step.id, step); + this.registerDoneListeners(step); + }); + this._onDidAddCategory.fire(this.getCategoryProgress(category)); + } + + private getStep(id: string): IGettingStartedStep { + const step = this.steps.get(id); + if (!step) { throw Error('Attempting to access step which does not exist in registry ' + id); } + return step; } } -const convertPaths = (path: string | { hc: string, dark: string, light: string }): { hc: URI, dark: URI, light: URI } => { - const convertPath = (path: string) => path.startsWith('https://') +const idForStartEntry = (entry: IStartEntry): string => `${entry.title}#${entry.command}`; + +const parseDescription = (desc: string): LinkedText[] => desc.split('\n').filter(x => x).map(text => parseLinkedText(text)); + + +const convertInternalMediaPathToFileURI = (path: string) => path.startsWith('https://') + ? URI.parse(path, true) + : FileAccess.asFileUri('vs/workbench/contrib/welcome/gettingStarted/common/media/' + path, require); + +const convertInternalMediaPathsToBrowserURIs = (path: string | { hc: string, dark: string, light: string }): { hc: URI, dark: URI, light: URI } => { + const convertInternalMediaPathToBrowserURI = (path: string) => path.startsWith('https://') ? URI.parse(path, true) : FileAccess.asBrowserUri('vs/workbench/contrib/welcome/gettingStarted/common/media/' + path, require); if (typeof path === 'string') { - const converted = convertPath(path); + const converted = convertInternalMediaPathToBrowserURI(path); return { hc: converted, dark: converted, light: converted }; } else { return { - hc: convertPath(path.hc), - light: convertPath(path.light), - dark: convertPath(path.dark) + hc: convertInternalMediaPathToBrowserURI(path.hc), + light: convertInternalMediaPathToBrowserURI(path.light), + dark: convertInternalMediaPathToBrowserURI(path.dark) }; } }; @@ -557,7 +604,7 @@ registerAction2(class extends Action2 { for (const key in record) { if (Object.prototype.hasOwnProperty.call(record, key)) { try { - gettingStartedService.deprogressTask(key); + gettingStartedService.deprogressStep(key); } catch (e) { console.error(e); } diff --git a/src/vs/workbench/contrib/welcome/gettingStarted/common/gettingStartedContent.ts b/src/vs/workbench/contrib/welcome/gettingStarted/common/gettingStartedContent.ts index ee511694..eae3e439 100644 --- a/src/vs/workbench/contrib/welcome/gettingStarted/common/gettingStartedContent.ts +++ b/src/vs/workbench/contrib/welcome/gettingStarted/common/gettingStartedContent.ts @@ -11,19 +11,19 @@ import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; const setupIcon = registerIcon('getting-started-setup', Codicon.zap, localize('getting-started-setup-icon', "Icon used for the setup category of getting started")); const beginnerIcon = registerIcon('getting-started-beginner', Codicon.lightbulb, localize('getting-started-beginner-icon', "Icon used for the beginner category of getting started")); +const intermediateIcon = registerIcon('getting-started-intermediate', Codicon.mortarBoard, localize('getting-started-intermediate-icon', "Icon used for the intermediate category of getting started")); const codespacesIcon = registerIcon('getting-started-codespaces', Codicon.github, localize('getting-started-codespaces-icon', "Icon used for the codespaces category of getting started")); -export type BuiltinGettingStartedItem = { +export type BuiltinGettingStartedStep = { id: string title: string, description: string, - button: - | { title: string, command?: never, link: string } - | { title: string, command: string, link?: never }, doneOn: { commandExecuted: string, eventFired?: never } | { eventFired: string, commandExecuted?: never, } when?: string, - media: { type: 'image', path: string | { hc: string, light: string, dark: string }, altText: string }, + media: + | { type: 'image', path: string | { hc: string, light: string, dark: string }, altText: string } + | { type: 'markdown', path: string }, }; export type BuiltinGettingStartedCategory = { @@ -33,13 +33,23 @@ export type BuiltinGettingStartedCategory = { icon: ThemeIcon, when?: string, content: - | { type: 'items', items: BuiltinGettingStartedItem[] } + | { type: 'steps', steps: BuiltinGettingStartedStep[] } +}; + +export type BuiltinGettingStartedStartEntry = { + id: string + title: string, + description: string, + icon: ThemeIcon, + when?: string, + content: | { type: 'startEntry', command: string } }; -type GettingStartedContent = BuiltinGettingStartedCategory[]; +type GettingStartedWalkthroughContent = BuiltinGettingStartedCategory[]; +type GettingStartedStartEntryContent = BuiltinGettingStartedStartEntry[]; -export const content: GettingStartedContent = [ +export const startEntries: GettingStartedStartEntryContent = [ { id: 'topLevelNewFile', title: localize('gettingStarted.newFile.title', "New File"), @@ -88,6 +98,7 @@ export const content: GettingStartedContent = [ title: localize('gettingStarted.cloneRepo.title', "Clone Git Repository..."), description: localize('gettingStarted.cloneRepo.description', "Clone a git repository"), icon: Codicon.repoClone, + when: '!git.missing', content: { type: 'startEntry', command: 'git.clone', @@ -103,6 +114,9 @@ export const content: GettingStartedContent = [ command: 'workbench.action.showCommands', } }, +]; + +export const walkthroughs: GettingStartedWalkthroughContent = [ { id: 'Codespaces', title: localize('gettingStarted.codespaces.title', "Primer on Codespaces"), @@ -110,60 +124,40 @@ export const content: GettingStartedContent = [ when: 'remoteName == codespaces', description: localize('gettingStarted.codespaces.description', "Get up and running with your instant code environment."), content: { - type: 'items', - items: [ + type: 'steps', + steps: [ { - id: 'runProjectTask', + id: 'runProjectStep', title: localize('gettingStarted.runProject.title', "Build & run your app"), - description: localize('gettingStarted.runProject.description', "Build, run & debug your code in the cloud, right from the browser."), - button: { - title: localize('gettingStarted.runProject.button', "Start Debugging (F5)"), - command: 'workbench.action.debug.selectandstart' - }, + description: localize('gettingStarted.runProject.description', "Build, run & debug your code in the cloud, right from the browser.\n[Start Debugging](command:workbench.action.debug.selectandstart)"), doneOn: { commandExecuted: 'workbench.action.debug.selectandstart' }, media: { type: 'image', altText: 'Node.js project running debug mode and paused.', path: 'runProject.png' }, }, { - id: 'forwardPortsTask', + id: 'forwardPortsStep', title: localize('gettingStarted.forwardPorts.title', "Access your running application"), - description: localize('gettingStarted.forwardPorts.description', "Ports running within your codespace are automatically forwarded to the web, so you can open them in your browser."), - button: { - title: localize('gettingStarted.forwardPorts.button', "Show Ports Panel"), - command: '~remote.forwardedPorts.focus' - }, + description: localize('gettingStarted.forwardPorts.description', "Ports running within your codespace are automatically forwarded to the web, so you can open them in your browser.\n[Show Ports Panel](command:~remote.forwardedPorts.focus)"), doneOn: { commandExecuted: '~remote.forwardedPorts.focus' }, media: { type: 'image', altText: 'Ports panel.', path: 'forwardPorts.png' }, }, { id: 'pullRequests', title: localize('gettingStarted.pullRequests.title', "Pull requests at your fingertips"), - description: localize('gettingStarted.pullRequests.description', "Bring your GitHub workflow closer to your code, so you can review pull requests, add comments, merge branches, and more."), - button: { - title: localize('gettingStarted.pullRequests.button', "Open GitHub View"), - command: 'workbench.view.extension.github-pull-requests' - }, + description: localize('gettingStarted.pullRequests.description', "Bring your GitHub workflow closer to your code, so you can review pull requests, add comments, merge branches, and more.\n[Open GitHub View](command:workbench.view.extension.github-pull-requests)"), doneOn: { commandExecuted: 'workbench.view.extension.github-pull-requests' }, media: { type: 'image', altText: 'Preview for reviewing a pull request.', path: 'pullRequests.png' }, }, { id: 'remoteTerminal', title: localize('gettingStarted.remoteTerminal.title', "Run tasks in the integrated terminal"), - description: localize('gettingStarted.remoteTerminal.description', "Perform quick command-line tasks using the built-in terminal."), - button: { - title: localize('gettingStarted.remoteTerminal.button', "Focus Terminal"), - command: 'terminal.focus' - }, + description: localize('gettingStarted.remoteTerminal.description', "Perform quick command-line tasks using the built-in terminal.\n[Focus Terminal](command:terminal.focus)"), doneOn: { commandExecuted: 'terminal.focus' }, media: { type: 'image', altText: 'Remote terminal showing npm commands.', path: 'remoteTerminal.png' }, }, { id: 'openVSC', title: localize('gettingStarted.openVSC.title', "Develop remotely in VS Code"), - description: localize('gettingStarted.openVSC.description', "Access the power of your cloud development environment from your local VS Code. Set it up by installing the GitHub Codespaces extension and connecting your GitHub account."), - button: { - title: localize('gettingStarted.openVSC.button', "Open in VS Code"), - command: 'github.codespaces.openInStable' - }, + description: localize('gettingStarted.openVSC.description', "Access the power of your cloud development environment from your local VS Code. Set it up by installing the GitHub Codespaces extension and connecting your GitHub account.\n[Open in VS Code](command:github.codespaces.openInStable)"), when: 'isWeb', doneOn: { commandExecuted: 'github.codespaces.openInStable' }, media: { @@ -180,29 +174,24 @@ export const content: GettingStartedContent = [ { id: 'Setup', - title: localize('gettingStarted.setup.title', "Quick Setup"), + title: localize('gettingStarted.setup.title', "Customize your Setup"), description: localize('gettingStarted.setup.description', "Extend and customize VS Code to make it yours."), icon: setupIcon, when: 'remoteName != codespaces', content: { - type: 'items', - items: [ + type: 'steps', + steps: [ { id: 'pickColorTheme', title: localize('gettingStarted.pickColor.title', "Customize the look with themes"), - description: localize('gettingStarted.pickColor.description', "Pick a color theme to match your taste and mood while coding."), - button: { title: localize('gettingStarted.pickColor.button', "Pick a Theme"), command: 'workbench.action.selectTheme' }, + description: localize('gettingStarted.pickColor.description', "Pick a color theme to match your taste and mood while coding.\n[Pick a Theme](command:workbench.action.selectTheme)"), doneOn: { commandExecuted: 'workbench.action.selectTheme' }, media: { type: 'image', altText: 'Color theme preview for dark and light theme.', path: 'colorTheme.png', } }, { id: 'findLanguageExtensions', title: localize('gettingStarted.findLanguageExts.title', "Code in any language"), - description: localize('gettingStarted.findLanguageExts.description', "VS Code supports over 50+ programming languages. While many are built-in, others can be easily installed as extensions in one click."), - button: { - title: localize('gettingStarted.findLanguageExts.button', "Browse Language Extensions"), - command: 'workbench.extensions.action.showLanguageExtensions', - }, + description: localize('gettingStarted.findLanguageExts.description', "VS Code supports over 50+ programming languages. While many are built-in, others can be easily installed as extensions in one click.\n[Browse Language Extensions](command:workbench.extensions.action.showLanguageExtensions)"), doneOn: { commandExecuted: 'workbench.extensions.action.showLanguageExtensions' }, media: { type: 'image', altText: 'Language extensions', path: { @@ -214,12 +203,8 @@ export const content: GettingStartedContent = [ }, { id: 'keymaps', - title: localize('gettingStarted.keymaps.title', "Migrate your keyboard shortcuts"), - description: localize('gettingStarted.keymaps.description', "Keymap extensions bring your favorite keyboard shortcuts from other editors to VS Code."), - button: { - title: localize('gettingStarted.keymaps.button', "Keymap Extensions"), - command: 'workbench.extensions.action.showRecommendedKeymapExtensions', - }, + title: localize('gettingStarted.keymaps.title', "Switch from other editors"), + description: localize('gettingStarted.keymaps.description', "Bring your favorite keyboard shortcuts from other editors into VS Code with keymaps.\n[Browse Keymap Extensions](command:workbench.extensions.action.showRecommendedKeymapExtensions)"), doneOn: { commandExecuted: 'workbench.extensions.action.showRecommendedKeymapExtensions' }, media: { type: 'image', altText: 'List of keymap extensions.', path: { @@ -232,12 +217,8 @@ export const content: GettingStartedContent = [ { id: 'settingsSync', title: localize('gettingStarted.settingsSync.title', "Sync your favorite setup"), - description: localize('gettingStarted.settingsSync.description', "Never lose the perfect VS Code setup! Settings Sync will back up and share settings, keybindings & extensions across several VS Code instances."), + description: localize('gettingStarted.settingsSync.description', "Never lose the perfect VS Code setup! Settings Sync will back up and share settings, keybindings & extensions across several VS Code instances.\n[Enable Settings Sync](command:workbench.userDataSync.actions.turnOn)"), when: 'syncStatus != uninitialized', - button: { - title: localize('gettingStarted.settingsSync.button', "Enable Settings Sync"), - command: 'workbench.userDataSync.actions.turnOn', - }, doneOn: { eventFired: 'sync-enabled' }, media: { type: 'image', altText: 'The "Turn on Sync" entry in the settings gear menu.', path: { @@ -249,13 +230,9 @@ export const content: GettingStartedContent = [ }, { id: 'pickAFolderTask-Mac', - title: localize('gettingStarted.setup.OpenFolder.title', "Open your project"), - description: localize('gettingStarted.setup.OpenFolder.description', "Open a project folder to get started!"), - when: 'isMac', - button: { - title: localize('gettingStarted.setup.OpenFolder.button', "Pick a Folder"), - command: 'workbench.action.files.openFileFolder' - }, + title: localize('gettingStarted.setup.OpenFolder.title', "Open your project folder"), + description: localize('gettingStarted.setup.OpenFolder.description', "Open a project folder to start coding!\n[Pick a Folder](command:workbench.action.files.openFileFolder)"), + when: 'isMac && workspaceFolderCount == 0', doneOn: { commandExecuted: 'workbench.action.files.openFileFolder' }, media: { type: 'image', altText: 'Explorer view showing buttons for opening folder and cloning repository.', path: { @@ -267,13 +244,9 @@ export const content: GettingStartedContent = [ }, { id: 'pickAFolderTask-Other', - title: localize('gettingStarted.setup.OpenFolder.title', "Open your project"), - description: localize('gettingStarted.setup.OpenFolder.description2', "Open a folder to get started!"), - when: '!isMac', - button: { - title: localize('gettingStarted.setup.OpenFolder.button', "Pick a Folder"), - command: 'workbench.action.files.openFolder' - }, + title: localize('gettingStarted.setup.OpenFolder.title', "Open your project folder"), + description: localize('gettingStarted.setup.OpenFolder.description2', "Open a project folder to start coding!\n[Pick a Folder](command:workbench.action.files.openFolder)"), + when: '!isMac && workspaceFolderCount == 0', doneOn: { commandExecuted: 'workbench.action.files.openFolder' }, media: { type: 'image', altText: 'Explorer view showing buttons for opening folder and cloning repository.', path: { @@ -282,6 +255,20 @@ export const content: GettingStartedContent = [ hc: 'hc/openFolder.png', } } + }, + { + id: 'quickOpen', + title: localize('gettingStarted.quickOpen.title', "Quick open files"), + description: localize('gettingStarted.quickOpen.description', "Navigate between files in an instant with one keystroke. Tip: Open multiple files by pressing the right arrow key.\n[Quick Open a File](command:toSide:workbench.action.quickOpen)"), + when: 'workspaceFolderCount != 0', + doneOn: { commandExecuted: 'workbench.action.quickOpen' }, + media: { + type: 'image', altText: 'Go to file in quick search.', path: { + dark: 'dark/openFolder.png', + light: 'light/openFolder.png', + hc: 'hc/openFolder.png', + } + } } ] } @@ -293,16 +280,12 @@ export const content: GettingStartedContent = [ icon: beginnerIcon, description: localize('gettingStarted.beginner.description', "Jump right into VS Code and get an overview of the must-have features."), content: { - type: 'items', - items: [ + type: 'steps', + steps: [ { id: 'commandPaletteTask', title: localize('gettingStarted.commandPalette.title', "Find & run commands"), - description: localize('gettingStarted.commandPalette.description', "The easiest way to find everything VS Code can do. If you're ever looking for a feature or a shortcut, check here first!"), - button: { - title: localize('gettingStarted.commandPalette.button', "Open Command Palette"), - command: 'workbench.action.showCommands' - }, + description: localize('gettingStarted.commandPalette.description', "The easiest way to find everything VS Code can do. If you're ever looking for a feature or a shortcut, check here first!\n[Open Command Palette](command:workbench.action.showCommands)"), doneOn: { commandExecuted: 'workbench.action.showCommands' }, media: { type: 'image', altText: 'Command Palette overlay for searching and executing commands.', path: { @@ -315,12 +298,8 @@ export const content: GettingStartedContent = [ { id: 'terminal', title: localize('gettingStarted.terminal.title', "Convenient built-in terminal"), - description: localize('gettingStarted.terminal.description', "Quickly run shell commands and monitor build output, right next to your code."), - when: 'remoteName != codespaces', - button: { - title: localize('gettingStarted.terminal.button', "Show Terminal Panel"), - command: 'workbench.action.terminal.toggleTerminal' - }, + description: localize('gettingStarted.terminal.description', "Quickly run shell commands and monitor build output, right next to your code.\n[Show Terminal Panel](command:workbench.action.terminal.toggleTerminal)"), + when: 'remoteName != codespaces && !terminalIsOpen', doneOn: { commandExecuted: 'workbench.action.terminal.toggleTerminal' }, media: { type: 'image', altText: 'Integrated terminal running a few npm commands', path: { @@ -333,11 +312,7 @@ export const content: GettingStartedContent = [ { id: 'extensions', title: localize('gettingStarted.extensions.title', "Limitless extensibility"), - description: localize('gettingStarted.extensions.description', "Extensions are VS Code's power-ups. They range from handy productivity hacks, expanding out-of-the-box features, to adding completely new capabilities."), - button: { - title: localize('gettingStarted.extensions.button', "Browse Recommended Extensions"), - command: 'workbench.extensions.action.showRecommendedExtensions' - }, + description: localize('gettingStarted.extensions.description', "Extensions are VS Code's power-ups. They range from handy productivity hacks, expanding out-of-the-box features, to adding completely new capabilities.\n[Browse Recommended Extensions](command:workbench.extensions.action.showRecommendedExtensions)"), doneOn: { commandExecuted: 'workbench.extensions.action.showRecommendedExtensions' }, media: { type: 'image', altText: 'VS Code extension marketplace with featured language extensions', path: { @@ -349,12 +324,8 @@ export const content: GettingStartedContent = [ }, { id: 'settings', - title: localize('gettingStarted.settings.title', "Everything is a setting"), - description: localize('gettingStarted.settings.description', "Optimize every part of VS Code's look & feel to your liking. Enabling Settings Sync lets you share your personal tweaks across machines."), - button: { - title: localize('gettingStarted.settings.button', "Tweak my Settings"), - command: 'workbench.action.openSettings' - }, + title: localize('gettingStarted.settings.title', "Tune your settings"), + description: localize('gettingStarted.settings.description', "Tweak every aspect of VS Code and your extensions to your liking. Commonly used settings are listed first to get you started.\n[Tweak my Settings](command:toSide:workbench.action.openSettings)"), doneOn: { commandExecuted: 'workbench.action.openSettings' }, media: { type: 'image', altText: 'VS Code Settings', path: { @@ -367,15 +338,132 @@ export const content: GettingStartedContent = [ { id: 'videoTutorial', title: localize('gettingStarted.videoTutorial.title', "Lean back and learn"), - description: localize('gettingStarted.videoTutorial.description', "Watch the first in a series of short & practical video tutorials for VS Code's key features."), - button: { - title: localize('gettingStarted.videoTutorial.button', "Watch Tutorial"), - link: 'https://aka.ms/vscode-getting-started-video' - }, + description: localize('gettingStarted.videoTutorial.description', "Watch the first in a series of short & practical video tutorials for VS Code's key features.\n[Watch Tutorial](https://aka.ms/vscode-getting-started-video)"), doneOn: { eventFired: 'linkOpened:https://aka.ms/vscode-getting-started-video' }, media: { type: 'image', altText: 'VS Code Settings', path: 'tutorialVideo.png' }, } ] } + }, + + { + id: 'Intermediate', + title: localize('gettingStarted.intermediate.title', "Boost your Productivity"), + icon: intermediateIcon, + description: localize('gettingStarted.intermediate.description', "Optimize your development workflow with these tips & tricks."), + content: { + type: 'steps', + steps: [ + { + id: 'playground', + title: localize('gettingStarted.playground.title', "Redefine your editing skills"), + description: localize('gettingStarted.playground.description', "Want to code faster and smarter? Practice powerful code editing features in the interactive playground.\n[Open Interactive Playground](command:toSide:workbench.action.showInteractivePlayground)"), + doneOn: { commandExecuted: 'workbench.action.showInteractivePlayground' }, + media: { + type: 'image', altText: 'Interactive Playground.', path: { + dark: 'dark/playground.png', + light: 'light/playground.png', + hc: 'light/playground.png' + }, + }, + }, + { + id: 'splitview', + title: localize('gettingStarted.splitview.title', "Side by side editing"), + description: localize('gettingStarted.splitview.description', "Make the most of your screen estate by opening files side by side, vertically and horizontally.\n[Split Editor](command:workbench.action.splitEditor)"), + doneOn: { commandExecuted: 'workbench.action.splitEditor' }, + media: { + type: 'image', altText: 'Multiple editors in split view.', path: { + dark: 'dark/splitview.png', + light: 'light/splitview.png', + hc: 'light/splitview.png' + }, + }, + }, + { + id: 'debugging', + title: localize('gettingStarted.debug.title', "Watch your code in action"), + description: localize('gettingStarted.debug.description', "Accelerate your edit, build, test, and debug loop by setting up a launch configuration.\n[Run your Project](command:workbench.action.debug.selectandstart)"), + when: 'workspaceFolderCount != 0', + doneOn: { commandExecuted: 'workbench.action.debug.selectandstart' }, + media: { + type: 'image', altText: 'Run and debug view.', path: { + dark: 'dark/debug.png', + light: 'light/debug.png', + hc: 'light/debug.png' + }, + }, + }, + { + id: 'scmClone', + title: localize('gettingStarted.scm.title', "Track your code with Git"), + description: localize('gettingStarted.scmClone.description', "Set up the built-in version control for your project to track your changes and collaborate with others.\n[Clone Repository](command:git.clone)"), + when: 'config.git.enabled && !git.missing && workspaceFolderCount == 0', + doneOn: { commandExecuted: 'git.clone' }, + media: { + type: 'image', altText: 'Source Control view.', path: { + dark: 'dark/scm.png', + light: 'light/scm.png', + hc: 'light/scm.png' + }, + }, + }, + { + id: 'scmSetup', + title: localize('gettingStarted.scm.title', "Track your code with Git"), + description: localize('gettingStarted.scmSetup.description', "Set up the built-in version control for your project to track your changes and collaborate with others.\n[Initialize Git Repository](command:git.init)"), + when: 'config.git.enabled && !git.missing && workspaceFolderCount != 0 && gitOpenRepositoryCount == 0', + doneOn: { commandExecuted: 'git.init' }, + media: { + type: 'image', altText: 'Source Control view.', path: { + dark: 'dark/scm.png', + light: 'light/scm.png', + hc: 'light/scm.png' + }, + }, + }, + { + id: 'scm', + title: localize('gettingStarted.scm.title', "Track your code with Git"), + description: localize('gettingStarted.scm.description', "No more looking up Git commands! Git and GitHub workflows are seamlessly integrated.[Open Source Control](command:workbench.view.scm)"), + when: 'config.git.enabled && !git.missing && workspaceFolderCount != 0 && gitOpenRepositoryCount != 0 && activeViewlet != \'workbench.view.scm\'', + doneOn: { commandExecuted: 'workbench.view.scm.focus' }, + media: { + type: 'image', altText: 'Source Control view.', path: { + dark: 'dark/scm.png', + light: 'light/scm.png', + hc: 'light/scm.png' + }, + }, + }, + { + id: 'tasks', + title: localize('gettingStarted.tasks.title', "Automate your project tasks"), + when: 'workspaceFolderCount != 0', + description: localize('gettingStarted.tasks.description', "Create tasks for your common workflows and enjoy the integrated experience of running scripts and automatically checking results.\n[Run Auto-detected Tasks](command:workbench.action.tasks.runTask)"), + doneOn: { commandExecuted: 'workbench.action.tasks.runTask' }, + media: { + type: 'image', altText: 'Task runner.', path: { + dark: 'dark/tasks.png', + light: 'light/tasks.png', + hc: 'light/tasks.png' + }, + }, + }, + { + id: 'shortcuts', + title: localize('gettingStarted.shortcuts.title', "Customize your shortcuts"), + description: localize('gettingStarted.shortcuts.description', "Once you have discovered your favorite commands, create custom keyboard shortcuts for instant access.\n[Keyboard Shortcuts](command:toSide:workbench.action.openGlobalKeybindings)"), + doneOn: { commandExecuted: 'workbench.action.openGlobalKeybindings' }, + media: { + type: 'image', altText: 'Interactive shortcuts.', path: { + dark: 'dark/shortcuts.png', + light: 'light/shortcuts.png', + hc: 'light/shortcuts.png' + }, + } + } + ] + } } ]; diff --git a/src/vs/workbench/contrib/welcome/gettingStarted/common/media/dark/debug.png b/src/vs/workbench/contrib/welcome/gettingStarted/common/media/dark/debug.png new file mode 100644 index 0000000000000000000000000000000000000000..bcc41f8e435ec157cce80e552b6d11ea36d25038 GIT binary patch literal 167788 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

g%K!veeCUZ3Y7dL#wz*Yv!-12Nyp3I19y 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�j~?c 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 H9d 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

w>o-(2#^*`uVlZ>W;hMLf-=*xGSMO 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 tCfr?LZe%4hC?wshJ5*2P0;r;a1 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�i7i0bsW1rSpR)OzA1wk^q*#^qZ}faQQhTcX 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�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 a4(jxr<%QIT6_*E1L0aniY85It!v6qp1P)&S literal 0 HcmV?d00001 diff --git a/src/vs/workbench/contrib/welcome/gettingStarted/common/media/light/debug.png b/src/vs/workbench/contrib/welcome/gettingStarted/common/media/light/debug.png new file mode 100644 index 0000000000000000000000000000000000000000..b41aa76ef24cbb9bb45e4de587221dd73155a99a GIT binary patch literal 171717 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 N002ovPDHLkV1f;-tbzal literal 0 HcmV?d00001 diff --git a/src/vs/workbench/contrib/welcome/gettingStarted/common/media/light/scm.png b/src/vs/workbench/contrib/welcome/gettingStarted/common/media/light/scm.png new file mode 100644 index 0000000000000000000000000000000000000000..219fa3db965c21899e791ca128bc7cfb38891baf GIT binary patch literal 52177 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

D!KqogM$Ef=MAU;-;H~V~|GuZj2sC=& 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 { + public override run(): Promise { if (!welcomeOverlay) { welcomeOverlay = this.instantiationService.createInstance(WelcomeOverlay); } @@ -143,7 +143,7 @@ export class HideWelcomeOverlayAction extends Action { super(id, label); } - public run(): Promise { + public override run(): Promise { if (welcomeOverlay) { welcomeOverlay.hide(); } diff --git a/src/vs/workbench/contrib/welcome/page/browser/welcomePage.contribution.ts b/src/vs/workbench/contrib/welcome/page/browser/welcomePage.contribution.ts index 8b731eb0..5234bdf2 100644 --- a/src/vs/workbench/contrib/welcome/page/browser/welcomePage.contribution.ts +++ b/src/vs/workbench/contrib/welcome/page/browser/welcomePage.contribution.ts @@ -6,11 +6,11 @@ import { localize } from 'vs/nls'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { Registry } from 'vs/platform/registry/common/platform'; -import { WelcomePageContribution, WelcomePageAction, WelcomeInputFactory, DEFAULT_STARTUP_EDITOR_CONFIG } from 'vs/workbench/contrib/welcome/page/browser/welcomePage'; +import { WelcomePageContribution, WelcomePageAction, WelcomeInputSerializer, DEFAULT_STARTUP_EDITOR_CONFIG } from 'vs/workbench/contrib/welcome/page/browser/welcomePage'; import { IWorkbenchActionRegistry, Extensions as ActionExtensions, CATEGORIES } from 'vs/workbench/common/actions'; import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; -import { IEditorInputFactoryRegistry, Extensions as EditorExtensions } from 'vs/workbench/common/editor'; +import { IEditorInputFactoryRegistry, EditorExtensions } from 'vs/workbench/common/editor'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; Registry.as(ConfigurationExtensions.Configuration) @@ -22,7 +22,7 @@ Registry.as(WorkbenchExtensions.Workbench) Registry.as(ActionExtensions.WorkbenchActions) .registerWorkbenchAction(SyncActionDescriptor.from(WelcomePageAction), 'Help: Welcome', CATEGORIES.Help.value); -Registry.as(EditorExtensions.EditorInputFactories).registerEditorInputFactory(WelcomeInputFactory.ID, WelcomeInputFactory); +Registry.as(EditorExtensions.EditorInputFactories).registerEditorInputSerializer(WelcomeInputSerializer.ID, WelcomeInputSerializer); MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { group: '1_welcome', diff --git a/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts b/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts index 4adae228..8471fa8e 100644 --- a/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts +++ b/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts @@ -20,7 +20,7 @@ import { localize } from 'vs/nls'; import { Action, WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from 'vs/base/common/actions'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { FileAccess, Schemas } from 'vs/base/common/network'; -import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; +import { IWorkingCopyBackupService } from 'vs/workbench/services/workingCopy/common/workingCopyBackup'; import { getInstalledExtensions, IExtensionStatus, onExtensionChanged, isKeymapExtension } from 'vs/workbench/contrib/extensions/common/extensionsUtils'; import { IExtensionManagementService, IExtensionGalleryService, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IWorkbenchExtensionEnablementService, EnablementState } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; @@ -32,7 +32,7 @@ import { registerThemingParticipant } from 'vs/platform/theme/common/themeServic import { focusBorder, textLinkForeground, textLinkActiveForeground, foreground, descriptionForeground, contrastBorder, activeContrastBorder } from 'vs/platform/theme/common/colorRegistry'; import { getExtraColor } from 'vs/workbench/contrib/welcome/walkThrough/common/walkThroughUtils'; import { IExtensionsViewPaneContainer, IExtensionsWorkbenchService, VIEWLET_ID } from 'vs/workbench/contrib/extensions/common/extensions'; -import { IEditorInputFactory, EditorInput } from 'vs/workbench/common/editor'; +import { IEditorInputSerializer, EditorInput } from 'vs/workbench/common/editor'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { TimeoutTimer } from 'vs/base/common/async'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; @@ -53,7 +53,6 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, IConfigurationNode, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration'; import { ILogService } from 'vs/platform/log/common/log'; -import { IGettingStartedService } from 'vs/workbench/contrib/welcome/gettingStarted/browser/gettingStartedService'; export const DEFAULT_STARTUP_EDITOR_CONFIG: IConfigurationNode = { @@ -110,7 +109,7 @@ export class WelcomePageContribution implements IWorkbenchContribution { @IInstantiationService private readonly instantiationService: IInstantiationService, @IConfigurationService private readonly configurationService: IConfigurationService, @IEditorService private readonly editorService: IEditorService, - @IBackupFileService private readonly backupFileService: IBackupFileService, + @IWorkingCopyBackupService private readonly workingCopyBackupService: IWorkingCopyBackupService, @IFileService private readonly fileService: IFileService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @ILifecycleService private readonly lifecycleService: ILifecycleService, @@ -118,7 +117,6 @@ export class WelcomePageContribution implements IWorkbenchContribution { @ICommandService private readonly commandService: ICommandService, @ITelemetryService private readonly telemetryService: ITelemetryService, @ILogService private readonly logService: ILogService, - @IGettingStartedService _gettingStartedService: IGettingStartedService, // initializes event listeners @optional(ITASExperimentService) tasExperimentService: ITASExperimentService, ) { this.tasExperimentService = tasExperimentService; @@ -176,7 +174,7 @@ export class WelcomePageContribution implements IWorkbenchContribution { private async run() { const enabled = isWelcomePageEnabled(this.configurationService, this.contextService); if (enabled && this.lifecycleService.startupKind !== StartupKind.ReloadedWindow) { - const hasBackups = await this.backupFileService.hasBackups(); + const hasBackups = await this.workingCopyBackupService.hasBackups(); if (hasBackups) { return; } // Open the welcome even if we opened a set of default editors @@ -230,7 +228,7 @@ export class WelcomePageContribution implements IWorkbenchContribution { const editor = this.editorService.activeEditor; // Ensure that the welcome editor won't get opened more than once - if (editor?.getTypeId() === startupEditorTypeID || this.editorService.editors.some(e => e.getTypeId() === startupEditorTypeID)) { + if (editor?.typeId === startupEditorTypeID || this.editorService.editors.some(e => e.typeId === startupEditorTypeID)) { return; } const options: IEditorOptions = editor ? { pinned: false, index: 0 } : { pinned: false }; @@ -269,7 +267,7 @@ export class WelcomePageAction extends Action { super(id, label); } - public run(): Promise { + public override run(): Promise { return this.instantiationService.createInstance(WelcomePage) .openEditor() .then(() => undefined); @@ -418,7 +416,7 @@ class WelcomePage extends Disposable { ) { super(); - this._register(lifecycleService.onShutdown(() => this.dispose())); + this._register(lifecycleService.onDidShutdown(() => this.dispose())); const recentlyOpened = this.workspacesService.getRecentlyOpened(); const installedExtensions = this.instantiationService.invokeFunction(getInstalledExtensions); @@ -741,7 +739,7 @@ class WelcomePage extends Disposable { } } -export class WelcomeInputFactory implements IEditorInputFactory { +export class WelcomeInputSerializer implements IEditorInputSerializer { static readonly ID = welcomeInputTypeId; diff --git a/src/vs/workbench/contrib/welcome/walkThrough/browser/editor/editorWalkThrough.ts b/src/vs/workbench/contrib/welcome/walkThrough/browser/editor/editorWalkThrough.ts index 6e46aba0..4c055d9f 100644 --- a/src/vs/workbench/contrib/welcome/walkThrough/browser/editor/editorWalkThrough.ts +++ b/src/vs/workbench/contrib/welcome/walkThrough/browser/editor/editorWalkThrough.ts @@ -10,7 +10,7 @@ import { Action } from 'vs/base/common/actions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { WalkThroughInput, WalkThroughInputOptions } from 'vs/workbench/contrib/welcome/walkThrough/browser/walkThroughInput'; import { FileAccess, Schemas } from 'vs/base/common/network'; -import { IEditorInputFactory, EditorInput } from 'vs/workbench/common/editor'; +import { IEditorInputSerializer, EditorInput } from 'vs/workbench/common/editor'; import { EditorOverride } from 'vs/platform/editor/common/editor'; const typeId = 'workbench.editors.walkThroughInput'; @@ -39,14 +39,14 @@ export class EditorWalkThroughAction extends Action { super(id, label); } - public run(): Promise { + public override run(): Promise { const input = this.instantiationService.createInstance(WalkThroughInput, inputOptions); return this.editorService.openEditor(input, { pinned: true, override: EditorOverride.DISABLED }) .then(() => void (0)); } } -export class EditorWalkThroughInputFactory implements IEditorInputFactory { +export class EditorWalkThroughInputSerializer implements IEditorInputSerializer { static readonly ID = typeId; diff --git a/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThrough.contribution.ts b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThrough.contribution.ts index d1bdbc2d..939d52d8 100644 --- a/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThrough.contribution.ts +++ b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThrough.contribution.ts @@ -8,14 +8,14 @@ import { WalkThroughInput } from 'vs/workbench/contrib/welcome/walkThrough/brows import { WalkThroughPart } from 'vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart'; import { WalkThroughArrowUp, WalkThroughArrowDown, WalkThroughPageUp, WalkThroughPageDown } from 'vs/workbench/contrib/welcome/walkThrough/browser/walkThroughActions'; import { WalkThroughSnippetContentProvider } from 'vs/workbench/contrib/welcome/walkThrough/common/walkThroughContentProvider'; -import { EditorWalkThroughAction, EditorWalkThroughInputFactory } from 'vs/workbench/contrib/welcome/walkThrough/browser/editor/editorWalkThrough'; +import { EditorWalkThroughAction, EditorWalkThroughInputSerializer } from 'vs/workbench/contrib/welcome/walkThrough/browser/editor/editorWalkThrough'; import { Registry } from 'vs/platform/registry/common/platform'; -import { Extensions as EditorInputExtensions, IEditorInputFactoryRegistry } from 'vs/workbench/common/editor'; +import { EditorExtensions, IEditorInputFactoryRegistry } from 'vs/workbench/common/editor'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IWorkbenchActionRegistry, Extensions, CATEGORIES } from 'vs/workbench/common/actions'; import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; -import { IEditorRegistry, Extensions as EditorExtensions, EditorDescriptor } from 'vs/workbench/browser/editor'; +import { IEditorRegistry, EditorDescriptor } from 'vs/workbench/browser/editor'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; @@ -32,7 +32,7 @@ Registry.as(Extensions.WorkbenchActions) SyncActionDescriptor.from(EditorWalkThroughAction), 'Help: Interactive Playground', CATEGORIES.Help.value); -Registry.as(EditorInputExtensions.EditorInputFactories).registerEditorInputFactory(EditorWalkThroughInputFactory.ID, EditorWalkThroughInputFactory); +Registry.as(EditorExtensions.EditorInputFactories).registerEditorInputSerializer(EditorWalkThroughInputSerializer.ID, EditorWalkThroughInputSerializer); Registry.as(WorkbenchExtensions.Workbench) .registerWorkbenchContribution(WalkThroughSnippetContentProvider, LifecyclePhase.Starting); diff --git a/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughInput.ts b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughInput.ts index 09d5c3df..be873633 100644 --- a/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughInput.ts +++ b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughInput.ts @@ -3,10 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { EditorInput, EditorModel, ITextEditorModel } from 'vs/workbench/common/editor'; +import { EditorInput, EditorModel } from 'vs/workbench/common/editor'; import { URI } from 'vs/base/common/uri'; import { DisposableStore, IReference } from 'vs/base/common/lifecycle'; -import { ITextModelService } from 'vs/editor/common/services/resolverService'; +import { ITextEditorModel, ITextModelService } from 'vs/editor/common/services/resolverService'; import * as marked from 'vs/base/common/marked/marked'; import { Schemas } from 'vs/base/common/network'; import { isEqual } from 'vs/base/common/resources'; @@ -30,7 +30,7 @@ export class WalkThroughModel extends EditorModel { return this.snippetRefs.map(snippet => snippet.object); } - dispose() { + override dispose() { this.snippetRefs.forEach(ref => ref.dispose()); super.dispose(); } @@ -62,15 +62,15 @@ export class WalkThroughInput extends EditorInput { super(); } - getTypeId(): string { + override get typeId(): string { return this.options.typeId; } - getName(): string { + override getName(): string { return this.options.name; } - getDescription(): string { + override getDescription(): string { return this.options.description || ''; } @@ -78,7 +78,7 @@ export class WalkThroughInput extends EditorInput { return this.options.telemetryFrom; } - getTelemetryDescriptor(): { [key: string]: unknown; } { + override getTelemetryDescriptor(): { [key: string]: unknown; } { const descriptor = super.getTelemetryDescriptor(); descriptor['target'] = this.getTelemetryFrom(); /* __GDPR__FRAGMENT__ @@ -97,7 +97,7 @@ export class WalkThroughInput extends EditorInput { return this.options.layout; } - resolve(): Promise { + override resolve(): Promise { if (!this.promise) { this.promise = requireToContent(this.options.resource) .then(content => { @@ -124,7 +124,7 @@ export class WalkThroughInput extends EditorInput { return this.promise; } - matches(otherInput: unknown): boolean { + override matches(otherInput: unknown): boolean { if (super.matches(otherInput) === true) { return true; } @@ -139,7 +139,7 @@ export class WalkThroughInput extends EditorInput { return false; } - dispose(): void { + override dispose(): void { if (this.promise) { this.promise.then(model => model.dispose()); this.promise = null; diff --git a/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts index 2f7b9e16..7b1af890 100644 --- a/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts +++ b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts @@ -190,7 +190,7 @@ export class WalkThroughPart extends EditorPane { this.notificationService.info(localize('walkThrough.gitNotFound', "It looks like Git is not installed on your system.")); return; } - this.openerService.open(this.addFrom(uri)); + this.openerService.open(this.addFrom(uri), { allowCommands: true }); } private addFrom(uri: URI) { @@ -226,7 +226,7 @@ export class WalkThroughPart extends EditorPane { } } - focus(): void { + override focus(): void { let active = document.activeElement; while (active && active !== this.content) { active = active.parentElement; @@ -267,7 +267,7 @@ export class WalkThroughPart extends EditorPane { this.scrollbar.setScrollPosition({ scrollTop: scrollPosition.scrollTop + scrollDimensions.height }); } - setInput(input: WalkThroughInput, options: EditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise { + override setInput(input: WalkThroughInput, options: EditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise { if (this.input instanceof WalkThroughInput) { this.saveTextEditorViewState(this.input); } @@ -313,10 +313,13 @@ export class WalkThroughPart extends EditorPane { model.snippets.forEach((snippet, i) => { const model = snippet.textEditorModel; + if (!model) { + return; + } const id = `snippet-${model.uri.fragment}`; const div = innerContent.querySelector(`#${id.replace(/[\\.]/g, '\\$&')}`) as HTMLElement; - const options = this.getEditorOptions(snippet.textEditorModel.getModeId()); + const options = this.getEditorOptions(model.getModeId()); const telemetryData = { target: this.input instanceof WalkThroughInput ? this.input.getTelemetryFrom() : undefined, snippet: i @@ -413,7 +416,7 @@ export class WalkThroughPart extends EditorPane { this.loadTextEditorViewState(input); this.updatedScrollPosition(); this.contentDisposables.push(Gesture.addTarget(innerContent)); - this.contentDisposables.push(domEvent(innerContent, TouchEventType.Change)(this.onTouchChange, this, this.disposables)); + this.contentDisposables.push(domEvent(innerContent, TouchEventType.Change)(e => this.onTouchChange(e as GestureEvent), this, this.disposables)); }); } @@ -499,7 +502,7 @@ export class WalkThroughPart extends EditorPane { } } - public clearInput(): void { + public override clearInput(): void { if (this.input instanceof WalkThroughInput) { this.saveTextEditorViewState(this.input); } @@ -507,7 +510,7 @@ export class WalkThroughPart extends EditorPane { super.clearInput(); } - protected saveState(): void { + protected override saveState(): void { if (this.input instanceof WalkThroughInput) { this.saveTextEditorViewState(this.input); } @@ -515,7 +518,7 @@ export class WalkThroughPart extends EditorPane { super.saveState(); } - dispose(): void { + override dispose(): void { this.editorFocus.reset(); this.contentDisposables = dispose(this.contentDisposables); this.disposables.dispose(); diff --git a/src/vs/workbench/contrib/welcome/walkthroughs/browser/walkthroughs.contribution.ts b/src/vs/workbench/contrib/welcome/walkthroughs/browser/walkthroughs.contribution.ts deleted file mode 100644 index 401d3342..00000000 --- a/src/vs/workbench/contrib/welcome/walkthroughs/browser/walkthroughs.contribution.ts +++ /dev/null @@ -1,129 +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 { localize } from 'vs/nls'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { Extensions as EditorInputExtensions, IEditorInputFactoryRegistry } from 'vs/workbench/common/editor'; -import { MenuId, registerAction2, Action2 } from 'vs/platform/actions/common/actions'; -import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { ContextKeyDefinedExpr, ContextKeyEqualsExpr } from 'vs/platform/contextkey/common/contextkey'; -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 { inWalkthroughsContext, WalkthroughsInput, WalkthroughsInputFactory, WalkthroughsPage } from 'vs/workbench/contrib/welcome/walkthroughs/browser/walkthroughs'; - -registerAction2(class extends Action2 { - constructor() { - super({ - id: 'workbench.action.showWalkthroughs', - title: localize('Walkthroughs', "Walkthroughs"), - category: localize('help', "Help"), - f1: true, - menu: { - id: MenuId.MenubarHelpMenu, - group: '1_welcome', - when: ContextKeyDefinedExpr.create('config.workbench.experimental.walkthroughs'), - order: 2, - } - }); - } - - public run(accessor: ServicesAccessor) { - accessor.get(IEditorService).openEditor(new WalkthroughsInput({}), {}); - } -}); - -Registry.as(EditorInputExtensions.EditorInputFactories).registerEditorInputFactory(WalkthroughsInput.ID, WalkthroughsInputFactory); -Registry.as(EditorExtensions.Editors).registerEditor( - EditorDescriptor.create( - WalkthroughsPage, - WalkthroughsPage.ID, - localize('walkthroughs', "Walkthroughs") - ), - [ - new SyncDescriptor(WalkthroughsInput) - ] -); - -const category = localize('walkthroughs', "Walkthroughs"); - -registerAction2(class extends Action2 { - constructor() { - super({ - id: 'walkthroughs.goBack', - title: localize('walkthroughs.goBack', "Go Back"), - category, - keybinding: { - weight: KeybindingWeight.EditorContrib, - primary: KeyCode.Escape, - when: inWalkthroughsContext - }, - precondition: ContextKeyEqualsExpr.create('activeEditor', 'walkthroughsPage'), - f1: true - }); - } - - run(accessor: ServicesAccessor) { - const editorService = accessor.get(IEditorService); - const editorPane = editorService.activeEditorPane; - if (editorPane instanceof WalkthroughsPage) { - editorPane.escape(); - } - } -}); - -registerAction2(class extends Action2 { - constructor() { - super({ - id: 'walkthroughs.next', - title: localize('walkthroughs.goNext', "Next"), - category, - keybinding: { - weight: KeybindingWeight.EditorContrib, - primary: KeyCode.DownArrow, - secondary: [KeyCode.RightArrow], - when: inWalkthroughsContext - }, - precondition: ContextKeyEqualsExpr.create('activeEditor', 'walkthroughsPage'), - f1: true - }); - } - - run(accessor: ServicesAccessor) { - const editorService = accessor.get(IEditorService); - const editorPane = editorService.activeEditorPane; - if (editorPane instanceof WalkthroughsPage) { - editorPane.focusNext(); - } - } -}); - -registerAction2(class extends Action2 { - constructor() { - super({ - id: 'walkthroughs.prev', - title: localize('walkthroughs.goPrev', "Previous"), - category, - keybinding: { - weight: KeybindingWeight.EditorContrib, - primary: KeyCode.UpArrow, - secondary: [KeyCode.LeftArrow], - when: inWalkthroughsContext - }, - precondition: ContextKeyEqualsExpr.create('activeEditor', 'walkthroughsPage'), - f1: true - }); - } - - run(accessor: ServicesAccessor) { - const editorService = accessor.get(IEditorService); - const editorPane = editorService.activeEditorPane; - if (editorPane instanceof WalkthroughsPage) { - editorPane.focusPrevious(); - } - } -}); diff --git a/src/vs/workbench/contrib/welcome/walkthroughs/browser/walkthroughs.css b/src/vs/workbench/contrib/welcome/walkthroughs/browser/walkthroughs.css deleted file mode 100644 index a468ee93..00000000 --- a/src/vs/workbench/contrib/welcome/walkthroughs/browser/walkthroughs.css +++ /dev/null @@ -1,348 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -.file-icons-enabled .show-file-icons .vscode_getting_started_page-name-file-icon.file-icon::before { - content: ' '; - background-image: url('../../../../browser/media/code-icon.svg'); -} - -.monaco-workbench .part.editor > .content .walkthroughsContainer { - box-sizing: border-box; - padding: 10px 20px; - line-height: 22px; - position: relative; - overflow: hidden; - height: inherit; - width: 100%; - user-select: initial; - -webkit-user-select: initial; -} - -.monaco-workbench .part.editor > .content .walkthroughsContainer img { - max-width: 100%; - max-height: 100%; -} - -.monaco-workbench .part.editor > .content .walkthroughsContainer .gettingStartedSlide { - width: 100%; - height: 100%; - position: absolute; - left: 0; - top: 0; -} - -.monaco-workbench .part.editor > .content .walkthroughsContainer.animationReady .gettingStartedSlide { - /* keep consistant with SLIDE_TRANSITION_TIME_MS in gettingStarted.ts */ - transition: left 0.25s, opacity 0.25s; -} - -.monaco-workbench .part.editor > .content .walkthroughsContainer .gettingStartedSlide.categories { - display: flex; - flex-direction: column; - overflow: hidden; -} - -.monaco-workbench .part.editor > .content .walkthroughsContainer .gettingStartedSlide.categories .gap { - flex: 150px 0 1000 -} - -.monaco-workbench .part.editor > .content .walkthroughsContainer .gettingStartedSlide.categories .header { - display: flex; - align-items: center; - justify-content: center; - flex-direction: column; -} - -.monaco-workbench .part.editor > .content .walkthroughsContainer .gettingStartedSlide.categories .category-title { - margin-bottom: 4px; - font-weight: 600; -} - -.monaco-workbench .part.editor > .content .walkthroughsContainer .gettingStartedSlide.categories .category-description-container { - width: 100% -} - -.monaco-workbench .part.editor > .content .walkthroughsContainer .gettingStartedSlide.categories .category-progress { - margin-top: 12px; - font-size: 12px; -} - -.monaco-workbench .part.editor > .content .walkthroughsContainer .gettingStartedSlide.categories .progress-bar-outer { - height: 8px; - border-radius: 4px; - margin-top: 4px; -} - -.monaco-workbench .part.editor > .content .walkthroughsContainer .gettingStartedSlide.categories .progress-bar-inner { - height: 100%; - border-radius: 4px; -} - -.monaco-workbench .part.editor > .content .walkthroughsContainer .gettingStartedSlide.categories .getting-started-categories-container { - display: flex; - flex-wrap: wrap; - justify-content: center; - max-width: 900px; - margin: 32px auto; -} - -.monaco-workbench .part.editor > .content .walkthroughsContainer .gettingStartedSlide.categories .getting-started-categories-scrolling-container { - overflow: scroll; - height: 100%; -} - -.monaco-workbench .part.editor > .content .walkthroughsContainer .gettingStartedSlide .getting-started-category { - width: 330px; - min-height: 80px; - font-size: 13px; - line-height: normal; - margin: 12px; - text-align: left; - display: flex; -} - -.monaco-workbench .part.editor > .content .walkthroughsContainer .gettingStartedSlide.categories .getting-started-category { - padding-right: 46px; -} - -.monaco-workbench .part.editor > .content .walkthroughsContainer .gettingStartedSlide .getting-started-category .codicon { - margin-right: 10px; - font-size: 32px; -} - -.monaco-workbench .part.editor > .content .walkthroughsContainer .gettingStartedSlide .getting-started-category img.category-icon { - margin-right: 10px; - max-width: 32px; - max-height: 32px; -} - -.monaco-workbench .part.editor > .content .walkthroughsContainer .gettingStartedSlide.detail { - display: flex; - flex-direction: column; - overflow: hidden; -} - -.monaco-workbench .part.editor > .content .walkthroughsContainer .gettingStartedSlide.detail .gap { - flex: 150px 0 1000 -} - -.monaco-workbench .part.editor > .content .walkthroughsContainer .gettingStartedSlide.detail .getting-started-category { - width: 330px; - display: flex; - padding: 10px 0 20px 12px; - margin: 0; - min-height: auto; -} - -.monaco-workbench .part.editor > .content .walkthroughsContainer .gettingStartedSlide.detail .getting-started-detail-columns .gap { - flex: 150px 1 1000 -} - -.monaco-workbench .part.editor > .content .walkthroughsContainer .gettingStartedSlide.detail .getting-started-category .codicon { - margin-left:0; - font-size: 22pt; -} - -.monaco-workbench .part.editor > .content .walkthroughsContainer .gettingStartedSlide.detail .getting-started-detail-columns { - display: flex; - justify-content: flex-start; - padding: 40px 40px 0; - max-height: calc(100% - 40px); -} - -.monaco-workbench .part.editor > .content .walkthroughsContainer .gettingStartedSlide.detail .getting-started-task { - display: flex; - width: 100%; - overflow: hidden; - transition: height .1s linear; -} - -.monaco-workbench .part.editor > .content .walkthroughsContainer .gettingStartedSlide.detail .getting-started-task:not(.expanded) { - height: 54px; - background: none; - opacity: 0.8; -} - -.monaco-workbench .part.editor > .content .walkthroughsContainer .gettingStartedSlide.detail .getting-started-detail-columns .getting-started-detail-left > div { - width: 100%; -} - -.monaco-workbench .part.editor > .content .walkthroughsContainer .gettingStartedSlide.detail .getting-started-task:not(.expanded) .task-description, -.monaco-workbench .part.editor > .content .walkthroughsContainer .gettingStartedSlide.detail .getting-started-task:not(.expanded) .image-description, -.monaco-workbench .part.editor > .content .walkthroughsContainer .gettingStartedSlide.detail .getting-started-task:not(.expanded) .actions, -.monaco-workbench .part.editor > .content .walkthroughsContainer .gettingStartedSlide.detail .getting-started-task:not(.expanded) button, -.monaco-workbench .part.editor > .content .walkthroughsContainer .gettingStartedSlide.detail .getting-started-task:not(.expanded) a { - visibility: hidden; -} - -.monaco-workbench .part.editor > .content .walkthroughsContainer .gettingStartedSlide.detail .getting-started-task .task-description-container { - width: 100%; -} - -.monaco-workbench .part.editor > .content .walkthroughsContainer .gettingStartedSlide.detail .getting-started-task .task-description { - padding-top: 8px; -} - -.monaco-workbench .part.editor > .content .walkthroughsContainer .gettingStartedSlide.detail .getting-started-task .actions { - margin-top: 12px; - display: flex; - align-items: center; -} - -.monaco-workbench .part.editor > .content .walkthroughsContainer .gettingStartedSlide.detail .getting-started-task .task-next { - margin-left: auto; - margin-right: 10px; - padding: 6px 12px; -} - -.monaco-workbench .part.editor > .content .walkthroughsContainer .gettingStartedSlide.detail .getting-started-task .codicon.hidden { - display: none; -} - -.monaco-workbench .part.editor > .content .walkthroughsContainer .gettingStartedSlide.detail .getting-started-task .codicon { - margin-right: 8px; -} - -.monaco-workbench .part.editor > .content .walkthroughsContainer .gettingStartedSlide.detail .getting-started-task-action { - padding: 6px 12px; - font-size: 13px; - margin-bottom: 0; -} - -.monaco-workbench .part.editor > .content .walkthroughsContainer .gettingStartedSlide.detail .getting-started-detail-left { - min-width: 330px; - width: 40%; - max-width: 400px; - display: flex; - flex-direction: column; -} - -.monaco-workbench .part.editor > .content .walkthroughsContainer .gettingStartedSlide.detail .full-height-scrollable { - height: 100%; -} - -.monaco-workbench .part.editor > .content .walkthroughsContainer .gettingStartedSlide.detail .getting-started-detail-container { - height: 100%; -} - -.monaco-workbench .part.editor > .content .walkthroughsContainer .gettingStartedSlide.detail .gettingStartedDetailsContent { - height: 100%; - display: flex; - flex-direction: column; -} - -.monaco-workbench .part.editor > .content .walkthroughsContainer .gettingStartedSlide.detail .getting-started-detail-right { - display: flex; - align-items: flex-start; - justify-content: center; - width: 66%; - min-height: 300px; - padding: 0px 0 20px 44px; - min-width: 400px; - max-width: 800px; -} - -.monaco-workbench .part.editor > .content .walkthroughsContainer .gettingStartedSlide.detail .getting-started-detail-right img { - object-fit: contain; - cursor: unset; -} - -.monaco-workbench .part.editor > .content .walkthroughsContainer .gettingStartedSlide.detail .getting-started-detail-right img.clickable { - cursor: pointer; -} - -.monaco-workbench .part.editor > .content .walkthroughsContainer button { - border: none; - color: inherit; - text-align: left; - padding: 16px; - margin: 1px 0; /* makes room for focus border */ - font-family: inherit; -} -.monaco-workbench .part.editor > .content .walkthroughsContainer button:hover { - cursor: pointer; -} - -.monaco-workbench .part.editor > .content .walkthroughsContainer button:focus { - outline-style: solid; -} - -.monaco-workbench .part.editor > .content .walkthroughsContainer .prev-button.button-link { - position: absolute; - left: 40px; - top: 5px; - padding: 0 2px 2px; - margin: 10px; - z-index: 1; -} - -.monaco-workbench .part.editor > .content .walkthroughsContainer .prev-button:hover { - cursor: pointer; -} - -.monaco-workbench .part.editor > .content .walkthroughsContainer .prev-button .codicon { - position: relative; - top: 3px; - left: -4px; -} - -.monaco-workbench .part.editor > .content .walkthroughsContainer .gettingStartedSlide .skip { - display: block; - margin: 2px auto; - width: fit-content; - text-align: center; -} - -.monaco-workbench .part.editor > .content .walkthroughsContainer .gettingStartedSlide h1 { - font-size: 32px; - font-weight: normal; - border-bottom: none; - margin: 0; - padding: 0; -} - -.monaco-workbench .part.editor > .content .walkthroughsContainer .gettingStartedSlide h2 { - font-weight: normal; - line-height: 26px; - margin: 0 0 4px 0; -} - -.monaco-workbench .part.editor > .content .walkthroughsContainer .gettingStartedSlide h3 { - font-size: 13px; - font-weight: 700; - margin: 0; -} - -.monaco-workbench .part.editor > .content .walkthroughsContainer .gettingStartedSlide .subtitle { - font-size: 16px; - margin: 0; - padding: 0; -} - -.monaco-workbench .part.editor > .content .walkthroughsContainer .gettingStarted.showCategories .detail { - left: 100%; - opacity: 0; -} - -.monaco-workbench .part.editor > .content .walkthroughsContainer .gettingStarted.showDetails .categories { - left: -100%; - opacity: 0; -} - -.monaco-workbench .part.editor > .content .walkthroughsContainer .button-link { - padding: 0; - background: transparent; - margin: 2px; - cursor: pointer; -} - -.monaco-workbench .part.editor > .content .walkthroughsContainer .button-link:hover { - text-decoration: underline; - background: transparent; -} - -.monaco-workbench .part.editor > .content .walkthroughsContainer .gettingStartedSlide .showOnStartup { - text-align: center; -} diff --git a/src/vs/workbench/contrib/welcome/walkthroughs/browser/walkthroughs.ts b/src/vs/workbench/contrib/welcome/walkthroughs/browser/walkthroughs.ts deleted file mode 100644 index edb26a5f..00000000 --- a/src/vs/workbench/contrib/welcome/walkthroughs/browser/walkthroughs.ts +++ /dev/null @@ -1,721 +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 'vs/css!./walkthroughs'; -import { localize } from 'vs/nls'; -import { IInstantiationService, optional } from 'vs/platform/instantiation/common/instantiation'; -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 { ICommandService } from 'vs/platform/commands/common/commands'; -import { IGettingStartedCategoryWithProgress, IGettingStartedCategoryDescriptor, IGettingStartedService } from 'vs/workbench/contrib/welcome/gettingStarted/browser/gettingStartedService'; -import { IThemeService, registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService'; -import { welcomePageBackground, welcomePageProgressBackground, welcomePageProgressForeground, welcomePageTileBackground, welcomePageTileHoverBackground } from 'vs/workbench/contrib/welcome/page/browser/welcomePageColors'; -import { activeContrastBorder, buttonBackground, buttonForeground, buttonHoverBackground, contrastBorder, descriptionForeground, focusBorder, foreground, textLinkActiveForeground, textLinkForeground } from 'vs/platform/theme/common/colorRegistry'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; -import { gettingStartedCheckedCodicon, gettingStartedUncheckedCodicon } from 'vs/workbench/contrib/welcome/gettingStarted/browser/gettingStartedIcons'; -import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { URI } from 'vs/base/common/uri'; -import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; -import { IStorageService } from 'vs/platform/storage/common/storage'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import { Schemas } from 'vs/base/common/network'; -import { IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { ITASExperimentService } from 'vs/workbench/services/experiment/common/experimentService'; - -const SLIDE_TRANSITION_TIME_MS = 250; - -export const walkthroughsInputTypeId = 'workbench.editors.walkthroughsInput'; - -export const inWalkthroughsContext = new RawContextKey('inWalkthroughs', false); - -export class WalkthroughsInput extends EditorInput { - - get resource(): URI | undefined { - return URI.from({ scheme: Schemas.walkThrough, authority: 'vscode_getting_started_page' }); - } - getTypeId(): string { - return WalkthroughsInput.ID; - } - - matches(other: unknown) { - if (other instanceof WalkthroughsInput) { - return true; - } - return false; - } - - static readonly ID = walkthroughsInputTypeId; - - constructor( - options: { selectedCategory?: string, selectedTask?: string } - ) { - super(); - this.selectedCategory = options.selectedCategory; - this.selectedTask = options.selectedTask; - } - - getName() { - return localize('gettingStarted', "Getting Started"); - } - - selectedCategory: string | undefined; - selectedTask: string | undefined; -} - -export class WalkthroughsPage extends EditorPane { - - public static ID = 'walkthroughsPage'; - - private editorInput!: WalkthroughsInput; - private inProgressScroll = Promise.resolve(); - - private dispatchListeners: DisposableStore = new DisposableStore(); - private taskDisposables: DisposableStore = new DisposableStore(); - - private gettingStartedCategories: IGettingStartedCategoryWithProgress[]; - private currentCategory: IGettingStartedCategoryWithProgress | undefined; - - private categoriesScrollbar: DomScrollableElement | undefined; - private detailsScrollbar: DomScrollableElement | undefined; - private detailImageScrollbar: DomScrollableElement | undefined; - - private container: HTMLElement; - - private contextService: IContextKeyService; - private tasExperimentService?: ITASExperimentService; - private previousSelection?: string; - - constructor( - @ICommandService private readonly commandService: ICommandService, - @IKeybindingService private readonly keybindingService: IKeybindingService, - @IGettingStartedService private readonly gettingStartedService: IGettingStartedService, - @ITelemetryService telemetryService: ITelemetryService, - @IOpenerService private readonly openerService: IOpenerService, - @IThemeService themeService: IThemeService, - @IStorageService storageService: IStorageService, - @IContextKeyService contextService: IContextKeyService, - @optional(ITASExperimentService) tasExperimentService: ITASExperimentService, - ) { - - super(WalkthroughsPage.ID, telemetryService, themeService, storageService); - - this.container = $('.walkthroughsContainer'); - this.tasExperimentService = tasExperimentService; - - this.contextService = this._register(contextService.createScoped(this.container)); - inWalkthroughsContext.bindTo(this.contextService).set(true); - - this.gettingStartedCategories = this.gettingStartedService.getCategories(); - this._register(this.dispatchListeners); - this._register(this.gettingStartedService.onDidAddTask(task => { - this.gettingStartedCategories = this.gettingStartedService.getCategories(); - this.buildCategoriesSlide(); - })); - - this._register(this.gettingStartedService.onDidProgressTask(task => { - const category = this.gettingStartedCategories.find(category => category.id === task.category); - if (!category) { throw Error('Could not find category with ID: ' + task.category); } - if (category.content.type !== 'items') { throw Error('internaal error: progressing task in a non-items category'); } - const ourTask = category.content.items.find(_task => _task.id === task.id); - if (!ourTask) { - throw Error('Could not find task with ID: ' + task.id); - } - ourTask.done = task.done; - if (category.id === this.currentCategory?.id) { - const badgeelements = assertIsDefined(document.querySelectorAll(`[data-done-task-id="${task.id}"]`)); - badgeelements.forEach(badgeelement => { - if (task.done) { - badgeelement.parentElement?.setAttribute('aria-checked', 'true'); - badgeelement.classList.remove(...ThemeIcon.asClassNameArray(gettingStartedUncheckedCodicon)); - badgeelement.classList.add('complete', ...ThemeIcon.asClassNameArray(gettingStartedCheckedCodicon)); - } - else { - badgeelement.parentElement?.setAttribute('aria-checked', 'false'); - badgeelement.classList.add(...ThemeIcon.asClassNameArray(gettingStartedUncheckedCodicon)); - badgeelement.classList.remove('complete', ...ThemeIcon.asClassNameArray(gettingStartedCheckedCodicon)); - } - }); - } - this.updateCategoryProgress(); - })); - } - - async setInput(newInput: WalkthroughsInput, options: EditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken) { - this.container.classList.remove('animationReady'); - this.editorInput = newInput; - await super.setInput(newInput, options, context, token); - await this.buildCategoriesSlide(); - setTimeout(() => this.container.classList.add('animationReady'), 0); - } - - private registerDispatchListeners() { - this.dispatchListeners.clear(); - - this.container.querySelectorAll('[x-dispatch]').forEach(element => { - const [command, argument] = (element.getAttribute('x-dispatch') ?? '').split(':'); - if (command) { - this.dispatchListeners.add(addDisposableListener(element, 'click', (e) => { - - this.commandService.executeCommand('workbench.action.keepEditor'); - - type GettingStartedActionClassification = { - command: { classification: 'PublicNonPersonalData', purpose: 'FeatureInsight' }; - argument: { classification: 'PublicNonPersonalData', purpose: 'FeatureInsight' }; - }; - type GettingStartedActionEvent = { - command: string; - argument: string | undefined; - }; - this.telemetryService.publicLog2('gettingStarted.ActionExecuted', { command, argument }); - - switch (command) { - case 'scrollPrev': { - this.scrollPrev(); - break; - } - case 'skip': { - this.runSkip(); - break; - } - case 'selectCategory': { - const selectedCategory = this.gettingStartedCategories.find(category => category.id === argument); - if (!selectedCategory) { throw Error('Could not find category with ID ' + argument); } - if (selectedCategory.content.type === 'startEntry') { - this.commandService.executeCommand(selectedCategory.content.command); - } else { - this.scrollToCategory(argument); - } - break; - } - case 'selectTask': { - this.selectTask(argument); - e.stopPropagation(); - break; - } - case 'runTaskAction': { - if (!this.currentCategory || this.currentCategory.content.type !== 'items') { - throw Error('cannot run task action for category of non items type' + this.currentCategory?.id); - } - const taskToRun = assertIsDefined(this.currentCategory?.content.items.find(task => task.id === argument)); - if (taskToRun.button.command) { - this.commandService.executeCommand(taskToRun.button.command); - } else if (taskToRun.button.link) { - this.openerService.open(taskToRun.button.link); - this.gettingStartedService.progressByEvent('linkOpened:' + taskToRun.button.link); - } else { - throw Error('Task ' + JSON.stringify(taskToRun) + ' does not have an associated action'); - } - e.stopPropagation(); - break; - } - default: { - console.error('Dispatch to', command, argument, 'not defined'); - break; - } - } - })); - } - }); - } - - private selectTask(id: string | undefined, contractIfAlreadySelected = true, delayFocus = true) { - const mediaElement = assertIsDefined(this.container.querySelector('.getting-started-media') as HTMLImageElement); - this.taskDisposables.clear(); - if (id) { - const taskElement = assertIsDefined(this.container.querySelector(`[data-task-id="${id}"]`)); - taskElement.parentElement?.querySelectorAll('.expanded').forEach(node => { - node.classList.remove('expanded'); - node.style.height = ``; - node.setAttribute('aria-expanded', 'false'); - }); - setTimeout(() => (taskElement as HTMLElement).focus(), delayFocus ? SLIDE_TRANSITION_TIME_MS : 0); - if (this.editorInput.selectedTask === id && contractIfAlreadySelected) { - this.previousSelection = this.editorInput.selectedTask; - this.editorInput.selectedTask = undefined; - return; - } - taskElement.style.height = `${taskElement.scrollHeight}px`; - if (!this.currentCategory || this.currentCategory.content.type !== 'items') { - throw Error('cannot expand task for category of non items type' + this.currentCategory?.id); - } - this.editorInput.selectedTask = id; - const taskToExpand = assertIsDefined(this.currentCategory.content.items.find(task => task.id === id)); - - mediaElement.setAttribute('alt', taskToExpand.media.altText); - this.updateMediaSourceForColorMode(mediaElement, taskToExpand.media.path); - this.taskDisposables.add(addDisposableListener(mediaElement, 'load', () => mediaElement.width = mediaElement.naturalWidth * 2 / 3)); - if (taskToExpand.button.link) { - this.taskDisposables.add(addDisposableListener(mediaElement, 'click', () => taskElement.querySelector('button')?.click())); - mediaElement.classList.add('clickable'); - } else { - mediaElement.classList.remove('clickable'); - } - this.taskDisposables.add(this.themeService.onDidColorThemeChange(() => this.updateMediaSourceForColorMode(mediaElement, taskToExpand.media.path))); - taskElement.classList.add('expanded'); - taskElement.setAttribute('aria-expanded', 'true'); - } else { - this.editorInput.selectedTask = undefined; - mediaElement.setAttribute('src', ''); - mediaElement.setAttribute('alt', ''); - } - setTimeout(() => { - // rescan after animation finishes - this.detailsScrollbar?.scanDomNode(); - this.detailImageScrollbar?.scanDomNode(); - }, 100); - this.detailsScrollbar?.scanDomNode(); - this.detailImageScrollbar?.scanDomNode(); - } - - private updateMediaSourceForColorMode(element: HTMLImageElement, sources: { hc: URI, dark: URI, light: URI }) { - const themeType = this.themeService.getColorTheme().type; - element.src = sources[themeType].toString(true); - } - - createEditor(parent: HTMLElement) { - const tasksContent = - $('.gettingStartedDetailsContent', {}, - $('.gap'), - $('.getting-started-detail-columns', {}, - $('.gap'), - $('.getting-started-detail-left', {}, - $('.getting-started-detail-title')), - $('.getting-started-detail-right', {}, - $('img.getting-started-media')), - $('.gap'), - ), - $('.gap') - ); - - const tasksSlide = - $('.gettingStartedSlideDetails.gettingStartedSlide.detail', {}, - $('button.prev-button.button-link', { 'x-dispatch': 'scrollPrev' }, $('span.scroll-button.codicon.codicon-chevron-left'), localize('more', "More")), - tasksContent - ); - - const gettingStartedPage = - $('.gettingStarted.welcomePageFocusElement', { - role: 'document', - tabIndex: '0', - 'aria-label': localize('gettingStartedLabel', "Getting Started. Overview of how to get up to speed with your editor.") - }, - $('.gettingStartedSlideCategory.gettingStartedSlide.categories'), - tasksSlide - ); - - - if (this.detailImageScrollbar) { this.detailImageScrollbar.dispose(); } - this.detailImageScrollbar = this._register(new DomScrollableElement(tasksContent, { className: 'full-height-scrollable' })); - tasksSlide.appendChild(this.detailImageScrollbar.getDomNode()); - this.detailImageScrollbar.scanDomNode(); - - this.container.appendChild(gettingStartedPage); - parent.appendChild(this.container); - } - - private async buildCategoriesSlide() { - const categoryElements = this.gettingStartedCategories.filter(category => category.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)); - - return $('button.getting-started-category', - { - 'x-dispatch': 'selectCategory:' + category.id, - 'role': 'listitem', - }, - this.iconWidgetFor(category), - categoryDescriptionElement); - }); - - const categoryScrollContainer = $('.getting-started-categories-scrolling-container'); - const categoriesContainer = $('.getting-started-categories-container', { 'role': 'list' }); - categoryElements.forEach(element => { - categoriesContainer.appendChild(element); - }); - - categoryScrollContainer.appendChild(categoriesContainer); - - if (this.categoriesScrollbar) { this.categoriesScrollbar.dispose(); } - this.categoriesScrollbar = this._register(new DomScrollableElement(categoryScrollContainer, {})); - 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('walkthroughs', "Walkthroughs")), - ), - this.categoriesScrollbar.getDomNode(), - $('.gap') - ); - this.categoriesScrollbar.scanDomNode(); - - this.updateCategoryProgress(); - this.registerDispatchListeners(); - - - if (this.editorInput.selectedCategory) { - this.currentCategory = this.gettingStartedCategories.find(category => category.id === this.editorInput.selectedCategory); - if (!this.currentCategory) { - throw Error('Could not restore to category ' + this.editorInput.selectedCategory + ' as it was not found'); - } - this.buildCategorySlide(this.editorInput.selectedCategory, this.editorInput.selectedTask); - this.setSlide('details'); - return; - } - - const someItemsComplete = this.gettingStartedCategories.some(categry => categry.content.type === 'items' && categry.content.stepsComplete); - if (!someItemsComplete) { - const fistContentBehaviour = await Promise.race([ - this.tasExperimentService?.getTreatment<'index' | 'openToFirstCategory'>('GettingStartedFirstContent'), - new Promise<'index'>(resolve => setTimeout(() => resolve('index'), 1000)), - ]); - - if (fistContentBehaviour === 'openToFirstCategory') { - this.currentCategory = assertIsDefined(this.gettingStartedCategories.find(category => category.content.type === 'items')); - this.editorInput.selectedCategory = this.currentCategory?.id; - this.buildCategorySlide(this.editorInput.selectedCategory); - this.setSlide('details'); - return; - } - } - - this.setSlide('categories'); - } - - layout() { - this.categoriesScrollbar?.scanDomNode(); - this.detailsScrollbar?.scanDomNode(); - this.detailImageScrollbar?.scanDomNode(); - - // Don't let our spacer elements move around based on internal content changes, only when the external size changes - this.container.querySelectorAll('.gap').forEach(element => { - element.style.width = ''; - element.style.height = ''; - setTimeout(() => { - element.style.width = `${element.clientWidth}px`; - element.style.height = `${element.clientHeight}px`; - }, 0); - }); - } - - private updateCategoryProgress() { - document.querySelectorAll('.category-progress').forEach(element => { - const categoryID = element.getAttribute('x-data-category-id'); - const category = this.gettingStartedCategories.find(category => category.id === categoryID); - if (!category) { throw Error('Could not find category with ID ' + categoryID); } - if (category.content.type !== 'items') { throw Error('Category with ID ' + categoryID + ' is not of items type'); } - const numDone = category.content.items.filter(task => task.done).length; - const numTotal = category.content.items.length; - - const message = assertIsDefined(element.firstChild); - const bar = assertIsDefined(element.querySelector('.progress-bar-inner')) as HTMLDivElement; - bar.setAttribute('aria-valuemin', '0'); - bar.setAttribute('aria-valuenow', '' + numDone); - bar.setAttribute('aria-valuemax', '' + numTotal); - - bar.style.width = `${(numDone / numTotal) * 100}%`; - - if (numTotal === numDone) { - message.textContent = `All items complete!`; - } - else { - message.textContent = `${numDone} of ${numTotal} items complete`; - } - }); - } - - private async scrollToCategory(categoryID: string) { - this.inProgressScroll = this.inProgressScroll.then(async () => { - this.clearDetialView(); - this.editorInput.selectedCategory = categoryID; - this.currentCategory = this.gettingStartedCategories.find(category => category.id === categoryID); - this.buildCategorySlide(categoryID); - this.setSlide('details'); - }); - } - - private iconWidgetFor(category: IGettingStartedCategoryDescriptor) { - return category.icon.type === 'icon' ? $(ThemeIcon.asCSSSelector(category.icon.icon)) : $('img.category-icon', { src: category.icon.path }); - } - - private buildCategorySlide(categoryID: string, selectedItem?: string) { - const category = this.gettingStartedCategories.find(category => category.id === categoryID); - let foundNext = false; - const nextCategory = this.gettingStartedCategories.find(category => { - if (foundNext && category.content.type === 'items') { return true; } - if (category.id === categoryID) { foundNext = true; } - return false; - }); - - if (!category) { throw Error('could not find category with ID ' + categoryID); } - if (category.content.type !== 'items') { throw Error('category with ID ' + categoryID + ' is not of items type'); } - - const leftColumn = assertIsDefined(this.container.querySelector('.getting-started-detail-left')); - const detailTitle = assertIsDefined(this.container.querySelector('.getting-started-detail-title')); - const oldTitle = detailTitle.querySelector('.getting-started-category'); - if (oldTitle) { detailTitle.removeChild(oldTitle); } - - detailTitle.appendChild( - $('.getting-started-category', - {}, - this.iconWidgetFor(category), - $('.category-description-container', {}, - $('h2.category-title', {}, category.title), - $('.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', {}, - $('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")),] - : [] - )) - ))); - - const detailContainer = $('.getting-started-detail-container', { 'role': 'list' }); - if (this.detailsScrollbar) { this.detailsScrollbar.getDomNode().remove(); this.detailsScrollbar.dispose(); } - this.detailsScrollbar = this._register(new DomScrollableElement(detailContainer, { className: 'full-height-scrollable' })); - categoryElements.forEach(element => detailContainer.appendChild(element)); - leftColumn.appendChild(this.detailsScrollbar.getDomNode()); - - const toExpand = category.content.items.find(item => !item.done) ?? category.content.items[0]; - this.selectTask(selectedItem ?? toExpand.id, false); - this.detailsScrollbar.scanDomNode(); - this.registerDispatchListeners(); - } - - private clearDetialView() { - const detailContainer = (this.container.querySelector('.getting-started-detail-container')); - detailContainer?.remove(); - const detailTitle = assertIsDefined(this.container.querySelector('.getting-started-detail-title')); - while (detailTitle.firstChild) { detailTitle.removeChild(detailTitle.firstChild); } - } - - private getKeybindingLabel(command: string) { - const binding = this.keybindingService.lookupKeybinding(command); - if (!binding) { return ''; } - else { return ` (${binding.getLabel()})`; } - } - - private async scrollPrev() { - this.inProgressScroll = this.inProgressScroll.then(async () => { - this.currentCategory = undefined; - this.editorInput.selectedCategory = undefined; - this.editorInput.selectedTask = undefined; - this.selectTask(undefined); - this.setSlide('categories'); - }); - } - - private runSkip() { - this.commandService.executeCommand('workbench.action.closeActiveEditor'); - } - - escape() { - if (this.editorInput.selectedCategory) { - this.scrollPrev(); - } else { - this.runSkip(); - } - } - - focusNext() { - if (this.editorInput.selectedCategory) { - const allTasks = this.currentCategory?.content.type === 'items' && this.currentCategory.content.items; - if (allTasks) { - const toFind = this.editorInput.selectedTask ?? this.previousSelection; - const selectedIndex = allTasks.findIndex(task => task.id === toFind); - if (allTasks[selectedIndex + 1]?.id) { this.selectTask(allTasks[selectedIndex + 1]?.id, true, false); } - } - } else { - (document.activeElement?.nextElementSibling as HTMLElement)?.focus?.(); - } - } - - focusPrevious() { - if (this.editorInput.selectedCategory) { - const allTasks = this.currentCategory?.content.type === 'items' && this.currentCategory.content.items; - if (allTasks) { - const toFind = this.editorInput.selectedTask ?? this.previousSelection; - const selectedIndex = allTasks.findIndex(task => task.id === toFind); - if (allTasks[selectedIndex - 1]?.id) { this.selectTask(allTasks[selectedIndex - 1]?.id, true, false); } - } - } else { - (document.activeElement?.previousElementSibling as HTMLElement)?.focus?.(); - } - } - - 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') { - slideManager.classList.remove('showDetails'); - 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(); - } else { - slideManager.classList.add('showDetails'); - slideManager.classList.remove('showCategories'); - this.container.querySelector('.gettingStartedSlideDetails')!.querySelectorAll('button').forEach(button => button.disabled = false); - this.container.querySelector('.gettingStartedSlideCategory')!.querySelectorAll('button').forEach(button => button.disabled = true); - } - } -} - -export class WalkthroughsInputFactory implements IEditorInputFactory { - public canSerialize(editorInput: WalkthroughsInput): boolean { - return true; - } - - public serialize(editorInput: WalkthroughsInput): string { - return JSON.stringify({ selectedCategory: editorInput.selectedCategory, selectedTask: editorInput.selectedTask }); - } - - public deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): WalkthroughsInput { - try { - const { selectedCategory, selectedTask } = JSON.parse(serializedEditorInput); - return new WalkthroughsInput({ selectedCategory, selectedTask }); - } catch { } - return new WalkthroughsInput({}); - } -} - -registerThemingParticipant((theme, collector) => { - - const backgroundColor = theme.getColor(welcomePageBackground); - if (backgroundColor) { - collector.addRule(`.monaco-workbench .part.editor > .content .welcomePageContainer { background-color: ${backgroundColor}; }`); - } - - const foregroundColor = theme.getColor(foreground); - if (foregroundColor) { - collector.addRule(`.monaco-workbench .part.editor > .content .walkthroughsContainer { color: ${foregroundColor}; }`); - } - - const descriptionColor = theme.getColor(descriptionForeground); - if (descriptionColor) { - collector.addRule(`.monaco-workbench .part.editor > .content .walkthroughsContainer .description { color: ${descriptionColor}; }`); - collector.addRule(`.monaco-workbench .part.editor > .content .walkthroughsContainer .category-progress .message { color: ${descriptionColor}; }`); - } - - const iconColor = theme.getColor(textLinkForeground); - if (iconColor) { - collector.addRule(`.monaco-workbench .part.editor > .content .walkthroughsContainer .getting-started-category .codicon { color: ${iconColor} }`); - collector.addRule(`.monaco-workbench .part.editor > .content .walkthroughsContainer .gettingStartedSlide.detail .getting-started-task .codicon.complete { color: ${iconColor} } `); - collector.addRule(`.monaco-workbench .part.editor > .content .walkthroughsContainer .gettingStartedSlide.detail .getting-started-task.expanded .codicon { color: ${iconColor} } `); - } - - const buttonColor = theme.getColor(welcomePageTileBackground); - if (buttonColor) { - collector.addRule(`.monaco-workbench .part.editor > .content .walkthroughsContainer button { background: ${buttonColor}; }`); - } - - const buttonHoverColor = theme.getColor(welcomePageTileHoverBackground); - if (buttonHoverColor) { - collector.addRule(`.monaco-workbench .part.editor > .content .walkthroughsContainer button:hover { background: ${buttonHoverColor}; }`); - } - if (buttonColor && buttonHoverColor) { - collector.addRule(`.monaco-workbench .part.editor > .content .walkthroughsContainer button.expanded:hover { background: ${buttonColor}; }`); - } - - const emphasisButtonForeground = theme.getColor(buttonForeground); - if (emphasisButtonForeground) { - collector.addRule(`.monaco-workbench .part.editor > .content .walkthroughsContainer button.emphasis { color: ${emphasisButtonForeground}; }`); - } - - const emphasisButtonBackground = theme.getColor(buttonBackground); - if (emphasisButtonBackground) { - collector.addRule(`.monaco-workbench .part.editor > .content .walkthroughsContainer button.emphasis { background: ${emphasisButtonBackground}; }`); - } - - const pendingItemColor = theme.getColor(descriptionForeground); - if (pendingItemColor) { - collector.addRule(`.monaco-workbench .part.editor > .content .walkthroughsContainer .gettingStartedSlide.detail .getting-started-task .codicon { color: ${pendingItemColor} } `); - } - - const emphasisButtonHoverBackground = theme.getColor(buttonHoverBackground); - if (emphasisButtonHoverBackground) { - collector.addRule(`.monaco-workbench .part.editor > .content .walkthroughsContainer button.emphasis:hover { background: ${emphasisButtonHoverBackground}; }`); - } - - const link = theme.getColor(textLinkForeground); - if (link) { - collector.addRule(`.monaco-workbench .part.editor > .content .walkthroughsContainer a { color: ${link}; }`); - collector.addRule(`.monaco-workbench .part.editor > .content .walkthroughsContainer .button-link { color: ${link}; }`); - collector.addRule(`.monaco-workbench .part.editor > .content .walkthroughsContainer .button-link * { color: ${link}; }`); - } - const activeLink = theme.getColor(textLinkActiveForeground); - if (activeLink) { - collector.addRule(`.monaco-workbench .part.editor > .content .walkthroughsContainer a:hover, - .monaco-workbench .part.editor > .content .walkthroughsContainer a:active { color: ${activeLink}; }`); - } - const focusColor = theme.getColor(focusBorder); - if (focusColor) { - collector.addRule(`.monaco-workbench .part.editor > .content .walkthroughsContainer a:focus { outline-color: ${focusColor}; }`); - } - const border = theme.getColor(contrastBorder); - if (border) { - collector.addRule(`.monaco-workbench .part.editor > .content .walkthroughsContainer button { border-color: ${border}; border: 1px solid; }`); - } - const activeBorder = theme.getColor(activeContrastBorder); - if (activeBorder) { - collector.addRule(`.monaco-workbench .part.editor > .content .walkthroughsContainer button:hover { outline-color: ${activeBorder}; }`); - } - - const progressBackground = theme.getColor(welcomePageProgressBackground); - if (progressBackground) { - collector.addRule(`.monaco-workbench .part.editor > .content .walkthroughsContainer .gettingStartedSlide.categories .progress-bar-outer { background-color: ${progressBackground}; }`); - } - const progressForeground = theme.getColor(welcomePageProgressForeground); - if (progressForeground) { - collector.addRule(`.monaco-workbench .part.editor > .content .walkthroughsContainer .gettingStartedSlide.categories .progress-bar-inner { background-color: ${progressForeground}; }`); - } -}); diff --git a/src/vs/workbench/contrib/workspace/browser/media/trusted-badge.png b/src/vs/workbench/contrib/workspace/browser/media/trusted-badge.png new file mode 100644 index 0000000000000000000000000000000000000000..c53b0eea12ff26f5744503a94c74b23dd2191376 GIT binary patch literal 4330 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 TGZ_7v00000NkvXXu0mjf(=Cql literal 0 HcmV?d00001 diff --git a/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts b/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts index 00c1906c..4bdd5fa9 100644 --- a/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts +++ b/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts @@ -8,180 +8,241 @@ import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { localize } from 'vs/nls'; import { Action2, MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions'; +import { ConfigurationScope, Extensions as ConfigurationExtensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { Severity } from 'vs/platform/notification/common/notification'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IWorkspaceTrustService, WorkspaceTrustState, WorkspaceTrustStateChangeEvent, workspaceTrustStateToString } from 'vs/platform/workspace/common/workspaceTrust'; +import { IWorkspaceTrustManagementService, IWorkspaceTrustRequestService, WorkspaceTrustRequestOptions, workspaceTrustToString } from 'vs/platform/workspace/common/workspaceTrust'; import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; -import { IActivityService, IconBadge } from 'vs/workbench/services/activity/common/activity'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; -import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; import { Codicon } from 'vs/base/common/codicons'; import { ThemeColor } from 'vs/workbench/api/common/extHostTypes'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IStatusbarEntry, IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment } from 'vs/workbench/services/statusbar/common/statusbar'; -import { IEditorRegistry, Extensions as EditorExtensions, EditorDescriptor } from 'vs/workbench/browser/editor'; +import { IEditorRegistry, EditorDescriptor } from 'vs/workbench/browser/editor'; import { WorkspaceTrustEditor } from 'vs/workbench/contrib/workspace/browser/workspaceTrustEditor'; import { WorkspaceTrustEditorInput } from 'vs/workbench/services/workspaces/browser/workspaceTrustEditorInput'; -import { WorkspaceTrustContext, WORKSPACE_TRUST_ENABLED } from 'vs/workbench/services/workspaces/common/workspaceTrust'; -import { EditorInput, Extensions as EditorInputExtensions, IEditorInputFactory, IEditorInputFactoryRegistry } from 'vs/workbench/common/editor'; +import { isWorkspaceTrustEnabled, WorkspaceTrustContext, WORKSPACE_TRUST_ENABLED, WORKSPACE_TRUST_STARTUP_PROMPT } from 'vs/workbench/services/workspaces/common/workspaceTrust'; +import { EditorInput, IEditorInputSerializer, IEditorInputFactoryRegistry, EditorExtensions } from 'vs/workbench/common/editor'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { isWeb } from 'vs/base/common/platform'; +import { IsWebContext } from 'vs/platform/contextkey/common/contextkeys'; +import { dirname, resolve } from 'vs/base/common/path'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import product from 'vs/platform/product/common/product'; +import { MarkdownString } from 'vs/base/common/htmlContent'; +import { isSingleFolderWorkspaceIdentifier, toWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; +import { Schemas } from 'vs/base/common/network'; +import { STATUS_BAR_PROMINENT_ITEM_BACKGROUND, STATUS_BAR_PROMINENT_ITEM_FOREGROUND } from 'vs/workbench/common/theme'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; + +const STARTUP_PROMPT_SHOWN_KEY = 'workspace.trust.startupPrompt.shown'; -const workspaceTrustIcon = registerIcon('workspace-trust-icon', Codicon.shield, localize('workspaceTrustIcon', "Icon for workspace trust badge.")); /* * Trust Request UX Handler */ export class WorkspaceTrustRequestHandler extends Disposable implements IWorkbenchContribution { - private readonly requestModel = this.workspaceTrustService.requestModel; - private readonly badgeDisposable = this._register(new MutableDisposable()); constructor( - @IHostService private readonly hostService: IHostService, @IDialogService private readonly dialogService: IDialogService, - @IActivityService private readonly activityService: IActivityService, @ICommandService private readonly commandService: ICommandService, - @IConfigurationService private readonly configurationService: IConfigurationService, - @ITelemetryService private readonly telemetryService: ITelemetryService, - @IExtensionService private readonly extensionService: IExtensionService, @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, - @IWorkspaceTrustService private readonly workspaceTrustService: IWorkspaceTrustService, + @IWorkspaceTrustManagementService private readonly workspaceTrustManagementService: IWorkspaceTrustManagementService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @IStorageService private readonly storageService: IStorageService, + @IWorkspaceTrustRequestService private readonly workspaceTrustRequestService: IWorkspaceTrustRequestService, ) { super(); - this.registerListeners(); - } - - private toggleRequestBadge(visible: boolean): void { - this.badgeDisposable.clear(); - - if (visible) { - this.badgeDisposable.value = this.activityService.showGlobalActivity({ - badge: new IconBadge(workspaceTrustIcon, () => localize('requestTrustIconText', "Some features require workspace trust.")), - priority: 10 - }); + if (isWorkspaceTrustEnabled(configurationService)) { + this.registerListeners(); + this.showModalOnStart(); } } + private get startupPromptSetting(): 'always' | 'once' | 'never' { + return this.configurationService.getValue(WORKSPACE_TRUST_STARTUP_PROMPT); + } + + private get useWorkspaceLanguage(): boolean { + return !isSingleFolderWorkspaceIdentifier(toWorkspaceIdentifier(this.workspaceContextService.getWorkspace())); + } + + private get modalTitle(): string { + return this.useWorkspaceLanguage ? + localize('workspaceTrust', "Do you trust the authors of the files in this workspace?") : + localize('folderTrust', "Do you trust the authors of the files in this folder?"); + } + + private async doShowModal(question: string, trustedOption: { label: string, sublabel: string }, untrustedOption: { label: string, sublabel: string }, markdownStrings: string[], trustParentString?: string): Promise { + const result = await this.dialogService.show( + Severity.Info, + question, + [ + trustedOption.label, + untrustedOption.label, + ], + { + checkbox: trustParentString ? { + label: trustParentString + } : undefined, + custom: { + buttonDetails: [ + trustedOption.sublabel, + untrustedOption.sublabel + ], + disableCloseAction: true, + icon: Codicon.shield, + markdownDetails: markdownStrings.map(md => { return { markdown: new MarkdownString(md) }; }) + }, + } + ); + + // Dialog result + switch (result.choice) { + case 0: + if (result.checkboxChecked) { + this.workspaceTrustManagementService.setParentFolderTrust(true); + } else { + this.workspaceTrustRequestService.completeRequest(true); + } + break; + case 1: + this.workspaceTrustRequestService.cancelRequest(); + break; + } + + this.storageService.store(STARTUP_PROMPT_SHOWN_KEY, true, StorageScope.WORKSPACE, StorageTarget.MACHINE); + } + + private showModalOnStart(): void { + if (this.workspaceTrustManagementService.isWorkpaceTrusted()) { + return; + } + + if (this.startupPromptSetting === 'never') { + return; + } + + if (this.startupPromptSetting === 'once' && this.storageService.getBoolean(STARTUP_PROMPT_SHOWN_KEY, StorageScope.WORKSPACE, false)) { + return; + } + + let checkboxText: string | undefined; + const workspaceIdentifier = toWorkspaceIdentifier(this.workspaceContextService.getWorkspace())!; + const isSingleFolderWorkspace = isSingleFolderWorkspaceIdentifier(workspaceIdentifier); + if (isSingleFolderWorkspaceIdentifier(workspaceIdentifier) && workspaceIdentifier.uri.scheme === Schemas.file) { + checkboxText = localize('checkboxString', "I trust the authors of all files in the parent folder"); + } + + // Show Workspace Trust Start Dialog + this.doShowModal( + this.modalTitle, + { label: localize('trustOption', "Yes, I trust the authors"), sublabel: isSingleFolderWorkspace ? localize('trustFolderOptionDescription', "Trust folder and enable all features") : localize('trustWorkspaceOptionDescription', "Trust workspace and enable all features") }, + { label: localize('dontTrustOption', "No, I don't trust the authors"), sublabel: isSingleFolderWorkspace ? localize('dontTrustFolderOptionDescription', "Browse folder in restricted mode") : localize('dontTrustWorkspaceOptionDescription', "Browse workspace in restricted mode") }, + [ + !isSingleFolderWorkspace ? + localize('workspaceStartupTrustDetails', "{0} provides advanced editing features that may automatically execute files in this workspace.", product.nameShort) : + localize('folderStartupTrustDetails', "{0} provides advanced editing features that may automatically execute files in this folder.", product.nameShort), + localize('startupTrustRequestLearnMore', "If you don't trust the authors of these files, we recommend to continue in restricted mode as the files may be malicious. See [our docs](https://aka.ms/vscode-workspace-trust) to learn more.") + ], + checkboxText + ); + } + private registerListeners(): void { - this._register(this.requestModel.onDidInitiateRequest(async () => { - if (this.requestModel.trustRequestOptions) { - this.toggleRequestBadge(true); + this._register(this.workspaceTrustRequestService.onDidInitiateWorkspaceTrustRequest(async requestOptions => { + if (requestOptions.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 = requestOptions.message ?? defaultMessage; - type WorkspaceTrustRequestedEventClassification = { - modal: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; - workspaceId: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; - extensions: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; - }; + // Buttons + const buttons = requestOptions.buttons ?? [ + { label: this.useWorkspaceLanguage ? localize('grantWorkspaceTrustButton', "Trust Workspace & Continue") : localize('grantFolderTrustButton', "Trust Folder & Continue"), type: 'ContinueWithTrust' }, + { label: localize('manageWorkspaceTrustButton', "Manage"), type: 'Manage' } + ]; + // Add Cancel button if not provided + if (!buttons.some(b => b.type === 'Cancel')) { + buttons.push({ label: localize('cancelWorkspaceTrustButton', "Cancel"), type: 'Cancel' }); + } - type WorkspaceTrustRequestedEvent = { - modal: boolean, - workspaceId: string, - extensions: string[] - }; - - this.telemetryService.publicLog2('workspaceTrustRequested', { - modal: this.requestModel.trustRequestOptions.modal, - workspaceId: this.workspaceContextService.getWorkspace().id, - extensions: (await this.extensionService.getExtensions()).filter(ext => !!ext.workspaceTrust).map(ext => ext.identifier.value) - }); - - if (this.requestModel.trustRequestOptions.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.trustRequestOptions.message ?? defaultMessage; - - // Buttons - const buttons = this.requestModel.trustRequestOptions.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?"), - buttons.map(b => b.label), - { - 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), + // Dialog + const result = await this.dialogService.show( + Severity.Info, + this.modalTitle, + buttons.map(b => b.label), + { + cancelId: buttons.findIndex(b => b.type === 'Cancel'), + custom: { + icon: Codicon.shield, + markdownDetails: [ + { markdown: new MarkdownString(message) }, + { markdown: new MarkdownString(localize('immediateTrustRequestLearnMore', "If you don't trust the authors of these files, we do not recommend continuing as the files may be malicious. See [our docs](https://aka.ms/vscode-workspace-trust) to learn more.")) } + ] } - ); - - // Dialog result - switch (buttons[result.choice].type) { - case 'ContinueWithTrust': - this.requestModel.completeRequest(WorkspaceTrustState.Trusted); - break; - case 'ContinueWithoutTrust': - this.requestModel.completeRequest(undefined); - break; - case 'Manage': - this.requestModel.cancelRequest(); - await this.commandService.executeCommand('workbench.trust.manage'); - break; - case 'Cancel': - this.requestModel.cancelRequest(); - break; } + ); + + // Dialog result + switch (buttons[result.choice].type) { + case 'ContinueWithTrust': + this.workspaceTrustRequestService.completeRequest(true); + break; + case 'ContinueWithoutTrust': + this.workspaceTrustRequestService.completeRequest(undefined); + break; + case 'Manage': + this.workspaceTrustRequestService.cancelRequest(); + await this.commandService.executeCommand('workbench.trust.manage'); + break; + case 'Cancel': + this.workspaceTrustRequestService.cancelRequest(); + break; } } })); - this._register(this.requestModel.onDidCompleteRequest(trustState => { - if (trustState !== undefined && trustState !== WorkspaceTrustState.Unknown) { - this.toggleRequestBadge(false); + this._register(this.workspaceContextService.onWillChangeWorkspaceFolders(e => { + if (e.fromCache) { + return; } - })); - - this._register(this.workspaceTrustService.onDidChangeTrustState(trustState => { - if (trustState.currentTrustState !== undefined && trustState.currentTrustState !== WorkspaceTrustState.Unknown) { - this.toggleRequestBadge(false); + if (!isWorkspaceTrustEnabled(this.configurationService)) { + return; } + const trusted = this.workspaceTrustManagementService.isWorkpaceTrusted(); - type WorkspaceTrustStateChangedEventClassification = { - workspaceId: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; - previousState: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; - newState: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; - }; + return e.join(new Promise(async resolve => { + // Workspace is trusted and there are added/changed folders + if (trusted && (e.changes.added.length || e.changes.changed.length)) { + const addedFoldersTrustInfo = e.changes.added.map(folder => this.workspaceTrustManagementService.getFolderTrustInfo(folder.uri)); + if (!addedFoldersTrustInfo.map(i => i.trusted).every(trusted => trusted)) { + const result = await this.dialogService.show( + Severity.Info, + localize('addWorkspaceFolderMessage', "Do you trust the authors of the files in this folder?"), + [localize('yes', 'Yes'), localize('no', 'No')], + { + detail: localize('addWorkspaceFolderDetail', "You are adding files to a trusted workspace that are not currently trusted. Do you trust the authors of these new files?"), + cancelId: 1, + custom: { icon: Codicon.shield } + } + ); - type WorkspaceTrustStateChangedEvent = { - workspaceId: string, - previousState: WorkspaceTrustState, - newState: WorkspaceTrustState - }; + // Mark added/changed folders as trusted + this.workspaceTrustManagementService.setFoldersTrust(addedFoldersTrustInfo.map(i => i.uri), result.choice === 0); - this.telemetryService.publicLog2('workspaceTrustStateChanged', { - workspaceId: this.workspaceContextService.getWorkspace().id, - previousState: trustState.previousTrustState, - newState: trustState.currentTrustState - }); - })); - - this._register(this.configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration(WORKSPACE_TRUST_ENABLED)) { - const isEnabled = this.configurationService.getValue(WORKSPACE_TRUST_ENABLED); - if (!isEnabled || typeof isEnabled === 'boolean') { - this.dialogService.confirm({ - message: localize('trustConfigurationChangeMessage', "In order for this change to take effect, the window needs to be reloaded. Do you want to reload the window now?") - }).then(result => { - if (result.confirmed) { - this.hostService.reload(); - } - }); + resolve(); + } } - } + + resolve(); + })); })); } } @@ -192,58 +253,59 @@ Registry.as(WorkbenchExtensions.Workbench).regi * Status Bar Entry */ class WorkspaceTrustStatusbarItem extends Disposable implements IWorkbenchContribution { - private static readonly ID = 'status.workspaceTrust'; + private readonly entryId = `status.workspaceTrust.${this.workspaceService.getWorkspace().id}`; private readonly statusBarEntryAccessor: MutableDisposable; private pendingRequestContextKey = WorkspaceTrustContext.PendingRequest.key; private contextKeys = new Set([this.pendingRequestContextKey]); constructor( + @IConfigurationService configurationService: IConfigurationService, @IStatusbarService private readonly statusbarService: IStatusbarService, - @IWorkspaceTrustService private readonly workspaceTrustService: IWorkspaceTrustService, + @IWorkspaceTrustManagementService private readonly workspaceTrustManagementService: IWorkspaceTrustManagementService, + @IWorkspaceContextService private readonly workspaceService: IWorkspaceContextService, @IContextKeyService private readonly contextKeyService: IContextKeyService ) { super(); this.statusBarEntryAccessor = this._register(new MutableDisposable()); - if (this.workspaceTrustService.isWorkspaceTrustEnabled()) { - const entry = this.getStatusbarEntry(this.workspaceTrustService.getWorkspaceTrustState()); - this.statusBarEntryAccessor.value = this.statusbarService.addEntry(entry, WorkspaceTrustStatusbarItem.ID, localize('status.WorkspaceTrust', "Workspace Trust"), StatusbarAlignment.LEFT, 0.99 * Number.MAX_VALUE /* Right of remote indicator */); - this._register(this.workspaceTrustService.onDidChangeTrustState(trustState => this.updateStatusbarEntry(trustState))); + if (isWorkspaceTrustEnabled(configurationService)) { + const entry = this.getStatusbarEntry(this.workspaceTrustManagementService.isWorkpaceTrusted()); + this.statusBarEntryAccessor.value = this.statusbarService.addEntry(entry, this.entryId, localize('status.WorkspaceTrust', "Workspace Trust"), StatusbarAlignment.LEFT, 0.99 * Number.MAX_VALUE /* Right of remote indicator */); + this._register(this.workspaceTrustManagementService.onDidChangeTrust(trusted => this.updateStatusbarEntry(trusted))); this._register(this.contextKeyService.onDidChangeContext((contextChange) => { if (contextChange.affectsSome(this.contextKeys)) { - this.updateVisibility(this.workspaceTrustService.getWorkspaceTrustState()); + this.updateVisibility(this.workspaceTrustManagementService.isWorkpaceTrusted()); } })); - this.updateVisibility(this.workspaceTrustService.getWorkspaceTrustState()); + this.updateVisibility(this.workspaceTrustManagementService.isWorkpaceTrusted()); } } - private getStatusbarEntry(state: WorkspaceTrustState): IStatusbarEntry { - const text = workspaceTrustStateToString(state); - const backgroundColor = state === WorkspaceTrustState.Trusted ? - 'transparent' : new ThemeColor('statusBarItem.prominentBackground'); - const color = state === WorkspaceTrustState.Trusted ? '#00dd3b' : '#ff5462'; + private getStatusbarEntry(trusted: boolean): IStatusbarEntry { + const text = workspaceTrustToString(trusted); + const backgroundColor = new ThemeColor(STATUS_BAR_PROMINENT_ITEM_BACKGROUND); + const color = new ThemeColor(STATUS_BAR_PROMINENT_ITEM_FOREGROUND); return { - text: state === WorkspaceTrustState.Trusted ? `$(shield)` : `$(shield) ${text}`, - ariaLabel: localize('status.WorkspaceTrust', "Workspace Trust"), - tooltip: localize('status.WorkspaceTrust', "Workspace Trust"), + text: trusted ? `$(shield)` : `$(shield) ${text}`, + ariaLabel: trusted ? localize('status.ariaTrusted', "This workspace is trusted.") : localize('status.ariaUntrusted', "Restricted Mode: Some features are disabled because this workspace is not trusted."), + tooltip: trusted ? localize('status.tooltipTrusted', "This workspace is trusted.") : localize('status.tooltipUntrusted', "Some features are disabled because this workspace is not trusted."), command: 'workbench.trust.manage', backgroundColor, color }; } - private updateVisibility(trustState: WorkspaceTrustState): void { + private updateVisibility(trusted: boolean): void { const pendingRequest = this.contextKeyService.getContextKeyValue(this.pendingRequestContextKey) === true; - this.statusbarService.updateEntryVisibility(WorkspaceTrustStatusbarItem.ID, trustState === WorkspaceTrustState.Untrusted || pendingRequest); + this.statusbarService.updateEntryVisibility(this.entryId, !trusted || pendingRequest); } - private updateStatusbarEntry(trustStateChange: WorkspaceTrustStateChangeEvent): void { - this.statusBarEntryAccessor.value?.update(this.getStatusbarEntry(trustStateChange.currentTrustState)); - this.updateVisibility(trustStateChange.currentTrustState); + private updateStatusbarEntry(trusted: boolean): void { + this.statusBarEntryAccessor.value?.update(this.getStatusbarEntry(trusted)); + this.updateVisibility(trusted); } } @@ -255,7 +317,7 @@ Registry.as(WorkbenchExtensions.Workbench).regi /** * Trusted Workspace GUI Editor */ -class WorkspaceTrustEditorInputFactory implements IEditorInputFactory { +class WorkspaceTrustEditorInputSerializer implements IEditorInputSerializer { canSerialize(editorInput: EditorInput): boolean { return true; @@ -270,8 +332,8 @@ class WorkspaceTrustEditorInputFactory implements IEditorInputFactory { } } -Registry.as(EditorInputExtensions.EditorInputFactories) - .registerEditorInputFactory(WorkspaceTrustEditorInput.ID, WorkspaceTrustEditorInputFactory); +Registry.as(EditorExtensions.EditorInputFactories) + .registerEditorInputSerializer(WorkspaceTrustEditorInput.ID, WorkspaceTrustEditorInputSerializer); Registry.as(EditorExtensions.Editors).registerEditor( EditorDescriptor.create( @@ -288,85 +350,6 @@ Registry.as(EditorExtensions.Editors).registerEditor( * Actions */ -// Grant Workspace Trust -registerAction2(class extends Action2 { - constructor() { - super({ - id: 'workbench.trust.grant', - title: { - original: 'Grant Workspace Trust', - value: localize('grantWorkspaceTrust', "Grant Workspace Trust") - }, - category: localize('workspacesCategory', "Workspaces"), - f1: true, - precondition: WorkspaceTrustContext.TrustState.isEqualTo(WorkspaceTrustState.Trusted).negate(), - menu: { - id: MenuId.GlobalActivity, - when: WorkspaceTrustContext.PendingRequest, - group: '6_workspace_trust', - order: 10 - }, - }); - } - - async run(accessor: ServicesAccessor) { - const dialogService = accessor.get(IDialogService); - const workspaceTrustService = accessor.get(IWorkspaceTrustService); - - const result = await dialogService.confirm({ - message: localize('grantWorkspaceTrust', "Grant Workspace Trust"), - detail: localize('confirmGrantWorkspaceTrust', "Granting trust to the workspace will enable features that may pose a security risk if the contents of the workspace cannot be trusted. Are you sure you want to trust this workspace?"), - primaryButton: localize('yes', 'Yes'), - secondaryButton: localize('no', 'No') - }); - - if (result.confirmed) { - workspaceTrustService.requestModel.completeRequest(WorkspaceTrustState.Trusted); - } - - return; - } -}); - -// Deny Workspace Trust -registerAction2(class extends Action2 { - constructor() { - super({ - id: 'workbench.trust.deny', - title: { - original: 'Deny Workspace Trust', - value: localize('denyWorkspaceTrust', "Deny Workspace Trust") - }, - category: localize('workspacesCategory', "Workspaces"), - f1: true, - precondition: WorkspaceTrustContext.TrustState.isEqualTo(WorkspaceTrustState.Untrusted).negate(), - menu: { - id: MenuId.GlobalActivity, - when: WorkspaceTrustContext.PendingRequest, - group: '6_workspace_trust', - order: 20 - }, - }); - } - - async run(accessor: ServicesAccessor) { - const dialogService = accessor.get(IDialogService); - const workspaceTrustService = accessor.get(IWorkspaceTrustService); - - const result = await dialogService.confirm({ - message: localize('denyWorkspaceTrust', "Deny Workspace Trust"), - detail: localize('confirmDenyWorkspaceTrust', "Denying trust to the workspace will disable features that may pose a security risk if the contents of the workspace cannot be trusted. Are you sure you want to deny trust to this workspace?"), - primaryButton: localize('yes', 'Yes'), - secondaryButton: localize('no', 'No') - }); - - if (result.confirmed) { - workspaceTrustService.requestModel.completeRequest(WorkspaceTrustState.Untrusted); - } - return; - } -}); - // Manage Workspace Trust registerAction2(class extends Action2 { constructor() { @@ -381,7 +364,7 @@ registerAction2(class extends Action2 { id: MenuId.GlobalActivity, group: '6_workspace_trust', order: 40, - when: ContextKeyExpr.and(ContextKeyExpr.equals(`config.${WORKSPACE_TRUST_ENABLED}`, true), WorkspaceTrustContext.PendingRequest.negate()) + when: ContextKeyExpr.and(IsWebContext.negate(), ContextKeyExpr.equals(`config.${WORKSPACE_TRUST_ENABLED}`, true), WorkspaceTrustContext.PendingRequest.negate()) }, }); } @@ -404,5 +387,165 @@ MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { }, group: '6_workspace_trust', order: 40, - when: ContextKeyExpr.and(ContextKeyExpr.equals(`config.${WORKSPACE_TRUST_ENABLED}`, true), WorkspaceTrustContext.PendingRequest) + when: ContextKeyExpr.and(IsWebContext.negate(), ContextKeyExpr.equals(`config.${WORKSPACE_TRUST_ENABLED}`, true), WorkspaceTrustContext.PendingRequest) }); + +/* + * Configuration + */ +Registry.as(ConfigurationExtensions.Configuration) + .registerConfiguration({ + id: 'security', + scope: ConfigurationScope.APPLICATION, + title: localize('securityConfigurationTitle', "Security"), + type: 'object', + order: 7, + properties: { + [WORKSPACE_TRUST_ENABLED]: { + type: 'boolean', + default: false, + included: !isWeb, + description: localize('workspace.trust.description', "Controls whether or not workspace trust is enabled within VS Code."), + scope: ConfigurationScope.APPLICATION + }, + [WORKSPACE_TRUST_STARTUP_PROMPT]: { + type: 'string', + default: 'once', + included: !isWeb, + description: localize('workspace.trust.startupPrompt.description', "Controls when the startup prompt to trust a workspace is shown."), + scope: ConfigurationScope.APPLICATION, + enum: ['always', 'once', 'never'], + enumDescriptions: [ + localize('workspace.trust.startupPrompt.always', "Ask for trust every time an untrusted workspace is opened."), + localize('workspace.trust.startupPrompt.once', "Ask for trust the first time an untrusted workspace is opened."), + localize('workspace.trust.startupPrompt.never', "Do not ask for trust when an untrusted workspace is opened."), + ] + } + } + }); + +/** + * Telemetry + */ +class WorkspaceTrustTelemetryContribution extends Disposable implements IWorkbenchContribution { + constructor( + @IConfigurationService private readonly configurationService: IConfigurationService, + @IExtensionService private readonly extensionService: IExtensionService, + @ITelemetryService private readonly telemetryService: ITelemetryService, + @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, + @IWorkspaceTrustManagementService private readonly workspaceTrustManagementService: IWorkspaceTrustManagementService, + @IWorkspaceTrustRequestService private readonly workspaceTrustRequestService: IWorkspaceTrustRequestService + ) { + super(); + + this._register(this.workspaceTrustManagementService.onDidChangeTrust(isTrusted => this.logWorkspaceTrustChangeEvent(isTrusted))); + this._register(this.workspaceTrustRequestService.onDidInitiateWorkspaceTrustRequest(options => this.logWorkspaceTrustRequest(options))); + + this.logInitialWorkspaceTrustInfo(); + } + + private logInitialWorkspaceTrustInfo(): void { + if (!isWorkspaceTrustEnabled(this.configurationService)) { + return; + } + + type WorkspaceTrustInfoEventClassification = { + trustedFoldersCount: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; + }; + + type WorkspaceTrustInfoEvent = { + trustedFoldersCount: number, + }; + + this.telemetryService.publicLog2('workspaceTrustFolderCounts', { + trustedFoldersCount: this.workspaceTrustManagementService.getTrustedFolders().length, + }); + } + + private logWorkspaceTrustChangeEvent(isTrusted: boolean): void { + if (!isWorkspaceTrustEnabled(this.configurationService)) { + return; + } + + type WorkspaceTrustStateChangedEvent = { + workspaceId: string, + isTrusted: boolean + }; + + type WorkspaceTrustStateChangedEventClassification = { + workspaceId: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; + isTrusted: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; + }; + + this.telemetryService.publicLog2('workspaceTrustStateChanged', { + workspaceId: this.workspaceContextService.getWorkspace().id, + isTrusted: isTrusted + }); + + if (isTrusted) { + type WorkspaceTrustFolderInfoEventClassification = { + trustedFolderDepth: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; + workspaceFolderDepth: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; + delta: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; + }; + + type WorkspaceTrustFolderInfoEvent = { + trustedFolderDepth: number, + workspaceFolderDepth: number, + delta: number + }; + + const getDepth = (folder: string): number => { + let resolvedPath = resolve(folder); + + let depth = 0; + while (dirname(resolvedPath) !== resolvedPath && depth < 100) { + resolvedPath = dirname(resolvedPath); + depth++; + } + + return depth; + }; + + for (const folder of this.workspaceContextService.getWorkspace().folders) { + const { trusted, uri } = this.workspaceTrustManagementService.getFolderTrustInfo(folder.uri); + if (!trusted) { + continue; + } + + const workspaceFolderDepth = getDepth(folder.uri.fsPath); + const trustedFolderDepth = getDepth(uri.fsPath); + const delta = workspaceFolderDepth - trustedFolderDepth; + + this.telemetryService.publicLog2('workspaceFolderDepthBelowTrustedFolder', { workspaceFolderDepth, trustedFolderDepth, delta }); + } + } + } + + private async logWorkspaceTrustRequest(options: WorkspaceTrustRequestOptions): Promise { + if (!isWorkspaceTrustEnabled(this.configurationService)) { + return; + } + + type WorkspaceTrustRequestedEventClassification = { + modal: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; + workspaceId: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; + extensions: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; + }; + + type WorkspaceTrustRequestedEvent = { + modal: boolean, + workspaceId: string, + extensions: string[] + }; + + this.telemetryService.publicLog2('workspaceTrustRequested', { + modal: options.modal, + workspaceId: this.workspaceContextService.getWorkspace().id, + extensions: (await this.extensionService.getExtensions()).filter(ext => !!ext.capabilities?.untrustedWorkspaces).map(ext => ext.identifier.value) + }); + } +} + +Registry.as(WorkbenchExtensions.Workbench) + .registerWorkbenchContribution(WorkspaceTrustTelemetryContribution, LifecyclePhase.Restored); diff --git a/src/vs/workbench/contrib/workspace/browser/workspaceTrustColors.ts b/src/vs/workbench/contrib/workspace/browser/workspaceTrustColors.ts index 6bc8edd3..9260d626 100644 --- a/src/vs/workbench/contrib/workspace/browser/workspaceTrustColors.ts +++ b/src/vs/workbench/contrib/workspace/browser/workspaceTrustColors.ts @@ -6,6 +6,9 @@ import { localize } from 'vs/nls'; import { editorErrorForeground, registerColor } from 'vs/platform/theme/common/colorRegistry'; import { debugIconStartForeground } from 'vs/workbench/contrib/debug/browser/debugColors'; +import { welcomePageTileBackground } from 'vs/workbench/contrib/welcome/page/browser/welcomePageColors'; -export const trustedForegroundColor = registerColor('workspaceTrust.trustedForergound', { dark: debugIconStartForeground, light: debugIconStartForeground, hc: debugIconStartForeground }, localize('workspaceTrustTrustedColor', 'Color used when indicating a workspace is trusted.')); +export const trustedForegroundColor = registerColor('workspaceTrust.trustedForegound', { dark: debugIconStartForeground, light: debugIconStartForeground, hc: debugIconStartForeground }, localize('workspaceTrustTrustedColor', 'Color used when indicating a workspace is trusted.')); export const untrustedForegroundColor = registerColor('workspaceTrust.untrustedForeground', { dark: editorErrorForeground, light: editorErrorForeground, hc: editorErrorForeground }, localize('workspaceTrustUntrustedColor', 'Color used when indicating a workspace is not trusted.')); + +export const trustEditorTileBackgroundColor = registerColor('workspaceTrust.tileBackground', { dark: welcomePageTileBackground, light: welcomePageTileBackground, hc: welcomePageTileBackground }, localize('workspaceTrust.tileBackground', 'Background color for the tiles on the Workspace Trust page.')); diff --git a/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.css b/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.css index 5711d0dc..de08b354 100644 --- a/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.css +++ b/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.css @@ -17,6 +17,10 @@ height: calc(100% - 11px); } +.workspace-trust-editor:focus { + outline: none !important; +} + .workspace-trust-editor > .workspace-trust-header { padding: 14px; display: flex; @@ -37,44 +41,14 @@ margin-right: 8px; } -.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 .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 .workspace-trust-header .workspace-trust-title .workspace-trust-title-icon { + color: var(--workspace-trust-selected-state-color) !important; } .workspace-trust-editor .workspace-trust-header .workspace-trust-description { cursor: default; user-select: text; - max-width: 500px; -} - -/** Buttons Container */ -.workspace-trust-editor .workspace-trust-header .workspace-trust-buttons-row { - display: flex; - align-items: center; - justify-content: flex-end; - padding: 20px 0 10px 0; - overflow: hidden; /* buttons row should never overflow */ - white-space: nowrap; -} - -/** Buttons */ -.workspace-trust-editor .workspace-trust-header .workspace-trust-buttons-row .workspace-trust-buttons { - display: flex; - overflow: hidden; -} - -.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; - overflow: hidden; - text-overflow: ellipsis; - margin: 4px 5px; /* allows button focus outline to be visible */ - outline-offset: 2px !important; + max-width: 600px; } .workspace-trust-editor .workspace-trust-section-title { @@ -83,6 +57,12 @@ padding-bottom: 5px; } +.workspace-trust-editor .workspace-trust-subsection-title { + font-size: 16px; + font-weight: 600; + line-height: 32px; +} + .workspace-trust-editor .workspace-trust-editor-body { height: 100%; } @@ -92,91 +72,133 @@ padding: 14px; cursor: default; user-select: text; + display: flex; + flex-direction: row; + justify-content: space-evenly; } -.workspace-trust-editor .workspace-trust-features .workspace-trust-extensions-list { +.workspace-trust-editor .workspace-trust-features .workspace-trust-limitations { + min-height: 315px; +} + +.workspace-trust-editor.trusted .workspace-trust-features .workspace-trust-limitations.trusted { + border-width: 2px; + border-color: var(--workspace-trust-selected-state-color) !important; +} + +.workspace-trust-editor.untrusted .workspace-trust-features .workspace-trust-limitations.untrusted { + border-width: 2px; + border-color: var(--workspace-trust-selected-state-color) !important; +} + +.workspace-trust-editor .workspace-trust-features .workspace-trust-limitations ul { + list-style: none; + padding-inline-start: 0px; +} + +.workspace-trust-editor .workspace-trust-features .workspace-trust-limitations li { + display: flex; padding-bottom: 10px; } -.workspace-trust-editor .workspace-trust-features .workspace-trust-extension-list-title { +.workspace-trust-editor .workspace-trust-features .workspace-trust-limitations .list-item-icon { + padding-right: 5px; + line-height: 24px; +} + +.workspace-trust-editor .workspace-trust-features .workspace-trust-limitations.trusted .list-item-icon { + color: var(--workspace-trust-trusted-color) !important; + font-size: 18px; +} + +.workspace-trust-editor .workspace-trust-features .workspace-trust-limitations.untrusted .list-item-icon { + color: var(--workspace-trust-untrusted-color) !important; + font-size: 20px; +} + +.workspace-trust-editor .workspace-trust-features .workspace-trust-limitations .list-item-text { font-size: 16px; - font-weight: 600; - line-height: 32px; + line-height: 24px; } - -.workspace-trust-editor .workspace-trust-features .workspace-trust-extension-list-description { - padding-bottom: 15px; -} - - -.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 { +.workspace-trust-editor .workspace-trust-features .workspace-trust-limitations-header { 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 .workspace-trust-features .workspace-trust-extension-list-entry .workspace-trust-extension-text { +.workspace-trust-editor .workspace-trust-features .workspace-trust-limitations-header .workspace-trust-limitations-title { + font-size: 16px; + font-weight: 600; + line-height: 24px; + padding: 10px 0px; display: flex; - flex-direction: column; - margin-left: 5px; -} -.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 .workspace-trust-features .workspace-trust-extension-list-entry .workspace-trust-extension-name { - font-weight: 600; +.workspace-trust-editor .workspace-trust-features .workspace-trust-limitations-header .workspace-trust-limitations-title .workspace-trust-title-icon { + font-size: 24px; + margin-right: 8px; + display: none; +} + +.workspace-trust-editor.trusted .workspace-trust-features .workspace-trust-limitations.trusted .workspace-trust-limitations-header .workspace-trust-limitations-title .workspace-trust-title-icon { + display: unset; + color: var(--workspace-trust-selected-state-color) !important; +} + +.workspace-trust-editor.untrusted .workspace-trust-features .workspace-trust-limitations.untrusted .workspace-trust-limitations-header .workspace-trust-limitations-title .workspace-trust-title-icon { + display: unset; + color: var(--workspace-trust-selected-state-color) !important; +} + +.workspace-trust-editor .workspace-trust-features .workspace-trust-untrusted-description { + font-style: italic; + padding-bottom: 10px; +} + +/** Buttons Container */ +.workspace-trust-editor .workspace-trust-features .workspace-trust-buttons-row { + display: flex; + align-items: center; + justify-content: center; + padding: 5px 0 10px 0; + overflow: hidden; /* buttons row should never overflow */ + white-space: nowrap; + margin-top: auto; +} + +/** Buttons */ +.workspace-trust-editor .workspace-trust-features .workspace-trust-buttons-row .workspace-trust-buttons { + display: flex; + overflow: hidden; +} + +.workspace-trust-editor .workspace-trust-features .workspace-trust-buttons-row .workspace-trust-buttons .monaco-button { + width: fit-content; + width: -moz-fit-content; + padding: 5px 10px; + overflow: hidden; + text-overflow: ellipsis; + margin: 4px 5px; /* allows button focus outline to be visible */ + outline-offset: 2px !important; +} + +.workspace-trust-editor .workspace-trust-features .workspace-trust-buttons-row .workspace-trust-buttons .monaco-button-dropdown { + padding: 0 2px; +} + +.workspace-trust-editor .workspace-trust-features .workspace-trust-buttons-row .workspace-trust-buttons .monaco-button-dropdown .monaco-button { + margin-left: 1px; + margin-right: 1px; +} + +.workspace-trust-editor .workspace-trust-features .workspace-trust-buttons-row .workspace-trust-buttons .monaco-button-dropdown .monaco-dropdown-button { + padding: 5px 0px; +} + +.workspace-trust-limitations { + width: 50%; + max-width: 350px; } /** Settings */ @@ -194,6 +216,10 @@ max-width: initial; } +.workspace-trust-editor .settings-editor .settings-body .settings-tree-container .monaco-list-rows { + background: unset; +} + .workspace-trust-editor .settings-editor .settings-body .settings-tree-container .monaco-list-row .monaco-tl-contents { padding-left: 0; padding-right: 0; @@ -203,3 +229,29 @@ width: 100%; max-width: 100%; } + +.workspace-trust-editor .settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-list .setting-list-object-widget .setting-list-object-key, +.workspace-trust-editor .settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-list .setting-list-object-widget .setting-list-object-input-key { + margin-left: 4px; + min-width: 20%; +} + +.workspace-trust-intro-dialog { + min-width: min(50vw, 500px); + padding-right: 24px; +} + +.workspace-trust-intro-dialog .workspace-trust-dialog-image-row p { + display: flex; + align-items: center; +} + +.workspace-trust-intro-dialog .workspace-trust-dialog-image-row.badge-row img { + max-height: 40px; + padding-right: 10px; +} + +.workspace-trust-intro-dialog .workspace-trust-dialog-image-row.status-bar img { + max-height: 32px; + padding-right: 10px; +} diff --git a/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.ts b/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.ts index 853d136d..55326053 100644 --- a/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.ts +++ b/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.ts @@ -6,43 +6,52 @@ import { $, append, clearNode, Dimension, EventHelper } from 'vs/base/browser/dom'; import { ButtonBar } from 'vs/base/browser/ui/button/button'; import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; -import * as arrays from 'vs/base/common/arrays'; +import { Action } from 'vs/base/common/actions'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Codicon, registerCodicon } from 'vs/base/common/codicons'; +import { Color, RGBA } from 'vs/base/common/color'; +import { debounce } from 'vs/base/common/decorators'; import { Iterable } from 'vs/base/common/iterator'; -import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; +import { splitName } from 'vs/base/common/labels'; +import { DisposableStore } from 'vs/base/common/lifecycle'; import { parseLinkedText } from 'vs/base/common/linkedText'; +import { Schemas } from 'vs/base/common/network'; +import { isEqual } from 'vs/base/common/resources'; 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'; -import { ExtensionWorkspaceTrustRequirement } from 'vs/platform/extensions/common/extensions'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { ExtensionUntrustedWorkpaceSupportType } from 'vs/platform/extensions/common/extensions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IPromptChoiceWithMenu, Severity } from 'vs/platform/notification/common/notification'; import { Link } from 'vs/platform/opener/browser/link'; +import product from 'vs/platform/product/common/product'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { foreground } from 'vs/platform/theme/common/colorRegistry'; import { attachButtonStyler, attachLinkStyler, attachStylerCallback } from 'vs/platform/theme/common/styler'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IColorTheme, ICssStyleCollector, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { WorkspaceTrustState } from 'vs/platform/workspace/common/workspaceTrust'; +import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust'; +import { isSingleFolderWorkspaceIdentifier, toWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; 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 { ChoiceAction } from 'vs/workbench/common/notifications'; +import { ACTIVITY_BAR_BADGE_BACKGROUND } from 'vs/workbench/common/theme'; +import { IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; import { getInstalledExtensions, IExtensionStatus } from 'vs/workbench/contrib/extensions/common/extensionsUtils'; import { trustedForegroundColor, untrustedForegroundColor } from 'vs/workbench/contrib/workspace/browser/workspaceTrustColors'; import { IWorkspaceTrustSettingChangeEvent, WorkspaceTrustSettingArrayRenderer, WorkspaceTrustTree, WorkspaceTrustTreeModel } from 'vs/workbench/contrib/workspace/browser/workspaceTrustTree'; +import { filterSettingsRequireWorkspaceTrust, IWorkbenchConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; +import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService'; import { WorkspaceTrustEditorInput } from 'vs/workbench/services/workspaces/browser/workspaceTrustEditorInput'; -import { WorkspaceTrustEditorModel } from 'vs/workbench/services/workspaces/common/workspaceTrust'; -const untrustedIcon = registerCodicon('workspace-untrusted-icon', Codicon.workspaceUntrusted); -const trustedIcon = registerCodicon('workspace-trusted-icon', Codicon.workspaceTrusted); -const unknownIcon = registerCodicon('workspace-unknown-icon', Codicon.workspaceUnknown); +const shieldIcon = registerCodicon('workspace-trust-icon', Codicon.shield); -class WorkspaceTrustExtensionDelegate extends Delegate { - getHeight() { return super.getHeight() + 36; } -} +const checkListIcon = registerCodicon('workspace-trusted-check-icon', Codicon.check); +const xListIcon = registerCodicon('workspace-trusted-x-icon', Codicon.x); export class WorkspaceTrustEditor extends EditorPane { static readonly ID: string = 'workbench.editor.workspaceTrust'; @@ -54,22 +63,17 @@ export class WorkspaceTrustEditor extends EditorPane { private headerTitleIcon!: HTMLElement; private headerTitleText!: HTMLElement; private headerDescription!: HTMLElement; - private headerButtons!: HTMLElement; private bodyScrollBar!: DomScrollableElement; // Affected Features Section private affectedFeaturesContainer!: HTMLElement; - private extensionsContainer!: HTMLElement; - private onDemandExtensionsContainer!: HTMLElement; - private onStartExtensionsContainer!: HTMLElement; // Settings Section private configurationContainer!: HTMLElement; private trustSettingsTree!: WorkspaceTrustTree; private workspaceTrustSettingsTreeModel!: WorkspaceTrustTreeModel; - private workspaceTrustEditorModel!: WorkspaceTrustEditorModel; constructor( @ITelemetryService telemetryService: ITelemetryService, @@ -77,7 +81,12 @@ export class WorkspaceTrustEditor extends EditorPane { @IStorageService storageService: IStorageService, @IWorkspaceContextService private readonly workspaceService: IWorkspaceContextService, @IExtensionsWorkbenchService private readonly extensionWorkbenchService: IExtensionsWorkbenchService, - @IInstantiationService private readonly instantiationService: IInstantiationService + @IExtensionManifestPropertiesService private readonly extensionManifestPropertiesService: IExtensionManifestPropertiesService, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IContextMenuService private readonly contextMenuService: IContextMenuService, + @IDialogService private readonly dialogService: IDialogService, + @IWorkspaceTrustManagementService private readonly workspaceTrustManagementService: IWorkspaceTrustManagementService, + @IWorkbenchConfigurationService private readonly configurationService: IWorkbenchConfigurationService, ) { super(WorkspaceTrustEditor.ID, telemetryService, themeService, storageService); } protected createEditor(parent: HTMLElement): void { @@ -96,92 +105,92 @@ export class WorkspaceTrustEditor extends EditorPane { 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() || ''); - this.rootElement.style.setProperty('--workspace-trust-state-untrusted-color', colors.untrustedForegroundColor?.toString() || ''); + this._register(attachStylerCallback(this.themeService, { ACTIVITY_BAR_BADGE_BACKGROUND, trustedForegroundColor, untrustedForegroundColor }, colors => { + this.rootElement.style.setProperty('--workspace-trust-trusted-color', colors.trustedForegroundColor?.toString() || ''); + this.rootElement.style.setProperty('--workspace-trust-untrusted-color', colors.untrustedForegroundColor?.toString() || ''); + this.rootElement.style.setProperty('--workspace-trust-selected-state-color', colors.ACTIVITY_BAR_BADGE_BACKGROUND?.toString() || ''); + })); + + this._register(registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { + const foregroundColor = theme.getColor(foreground); + if (foregroundColor) { + const fgWithOpacity = new Color(new RGBA(foregroundColor.rgba.r, foregroundColor.rgba.g, foregroundColor.rgba.b, 0.3)); + collector.addRule(`.workspace-trust-editor .workspace-trust-features .workspace-trust-limitations { border: 1px solid ${fgWithOpacity}; margin: 0px 4px; display: flex; flex-direction: column; padding: 10px 40px;}`); + } })); } - async setInput(input: WorkspaceTrustEditorInput, options: EditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise { + override async setInput(input: WorkspaceTrustEditorInput, options: EditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise { await super.setInput(input, options, context, token); if (token.isCancellationRequested) { return; } - const model = await input.resolve(); - if (token.isCancellationRequested || !(model instanceof WorkspaceTrustEditorModel)) { + this.registerListeners(); + this.render(); + } + + private registerListeners(): void { + this._register(this.extensionWorkbenchService.onChange(() => this.render())); + this._register(this.configurationService.onDidChangeRestrictedSettings(() => this.render())); + this._register(this.workspaceTrustManagementService.onDidChangeTrust(() => this.render())); + this._register(this.workspaceTrustManagementService.onDidChangeTrustedFolders(() => this.render())); + } + + private getHeaderContainerClass(trusted: boolean): string { + if (trusted) { + return 'workspace-trust-header workspace-trust-trusted'; + } + + return 'workspace-trust-header workspace-trust-untrusted'; + } + + private useWorkspaceLanguage(): boolean { + return !isSingleFolderWorkspaceIdentifier(toWorkspaceIdentifier(this.workspaceService.getWorkspace())); + } + + private getHeaderTitleText(trusted: boolean): string { + + if (trusted) { + return this.useWorkspaceLanguage() ? localize('trustedHeaderWorkspace', "You trust this workspace") : localize('trustedHeaderFolder', "You trust this folder"); + } + + return this.useWorkspaceLanguage() ? localize('untrustedHeaderWorkspace', "You are in restricted mode") : localize('untrustedHeaderFolder', "You are in Restricted Mode"); + } + + private getHeaderDescriptionText(trusted: boolean): string { + if (trusted) { + return localize('trustedDescription', "All features are enabled because trust has been granted to the workspace. [Learn more](https://aka.ms/vscode-workspace-trust)."); + } + + return localize('untrustedDescription', "{0} is in a restricted mode intended for safe code browsing. [Learn more](https://aka.ms/vscode-workspace-trust).", product.nameShort); + } + + private getHeaderTitleIconClassNames(trusted: boolean): string[] { + return shieldIcon.classNamesArray; + } + + private rendering = false; + private rerenderDisposables: DisposableStore = this._register(new DisposableStore()); + @debounce(100) + private async render() { + if (this.rendering) { return; } - this.registerListeners(model); - - this.render(model); - - this.workspaceTrustEditorModel = model; - } - - private registerListeners(model: WorkspaceTrustEditorModel): void { - this._register(model.dataModel.onDidChangeTrustState(() => { - this.render(model); - })); - - this._register(this.extensionWorkbenchService.onChange(() => { - this.render(model); - })); - } - - private getHeaderContainerClass(trustState: WorkspaceTrustState): string { - switch (trustState) { - case WorkspaceTrustState.Trusted: - return 'workspace-trust-header workspace-trust-trusted'; - case WorkspaceTrustState.Untrusted: - return 'workspace-trust-header workspace-trust-untrusted'; - case WorkspaceTrustState.Unknown: - return 'workspace-trust-header workspace-trust-unknown'; - } - } - - private getHeaderTitleText(trustState: WorkspaceTrustState): string { - switch (trustState) { - case WorkspaceTrustState.Trusted: - return localize('trustedHeader', "This workspace is trusted"); - case WorkspaceTrustState.Untrusted: - return localize('untrustedHeader', "This workspace is not trusted"); - case WorkspaceTrustState.Unknown: - return localize('unknownHeader', "Do you want to trust this workspace?"); - } - } - - private getHeaderDescriptionText(trustState: WorkspaceTrustState): string { - switch (trustState) { - case WorkspaceTrustState.Trusted: - case WorkspaceTrustState.Untrusted: - case WorkspaceTrustState.Unknown: - return localize('unknownHeaderDescription', "Trust is required for certain extensions to function in this workspace. [Learn more](https://aka.ms/vscode-workspace-trust)."); - } - } - - private getHeaderTitleIconClassNames(trustState: WorkspaceTrustState): string[] { - switch (trustState) { - case WorkspaceTrustState.Trusted: - return trustedIcon.classNamesArray; - case WorkspaceTrustState.Untrusted: - return untrustedIcon.classNamesArray; - case WorkspaceTrustState.Unknown: - return unknownIcon.classNamesArray; - } - } - - private rerenderDisposables: DisposableStore = this._register(new DisposableStore()); - private async render(model: WorkspaceTrustEditorModel) { + this.rendering = true; this.rerenderDisposables.clear(); + const isWorkspaceTrusted = this.workspaceTrustManagementService.isWorkpaceTrusted(); + this.rootElement.classList.toggle('trusted', isWorkspaceTrusted); + this.rootElement.classList.toggle('untrusted', !isWorkspaceTrusted); + // Header Section - this.headerTitleText.innerText = this.getHeaderTitleText(model.currentWorkspaceTrustState); + this.headerTitleText.innerText = this.getHeaderTitleText(isWorkspaceTrusted); this.headerTitleIcon.className = 'workspace-trust-title-icon'; - this.headerTitleIcon.classList.add(...this.getHeaderTitleIconClassNames(model.currentWorkspaceTrustState)); + this.headerTitleIcon.classList.add(...this.getHeaderTitleIconClassNames(isWorkspaceTrusted)); this.headerDescription.innerText = ''; - const linkedText = parseLinkedText(this.getHeaderDescriptionText(model.currentWorkspaceTrustState)); + const linkedText = parseLinkedText(this.getHeaderDescriptionText(isWorkspaceTrusted)); const p = append(this.headerDescription, $('p')); for (const node of linkedText.nodes) { if (typeof node === 'string') { @@ -194,89 +203,35 @@ export class WorkspaceTrustEditor extends EditorPane { } } - this.headerContainer.className = this.getHeaderContainerClass(model.currentWorkspaceTrustState); + this.headerContainer.className = this.getHeaderContainerClass(isWorkspaceTrusted); - clearNode(this.headerButtons); - const buttonBar = this.rerenderDisposables.add(new ButtonBar(this.headerButtons)); - - const createButton = (label: string, enabled: boolean, callback: () => void) => { - const button = buttonBar.addButton(); - button.label = label; - button.enabled = enabled; - this.rerenderDisposables.add(button.onDidClick(e => { - if (e) { - EventHelper.stop(e); - } - - callback(); - })); - - this.rerenderDisposables.add(attachButtonStyler(button, this.themeService)); - }; - - - const setTrustState = (state: WorkspaceTrustState) => { - this.workspaceService.getWorkspace().folders.forEach(folder => { - this.workspaceTrustEditorModel.dataModel.setFolderTrustState(folder.uri, state); - }); - }; - - createButton(localize('trustButton', "Trust"), model.currentWorkspaceTrustState !== WorkspaceTrustState.Trusted, () => setTrustState(WorkspaceTrustState.Trusted)); - createButton(localize('doNotTrustButton', "Don't Trust"), model.currentWorkspaceTrustState !== WorkspaceTrustState.Untrusted, () => setTrustState(WorkspaceTrustState.Untrusted)); + // Settings + const settingsRequiringTrustedWorkspaceCount = filterSettingsRequireWorkspaceTrust(this.configurationService.restrictedSettings.default).length; // Features List const installedExtensions = await this.instantiationService.invokeFunction(getInstalledExtensions); - const onDemandExtensions = await this.getExtensionsByTrustRequirement(installedExtensions, 'onDemand'); - const onStartExtensions = await this.getExtensionsByTrustRequirement(installedExtensions, 'onStart'); + const onDemandExtensionCount = this.getExtensionCountByUntrustedWorkspaceSupport(installedExtensions, 'limited'); + const onStartExtensionCount = this.getExtensionCountByUntrustedWorkspaceSupport(installedExtensions, false); - this.renderExtensionList( - localize('onStartExtensions', "Disabled Extensions"), - localize('onStartExtensionsDescription', "The following extensions require the workspace to be trusted. They will be disabled while the workspace is not trusted."), - this.onStartExtensionsContainer, - onStartExtensions); - this.renderExtensionList( - localize('onDemandExtensions', "Limited Extensions"), - localize('onDemandExtensionsDescription', "The following extensions can function partially in a non-trusted workspace. Some functionality will be turned off while the workspace is not trusted."), - this.onDemandExtensionsContainer, - onDemandExtensions); + this.renderAffectedFeatures(settingsRequiringTrustedWorkspaceCount, onDemandExtensionCount + onStartExtensionCount); // Configuration Tree - this.workspaceTrustSettingsTreeModel.update(model.dataModel.getTrustStateInfo()); + this.workspaceTrustSettingsTreeModel.update(this.workspaceTrustManagementService.getTrustedFolders()); this.trustSettingsTree.setChildren(null, Iterable.map(this.workspaceTrustSettingsTreeModel.settings, s => { return { element: s }; })); + this.bodyScrollBar.getDomNode().style.height = `calc(100% - ${this.headerContainer.clientHeight}px)`; this.bodyScrollBar.scanDomNode(); + this.rendering = false; } - private async getExtensionsByTrustRequirement(extensions: IExtensionStatus[], trustRequirement: ExtensionWorkspaceTrustRequirement): Promise { - const filtered = extensions.filter(ext => ext.local.manifest.workspaceTrust?.required === trustRequirement); - const ids = filtered.map(ext => ext.identifier.id); - - return getExtensions(ids, this.extensionWorkbenchService); - } - - private renderExtensionList(title: string, description: string, parent: HTMLElement, extensions: IExtension[]) { - clearNode(parent); - - if (!extensions.length) { - return; + private getExtensionCountByUntrustedWorkspaceSupport(extensions: IExtensionStatus[], trustRequestType: ExtensionUntrustedWorkpaceSupportType): number { + const filtered = extensions.filter(ext => this.extensionManifestPropertiesService.getExtensionUntrustedWorkspaceSupportType(ext.local.manifest) === trustRequestType); + const set = new Set(); + for (const ext of filtered) { + set.add(ext.identifier.id); } - const titleElement = append(parent, $('.workspace-trust-extension-list-title')); - titleElement.innerText = title; - const descriptionElement = append(parent, $('.workspace-trust-extension-list-description')); - descriptionElement.innerText = description; - - const content = $('div', { class: 'subcontent' }); - const scrollableContent = new DomScrollableElement(content, { useShadows: false }); - append(parent, scrollableContent.getDomNode()); - - const extensionsGridView = this.instantiationService.createInstance(ExtensionsGridView, content, new WorkspaceTrustExtensionDelegate()); - extensionsGridView.setExtensions(extensions); - scrollableContent.scanDomNode(); - - this.rerenderDisposables.add(scrollableContent); - this.rerenderDisposables.add(extensionsGridView); - this.rerenderDisposables.add(toDisposable(arrays.insert(this.layoutParticipants, { layout: () => scrollableContent.scanDomNode() }))); + return set.size; } private createHeaderElement(parent: HTMLElement): void { @@ -285,17 +240,11 @@ export class WorkspaceTrustEditor extends EditorPane { this.headerTitleIcon = append(this.headerTitleContainer, $('.workspace-trust-title-icon')); this.headerTitleText = append(this.headerTitleContainer, $('.workspace-trust-title-text')); this.headerDescription = append(this.headerContainer, $('.workspace-trust-description')); - - const buttonsRow = append(this.headerContainer, $('.workspace-trust-buttons-row')); - this.headerButtons = append(buttonsRow, $('.workspace-trust-buttons')); } private createConfigurationElement(parent: HTMLElement): void { this.configurationContainer = append(parent, $('.workspace-trust-settings.settings-editor')); - const titleContainer = append(this.configurationContainer, $('.workspace-trust-section-title')); - titleContainer.innerText = localize('configurationSectionTitle', "Configure All Workspaces"); - const settingsBody = append(this.configurationContainer, $('.workspace-trust-settings-body.settings-body')); const workspaceTrustTreeContainer = append(settingsBody, $('.workspace-trust-settings-tree-container.settings-tree-container')); @@ -312,25 +261,208 @@ export class WorkspaceTrustEditor extends EditorPane { private createAffectedFeaturesElement(parent: HTMLElement): void { this.affectedFeaturesContainer = append(parent, $('.workspace-trust-features')); - const titleContainer = append(this.affectedFeaturesContainer, $('.workspace-trust-section-title')); - titleContainer.innerText = localize('affectedFeaturesTitle', "Features Affected By Workspace Trust"); + } - this.extensionsContainer = append(this.affectedFeaturesContainer, $('.workspace-trust-extensions')); - this.onDemandExtensionsContainer = append(this.extensionsContainer, $('.workspace-trust-extensions-list')); - this.onStartExtensionsContainer = append(this.extensionsContainer, $('.workspace-trust-extensions-list')); + private renderAffectedFeatures(numSettings: number, numExtensions: number): void { + clearNode(this.affectedFeaturesContainer); + const trustedContainer = append(this.affectedFeaturesContainer, $('.workspace-trust-limitations.trusted')); + this.renderLimitationsHeaderElement(trustedContainer, + this.useWorkspaceLanguage() ? localize('trustedWorkspace', "In a trusted workspace") : localize('trustedFolder', "In a Trusted Folder"), + this.useWorkspaceLanguage() ? localize('trustedWorkspaceSubtitle', "You trust the authors of the files in the current workspace. All features are enabled:") : localize('trustedFolderSubtitle', "You trust the authors of the files in the current folder. All features are enabled:")); + this.renderLimitationsListElement(trustedContainer, [ + localize('trustedTasks', "Tasks will be allowed to run"), + localize('trustedDebugging', "Debugging will be enabled"), + localize('trustedSettings', "All workspace settings will be applied"), + localize('trustedExtensions', "All extensions will be enabled") + ], checkListIcon.classNamesArray); + + const untrustedContainer = append(this.affectedFeaturesContainer, $('.workspace-trust-limitations.untrusted')); + this.renderLimitationsHeaderElement(untrustedContainer, + localize('untrustedWorkspace', "In Restricted Mode"), + this.useWorkspaceLanguage() ? localize('untrustedWorkspaceSubtitle', "You do not trust the authors of the files in the current workspace. The following features are disabled:") : localize('untrustedFolderSubtitle', "You do not trust the authors of the files in the current folder. The following features are disabled:")); + + this.renderLimitationsListElement(untrustedContainer, [ + localize('untrustedTasks', "Tasks will be disabled"), + localize('untrustedDebugging', "Debugging will be disabled"), + numSettings ? localize('untrustedSettings', "[{0} workspace settings](command:{1}) will not be applied", numSettings, 'settings.filterUntrusted') : localize('no untrustedSettings', "Workspace settings requiring trust will not be applied"), + localize('untrustedExtensions', "[{0} extensions](command:{1}) will be disabled or have limited functionality", numExtensions, 'workbench.extensions.action.listTrustRequiredExtensions') + ], xListIcon.classNamesArray); + + if (!this.workspaceTrustManagementService.isWorkpaceTrusted()) { + this.addTrustButtonToElement(trustedContainer); + } + + if (this.isTrustedExplicitlyOnly()) { + this.addDontTrustButtonToElement(untrustedContainer); + } else { + this.addTrustedTextToElement(untrustedContainer); + } + } + + private isTrustedExplicitlyOnly(): boolean { + // Can only be trusted explicitly in the single folder scenario + const workspaceIdentifier = toWorkspaceIdentifier(this.workspaceService.getWorkspace()); + if (!(isSingleFolderWorkspaceIdentifier(workspaceIdentifier) && workspaceIdentifier.uri.scheme === Schemas.file)) { + return false; + } + + // If the current folder isn't trusted directly, return false + const trustInfo = this.workspaceTrustManagementService.getFolderTrustInfo(workspaceIdentifier.uri); + if (!trustInfo.trusted || !isEqual(workspaceIdentifier.uri, trustInfo.uri)) { + return false; + } + + // Check if the parent is also trusted + if (this.workspaceTrustManagementService.canSetParentFolderTrust()) { + const { parentPath } = splitName(workspaceIdentifier.uri.fsPath); + const parentIsTrusted = this.workspaceTrustManagementService.getFolderTrustInfo(URI.file(parentPath)).trusted; + if (parentIsTrusted) { + return false; + } + } + + return true; + } + + private createButton(parent: HTMLElement, action: Action, enabled?: boolean): void { + const buttonRow = append(parent, $('.workspace-trust-buttons-row')); + const buttonContainer = append(buttonRow, $('.workspace-trust-buttons')); + const buttonBar = this.rerenderDisposables.add(new ButtonBar(buttonContainer)); + + const button = + action instanceof ChoiceAction && action.menu?.length ? + buttonBar.addButtonWithDropdown({ + title: true, + actions: action.menu ?? [], + contextMenuProvider: this.contextMenuService + }) : + buttonBar.addButton(); + + button.label = action.label; + button.enabled = enabled !== undefined ? enabled : action.enabled; + + this.rerenderDisposables.add(button.onDidClick(e => { + if (e) { + EventHelper.stop(e, true); + } + + action.run(); + })); + + this.rerenderDisposables.add(attachButtonStyler(button, this.themeService)); + } + + private addTrustButtonToElement(parent: HTMLElement): void { + if (this.workspaceTrustManagementService.canSetWorkspaceTrust()) { + + const trustUris = async (uris?: URI[]) => { + if (!uris) { + this.workspaceTrustManagementService.setWorkspaceTrust(true); + } else { + this.workspaceTrustManagementService.setFoldersTrust(uris, true); + } + }; + + const trustChoiceWithMenu: IPromptChoiceWithMenu = { + isSecondary: false, + label: localize('trustButton', "Trust"), + menu: [], + run: () => { + trustUris(); + } + }; + + const workspaceIdentifier = toWorkspaceIdentifier(this.workspaceService.getWorkspace()); + if (isSingleFolderWorkspaceIdentifier(workspaceIdentifier) && workspaceIdentifier.uri.scheme === Schemas.file) { + const { parentPath } = splitName(workspaceIdentifier.uri.fsPath); + if (parentPath) { + trustChoiceWithMenu.menu.push({ + label: localize('trustParentButton', "Trust All in Parent Folder"), + run: () => { + trustUris([URI.file(parentPath)]); + } + }); + } + } + + const isWorkspaceTrusted = this.workspaceTrustManagementService.isWorkpaceTrusted(); + this.createButton(parent, new ChoiceAction('workspace.trust.button.action', trustChoiceWithMenu), !isWorkspaceTrusted); + } + } + + private addDontTrustButtonToElement(parent: HTMLElement): void { + if (this.workspaceTrustManagementService.canSetWorkspaceTrust() && this.isTrustedExplicitlyOnly()) { + this.createButton(parent, new Action('workspace.trust.button.action.deny', localize('dontTrustButton', "Don't Trust"), undefined, true, async () => { + await this.workspaceTrustManagementService.setWorkspaceTrust(false); + })); + } + } + + private addTrustedTextToElement(parent: HTMLElement): void { + const isWorkspaceTrusted = this.workspaceTrustManagementService.isWorkpaceTrusted(); + const canSetWorkspaceTrust = this.workspaceTrustManagementService.canSetWorkspaceTrust(); + + if (canSetWorkspaceTrust && isWorkspaceTrusted) { + const textElement = append(parent, $('.workspace-trust-untrusted-description')); + textElement.innerText = this.useWorkspaceLanguage() ? localize('untrustedWorkspaceReason', "This workspace is trusted via one or more of the trusted folders below.") : localize('untrustedFolderReason', "This folder is trusted via one or more of the trusted folders below."); + } + } + + private renderLimitationsHeaderElement(parent: HTMLElement, headerText: string, subtitleText: string): void { + const limitationsHeaderContainer = append(parent, $('.workspace-trust-limitations-header')); + const titleElement = append(limitationsHeaderContainer, $('.workspace-trust-limitations-title')); + const textElement = append(titleElement, $('.workspace-trust-limitations-title-text')); + const subtitleElement = append(limitationsHeaderContainer, $('.workspace-trust-limitations-subtitle')); + + textElement.innerText = headerText; + subtitleElement.innerText = subtitleText; + } + + private renderLimitationsListElement(parent: HTMLElement, limitations: string[], iconClassNames: string[]): void { + const listContainer = append(parent, $('.workspace-trust-limitations-list-container')); + const limitationsList = append(listContainer, $('ul')); + for (const limitation of limitations) { + const limitationListItem = append(limitationsList, $('li')); + const icon = append(limitationListItem, $('.list-item-icon')); + const text = append(limitationListItem, $('.list-item-text')); + + icon.classList.add(...iconClassNames); + + const linkedText = parseLinkedText(limitation); + for (const node of linkedText.nodes) { + if (typeof node === 'string') { + append(text, document.createTextNode(node)); + } else { + const link = this.instantiationService.createInstance(Link, node); + append(text, link.el); + this.rerenderDisposables.add(link); + this.rerenderDisposables.add(attachLinkStyler(link, this.themeService)); + } + } + } } private onDidChangeSetting(change: IWorkspaceTrustSettingChangeEvent) { - if (this.workspaceTrustEditorModel) { - if (isArray(change.value)) { - if (change.key === 'trustedFolders') { - this.workspaceTrustEditorModel.dataModel.setTrustedFolders(change.value.map(item => URI.file(item))); - } + const applyChangesWithPrompt = async (showPrompt: boolean, applyChanges: () => void) => { + if (showPrompt) { + const message = localize('workspaceTrustSettingModificationMessage', "Update Workspace Trust Settings"); + const detail = localize('workspaceTrustTransitionDetail', "In order to safely complete this action, all affected windows will have to be reloaded. Are you sure you want to proceed with this action?"); + const primaryButton = localize('workspaceTrustTransitionPrimaryButton', "Yes"); + const secondaryButton = localize('workspaceTrustTransitionSecondaryButton', "No"); - if (change.key === 'untrustedFolders') { - this.workspaceTrustEditorModel.dataModel.setUntrustedFolders(change.value.map(item => URI.file(item))); + const result = await this.dialogService.show(Severity.Info, message, [primaryButton, secondaryButton], { cancelId: 1, detail, custom: { icon: Codicon.shield } }); + if (result.choice !== 0) { + return; } } + + applyChanges(); + }; + + if (isArray(change.value)) { + if (change.key === 'trustedFolders') { + applyChangesWithPrompt(false, () => this.workspaceTrustManagementService.setTrustedFolders(change.value!)); + } } } @@ -346,8 +478,6 @@ export class WorkspaceTrustEditor extends EditorPane { 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 4da885f5..94f24228 100644 --- a/src/vs/workbench/contrib/workspace/browser/workspaceTrustTree.ts +++ b/src/vs/workbench/contrib/workspace/browser/workspaceTrustTree.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { addDisposableListener, append, EventType, $, createStyleSheet, trackFocus } from 'vs/base/browser/dom'; +import { addDisposableListener, append, EventType, $, createStyleSheet, trackFocus, addStandardDisposableListener } from 'vs/base/browser/dom'; import { DefaultStyleController, IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { IList } from 'vs/base/browser/ui/tree/indexTreeModel'; import { IObjectTreeOptions } from 'vs/base/browser/ui/tree/objectTree'; @@ -23,12 +23,21 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IListService, WorkbenchObjectTree } from 'vs/platform/list/browser/listService'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { editorBackground, errorForeground, focusBorder, foreground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground } from 'vs/platform/theme/common/colorRegistry'; -import { IColorTheme, ICssStyleCollector, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { IColorTheme, ICssStyleCollector, IThemeService, registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { NonCollapsibleObjectTreeModel } from 'vs/workbench/contrib/preferences/browser/settingsTree'; -import { focusedRowBackground, focusedRowBorder, IListDataItem, ISettingListChangeEvent, ListSettingWidget, rowHoverBackground, settingsHeaderForeground } from 'vs/workbench/contrib/preferences/browser/settingsWidgets'; -import { attachStyler } from 'vs/platform/theme/common/styler'; +import { AbstractListSettingWidget, focusedRowBackground, focusedRowBorder, ISettingListChangeEvent, rowHoverBackground, settingsHeaderForeground, settingsSelectBackground, settingsTextInputBorder, settingsTextInputForeground } from 'vs/workbench/contrib/preferences/browser/settingsWidgets'; +import { attachButtonStyler, attachInputBoxStyler, attachStyler } from 'vs/platform/theme/common/styler'; import { CachedListVirtualDelegate } from 'vs/base/browser/ui/list/list'; -import { IWorkspaceTrustStateInfo, WorkspaceTrustState } from 'vs/platform/workspace/common/workspaceTrust'; +import { IAction } from 'vs/base/common/actions'; +import { settingsEditIcon, settingsRemoveIcon } from 'vs/workbench/contrib/preferences/browser/preferencesIcons'; +import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; +import { KeyCode } from 'vs/base/common/keyCodes'; +import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox'; +import { Button } from 'vs/base/browser/ui/button/button'; +import { disposableTimeout } from 'vs/base/common/async'; +import { URI, UriComponents } from 'vs/base/common/uri'; +import { Schemas } from 'vs/base/common/network'; +import { ILabelService } from 'vs/platform/label/common/label'; export class WorkspaceTrustSettingsTreeEntry { @@ -38,9 +47,9 @@ export class WorkspaceTrustSettingsTreeEntry { key: string; description: string; }; - value: string[]; + value: URI[]; - constructor(key: string, displayLabel: string, description: string, value: string[]) { + constructor(key: string, displayLabel: string, description: string, value: URI[]) { this.setting = { key, description }; this.displayLabel = displayLabel; this.value = value; @@ -49,7 +58,7 @@ export class WorkspaceTrustSettingsTreeEntry { } export interface IWorkspaceTrustSettingItemTemplate { - onChange?: (value: T) => void; + onChange?: (value: T, type: WorkspaceTrustSettingListItemChangeType) => void; toDispose: DisposableStore; context?: WorkspaceTrustSettingsTreeEntry; @@ -60,14 +69,166 @@ export interface IWorkspaceTrustSettingItemTemplate { elementDisposables: DisposableStore; } -interface IWorkspaceTrustSettingListItemTemplate extends IWorkspaceTrustSettingItemTemplate { - listWidget: ListSettingWidget; +export interface IWorkspaceTrustUriDataItem extends UriComponents { } + +class WorkspaceTrustFolderSettingWidget extends AbstractListSettingWidget { + constructor( + container: HTMLElement, + @ILabelService protected readonly labelService: ILabelService, + @IThemeService themeService: IThemeService, + @IContextViewService contextViewService: IContextViewService + ) { + super(container, themeService, contextViewService); + } + + protected getEmptyItem(): IWorkspaceTrustUriDataItem { + return URI.file(''); + } + + protected getContainerClasses() { + return ['workspace-trust-uri-setting-widget', 'setting-list-object-widget']; + } + + protected getActionsForItem(item: IWorkspaceTrustUriDataItem, idx: number): IAction[] { + return [ + { + class: ThemeIcon.asClassName(settingsEditIcon), + enabled: true, + id: 'workbench.action.editListItem', + tooltip: this.getLocalizedStrings().editActionTooltip, + run: () => this.editSetting(idx) + }, + { + class: ThemeIcon.asClassName(settingsRemoveIcon), + enabled: true, + id: 'workbench.action.removeListItem', + tooltip: this.getLocalizedStrings().deleteActionTooltip, + run: () => this._onDidChangeList.fire({ originalItem: item, item: undefined, targetIndex: idx }) + } + ] as IAction[]; + } + + protected override renderHeader() { + const header = $('.setting-list-row-header'); + const hostHeader = append(header, $('.setting-list-object-key')); + const pathHeader = append(header, $('.setting-list-object-value')); + const { hostHeaderText, pathHeaderText } = this.getLocalizedStrings(); + + hostHeader.textContent = hostHeaderText; + pathHeader.textContent = pathHeaderText; + + return header; + } + + protected renderItem(item: IWorkspaceTrustUriDataItem): HTMLElement { + const rowElement = $('.setting-list-row'); + rowElement.classList.add('setting-list-object-row'); + + const hostElement = append(rowElement, $('.setting-list-object-key')); + const pathElement = append(rowElement, $('.setting-list-object-value')); + + hostElement.textContent = item.authority ? this.labelService.getHostLabel(item.scheme, item.authority) : localize('localAuthority', "Local"); + pathElement.textContent = item.scheme === Schemas.file ? URI.revive(item).fsPath : item.path; + + return rowElement; + } + + + protected renderEdit(item: IWorkspaceTrustUriDataItem, idx: number): HTMLElement { + const rowElement = $('.setting-list-edit-row'); + + const hostElement = append(rowElement, $('.setting-list-object-key')); + hostElement.textContent = item.authority ? this.labelService.getHostLabel(item.scheme, item.authority) : localize('localAuthority', "Local"); + + const updatedItem = () => { + if (item.scheme === Schemas.file) { + return URI.file(pathInput.value); + } else { + return URI.revive(item).with({ path: pathInput.value }); + } + }; + + const onKeyDown = (e: StandardKeyboardEvent) => { + if (e.equals(KeyCode.Enter)) { + this.handleItemChange(item, updatedItem(), idx); + } else if (e.equals(KeyCode.Escape)) { + this.cancelEdit(); + e.preventDefault(); + } + rowElement?.focus(); + }; + + const pathInput = new InputBox(rowElement, this.contextViewService, { + placeholder: this.getLocalizedStrings().inputPlaceholder + }); + + pathInput.element.classList.add('setting-list-valueInput'); + this.listDisposables.add(attachInputBoxStyler(pathInput, this.themeService, { + inputBackground: settingsSelectBackground, + inputForeground: settingsTextInputForeground, + inputBorder: settingsTextInputBorder + })); + this.listDisposables.add(pathInput); + pathInput.value = item.scheme === Schemas.file ? URI.revive(item).fsPath : item.path; + + this.listDisposables.add( + addStandardDisposableListener(pathInput.inputElement, EventType.KEY_DOWN, onKeyDown) + ); + + const okButton = this._register(new Button(rowElement)); + okButton.label = localize('okButton', "OK"); + okButton.element.classList.add('setting-list-ok-button'); + + this.listDisposables.add(attachButtonStyler(okButton, this.themeService)); + this.listDisposables.add(okButton.onDidClick(() => this.handleItemChange(item, updatedItem(), idx))); + + const cancelButton = this._register(new Button(rowElement)); + cancelButton.label = localize('cancelButton', "Cancel"); + cancelButton.element.classList.add('setting-list-cancel-button'); + + this.listDisposables.add(attachButtonStyler(cancelButton, this.themeService)); + this.listDisposables.add(cancelButton.onDidClick(() => this.cancelEdit())); + + this.listDisposables.add( + disposableTimeout(() => { + pathInput.focus(); + pathInput.select(); + }) + ); + + return rowElement; + } + + protected isItemNew(item: IWorkspaceTrustUriDataItem): boolean { + return item.path === ''; + } + + protected getLocalizedRowTitle(item: IWorkspaceTrustUriDataItem): string { + return localize('trustedRow', "Trusted Path: {0}", this.labelService.getUriLabel(URI.from(item))); + } + + protected getLocalizedStrings() { + return { + deleteActionTooltip: localize('removePath', "Remove Path"), + editActionTooltip: localize('editPath', "Edit Path"), + addButtonLabel: localize('addPath', "Add Path"), + hostHeaderText: localize('hostHeaderText', "Host"), + pathHeaderText: localize('pathHeaderText', "Path"), + inputPlaceholder: localize('pathInputPlaceholder', "Path Item..."), + }; + } +} + +interface IWorkspaceTrustSettingListItemTemplate extends IWorkspaceTrustSettingItemTemplate { + listWidget: WorkspaceTrustFolderSettingWidget; validationErrorMessageElement: HTMLElement; } +export type WorkspaceTrustSettingListItemChangeType = 'added' | 'removed' | 'changed'; export interface IWorkspaceTrustSettingChangeEvent { key: string; - value: any; // undefined => reset/unconfigure + value: URI[] | undefined; // undefined => reset/unconfigure + type: WorkspaceTrustSettingListItemChangeType; } @@ -165,7 +326,7 @@ export class WorkspaceTrustSettingArrayRenderer extends Disposable implements IT const validationErrorMessageElement = $('.setting-item-validation-message'); descriptionElement.after(validationErrorMessageElement); - const listWidget = this._instantiationService.createInstance(ListSettingWidget, common.controlElement); + const listWidget = this._instantiationService.createInstance(WorkspaceTrustFolderSettingWidget, common.controlElement); listWidget.domNode.classList.add(WorkspaceTrustSettingArrayRenderer.CONTROL_CLASS); common.toDispose.add(listWidget); @@ -179,9 +340,9 @@ export class WorkspaceTrustSettingArrayRenderer extends Disposable implements IT common.toDispose.add( listWidget.onDidChangeList(e => { - const newList = this.computeNewList(template, e); + const { list: newList, changeType } = this.computeNewList(template, e); if (newList !== null && template.onChange) { - template.onChange(newList); + template.onChange(newList, changeType); } }) ); @@ -189,39 +350,45 @@ export class WorkspaceTrustSettingArrayRenderer extends Disposable implements IT return template; } - private computeNewList(template: IWorkspaceTrustSettingListItemTemplate, e: ISettingListChangeEvent): string[] | undefined | null { + private computeNewList(template: IWorkspaceTrustSettingListItemTemplate, e: ISettingListChangeEvent): { list: URI[] | null, changeType: WorkspaceTrustSettingListItemChangeType } { if (template.context) { - let newValue: string[] = []; + let newValue: URI[] = []; + + let changeType: WorkspaceTrustSettingListItemChangeType = 'changed'; if (isArray(template.context.value)) { newValue = [...template.context.value]; } if (e.targetIndex !== undefined) { // Delete value - if (!e.item?.value && e.originalItem.value && e.targetIndex > -1) { + if (!e.item?.path && e.originalItem.path && e.targetIndex > -1) { newValue.splice(e.targetIndex, 1); + changeType = 'removed'; } // Update value - else if (e.item?.value && e.originalItem.value) { + else if (e.item?.path && e.originalItem.path) { if (e.targetIndex > -1) { - newValue[e.targetIndex] = e.item.value; + newValue[e.targetIndex] = URI.revive(e.item); + changeType = e.targetIndex < template.context.value.length ? 'changed' : 'added'; } // For some reason, we are updating and cannot find original value // Just append the value in this case else { - newValue.push(e.item.value); + newValue.push(URI.revive(e.item)); + changeType = 'added'; } } // Add value - else if (e.item?.value && !e.originalItem.value && e.targetIndex >= newValue.length) { - newValue.push(e.item.value); + else if (e.item?.path && !e.originalItem.path && e.targetIndex >= newValue.length) { + newValue.push(URI.revive(e.item)); + changeType = 'added'; } } - return newValue; + return { list: newValue, changeType }; } - return undefined; + return { list: null, changeType: 'changed' }; } renderElement(node: ITreeNode, index: number, template: IWorkspaceTrustSettingListItemTemplate): void { @@ -235,21 +402,21 @@ export class WorkspaceTrustSettingArrayRenderer extends Disposable implements IT template.descriptionElement.innerText = element.setting.description; - const onChange = (value: any) => this._onDidChangeSetting.fire({ key: element.setting.key, value }); + const onChange = (value: any, type: WorkspaceTrustSettingListItemChangeType) => this._onDidChangeSetting.fire({ key: element.setting.key, value, type }); this.renderValue(element, template, onChange); } - protected renderValue(dataElement: WorkspaceTrustSettingsTreeEntry, template: IWorkspaceTrustSettingListItemTemplate, onChange: (value: string[] | undefined) => void): void { + protected renderValue(dataElement: WorkspaceTrustSettingsTreeEntry, template: IWorkspaceTrustSettingListItemTemplate, onChange: (value: URI[] | undefined, type: WorkspaceTrustSettingListItemChangeType) => void): void { const value = getListDisplayValue(dataElement); template.listWidget.setValue(value); template.context = dataElement; - template.onChange = (v) => { - onChange(v); + template.onChange = (v, t) => { + onChange(v, t); renderArrayValidations(dataElement, template, v, false); }; - renderArrayValidations(dataElement, template, value.map(v => v.value), true); + renderArrayValidations(dataElement, template, value.map(v => URI.revive(v)), true); } disposeTemplate(template: IWorkspaceTrustSettingItemTemplate): void { @@ -396,7 +563,7 @@ export class WorkspaceTrustTree extends WorkbenchObjectTree>, options: IObjectTreeOptions): ITreeModel { + protected override createModel(user: string, view: IList>, options: IObjectTreeOptions): ITreeModel { return new NonCollapsibleObjectTreeModel(user, view, options); } } @@ -405,24 +572,13 @@ export class WorkspaceTrustTreeModel { settings: WorkspaceTrustSettingsTreeEntry[] = []; - update(trustInfo: IWorkspaceTrustStateInfo): void { + update(trustedFolders: URI[]): void { this.settings = []; - if (trustInfo.localFolders) { - const trustedFolders = trustInfo.localFolders.filter(folder => folder.trustState === WorkspaceTrustState.Trusted).map(folder => folder.uri); - const untrustedFolders = trustInfo.localFolders.filter(folder => folder.trustState === WorkspaceTrustState.Untrusted).map(folder => folder.uri); - - this.settings.push(new WorkspaceTrustSettingsTreeEntry( - 'trustedFolders', - localize('trustedFolders', "Trusted Folders"), - localize('trustedFoldersDescription', "All workspaces under the following folders will be trusted. In the event of a conflict with untrusted folders, the nearest parent will determine trust."), - trustedFolders)); - - this.settings.push(new WorkspaceTrustSettingsTreeEntry( - 'untrustedFolders', - localize('untrustedFolders', "Untrusted Folders"), - localize('untrustedFoldersDescription', "All workspaces under the following folders will not be trusted. In the event of a conflict with trusted folders, the nearest parent will determine trust."), - untrustedFolders)); - } + this.settings.push(new WorkspaceTrustSettingsTreeEntry( + 'trustedFolders', + localize('trustedFolders', "Trusted Folders"), + localize('trustedFoldersDescription', "You trust the following folders and their children: "), + trustedFolders)); } } @@ -455,18 +611,14 @@ class WorkspaceTrustTreeDelegate extends CachedListVirtualDelegate { - return { - value: key - }; - }); + return element.value; } -function renderArrayValidations(dataElement: WorkspaceTrustSettingsTreeEntry, template: IWorkspaceTrustSettingListItemTemplate, v: string[] | undefined, arg3: boolean) { +function renderArrayValidations(dataElement: WorkspaceTrustSettingsTreeEntry, template: IWorkspaceTrustSettingListItemTemplate, v: URI[] | undefined, arg3: boolean) { } diff --git a/src/vs/workbench/electron-browser/desktop.main.ts b/src/vs/workbench/electron-browser/desktop.main.ts index ff859c92..3a805673 100644 --- a/src/vs/workbench/electron-browser/desktop.main.ts +++ b/src/vs/workbench/electron-browser/desktop.main.ts @@ -4,38 +4,26 @@ *--------------------------------------------------------------------------------------------*/ import * as fs from 'fs'; -import * as os from 'os'; import { gracefulify } from 'graceful-fs'; -import { getUserDataPath } from 'vs/platform/environment/node/userDataPath'; -import { INativeWorkbenchConfiguration, NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; +import { INativeWorkbenchConfiguration, INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; import { ILogService } from 'vs/platform/log/common/log'; import { Schemas } from 'vs/base/common/network'; 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 { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; -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'; +import { SharedDesktopMain } from 'vs/workbench/electron-sandbox/shared.desktop.main'; class DesktopMain extends SharedDesktopMain { constructor(configuration: INativeWorkbenchConfiguration) { - super(configuration, new NativeWorkbenchEnvironmentService(configuration, { homeDir: os.homedir(), tmpDir: os.tmpdir(), userDataDir: getUserDataPath(configuration) }, productService)); + super(configuration); // Enable gracefulFs gracefulify(fs); } - protected joinOpen(instantiationService: IInstantiationService): void { - - // Driver - if (this.configuration.driver) { - instantiationService.invokeFunction(async accessor => this._register(await registerWindowDriver(accessor, this.configuration.windowId))); - } - } - - protected registerFileSystemProviders(fileService: IFileService, logService: ILogService, nativeHostService: INativeHostService): void { + protected registerFileSystemProviders(environmentService: INativeWorkbenchEnvironmentService, fileService: IFileService, logService: ILogService, nativeHostService: INativeHostService): void { // Local Files const diskFileSystemProvider = this._register(new DiskFileSystemProvider(logService, nativeHostService)); diff --git a/src/vs/workbench/electron-sandbox/actions/windowActions.ts b/src/vs/workbench/electron-sandbox/actions/windowActions.ts index c6567c33..12cf59e8 100644 --- a/src/vs/workbench/electron-sandbox/actions/windowActions.ts +++ b/src/vs/workbench/electron-sandbox/actions/windowActions.ts @@ -35,7 +35,7 @@ export class CloseCurrentWindowAction extends Action { super(id, label); } - async run(): Promise { + override async run(): Promise { this.nativeHostService.closeWindow(); } } @@ -81,7 +81,7 @@ export class ZoomInAction extends BaseZoomAction { super(id, label, configurationService); } - async run(): Promise { + override async run(): Promise { this.setConfiguredZoomLevel(getZoomLevel() + 1); } } @@ -99,7 +99,7 @@ export class ZoomOutAction extends BaseZoomAction { super(id, label, configurationService); } - async run(): Promise { + override async run(): Promise { this.setConfiguredZoomLevel(getZoomLevel() - 1); } } @@ -117,7 +117,7 @@ export class ZoomResetAction extends BaseZoomAction { super(id, label, configurationService); } - async run(): Promise { + override async run(): Promise { this.setConfiguredZoomLevel(0); } } @@ -149,7 +149,7 @@ export abstract class BaseSwitchWindow extends Action { protected abstract isQuickNavigate(): boolean; - async run(): Promise { + override async run(): Promise { const currentWindowId = this.nativeHostService.windowId; const windows = await this.nativeHostService.getWindows(); diff --git a/src/vs/workbench/electron-sandbox/desktop.contribution.ts b/src/vs/workbench/electron-sandbox/desktop.contribution.ts index 3cd32714..6b1eacf8 100644 --- a/src/vs/workbench/electron-sandbox/desktop.contribution.ts +++ b/src/vs/workbench/electron-sandbox/desktop.contribution.ts @@ -36,12 +36,20 @@ import { IJSONSchema } from 'vs/base/common/jsonSchema'; // Actions: Window (function registerWindowActions(): void { - registry.registerWorkbenchAction(SyncActionDescriptor.from(CloseCurrentWindowAction, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_W }), 'Close Window'); registry.registerWorkbenchAction(SyncActionDescriptor.from(SwitchWindow, { primary: 0, mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_W } }), 'Switch Window...'); registry.registerWorkbenchAction(SyncActionDescriptor.from(QuickSwitchWindow), 'Quick Switch Window...'); + // Close window + registry.registerWorkbenchAction(SyncActionDescriptor.from(CloseCurrentWindowAction, { + mac: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_W }, + linux: { primary: KeyMod.Alt | KeyCode.F4, secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_W] }, + win: { primary: KeyMod.Alt | KeyCode.F4, secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_W] } + } + ), 'Close Window'); + + // Close the window when the last editor is closed by reusing the same keybinding KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: CloseCurrentWindowAction.ID, // close the window when the last editor is closed by reusing the same keybinding + id: CloseCurrentWindowAction.ID, weight: KeybindingWeight.WorkbenchContrib, when: ContextKeyExpr.and(EditorsVisibleContext.toNegated(), SingleEditorGroupsContext), primary: KeyMod.CtrlCmd | KeyCode.KEY_W, @@ -51,6 +59,7 @@ import { IJSONSchema } from 'vs/base/common/jsonSchema'; } }); + // Quit KeybindingsRegistry.registerCommandAndKeybindingRule({ id: 'workbench.action.quit', weight: KeybindingWeight.WorkbenchContrib, diff --git a/src/vs/workbench/electron-sandbox/desktop.main.ts b/src/vs/workbench/electron-sandbox/desktop.main.ts index 97771758..51a21e48 100644 --- a/src/vs/workbench/electron-sandbox/desktop.main.ts +++ b/src/vs/workbench/electron-sandbox/desktop.main.ts @@ -3,28 +3,30 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { INativeWorkbenchConfiguration, NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; +import { INativeWorkbenchConfiguration, INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; import { ILogService } from 'vs/platform/log/common/log'; import { Schemas } from 'vs/base/common/network'; 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 { simpleHomeDir, simpleFileSystemProvider, simpleWorkspaceDir, simpleTmpDir, simpleUserDataDir } from 'vs/workbench/electron-sandbox/sandbox.simpleservices'; -import { productService, SharedDesktopMain } from 'vs/workbench/electron-sandbox/shared.desktop.main'; +import { initFileSystem, simpleFileSystemProvider, simpleWorkspaceDir } from 'vs/workbench/electron-sandbox/sandbox.simpleservices'; +import { SharedDesktopMain } from 'vs/workbench/electron-sandbox/shared.desktop.main'; class DesktopMain extends SharedDesktopMain { constructor(configuration: INativeWorkbenchConfiguration) { - 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)); + super({ ...configuration, workspace: { id: configuration.workspace?.id ?? '4064f6ec-cb38-4ad0-af64-ee6467e63c82', uri: simpleWorkspaceDir } }); } - protected registerFileSystemProviders(fileService: IFileService, logService: ILogService, nativeHostService: INativeHostService): void { + protected registerFileSystemProviders(environmentService: INativeWorkbenchEnvironmentService, fileService: IFileService, logService: ILogService): Promise { // Local Files fileService.registerProvider(Schemas.file, simpleFileSystemProvider); // User Data Provider fileService.registerProvider(Schemas.userData, new FileUserDataProvider(Schemas.file, simpleFileSystemProvider, Schemas.userData, logService)); + + // Init our in-memory file system + return initFileSystem(environmentService, fileService); } } diff --git a/src/vs/workbench/electron-sandbox/parts/dialogs/dialog.contribution.ts b/src/vs/workbench/electron-sandbox/parts/dialogs/dialog.contribution.ts index 1d117bd1..ea707f18 100644 --- a/src/vs/workbench/electron-sandbox/parts/dialogs/dialog.contribution.ts +++ b/src/vs/workbench/electron-sandbox/parts/dialogs/dialog.contribution.ts @@ -20,6 +20,7 @@ import { NativeDialogHandler } from 'vs/workbench/electron-sandbox/parts/dialogs import { DialogService } from 'vs/workbench/services/dialogs/common/dialogService'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { Disposable } from 'vs/base/common/lifecycle'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; export class DialogHandlerContribution extends Disposable implements IWorkbenchContribution { private nativeImpl: IDialogHandler; @@ -35,13 +36,14 @@ export class DialogHandlerContribution extends Disposable implements IWorkbenchC @ILayoutService layoutService: ILayoutService, @IThemeService themeService: IThemeService, @IKeybindingService keybindingService: IKeybindingService, + @IInstantiationService instantiationService: IInstantiationService, @IProductService productService: IProductService, @IClipboardService clipboardService: IClipboardService, @INativeHostService nativeHostService: INativeHostService ) { super(); - this.browserImpl = new BrowserDialogHandler(logService, layoutService, themeService, keybindingService, productService, clipboardService); + this.browserImpl = new BrowserDialogHandler(logService, layoutService, themeService, keybindingService, instantiationService, productService, clipboardService); this.nativeImpl = new NativeDialogHandler(logService, nativeHostService, productService, clipboardService); this.model = (this.dialogService as DialogService).model; @@ -76,7 +78,7 @@ export class DialogHandlerContribution extends Disposable implements IWorkbenchC // Message else if (this.currentDialog.args.showArgs) { const args = this.currentDialog.args.showArgs; - result = this.useCustomDialog || args.options?.useCustom ? + result = (this.useCustomDialog || args.options?.custom) ? await this.browserImpl.show(args.severity, args.message, args.buttons, args.options) : await this.nativeImpl.show(args.severity, args.message, args.buttons, args.options); } diff --git a/src/vs/workbench/electron-sandbox/parts/titlebar/menubarControl.ts b/src/vs/workbench/electron-sandbox/parts/titlebar/menubarControl.ts index 406fefe5..46ad42f8 100644 --- a/src/vs/workbench/electron-sandbox/parts/titlebar/menubarControl.ts +++ b/src/vs/workbench/electron-sandbox/parts/titlebar/menubarControl.ts @@ -39,7 +39,7 @@ export class NativeMenubarControl extends MenubarControl { @IStorageService storageService: IStorageService, @INotificationService notificationService: INotificationService, @IPreferencesService preferencesService: IPreferencesService, - @INativeWorkbenchEnvironmentService protected readonly environmentService: INativeWorkbenchEnvironmentService, + @INativeWorkbenchEnvironmentService environmentService: INativeWorkbenchEnvironmentService, @IAccessibilityService accessibilityService: IAccessibilityService, @IMenubarService private readonly menubarService: IMenubarService, @IHostService hostService: IHostService, diff --git a/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts b/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts index e6caf086..3096c69d 100644 --- a/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts @@ -41,14 +41,16 @@ export class TitlebarPart extends BrowserTitleBarPart { return 22; } - get minimumHeight(): number { return isMacintosh ? this.getMacTitlebarSize() / getZoomFactor() : super.minimumHeight; } - get maximumHeight(): number { return this.minimumHeight; } + override get minimumHeight(): number { return isMacintosh ? this.getMacTitlebarSize() / getZoomFactor() : super.minimumHeight; } + override get maximumHeight(): number { return this.minimumHeight; } + + protected override readonly environmentService: INativeWorkbenchEnvironmentService; constructor( @IContextMenuService contextMenuService: IContextMenuService, - @IConfigurationService protected readonly configurationService: IConfigurationService, + @IConfigurationService configurationService: IConfigurationService, @IEditorService editorService: IEditorService, - @INativeWorkbenchEnvironmentService protected readonly environmentService: INativeWorkbenchEnvironmentService, + @INativeWorkbenchEnvironmentService environmentService: INativeWorkbenchEnvironmentService, @IWorkspaceContextService contextService: IWorkspaceContextService, @IInstantiationService instantiationService: IInstantiationService, @IThemeService themeService: IThemeService, @@ -62,6 +64,8 @@ export class TitlebarPart extends BrowserTitleBarPart { @INativeHostService private readonly nativeHostService: INativeHostService ) { super(contextMenuService, configurationService, editorService, environmentService, contextService, instantiationService, themeService, labelService, storageService, layoutService, menuService, contextKeyService, hostService, productService); + + this.environmentService = environmentService; } private onUpdateAppIconDragBehavior(): void { @@ -105,7 +109,7 @@ export class TitlebarPart extends BrowserTitleBarPart { } } - protected onMenubarVisibilityChanged(visible: boolean): void { + protected override onMenubarVisibilityChanged(visible: boolean): void { // Hide title when toggling menu bar if ((isWindows || isLinux) && this.currentMenubarVisibility === 'toggle' && visible) { // Hack to fix issue #52522 with layered webkit-app-region elements appearing under cursor @@ -118,7 +122,7 @@ export class TitlebarPart extends BrowserTitleBarPart { super.onMenubarVisibilityChanged(visible); } - protected onConfigurationChanged(event: IConfigurationChangeEvent): void { + protected override onConfigurationChanged(event: IConfigurationChangeEvent): void { super.onConfigurationChanged(event); if (event.affectsConfiguration('window.doubleClickIconToClose')) { @@ -128,7 +132,7 @@ export class TitlebarPart extends BrowserTitleBarPart { } } - protected adjustTitleMarginToCenter(): void { + protected override adjustTitleMarginToCenter(): void { if (this.customMenubar && this.menubar) { const leftMarker = (this.appIcon ? this.appIcon.clientWidth : 0) + this.menubar.clientWidth + 10; const rightMarker = this.element.clientWidth - (this.windowControls ? this.windowControls.clientWidth : 0) - 10; @@ -150,7 +154,7 @@ export class TitlebarPart extends BrowserTitleBarPart { this.title.style.maxWidth = `calc(100vw - ${2 * ((this.windowControls?.clientWidth || 70) + 10)}px)`; } - protected installMenubar(): void { + protected override installMenubar(): void { super.installMenubar(); if (this.menubar) { @@ -162,7 +166,7 @@ export class TitlebarPart extends BrowserTitleBarPart { } } - createContentArea(parent: HTMLElement): HTMLElement { + override createContentArea(parent: HTMLElement): HTMLElement { const ret = super.createContentArea(parent); // Native menu controller @@ -219,7 +223,7 @@ export class TitlebarPart extends BrowserTitleBarPart { return ret; } - updateLayout(dimension: Dimension): void { + override updateLayout(dimension: Dimension): void { this.lastLayoutDimensions = dimension; if (getTitleBarStyle(this.configurationService) === 'custom') { diff --git a/src/vs/workbench/electron-sandbox/sandbox.simpleservices.ts b/src/vs/workbench/electron-sandbox/sandbox.simpleservices.ts index f5ab5d61..fc7dbe32 100644 --- a/src/vs/workbench/electron-sandbox/sandbox.simpleservices.ts +++ b/src/vs/workbench/electron-sandbox/sandbox.simpleservices.ts @@ -11,16 +11,10 @@ import { Event } from 'vs/base/common/event'; import { IAddressProvider } from 'vs/platform/remote/common/remoteAgentConnection'; 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 { IProcessEnvironment, isWindows, OperatingSystem } 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 { 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'; -import { IWorkspace, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; -import { CustomTask, ContributedTask, InMemoryTask, TaskRunSource, ConfiguringTask, TaskIdentifier, TaskSorter } from 'vs/workbench/contrib/tasks/common/tasks'; -import { TaskSystemInfo } from 'vs/workbench/contrib/tasks/common/taskSystem'; import { joinPath } from 'vs/base/common/resources'; import { VSBuffer } from 'vs/base/common/buffer'; import { TerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminalInstanceService'; @@ -33,13 +27,12 @@ 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'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; +import { IShellLaunchConfigResolveOptions, ITerminalProfile, ITerminalProfileResolverService } from 'vs/workbench/contrib/terminal/common/terminal'; +import { IShellLaunchConfig } from 'vs/platform/terminal/common/terminal'; //#region Environment -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 simpleWorkspaceDir = URI.file(isWindows ? '\\simpleWorkspace' : '/simpleWorkspace'); @@ -53,21 +46,23 @@ class SimpleFileSystemProvider extends InMemoryFileSystemProvider { } export const simpleFileSystemProvider = new SimpleFileSystemProvider(); -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+', +export async function initFileSystem(environmentService: INativeWorkbenchEnvironmentService, fileService: IFileService): Promise { + await fileService.createFolder(environmentService.userHome); + await fileService.createFolder(environmentService.tmpDir); -}, 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 }); + const userData = URI.file(environmentService.userDataPath); + await fileService.writeFile(joinPath(userData, 'User', 'settings.json'), VSBuffer.fromString(JSON.stringify({ + 'window.zoomLevel': 1, + 'workbench.colorTheme': 'Default Light+', + }, undefined, '\t'))); + + await fileService.writeFile(joinPath(userData, 'User', 'keybindings.json'), VSBuffer.fromString(JSON.stringify([ + { + 'key': 'f12', + 'command': 'workbench.action.toggleDevTools' + } + ], undefined, '\t'))); +} function createWorkspaceFile(parent: string, name: string, content: string = ''): void { simpleFileSystemProvider.writeFile(joinPath(simpleWorkspaceDir, parent, name), VSBuffer.fromString(content).buffer, { create: true, overwrite: true, unlock: false }); @@ -232,8 +227,8 @@ suite("Extension Tests", function () { // Defines a Mocha unit test test("Something 1", function() { - assert.equal(-1, [1, 2, 3].indexOf(5)); - assert.equal(-1, [1, 2, 3].indexOf(0)); + assert.strictEqual(-1, [1, 2, 3].indexOf(5)); + assert.strictEqual(-1, [1, 2, 3].indexOf(0)); }); });`); @@ -279,6 +274,7 @@ class SimpleWebviewService implements IWebviewService { declare readonly _serviceBrand: undefined; readonly activeWebview = undefined; + readonly onDidChangeActiveWebview = Event.None; createWebviewElement(id: string, options: WebviewOptions, contentOptions: WebviewContentOptions, extension: WebviewExtensionDescription | undefined): WebviewElement { throw new Error('Method not implemented.'); } createWebviewOverlay(id: string, options: WebviewOptions, contentOptions: WebviewContentOptions, extension: WebviewExtensionDescription | undefined): WebviewOverlay { throw new Error('Method not implemented.'); } @@ -313,53 +309,6 @@ registerSingleton(ITunnelService, SimpleTunnelService); //#endregion -//#region Task - -class SimpleTaskService implements ITaskService { - - declare readonly _serviceBrand: undefined; - - onDidStateChange = Event.None; - supportsMultipleTaskExecutions = false; - - configureAction(): Action { throw new Error('Method not implemented.'); } - build(): Promise { throw new Error('Method not implemented.'); } - runTest(): Promise { throw new Error('Method not implemented.'); } - run(task: CustomTask | ContributedTask | InMemoryTask | undefined, options?: ProblemMatcherRunOptions): Promise { throw new Error('Method not implemented.'); } - inTerminal(): boolean { throw new Error('Method not implemented.'); } - isActive(): Promise { throw new Error('Method not implemented.'); } - getActiveTasks(): Promise { throw new Error('Method not implemented.'); } - getBusyTasks(): Promise { throw new Error('Method not implemented.'); } - restart(task: Task): void { throw new Error('Method not implemented.'); } - terminate(task: Task): Promise { throw new Error('Method not implemented.'); } - terminateAll(): Promise { throw new Error('Method not implemented.'); } - tasks(filter?: TaskFilter): Promise { throw new Error('Method not implemented.'); } - taskTypes(): string[] { throw new Error('Method not implemented.'); } - getWorkspaceTasks(runSource?: TaskRunSource): Promise> { throw new Error('Method not implemented.'); } - readRecentTasks(): Promise<(CustomTask | ContributedTask | InMemoryTask | ConfiguringTask)[]> { throw new Error('Method not implemented.'); } - getTask(workspaceFolder: string | IWorkspace | IWorkspaceFolder, alias: string | TaskIdentifier, compareId?: boolean): Promise { throw new Error('Method not implemented.'); } - tryResolveTask(configuringTask: ConfiguringTask): Promise { throw new Error('Method not implemented.'); } - getTasksForGroup(group: string): Promise { throw new Error('Method not implemented.'); } - getRecentlyUsedTasks(): LinkedMap { throw new Error('Method not implemented.'); } - removeRecentlyUsedTask(taskRecentlyUsedKey: string): void { throw new Error('Method not implemented.'); } - migrateRecentTasks(tasks: Task[]): Promise { throw new Error('Method not implemented.'); } - createSorter(): TaskSorter { throw new Error('Method not implemented.'); } - getTaskDescription(task: CustomTask | ContributedTask | InMemoryTask | ConfiguringTask): string | undefined { throw new Error('Method not implemented.'); } - canCustomize(task: CustomTask | ContributedTask): boolean { throw new Error('Method not implemented.'); } - customize(task: CustomTask | ContributedTask | ConfiguringTask, properties?: {}, openConfig?: boolean): Promise { throw new Error('Method not implemented.'); } - openConfig(task: CustomTask | ConfiguringTask | undefined): Promise { throw new Error('Method not implemented.'); } - registerTaskProvider(taskProvider: ITaskProvider, type: string): IDisposable { throw new Error('Method not implemented.'); } - registerTaskSystem(scheme: string, taskSystemInfo: TaskSystemInfo): void { throw new Error('Method not implemented.'); } - registerSupportedExecutions(custom?: boolean, shell?: boolean, process?: boolean): void { throw new Error('Method not implemented.'); } - setJsonTasksSupported(areSuppored: Promise): void { throw new Error('Method not implemented.'); } - extensionCallbackTaskComplete(task: Task, result: number | undefined): Promise { throw new Error('Method not implemented.'); } -} - -registerSingleton(ITaskService, SimpleTaskService); - -//#endregion - - //#region Terminal Instance class SimpleTerminalInstanceService extends TerminalInstanceService { } @@ -369,6 +318,25 @@ registerSingleton(ITerminalInstanceService, SimpleTerminalInstanceService); //#endregion +//#region Terminal Profile Resolver Service + +class SimpleTerminalProfileResolverService implements ITerminalProfileResolverService { + + _serviceBrand: undefined; + + resolveIcon(shellLaunchConfig: IShellLaunchConfig, os: OperatingSystem): void { } + async resolveShellLaunchConfig(shellLaunchConfig: IShellLaunchConfig, options: IShellLaunchConfigResolveOptions): Promise { } + getDefaultProfile(options: IShellLaunchConfigResolveOptions): Promise { throw new Error('Method not implemented.'); } + getDefaultShell(options: IShellLaunchConfigResolveOptions): Promise { throw new Error('Method not implemented.'); } + getDefaultShellArgs(options: IShellLaunchConfigResolveOptions): Promise { throw new Error('Method not implemented.'); } + getShellEnvironment(remoteAuthority: string | undefined): Promise { throw new Error('Method not implemented.'); } + getSafeConfigValue(key: string, os: OperatingSystem): unknown | undefined { throw new Error('Method not implemented.'); } + getSafeConfigValueFullKey(key: string): unknown | undefined { throw new Error('Method not implemented.'); } +} + +registerSingleton(ITerminalProfileResolverService, SimpleTerminalProfileResolverService); + + //#region Search Service class SimpleSearchService extends SearchService { diff --git a/src/vs/workbench/electron-sandbox/shared.desktop.main.ts b/src/vs/workbench/electron-sandbox/shared.desktop.main.ts index f3180da9..fa857a32 100644 --- a/src/vs/workbench/electron-sandbox/shared.desktop.main.ts +++ b/src/vs/workbench/electron-sandbox/shared.desktop.main.ts @@ -3,8 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import product from 'vs/platform/product/common/product'; import { zoomLevelToZoomFactor } from 'vs/platform/windows/common/windows'; -import { mark } from 'vs/base/common/performance'; import { Workbench } from 'vs/workbench/browser/workbench'; import { NativeWindow } from 'vs/workbench/electron-sandbox/window'; import { setZoomLevel, setZoomFactor, setFullscreen } from 'vs/base/browser/browser'; @@ -12,7 +12,7 @@ import { domContentLoaded } from 'vs/base/browser/dom'; import { onUnexpectedError } from 'vs/base/common/errors'; import { URI } from 'vs/base/common/uri'; import { WorkspaceService } from 'vs/workbench/services/configuration/browser/configurationService'; -import { INativeWorkbenchConfiguration, INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; +import { INativeWorkbenchConfiguration, INativeWorkbenchEnvironmentService, NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier, IWorkspaceInitializationPayload, reviveIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { ILoggerService, ILogService } from 'vs/platform/log/common/log'; @@ -41,21 +41,17 @@ import { UriIdentityService } from 'vs/workbench/services/uriIdentity/common/uri import { KeyboardLayoutService } from 'vs/workbench/services/keybinding/electron-sandbox/nativeKeyboardLayout'; import { IKeyboardLayoutService } from 'vs/platform/keyboardLayout/common/keyboardLayout'; import { ElectronIPCMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService'; -import { LoggerChannelClient } from 'vs/platform/log/common/logIpc'; +import { LoggerChannelClient, LogLevelChannelClient } 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 }; +import { WorkspaceTrustManagementService } from 'vs/workbench/services/workspaces/common/workspaceTrust'; +import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust'; +import { registerWindowDriver } from 'vs/platform/driver/electron-sandbox/driver'; export abstract class SharedDesktopMain extends Disposable { - protected readonly productService: IProductService = productService; - constructor( - protected readonly configuration: INativeWorkbenchConfiguration, - protected readonly environmentService: INativeWorkbenchEnvironmentService + protected readonly configuration: INativeWorkbenchConfiguration ) { super(); @@ -101,10 +97,9 @@ export abstract class SharedDesktopMain extends Disposable { } async open(): Promise { - const services = await this.initServices(); - await domContentLoaded(); - mark('code/willStartWorkbench'); + // Init services and wait for DOM to be ready in parallel + const [services] = await Promise.all([this.initServices(), domContentLoaded()]); // Create Workbench const workbench = new Workbench(document.body, services.serviceCollection, services.logService); @@ -121,19 +116,20 @@ export abstract class SharedDesktopMain extends Disposable { // Logging services.logService.trace('workbench configuration', JSON.stringify(this.configuration)); - // Allow subclass to participate - this.joinOpen(instantiationService); + // Driver + if (this.configuration.driver) { + instantiationService.invokeFunction(async accessor => this._register(await registerWindowDriver(accessor, this.configuration.windowId))); + } } private registerListeners(workbench: Workbench, storageService: NativeStorageService): void { // Workbench Lifecycle this._register(workbench.onWillShutdown(event => event.join(storageService.close(), 'join.closeStorage'))); - this._register(workbench.onShutdown(() => this.dispose())); + this._register(workbench.onDidShutdown(() => this.dispose())); } - protected abstract registerFileSystemProviders(fileService: IFileService, logService: ILogService, nativeHostService: INativeHostService): void; - protected joinOpen(instantiationService: IInstantiationService): void { } + protected abstract registerFileSystemProviders(environmentService: INativeWorkbenchEnvironmentService, fileService: IFileService, logService: ILogService, nativeHostService: INativeHostService): void | Promise; private async initServices(): Promise<{ serviceCollection: ServiceCollection, logService: ILogService, storageService: NativeStorageService }> { const serviceCollection = new ServiceCollection(); @@ -156,18 +152,21 @@ export abstract class SharedDesktopMain extends Disposable { const mainProcessService = this._register(new ElectronIPCMainProcessService(this.configuration.windowId)); serviceCollection.set(IMainProcessService, mainProcessService); - // Environment - serviceCollection.set(INativeWorkbenchEnvironmentService, this.environmentService); - // Product - serviceCollection.set(IProductService, this.productService); + const productService: IProductService = { _serviceBrand: undefined, ...product }; + serviceCollection.set(IProductService, productService); + + // Environment + const environmentService = new NativeWorkbenchEnvironmentService(this.configuration, productService); + serviceCollection.set(INativeWorkbenchEnvironmentService, environmentService); // Logger - const loggerService = new LoggerChannelClient(mainProcessService.getChannel('logger')); + const logLevelChannelClient = new LogLevelChannelClient(mainProcessService.getChannel('logLevel')); + const loggerService = new LoggerChannelClient(environmentService.configuration.logLevel, logLevelChannelClient.onDidChangeLogLevel, mainProcessService.getChannel('logger')); serviceCollection.set(ILoggerService, loggerService); // Log - const logService = this._register(new NativeLogService(`renderer${this.configuration.windowId}`, loggerService, mainProcessService, this.environmentService)); + const logService = this._register(new NativeLogService(`renderer${this.configuration.windowId}`, environmentService.configuration.logLevel, loggerService, logLevelChannelClient, environmentService)); serviceCollection.set(ILogService, logService); // Remote @@ -193,7 +192,7 @@ export abstract class SharedDesktopMain extends Disposable { serviceCollection.set(ISignService, signService); // Remote Agent - const remoteAgentService = this._register(new RemoteAgentService(this.environmentService, this.productService, remoteAuthorityResolverService, signService, logService)); + const remoteAgentService = this._register(new RemoteAgentService(environmentService, productService, remoteAuthorityResolverService, signService, logService)); serviceCollection.set(IRemoteAgentService, remoteAgentService); // Native Host @@ -204,7 +203,10 @@ export abstract class SharedDesktopMain extends Disposable { const fileService = this._register(new FileService(logService)); serviceCollection.set(IFileService, fileService); - this.registerFileSystemProviders(fileService, logService, nativeHostService); + const result = this.registerFileSystemProviders(environmentService, fileService, logService, nativeHostService); + if (result instanceof Promise) { + await result; + } // Uri Identity const uriIdentityService = new UriIdentityService(fileService); @@ -230,10 +232,10 @@ export abstract class SharedDesktopMain extends Disposable { fileService.registerProvider(Schemas.vscodeRemote, remoteFileSystemProvider); } - const payload = this.resolveWorkspaceInitializationPayload(); + const payload = this.resolveWorkspaceInitializationPayload(environmentService); - const services = await Promise.all([ - this.createWorkspaceService(payload, fileService, remoteAgentService, uriIdentityService, logService).then(service => { + const [configurationService, storageService] = await Promise.all([ + this.createWorkspaceService(payload, environmentService, fileService, remoteAgentService, uriIdentityService, logService).then(service => { // Workspace serviceCollection.set(IWorkspaceContextService, service); @@ -244,7 +246,7 @@ export abstract class SharedDesktopMain extends Disposable { return service; }), - this.createStorageService(payload, mainProcessService).then(service => { + this.createStorageService(payload, environmentService, mainProcessService).then(service => { // Storage serviceCollection.set(IStorageService, service); @@ -261,6 +263,13 @@ export abstract class SharedDesktopMain extends Disposable { }) ]); + // Workspace Trust Service + const workspaceTrustManagementService = new WorkspaceTrustManagementService(configurationService, environmentService, storageService, uriIdentityService, configurationService); + serviceCollection.set(IWorkspaceTrustManagementService, workspaceTrustManagementService); + + // Update workspace trust so that configuration is updated accordingly + configurationService.updateWorkspaceTrust(workspaceTrustManagementService.isWorkpaceTrusted()); + this._register(workspaceTrustManagementService.onDidChangeTrust(() => configurationService.updateWorkspaceTrust(workspaceTrustManagementService.isWorkpaceTrusted()))); // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // @@ -275,10 +284,10 @@ export abstract class SharedDesktopMain extends Disposable { // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - return { serviceCollection, logService, storageService: services[1] }; + return { serviceCollection, logService, storageService }; } - private resolveWorkspaceInitializationPayload(): IWorkspaceInitializationPayload { + private resolveWorkspaceInitializationPayload(environmentService: INativeWorkbenchEnvironmentService): IWorkspaceInitializationPayload { let workspaceInitializationPayload: IWorkspaceInitializationPayload | undefined = this.configuration.workspace; // Fallback to empty workspace if we have no payload yet. @@ -286,7 +295,7 @@ export abstract class SharedDesktopMain extends Disposable { let id: string; if (this.configuration.backupPath) { id = basename(this.configuration.backupPath); // we know the backupPath must be a unique path so we leverage its name as workspace ID - } else if (this.environmentService.isExtensionDevelopment) { + } else if (environmentService.isExtensionDevelopment) { id = 'ext-dev'; // extension development window never stores backups and is a singleton } else { throw new Error('Unexpected window configuration without backupPath'); @@ -298,8 +307,8 @@ export abstract class SharedDesktopMain extends Disposable { return workspaceInitializationPayload; } - private async createWorkspaceService(payload: IWorkspaceInitializationPayload, fileService: FileService, remoteAgentService: IRemoteAgentService, uriIdentityService: IUriIdentityService, logService: ILogService): Promise { - const workspaceService = new WorkspaceService({ remoteAuthority: this.environmentService.remoteAuthority, configurationCache: new ConfigurationCache(URI.file(this.environmentService.userDataPath), fileService) }, this.environmentService, fileService, remoteAgentService, uriIdentityService, logService); + private async createWorkspaceService(payload: IWorkspaceInitializationPayload, environmentService: INativeWorkbenchEnvironmentService, fileService: FileService, remoteAgentService: IRemoteAgentService, uriIdentityService: IUriIdentityService, logService: ILogService): Promise { + const workspaceService = new WorkspaceService({ remoteAuthority: environmentService.remoteAuthority, configurationCache: new ConfigurationCache(URI.file(environmentService.userDataPath), fileService) }, environmentService, fileService, remoteAgentService, uriIdentityService, logService); try { await workspaceService.initialize(payload); @@ -312,8 +321,8 @@ export abstract class SharedDesktopMain extends Disposable { } } - private async createStorageService(payload: IWorkspaceInitializationPayload, mainProcessService: IMainProcessService): Promise { - const storageService = new NativeStorageService(payload, mainProcessService, this.environmentService); + private async createStorageService(payload: IWorkspaceInitializationPayload, environmentService: INativeWorkbenchEnvironmentService, mainProcessService: IMainProcessService): Promise { + const storageService = new NativeStorageService(payload, mainProcessService, environmentService); try { await storageService.initialize(); diff --git a/src/vs/workbench/electron-sandbox/window.ts b/src/vs/workbench/electron-sandbox/window.ts index 0e56bad0..d4e4e0ed 100644 --- a/src/vs/workbench/electron-sandbox/window.ts +++ b/src/vs/workbench/electron-sandbox/window.ts @@ -48,7 +48,8 @@ import { posix, dirname } from 'vs/base/common/path'; import { getBaseLabel } from 'vs/base/common/labels'; import { ITunnelService, extractLocalHostUriMetaDataForPortMapping } from 'vs/platform/remote/common/tunnel'; import { IWorkbenchLayoutService, Parts, positionFromString, Position } from 'vs/workbench/services/layout/browser/layoutService'; -import { IWorkingCopyService, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopy'; import { AutoSaveMode, IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { Event } from 'vs/base/common/event'; import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver'; @@ -57,6 +58,8 @@ import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editor import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { AuthInfo } from 'vs/base/parts/sandbox/electron-sandbox/electronTypes'; import { ILogService } from 'vs/platform/log/common/log'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { whenEditorClosed } from 'vs/workbench/browser/editor'; export class NativeWindow extends Disposable { @@ -105,7 +108,8 @@ export class NativeWindow extends Disposable { @IRemoteAuthorityResolverService private readonly remoteAuthorityResolverService: IRemoteAuthorityResolverService, @IDialogService private readonly dialogService: IDialogService, @IStorageService private readonly storageService: IStorageService, - @ILogService private readonly logService: ILogService + @ILogService private readonly logService: ILogService, + @IInstantiationService private readonly instantiationService: IInstantiationService ) { super(); @@ -467,7 +471,7 @@ export class NativeWindow extends Disposable { this.lifecycleService.when(LifecyclePhase.Ready).then(() => this.nativeHostService.notifyReady()); // Integrity warning - this.integrityService.isPure().then(res => this.titleService.updateProperties({ isPure: res.isPure })); + this.integrityService.isPure().then(({ isPure }) => this.titleService.updateProperties({ isPure })); // Root warning this.lifecycleService.when(LifecyclePhase.Restored).then(async () => { @@ -664,8 +668,8 @@ export class NativeWindow extends Disposable { private async trackClosedWaitFiles(waitMarkerFile: URI, resourcesToWaitFor: URI[]): Promise { - // Wait for the resources to be closed in the editor... - await this.editorService.whenClosed(resourcesToWaitFor.map(resource => ({ resource })), { waitForSaved: true }); + // Wait for the resources to be closed in the text editor... + await this.instantiationService.invokeFunction(accessor => whenEditorClosed(accessor, resourcesToWaitFor)); // ...before deleting the wait marker file await this.fileService.del(waitMarkerFile); diff --git a/src/vs/workbench/services/accessibility/electron-sandbox/accessibilityService.ts b/src/vs/workbench/services/accessibility/electron-sandbox/accessibilityService.ts index cdf58500..55fb3688 100644 --- a/src/vs/workbench/services/accessibility/electron-sandbox/accessibilityService.ts +++ b/src/vs/workbench/services/accessibility/electron-sandbox/accessibilityService.ts @@ -26,8 +26,6 @@ type AccessibilityMetricsClassification = { export class NativeAccessibilityService extends AccessibilityService implements IAccessibilityService { - declare readonly _serviceBrand: undefined; - private didSendTelemetry = false; private shouldAlwaysUnderlineAccessKeys: boolean | undefined = undefined; @@ -42,7 +40,7 @@ export class NativeAccessibilityService extends AccessibilityService implements this.setAccessibilitySupport(environmentService.configuration.accessibilitySupport ? AccessibilitySupport.Enabled : AccessibilitySupport.Disabled); } - async alwaysUnderlineAccessKeys(): Promise { + override async alwaysUnderlineAccessKeys(): Promise { if (!isWindows) { return false; } @@ -55,7 +53,7 @@ export class NativeAccessibilityService extends AccessibilityService implements return this.shouldAlwaysUnderlineAccessKeys; } - setAccessibilitySupport(accessibilitySupport: AccessibilitySupport): void { + override setAccessibilitySupport(accessibilitySupport: AccessibilitySupport): void { super.setAccessibilitySupport(accessibilitySupport); if (!this.didSendTelemetry && accessibilitySupport === AccessibilitySupport.Enabled) { diff --git a/src/vs/workbench/services/activity/browser/activityService.ts b/src/vs/workbench/services/activity/browser/activityService.ts index 8a3e3c59..a3a9eace 100644 --- a/src/vs/workbench/services/activity/browser/activityService.ts +++ b/src/vs/workbench/services/activity/browser/activityService.ts @@ -46,7 +46,7 @@ class ViewContainerActivityByView extends Disposable { } } - dispose() { + override dispose() { this.activityDisposable.dispose(); } } diff --git a/src/vs/workbench/services/activity/common/activity.ts b/src/vs/workbench/services/activity/common/activity.ts index b7665e85..8eda8c67 100644 --- a/src/vs/workbench/services/activity/common/activity.ts +++ b/src/vs/workbench/services/activity/common/activity.ts @@ -63,7 +63,7 @@ export class NumberBadge extends BaseBadge { this.number = number; } - getDescription(): string { + override getDescription(): string { return this.descriptorFn(this.number); } } diff --git a/src/vs/workbench/services/backup/common/backup.ts b/src/vs/workbench/services/backup/common/backup.ts deleted file mode 100644 index dfd54a9e..00000000 --- a/src/vs/workbench/services/backup/common/backup.ts +++ /dev/null @@ -1,78 +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 { URI } from 'vs/base/common/uri'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { ITextBufferFactory, ITextSnapshot } from 'vs/editor/common/model'; -import { CancellationToken } from 'vs/base/common/cancellation'; - -export const IBackupFileService = createDecorator('backupFileService'); - -export interface IResolvedBackup { - readonly value: ITextBufferFactory; - readonly meta?: T; -} - -/** - * A service that handles any I/O and state associated with the backup system. - */ -export interface IBackupFileService { - - readonly _serviceBrand: undefined; - - /** - * Finds out if there are any backups stored. - */ - hasBackups(): Promise; - - /** - * Finds out if the provided resource with the given version is backed up. - * - * Note: if the backup service has not been initialized yet, this may return - * the wrong result. Always use `resolve()` if you can do a long running - * operation. - */ - hasBackupSync(resource: URI, versionId?: number): boolean; - - /** - * Gets a list of file backups for the current workspace. - * - * @return The list of backups. - */ - getBackups(): Promise; - - /** - * Resolves the backup for the given resource if that exists. - * - * @param resource The resource to get the backup for. - * @return The backup file's backed up content and metadata if available or undefined - * if not backup exists. - */ - resolve(resource: URI): Promise | undefined>; - - /** - * Backs up a resource. - * - * @param resource The resource to back up. - * @param content The optional content of the resource as snapshot. - * @param versionId The optionsl version id of the resource to backup. - * @param meta The optional meta data of the resource to backup. This information - * can be restored later when loading the backup again. - * @param token The optional cancellation token if the operation can be cancelled. - */ - backup(resource: URI, content?: ITextSnapshot, versionId?: number, meta?: T, token?: CancellationToken): Promise; - - /** - * Discards the backup associated with a resource if it exists. - * - * @param resource The resource whose backup is being discarded discard to back up. - */ - discardBackup(resource: URI): Promise; - - /** - * Discards all backups. - */ - discardBackups(): Promise; -} diff --git a/src/vs/workbench/services/backup/common/backupFileService.ts b/src/vs/workbench/services/backup/common/backupFileService.ts deleted file mode 100644 index aaf72925..00000000 --- a/src/vs/workbench/services/backup/common/backupFileService.ts +++ /dev/null @@ -1,511 +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 { join } from 'vs/base/common/path'; -import { basename, isEqual, joinPath } from 'vs/base/common/resources'; -import { URI } from 'vs/base/common/uri'; -import { coalesce } from 'vs/base/common/arrays'; -import { equals, deepClone } from 'vs/base/common/objects'; -import { ResourceQueue } from 'vs/base/common/async'; -import { IResolvedBackup, IBackupFileService } from 'vs/workbench/services/backup/common/backup'; -import { IFileService, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; -import { ITextSnapshot } from 'vs/editor/common/model'; -import { createTextBufferFactoryFromStream, createTextBufferFactoryFromSnapshot } from 'vs/editor/common/model/textModel'; -import { ResourceMap } from 'vs/base/common/map'; -import { VSBuffer } from 'vs/base/common/buffer'; -import { TextSnapshotReadable, stringToSnapshot } from 'vs/workbench/services/textfile/common/textfiles'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { ILogService } from 'vs/platform/log/common/log'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import { Schemas } from 'vs/base/common/network'; -import { hash } from 'vs/base/common/hash'; - -export interface IBackupFilesModel { - resolve(backupRoot: URI): Promise; - - get(): URI[]; - has(resource: URI, versionId?: number, meta?: object): boolean; - - add(resource: URI, versionId?: number, meta?: object): void; - remove(resource: URI): void; - move(source: URI, target: URI): void; - - count(): number; - - clear(): void; -} - -interface IBackupCacheEntry { - versionId?: number; - meta?: object; -} - -export class BackupFilesModel implements IBackupFilesModel { - - private readonly cache = new ResourceMap(); - - constructor(private fileService: IFileService) { } - - async resolve(backupRoot: URI): Promise { - try { - const backupRootStat = await this.fileService.resolve(backupRoot); - if (backupRootStat.children) { - await Promise.all(backupRootStat.children - .filter(child => child.isDirectory) - .map(async backupSchema => { - - // Read backup directory for backups - const backupSchemaStat = await this.fileService.resolve(backupSchema.resource); - - // Remember known backups in our caches - if (backupSchemaStat.children) { - backupSchemaStat.children.forEach(backupHash => this.add(backupHash.resource)); - } - })); - } - } catch (error) { - // ignore any errors - } - } - - add(resource: URI, versionId = 0, meta?: object): void { - this.cache.set(resource, { versionId, meta: deepClone(meta) }); // make sure to not store original meta in our cache... - } - - count(): number { - return this.cache.size; - } - - has(resource: URI, versionId?: number, meta?: object): boolean { - const entry = this.cache.get(resource); - if (!entry) { - return false; // unknown resource - } - - if (typeof versionId === 'number' && versionId !== entry.versionId) { - return false; // different versionId - } - - if (meta && !equals(meta, entry.meta)) { - return false; // different metadata - } - - return true; - } - - get(): URI[] { - return [...this.cache.keys()]; - } - - remove(resource: URI): void { - this.cache.delete(resource); - } - - move(source: URI, target: URI): void { - const entry = this.cache.get(source); - if (entry) { - this.cache.delete(source); - this.cache.set(target, entry); - } - } - - clear(): void { - this.cache.clear(); - } -} - -export abstract class BackupFileService implements IBackupFileService { - - declare readonly _serviceBrand: undefined; - - private impl: BackupFileServiceImpl | InMemoryBackupFileService; - - constructor( - backupWorkspaceHome: URI | undefined, - @IFileService protected fileService: IFileService, - @ILogService private readonly logService: ILogService - ) { - this.impl = this.initialize(backupWorkspaceHome); - } - - private hashPath(resource: URI): string { - return hashPath(resource); - } - - private initialize(backupWorkspaceHome: URI | undefined): BackupFileServiceImpl | InMemoryBackupFileService { - if (backupWorkspaceHome) { - return new BackupFileServiceImpl(backupWorkspaceHome, this.hashPath, this.fileService, this.logService); - } - - return new InMemoryBackupFileService(this.hashPath); - } - - reinitialize(backupWorkspaceHome: URI | undefined): void { - - // Re-init implementation (unless we are running in-memory) - if (this.impl instanceof BackupFileServiceImpl) { - if (backupWorkspaceHome) { - this.impl.initialize(backupWorkspaceHome); - } else { - this.impl = new InMemoryBackupFileService(this.hashPath); - } - } - } - - hasBackups(): Promise { - return this.impl.hasBackups(); - } - - hasBackupSync(resource: URI, versionId?: number): boolean { - return this.impl.hasBackupSync(resource, versionId); - } - - backup(resource: URI, content?: ITextSnapshot, versionId?: number, meta?: T, token?: CancellationToken): Promise { - return this.impl.backup(resource, content, versionId, meta, token); - } - - discardBackup(resource: URI): Promise { - return this.impl.discardBackup(resource); - } - - discardBackups(): Promise { - return this.impl.discardBackups(); - } - - getBackups(): Promise { - return this.impl.getBackups(); - } - - resolve(resource: URI): Promise | undefined> { - return this.impl.resolve(resource); - } - - toBackupResource(resource: URI): URI { - return this.impl.toBackupResource(resource); - } -} - -class BackupFileServiceImpl extends Disposable implements IBackupFileService { - - private static readonly PREAMBLE_END_MARKER = '\n'; - private static readonly PREAMBLE_META_SEPARATOR = ' '; // using a character that is know to be escaped in a URI as separator - private static readonly PREAMBLE_MAX_LENGTH = 10000; - - declare readonly _serviceBrand: undefined; - - private backupWorkspacePath!: URI; - - private readonly ioOperationQueues = this._register(new ResourceQueue()); // queue IO operations to ensure write/delete file order - - private ready!: Promise; - private model!: IBackupFilesModel; - - constructor( - backupWorkspaceResource: URI, - private readonly hashPath: (resource: URI) => string, - @IFileService private readonly fileService: IFileService, - @ILogService private readonly logService: ILogService - ) { - super(); - - this.initialize(backupWorkspaceResource); - } - - initialize(backupWorkspaceResource: URI): void { - this.backupWorkspacePath = backupWorkspaceResource; - - this.ready = this.doInitialize(); - } - - private async doInitialize(): Promise { - this.model = new BackupFilesModel(this.fileService); - - // Resolve backup model - await this.model.resolve(this.backupWorkspacePath); - - // Migrate hashes as needed. We used to hash with a MD5 - // sum of the path but switched to our own simpler hash - // to avoid a node.js dependency. We still want to - // support the older hash so we: - // - iterate over all backups - // - detect if the file name length is 32 (MD5 length) - // - read the backup's target file path - // - rename the backup to the new hash - // - update the backup in our model - // - // TODO@bpasero remove me eventually - for (const backupResource of this.model.get()) { - if (basename(backupResource).length !== 32) { - continue; // not a MD5 hash, already uses new hash function - } - - try { - const resource = await this.readUri(backupResource); - if (!resource) { - this.logService.warn(`Backup: Unable to read target URI of backup ${backupResource} for migration to new hash.`); - continue; - } - - const expectedBackupResource = this.toBackupResource(resource); - if (!isEqual(expectedBackupResource, backupResource)) { - await this.fileService.move(backupResource, expectedBackupResource, true); - this.model.move(backupResource, expectedBackupResource); - } - } catch (error) { - this.logService.error(`Backup: Unable to migrate backup ${backupResource} to new hash.`); - } - } - - return this.model; - } - - async hasBackups(): Promise { - const model = await this.ready; - - return model.count() > 0; - } - - hasBackupSync(resource: URI, versionId?: number): boolean { - const backupResource = this.toBackupResource(resource); - - return this.model.has(backupResource, versionId); - } - - async backup(resource: URI, content?: ITextSnapshot, versionId?: number, meta?: T, token?: CancellationToken): Promise { - const model = await this.ready; - if (token?.isCancellationRequested) { - return; - } - - const backupResource = this.toBackupResource(resource); - if (model.has(backupResource, versionId, meta)) { - return; // return early if backup version id matches requested one - } - - return this.ioOperationQueues.queueFor(backupResource).queue(async () => { - if (token?.isCancellationRequested) { - return; - } - - let preamble: string | undefined = undefined; - - // With Metadata: URI + META-START + Meta + END - if (meta) { - const preambleWithMeta = `${resource.toString()}${BackupFileServiceImpl.PREAMBLE_META_SEPARATOR}${JSON.stringify(meta)}${BackupFileServiceImpl.PREAMBLE_END_MARKER}`; - if (preambleWithMeta.length < BackupFileServiceImpl.PREAMBLE_MAX_LENGTH) { - preamble = preambleWithMeta; - } - } - - // Without Metadata: URI + END - if (!preamble) { - preamble = `${resource.toString()}${BackupFileServiceImpl.PREAMBLE_END_MARKER}`; - } - - // Update content with value - await this.fileService.writeFile(backupResource, new TextSnapshotReadable(content || stringToSnapshot(''), preamble)); - - // Update model - model.add(backupResource, versionId, meta); - }); - } - - async discardBackups(): Promise { - const model = await this.ready; - - await this.deleteIgnoreFileNotFound(this.backupWorkspacePath); - - model.clear(); - } - - discardBackup(resource: URI): Promise { - const backupResource = this.toBackupResource(resource); - - return this.doDiscardBackup(backupResource); - } - - private async doDiscardBackup(backupResource: URI): Promise { - const model = await this.ready; - - return this.ioOperationQueues.queueFor(backupResource).queue(async () => { - await this.deleteIgnoreFileNotFound(backupResource); - - model.remove(backupResource); - }); - } - - private async deleteIgnoreFileNotFound(resource: URI): Promise { - try { - await this.fileService.del(resource, { recursive: true }); - } catch (error) { - if ((error).fileOperationResult !== FileOperationResult.FILE_NOT_FOUND) { - throw error; // re-throw any other error than file not found which is OK - } - } - } - - async getBackups(): Promise { - const model = await this.ready; - - const backups = await Promise.all(model.get().map(backupResource => this.readUri(backupResource))); - - return coalesce(backups); - } - - private async readUri(backupResource: URI): Promise { - const backupPreamble = await this.readToMatchingString(backupResource, BackupFileServiceImpl.PREAMBLE_END_MARKER, BackupFileServiceImpl.PREAMBLE_MAX_LENGTH); - if (!backupPreamble) { - return undefined; - } - - // Preamble with metadata: URI + META-START + Meta + END - const metaStartIndex = backupPreamble.indexOf(BackupFileServiceImpl.PREAMBLE_META_SEPARATOR); - if (metaStartIndex > 0) { - return URI.parse(backupPreamble.substring(0, metaStartIndex)); - } - - // Preamble without metadata: URI + END - else { - return URI.parse(backupPreamble); - } - } - - private async readToMatchingString(backupResource: URI, matchingString: string, maximumBytesToRead: number): Promise { - const contents = (await this.fileService.readFile(backupResource, { length: maximumBytesToRead })).value.toString(); - - const matchingStringIndex = contents.indexOf(matchingString); - if (matchingStringIndex >= 0) { - return contents.substr(0, matchingStringIndex); - } - - // Unable to find matching string in file - return undefined; - } - - async resolve(resource: URI): Promise | undefined> { - const backupResource = this.toBackupResource(resource); - - const model = await this.ready; - if (!model.has(backupResource)) { - return undefined; // require backup to be present - } - - // Metadata extraction - let metaRaw = ''; - let metaEndFound = false; - - // Add a filter method to filter out everything until the meta end marker - const metaPreambleFilter = (chunk: VSBuffer) => { - const chunkString = chunk.toString(); - - if (!metaEndFound) { - const metaEndIndex = chunkString.indexOf(BackupFileServiceImpl.PREAMBLE_END_MARKER); - if (metaEndIndex === -1) { - metaRaw += chunkString; - - return VSBuffer.fromString(''); // meta not yet found, return empty string - } - - metaEndFound = true; - metaRaw += chunkString.substring(0, metaEndIndex); // ensure to get last chunk from metadata - - return VSBuffer.fromString(chunkString.substr(metaEndIndex + 1)); // meta found, return everything after - } - - return chunk; - }; - - // Read backup into factory - const content = await this.fileService.readFileStream(backupResource); - const factory = await createTextBufferFactoryFromStream(content.value, metaPreambleFilter); - - // Extract meta data (if any) - let meta: T | undefined; - const metaStartIndex = metaRaw.indexOf(BackupFileServiceImpl.PREAMBLE_META_SEPARATOR); - if (metaStartIndex !== -1) { - try { - meta = JSON.parse(metaRaw.substr(metaStartIndex + 1)); - } catch (error) { - // ignore JSON parse errors - } - } - - // We have seen reports (e.g. https://github.com/microsoft/vscode/issues/78500) where - // if VSCode goes down while writing the backup file, the file can turn empty because - // it always first gets truncated and then written to. In this case, we will not find - // the meta-end marker ('\n') and as such the backup can only be invalid. We bail out - // here if that is the case. - if (!metaEndFound) { - this.logService.trace(`Backup: Could not find meta end marker in ${backupResource}. The file is probably corrupt (filesize: ${content.size}).`); - - return undefined; - } - - return { value: factory, meta }; - } - - toBackupResource(resource: URI): URI { - return joinPath(this.backupWorkspacePath, resource.scheme, this.hashPath(resource)); - } -} - -export class InMemoryBackupFileService implements IBackupFileService { - - declare readonly _serviceBrand: undefined; - - private backups = new Map(); - - constructor(private readonly hashPath: (resource: URI) => string) { } - - async hasBackups(): Promise { - return this.backups.size > 0; - } - - hasBackupSync(resource: URI, versionId?: number): boolean { - const backupResource = this.toBackupResource(resource); - - return this.backups.has(backupResource.toString()); - } - - async backup(resource: URI, content?: ITextSnapshot, versionId?: number, meta?: T, token?: CancellationToken): Promise { - const backupResource = this.toBackupResource(resource); - this.backups.set(backupResource.toString(), { content: content || stringToSnapshot(''), meta }); - } - - async resolve(resource: URI): Promise | undefined> { - const backupResource = this.toBackupResource(resource); - const backup = this.backups.get(backupResource.toString()); - if (backup) { - return { value: createTextBufferFactoryFromSnapshot(backup.content), meta: backup.meta as T | undefined }; - } - - return undefined; - } - - async getBackups(): Promise { - return Array.from(this.backups.keys()).map(key => URI.parse(key)); - } - - async discardBackup(resource: URI): Promise { - this.backups.delete(this.toBackupResource(resource).toString()); - } - - async discardBackups(): Promise { - this.backups.clear(); - } - - toBackupResource(resource: URI): URI { - return URI.file(join(resource.scheme, this.hashPath(resource))); - } -} - -/* - * Exported only for testing - */ -export function hashPath(resource: URI): string { - const str = resource.scheme === Schemas.file || resource.scheme === Schemas.untitled ? resource.fsPath : resource.toString(); - - return hash(str).toString(16); -} 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 deleted file mode 100644 index 106fb0a1..00000000 --- a/src/vs/workbench/services/backup/test/electron-browser/backupFileService.test.ts +++ /dev/null @@ -1,722 +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 { isWindows } from 'vs/base/common/platform'; -import { tmpdir } from 'os'; -import { promises, existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs'; -import { dirname, join } from 'vs/base/common/path'; -import { readdirSync, rimraf, writeFile } from 'vs/base/node/pfs'; -import { URI } from 'vs/base/common/uri'; -import { BackupFilesModel, hashPath } from 'vs/workbench/services/backup/common/backupFileService'; -import { createTextBufferFactory } from 'vs/editor/common/model/textModel'; -import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; -import { getRandomTestPath } from 'vs/base/test/node/testUtils'; -import { DefaultEndOfLine, ITextSnapshot } from 'vs/editor/common/model'; -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-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, 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'; -import { hash } from 'vs/base/common/hash'; -import { isEqual } from 'vs/base/common/resources'; - -class TestWorkbenchEnvironmentService extends NativeWorkbenchEnvironmentService { - - constructor(testDir: string, backupPath: string) { - super({ ...TestWorkbenchConfiguration, backupPath, 'user-data-dir': testDir }, TestEnvironmentPaths, TestProductService); - } -} - -export class NodeTestBackupFileService extends NativeBackupFileService { - - readonly fileService: IFileService; - - private backupResourceJoiners: Function[]; - private discardBackupJoiners: Function[]; - discardedBackups: URI[]; - private pendingBackupsArr: Promise[]; - - constructor(testDir: string, workspaceBackupPath: string) { - const environmentService = new TestWorkbenchEnvironmentService(testDir, workspaceBackupPath); - const logService = new NullLogService(); - const fileService = new FileService(logService); - const diskFileSystemProvider = new DiskFileSystemProvider(logService); - fileService.registerProvider(Schemas.file, diskFileSystemProvider); - fileService.registerProvider(Schemas.userData, new FileUserDataProvider(Schemas.file, diskFileSystemProvider, Schemas.userData, logService)); - - super(environmentService, fileService, logService); - - this.fileService = fileService; - this.backupResourceJoiners = []; - this.discardBackupJoiners = []; - this.discardedBackups = []; - this.pendingBackupsArr = []; - } - - async waitForAllBackups(): Promise { - await Promise.all(this.pendingBackupsArr); - } - - joinBackupResource(): Promise { - return new Promise(resolve => this.backupResourceJoiners.push(resolve)); - } - - async backup(resource: URI, content?: ITextSnapshot, versionId?: number, meta?: any, token?: CancellationToken): Promise { - const p = super.backup(resource, content, versionId, meta, token); - const removeFromPendingBackups = insert(this.pendingBackupsArr, p.then(undefined, undefined)); - - try { - await p; - } finally { - removeFromPendingBackups(); - } - - while (this.backupResourceJoiners.length) { - this.backupResourceJoiners.pop()!(); - } - } - - joinDiscardBackup(): Promise { - return new Promise(resolve => this.discardBackupJoiners.push(resolve)); - } - - async discardBackup(resource: URI): Promise { - await super.discardBackup(resource); - this.discardedBackups.push(resource); - - while (this.discardBackupJoiners.length) { - this.discardBackupJoiners.pop()!(); - } - } - - async getBackupContents(resource: URI): Promise { - const backupResource = this.toBackupResource(resource); - - const fileContents = await this.fileService.readFile(backupResource); - - return fileContents.value.toString(); - } -} - -suite('BackupFileService', () => { - - let testDir: string; - let backupHome: string; - let workspacesJsonPath: string; - let workspaceBackupPath: string; - let fooBackupPath: string; - let barBackupPath: string; - let untitledBackupPath: string; - let customFileBackupPath: string; - - let service: NodeTestBackupFileService; - - let workspaceResource = URI.file(isWindows ? 'c:\\workspace' : '/workspace'); - let fooFile = URI.file(isWindows ? 'c:\\Foo' : '/Foo'); - let customFile = URI.parse('customScheme://some/path'); - let customFileWithFragment = URI.parse('customScheme2://some/path#fragment'); - let barFile = URI.file(isWindows ? 'c:\\Bar' : '/Bar'); - let fooBarFile = URI.file(isWindows ? 'c:\\Foo Bar' : '/Foo Bar'); - let untitledFile = URI.from({ scheme: Schemas.untitled, path: 'Untitled-1' }); - - setup(async () => { - testDir = getRandomTestPath(tmpdir(), 'vsctests', 'backupfileservice'); - backupHome = join(testDir, 'Backups'); - workspacesJsonPath = join(backupHome, 'workspaces.json'); - workspaceBackupPath = join(backupHome, hashPath(workspaceResource)); - fooBackupPath = join(workspaceBackupPath, fooFile.scheme, hashPath(fooFile)); - barBackupPath = join(workspaceBackupPath, barFile.scheme, hashPath(barFile)); - untitledBackupPath = join(workspaceBackupPath, untitledFile.scheme, hashPath(untitledFile)); - customFileBackupPath = join(workspaceBackupPath, customFile.scheme, hashPath(customFile)); - - service = new NodeTestBackupFileService(testDir, workspaceBackupPath); - - await promises.mkdir(backupHome, { recursive: true }); - - return writeFile(workspacesJsonPath, ''); - }); - - teardown(() => { - return rimraf(testDir); - }); - - suite('hashPath', () => { - test('should correctly hash the path for untitled scheme URIs', () => { - const uri = URI.from({ - scheme: 'untitled', - path: 'Untitled-1' - }); - const actual = hashPath(uri); - // If these hashes change people will lose their backed up files! - assert.strictEqual(actual, '-7f9c1a2e'); - assert.strictEqual(actual, hash(uri.fsPath).toString(16)); - }); - - test('should correctly hash the path for file scheme URIs', () => { - const uri = URI.file('/foo'); - const actual = hashPath(uri); - // If these hashes change people will lose their backed up files! - if (isWindows) { - assert.strictEqual(actual, '20ffaa13'); - } else { - assert.strictEqual(actual, '20eb3560'); - } - assert.strictEqual(actual, hash(uri.fsPath).toString(16)); - }); - - test('should correctly hash the path for custom scheme URIs', () => { - const uri = URI.from({ - scheme: 'vscode-custom', - path: 'somePath' - }); - const actual = hashPath(uri); - // If these hashes change people will lose their backed up files! - assert.strictEqual(actual, '-44972d98'); - assert.strictEqual(actual, hash(uri.toString()).toString(16)); - }); - }); - - suite('getBackupResource', () => { - test('should get the correct backup path for text files', () => { - // Format should be: /// - const backupResource = fooFile; - const workspaceHash = hashPath(workspaceResource); - const filePathHash = hashPath(backupResource); - const expectedPath = URI.file(join(backupHome, workspaceHash, Schemas.file, filePathHash)).with({ scheme: Schemas.userData }).toString(); - assert.strictEqual(service.toBackupResource(backupResource).toString(), expectedPath); - }); - - test('should get the correct backup path for untitled files', () => { - // Format should be: /// - const backupResource = URI.from({ scheme: Schemas.untitled, path: 'Untitled-1' }); - const workspaceHash = hashPath(workspaceResource); - const filePathHash = hashPath(backupResource); - const expectedPath = URI.file(join(backupHome, workspaceHash, Schemas.untitled, filePathHash)).with({ scheme: Schemas.userData }).toString(); - assert.strictEqual(service.toBackupResource(backupResource).toString(), expectedPath); - }); - }); - - suite('backup', () => { - test('no text', async () => { - await service.backup(fooFile); - assert.strictEqual(readdirSync(join(workspaceBackupPath, 'file')).length, 1); - assert.strictEqual(existsSync(fooBackupPath), true); - assert.strictEqual(readFileSync(fooBackupPath).toString(), `${fooFile.toString()}\n`); - assert.ok(service.hasBackupSync(fooFile)); - }); - - test('text file', async () => { - await service.backup(fooFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).textBuffer.createSnapshot(false)); - assert.strictEqual(readdirSync(join(workspaceBackupPath, 'file')).length, 1); - assert.strictEqual(existsSync(fooBackupPath), true); - assert.strictEqual(readFileSync(fooBackupPath).toString(), `${fooFile.toString()}\ntest`); - assert.ok(service.hasBackupSync(fooFile)); - }); - - test('text file (with version)', async () => { - await service.backup(fooFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).textBuffer.createSnapshot(false), 666); - assert.strictEqual(readdirSync(join(workspaceBackupPath, 'file')).length, 1); - assert.strictEqual(existsSync(fooBackupPath), true); - assert.strictEqual(readFileSync(fooBackupPath).toString(), `${fooFile.toString()}\ntest`); - assert.ok(!service.hasBackupSync(fooFile, 555)); - assert.ok(service.hasBackupSync(fooFile, 666)); - }); - - test('text file (with meta)', async () => { - await service.backup(fooFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).textBuffer.createSnapshot(false), undefined, { etag: '678', orphaned: true }); - assert.strictEqual(readdirSync(join(workspaceBackupPath, 'file')).length, 1); - assert.strictEqual(existsSync(fooBackupPath), true); - assert.strictEqual(readFileSync(fooBackupPath).toString(), `${fooFile.toString()} {"etag":"678","orphaned":true}\ntest`); - assert.ok(service.hasBackupSync(fooFile)); - }); - - test('untitled file', async () => { - await service.backup(untitledFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).textBuffer.createSnapshot(false)); - assert.strictEqual(readdirSync(join(workspaceBackupPath, 'untitled')).length, 1); - assert.strictEqual(existsSync(untitledBackupPath), true); - assert.strictEqual(readFileSync(untitledBackupPath).toString(), `${untitledFile.toString()}\ntest`); - assert.ok(service.hasBackupSync(untitledFile)); - }); - - test('text file (ITextSnapshot)', async () => { - const model = createTextModel('test'); - - await service.backup(fooFile, model.createSnapshot()); - assert.strictEqual(readdirSync(join(workspaceBackupPath, 'file')).length, 1); - assert.strictEqual(existsSync(fooBackupPath), true); - assert.strictEqual(readFileSync(fooBackupPath).toString(), `${fooFile.toString()}\ntest`); - assert.ok(service.hasBackupSync(fooFile)); - - model.dispose(); - }); - - test('untitled file (ITextSnapshot)', async () => { - const model = createTextModel('test'); - - await service.backup(untitledFile, model.createSnapshot()); - assert.strictEqual(readdirSync(join(workspaceBackupPath, 'untitled')).length, 1); - assert.strictEqual(existsSync(untitledBackupPath), true); - assert.strictEqual(readFileSync(untitledBackupPath).toString(), `${untitledFile.toString()}\ntest`); - - model.dispose(); - }); - - test('text file (large file, ITextSnapshot)', async () => { - const largeString = (new Array(10 * 1024)).join('Large String\n'); - const model = createTextModel(largeString); - - await service.backup(fooFile, model.createSnapshot()); - assert.strictEqual(readdirSync(join(workspaceBackupPath, 'file')).length, 1); - assert.strictEqual(existsSync(fooBackupPath), true); - assert.strictEqual(readFileSync(fooBackupPath).toString(), `${fooFile.toString()}\n${largeString}`); - assert.ok(service.hasBackupSync(fooFile)); - - model.dispose(); - }); - - test('untitled file (large file, ITextSnapshot)', async () => { - const largeString = (new Array(10 * 1024)).join('Large String\n'); - const model = createTextModel(largeString); - - await service.backup(untitledFile, model.createSnapshot()); - assert.strictEqual(readdirSync(join(workspaceBackupPath, 'untitled')).length, 1); - assert.strictEqual(existsSync(untitledBackupPath), true); - assert.strictEqual(readFileSync(untitledBackupPath).toString(), `${untitledFile.toString()}\n${largeString}`); - assert.ok(service.hasBackupSync(untitledFile)); - - model.dispose(); - }); - - test('cancellation', async () => { - const cts = new CancellationTokenSource(); - const promise = service.backup(fooFile, undefined, undefined, undefined, cts.token); - cts.cancel(); - await promise; - - assert.strictEqual(existsSync(fooBackupPath), false); - assert.ok(!service.hasBackupSync(fooFile)); - }); - }); - - suite('discardBackup', () => { - test('text file', async () => { - await service.backup(fooFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).textBuffer.createSnapshot(false)); - assert.strictEqual(readdirSync(join(workspaceBackupPath, 'file')).length, 1); - assert.ok(service.hasBackupSync(fooFile)); - - await service.discardBackup(fooFile); - assert.strictEqual(existsSync(fooBackupPath), false); - assert.strictEqual(readdirSync(join(workspaceBackupPath, 'file')).length, 0); - assert.ok(!service.hasBackupSync(fooFile)); - }); - - test('untitled file', async () => { - await service.backup(untitledFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).textBuffer.createSnapshot(false)); - assert.strictEqual(readdirSync(join(workspaceBackupPath, 'untitled')).length, 1); - await service.discardBackup(untitledFile); - assert.strictEqual(existsSync(untitledBackupPath), false); - assert.strictEqual(readdirSync(join(workspaceBackupPath, 'untitled')).length, 0); - }); - }); - - suite('discardBackups', () => { - test('text file', async () => { - await service.backup(fooFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).textBuffer.createSnapshot(false)); - assert.strictEqual(readdirSync(join(workspaceBackupPath, 'file')).length, 1); - await service.backup(barFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).textBuffer.createSnapshot(false)); - assert.strictEqual(readdirSync(join(workspaceBackupPath, 'file')).length, 2); - await service.discardBackups(); - assert.strictEqual(existsSync(fooBackupPath), false); - assert.strictEqual(existsSync(barBackupPath), false); - assert.strictEqual(existsSync(join(workspaceBackupPath, 'file')), false); - }); - - test('untitled file', async () => { - await service.backup(untitledFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).textBuffer.createSnapshot(false)); - assert.strictEqual(readdirSync(join(workspaceBackupPath, 'untitled')).length, 1); - await service.discardBackups(); - assert.strictEqual(existsSync(untitledBackupPath), false); - assert.strictEqual(existsSync(join(workspaceBackupPath, 'untitled')), false); - }); - - test('can backup after discarding all', async () => { - await service.discardBackups(); - await service.backup(untitledFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).textBuffer.createSnapshot(false)); - assert.strictEqual(existsSync(workspaceBackupPath), true); - }); - }); - - suite('getBackups', () => { - test('("file") - text file', async () => { - await service.backup(fooFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).textBuffer.createSnapshot(false)); - const textFiles = await service.getBackups(); - assert.deepStrictEqual(textFiles.map(f => f.fsPath), [fooFile.fsPath]); - await service.backup(barFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).textBuffer.createSnapshot(false)); - const textFiles_1 = await service.getBackups(); - assert.deepStrictEqual(textFiles_1.map(f => f.fsPath), [fooFile.fsPath, barFile.fsPath]); - }); - - test('("file") - untitled file', async () => { - await service.backup(untitledFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).textBuffer.createSnapshot(false)); - const textFiles = await service.getBackups(); - assert.deepStrictEqual(textFiles.map(f => f.fsPath), [untitledFile.fsPath]); - }); - - test('("untitled") - untitled file', async () => { - await service.backup(untitledFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).textBuffer.createSnapshot(false)); - const textFiles = await service.getBackups(); - assert.deepStrictEqual(textFiles.map(f => f.fsPath), ['Untitled-1']); - }); - }); - - suite('resolve', () => { - - interface IBackupTestMetaData { - mtime?: number; - size?: number; - etag?: string; - orphaned?: boolean; - } - - test('should restore the original contents (untitled file)', async () => { - const contents = 'test\nand more stuff'; - - await testResolveBackup(untitledFile, contents); - }); - - test('should restore the original contents (untitled file with metadata)', async () => { - const contents = 'test\nand more stuff'; - - const meta = { - etag: 'the Etag', - size: 666, - mtime: Date.now(), - orphaned: true - }; - - await testResolveBackup(untitledFile, contents, meta); - }); - - test('should restore the original contents (text file)', async () => { - const contents = [ - 'Lorem ipsum ', - 'dolor öäü sit amet ', - 'consectetur ', - 'adipiscing ßß elit' - ].join(''); - - await testResolveBackup(fooFile, contents); - }); - - test('should restore the original contents (text file - custom scheme)', async () => { - const contents = [ - 'Lorem ipsum ', - 'dolor öäü sit amet ', - 'consectetur ', - 'adipiscing ßß elit' - ].join(''); - - await testResolveBackup(customFile, contents); - }); - - test('should restore the original contents (text file with metadata)', async () => { - const contents = [ - 'Lorem ipsum ', - 'dolor öäü sit amet ', - 'adipiscing ßß elit', - 'consectetur ' - ].join(''); - - const meta = { - etag: 'theEtag', - size: 888, - mtime: Date.now(), - orphaned: false - }; - - await testResolveBackup(fooFile, contents, meta); - }); - - test('should restore the original contents (text file with metadata changed once)', async () => { - const contents = [ - 'Lorem ipsum ', - 'dolor öäü sit amet ', - 'adipiscing ßß elit', - 'consectetur ' - ].join(''); - - const meta = { - etag: 'theEtag', - size: 888, - mtime: Date.now(), - orphaned: false - }; - - await testResolveBackup(fooFile, contents, meta); - - // Change meta and test again - meta.size = 999; - await testResolveBackup(fooFile, contents, meta); - }); - - test('should restore the original contents (text file with broken metadata)', async () => { - const contents = [ - 'Lorem ipsum ', - 'dolor öäü sit amet ', - 'adipiscing ßß elit', - 'consectetur ' - ].join(''); - - const meta = { - etag: 'theEtag', - size: 888, - mtime: Date.now(), - orphaned: false - }; - - await service.backup(fooFile, createTextBufferFactory(contents).create(DefaultEndOfLine.LF).textBuffer.createSnapshot(false), 1, meta); - - const fileContents = readFileSync(fooBackupPath).toString(); - assert.strictEqual(fileContents.indexOf(fooFile.toString()), 0); - - const metaIndex = fileContents.indexOf('{'); - const newFileContents = fileContents.substring(0, metaIndex) + '{{' + fileContents.substr(metaIndex); - writeFileSync(fooBackupPath, newFileContents); - - const backup = await service.resolve(fooFile); - assert.ok(backup); - assert.strictEqual(contents, snapshotToString(backup!.value.create(isWindows ? DefaultEndOfLine.CRLF : DefaultEndOfLine.LF).textBuffer.createSnapshot(true))); - assert.ok(!backup!.meta); - }); - - test('should restore the original contents (text file with metadata and fragment URI)', async () => { - const contents = [ - 'Lorem ipsum ', - 'dolor öäü sit amet ', - 'adipiscing ßß elit', - 'consectetur ' - ].join(''); - - const meta = { - etag: 'theEtag', - size: 888, - mtime: Date.now(), - orphaned: false - }; - - await testResolveBackup(customFileWithFragment, contents, meta); - }); - - test('should restore the original contents (text file with space in name with metadata)', async () => { - const contents = [ - 'Lorem ipsum ', - 'dolor öäü sit amet ', - 'adipiscing ßß elit', - 'consectetur ' - ].join(''); - - const meta = { - etag: 'theEtag', - size: 888, - mtime: Date.now(), - orphaned: false - }; - - await testResolveBackup(fooBarFile, contents, meta); - }); - - test('should restore the original contents (text file with too large metadata to persist)', async () => { - const contents = [ - 'Lorem ipsum ', - 'dolor öäü sit amet ', - 'adipiscing ßß elit', - 'consectetur ' - ].join(''); - - const meta = { - etag: (new Array(100 * 1024)).join('Large String'), - size: 888, - mtime: Date.now(), - orphaned: false - }; - - await testResolveBackup(fooBarFile, contents, meta, null); - }); - - test('should ignore invalid backups', async () => { - const contents = 'test\nand more stuff'; - - await service.backup(fooBarFile, createTextBufferFactory(contents).create(DefaultEndOfLine.LF).textBuffer.createSnapshot(false), 1); - - const backup = await service.resolve(fooBarFile); - if (!backup) { - throw new Error('Unexpected missing backup'); - } - - await service.fileService.writeFile(service.toBackupResource(fooBarFile), VSBuffer.fromString('')); - - let err: Error | undefined = undefined; - try { - await service.resolve(fooBarFile); - } catch (error) { - err = error; - } - - assert.ok(!err); - }); - - async function testResolveBackup(resource: URI, contents: string, meta?: IBackupTestMetaData, expectedMeta?: IBackupTestMetaData | null) { - if (typeof expectedMeta === 'undefined') { - expectedMeta = meta; - } - - await service.backup(resource, createTextBufferFactory(contents).create(DefaultEndOfLine.LF).textBuffer.createSnapshot(false), 1, meta); - - const backup = await service.resolve(resource); - assert.ok(backup); - assert.strictEqual(contents, snapshotToString(backup!.value.create(isWindows ? DefaultEndOfLine.CRLF : DefaultEndOfLine.LF).textBuffer.createSnapshot(true))); - - if (expectedMeta) { - assert.strictEqual(backup!.meta!.etag, expectedMeta.etag); - assert.strictEqual(backup!.meta!.size, expectedMeta.size); - assert.strictEqual(backup!.meta!.mtime, expectedMeta.mtime); - assert.strictEqual(backup!.meta!.orphaned, expectedMeta.orphaned); - } else { - assert.ok(!backup!.meta); - } - } - }); - - suite('BackupFilesModel', () => { - - test('simple', () => { - const model = new BackupFilesModel(service.fileService); - - const resource1 = URI.file('test.html'); - - assert.strictEqual(model.has(resource1), false); - - model.add(resource1); - - assert.strictEqual(model.has(resource1), true); - assert.strictEqual(model.has(resource1, 0), true); - assert.strictEqual(model.has(resource1, 1), false); - assert.strictEqual(model.has(resource1, 1, { foo: 'bar' }), false); - - model.remove(resource1); - - assert.strictEqual(model.has(resource1), false); - - model.add(resource1); - - assert.strictEqual(model.has(resource1), true); - assert.strictEqual(model.has(resource1, 0), true); - assert.strictEqual(model.has(resource1, 1), false); - - model.clear(); - - assert.strictEqual(model.has(resource1), false); - - model.add(resource1, 1); - - assert.strictEqual(model.has(resource1), true); - assert.strictEqual(model.has(resource1, 0), false); - assert.strictEqual(model.has(resource1, 1), true); - - const resource2 = URI.file('test1.html'); - const resource3 = URI.file('test2.html'); - const resource4 = URI.file('test3.html'); - - model.add(resource2); - model.add(resource3); - model.add(resource4, undefined, { foo: 'bar' }); - - assert.strictEqual(model.has(resource1), true); - assert.strictEqual(model.has(resource2), true); - assert.strictEqual(model.has(resource3), true); - - assert.strictEqual(model.has(resource4), true); - assert.strictEqual(model.has(resource4, undefined, { foo: 'bar' }), true); - assert.strictEqual(model.has(resource4, undefined, { bar: 'foo' }), false); - - const resource5 = URI.file('test4.html'); - model.move(resource4, resource5); - assert.strictEqual(model.has(resource4), false); - assert.strictEqual(model.has(resource5), true); - }); - - test('resolve', async () => { - await promises.mkdir(dirname(fooBackupPath), { recursive: true }); - writeFileSync(fooBackupPath, 'foo'); - const model = new BackupFilesModel(service.fileService); - - await model.resolve(URI.file(workspaceBackupPath)); - assert.strictEqual(model.has(URI.file(fooBackupPath)), true); - }); - - test('get', () => { - const model = new BackupFilesModel(service.fileService); - - assert.deepStrictEqual(model.get(), []); - - const file1 = URI.file('/root/file/foo.html'); - const file2 = URI.file('/root/file/bar.html'); - const untitled = URI.file('/root/untitled/bar.html'); - - model.add(file1); - model.add(file2); - model.add(untitled); - - assert.deepStrictEqual(model.get().map(f => f.fsPath), [file1.fsPath, file2.fsPath, untitled.fsPath]); - }); - }); - - suite('Hash migration', () => { - - test('works', async () => { - - // Prepare backups of the old MD5 hash format - mkdirSync(join(workspaceBackupPath, fooFile.scheme), { recursive: true }); - mkdirSync(join(workspaceBackupPath, untitledFile.scheme), { recursive: true }); - mkdirSync(join(workspaceBackupPath, customFile.scheme), { recursive: true }); - writeFileSync(join(workspaceBackupPath, fooFile.scheme, '8a8589a2f1c9444b89add38166f50229'), `${fooFile.toString()}\ntest file`); - writeFileSync(join(workspaceBackupPath, untitledFile.scheme, '13264068d108c6901b3592ea654fcd57'), `${untitledFile.toString()}\ntest untitled`); - writeFileSync(join(workspaceBackupPath, customFile.scheme, 'bf018572af7b38746b502893bd0adf6c'), `${customFile.toString()}\ntest custom`); - - service.reinitialize(URI.file(workspaceBackupPath)); - - const backups = await service.getBackups(); - assert.strictEqual(backups.length, 3); - assert.ok(backups.some(backup => isEqual(backup, fooFile))); - assert.ok(backups.some(backup => isEqual(backup, untitledFile))); - assert.ok(backups.some(backup => isEqual(backup, customFile))); - - assert.strictEqual(readdirSync(join(workspaceBackupPath, fooFile.scheme)).length, 1); - assert.strictEqual(existsSync(fooBackupPath), true); - assert.strictEqual(readFileSync(fooBackupPath).toString(), `${fooFile.toString()}\ntest file`); - assert.ok(service.hasBackupSync(fooFile)); - - assert.strictEqual(readdirSync(join(workspaceBackupPath, untitledFile.scheme)).length, 1); - assert.strictEqual(existsSync(untitledBackupPath), true); - assert.strictEqual(readFileSync(untitledBackupPath).toString(), `${untitledFile.toString()}\ntest untitled`); - assert.ok(service.hasBackupSync(untitledFile)); - - assert.strictEqual(readdirSync(join(workspaceBackupPath, customFile.scheme)).length, 1); - assert.strictEqual(existsSync(customFileBackupPath), true); - assert.strictEqual(readFileSync(customFileBackupPath).toString(), `${customFile.toString()}\ntest custom`); - assert.ok(service.hasBackupSync(customFile)); - }); - }); -}); diff --git a/src/vs/workbench/services/clipboard/browser/clipboardService.ts b/src/vs/workbench/services/clipboard/browser/clipboardService.ts index 7475c744..7e040278 100644 --- a/src/vs/workbench/services/clipboard/browser/clipboardService.ts +++ b/src/vs/workbench/services/clipboard/browser/clipboardService.ts @@ -23,7 +23,7 @@ export class BrowserClipboardService extends BaseBrowserClipboardService { super(); } - async readText(type?: string): Promise { + override async readText(type?: string): Promise { if (type) { return super.readText(type); } diff --git a/src/vs/workbench/services/commands/test/common/commandService.test.ts b/src/vs/workbench/services/commands/test/common/commandService.test.ts index 32bd443f..e4e13a41 100644 --- a/src/vs/workbench/services/commands/test/common/commandService.test.ts +++ b/src/vs/workbench/services/commands/test/common/commandService.test.ts @@ -27,7 +27,7 @@ suite('CommandService', function () { let lastEvent: string; let service = new CommandService(new InstantiationService(), new class extends NullExtensionService { - activateByEvent(activationEvent: string): Promise { + override activateByEvent(activationEvent: string): Promise { lastEvent = activationEvent; return super.activateByEvent(activationEvent); } @@ -46,7 +46,7 @@ suite('CommandService', function () { test('fwd activation error', async function () { const extensionService = new class extends NullExtensionService { - activateByEvent(activationEvent: string): Promise { + override activateByEvent(activationEvent: string): Promise { return Promise.reject(new Error('bad_activate')); } }; @@ -56,7 +56,7 @@ suite('CommandService', function () { await extensionService.whenInstalledExtensionsRegistered(); return service.executeCommand('foo').then(() => assert.ok(false), err => { - assert.equal(err.message, 'bad_activate'); + assert.strictEqual(err.message, 'bad_activate'); }); }); @@ -66,13 +66,13 @@ suite('CommandService', function () { let reg = CommandsRegistry.registerCommand('bar', () => callCounter += 1); let service = new CommandService(new InstantiationService(), new class extends NullExtensionService { - whenInstalledExtensionsRegistered() { + override whenInstalledExtensionsRegistered() { return new Promise(_resolve => { /*ignore*/ }); } }, new NullLogService()); service.executeCommand('bar'); - assert.equal(callCounter, 1); + assert.strictEqual(callCounter, 1); reg.dispose(); }); @@ -83,20 +83,20 @@ suite('CommandService', function () { const whenInstalledExtensionsRegistered = new Promise(_resolve => { resolveFunc = _resolve; }); let service = new CommandService(new InstantiationService(), new class extends NullExtensionService { - whenInstalledExtensionsRegistered() { + override whenInstalledExtensionsRegistered() { return whenInstalledExtensionsRegistered; } }, new NullLogService()); let r = service.executeCommand('bar'); - assert.equal(callCounter, 0); + assert.strictEqual(callCounter, 0); let reg = CommandsRegistry.registerCommand('bar', () => callCounter += 1); resolveFunc!(true); return r.then(() => { reg.dispose(); - assert.equal(callCounter, 1); + assert.strictEqual(callCounter, 1); }); }); @@ -107,7 +107,7 @@ suite('CommandService', function () { let events: string[] = []; let service = new CommandService(new InstantiationService(), new class extends NullExtensionService { - activateByEvent(event: string): Promise { + override activateByEvent(event: string): Promise { events.push(event); if (event === '*') { return new Promise(() => { }); //forever promise... @@ -129,8 +129,8 @@ suite('CommandService', function () { }, new NullLogService()); return service.executeCommand('farboo').then(() => { - assert.equal(callCounter, 1); - assert.deepEqual(events.sort(), ['*', 'onCommand:farboo'].sort()); + assert.strictEqual(callCounter, 1); + assert.deepStrictEqual(events.sort(), ['*', 'onCommand:farboo'].sort()); }).finally(() => { disposable.dispose(); }); @@ -142,7 +142,7 @@ suite('CommandService', function () { const disposables = new DisposableStore(); let service = new CommandService(new InstantiationService(), new class extends NullExtensionService { - activateByEvent(event: string): Promise { + override activateByEvent(event: string): Promise { if (event === '*') { return new Promise(() => { }); //forever promise... } @@ -170,7 +170,7 @@ suite('CommandService', function () { }, new NullLogService()); return service.executeCommand('farboo2').then(() => { - assert.deepEqual(actualOrder, expectedOrder); + assert.deepStrictEqual(actualOrder, expectedOrder); }).finally(() => { disposables.dispose(); }); diff --git a/src/vs/workbench/services/configuration/browser/configuration.ts b/src/vs/workbench/services/configuration/browser/configuration.ts index d4ce3c91..e18e3052 100644 --- a/src/vs/workbench/services/configuration/browser/configuration.ts +++ b/src/vs/workbench/services/configuration/browser/configuration.ts @@ -9,7 +9,7 @@ import * as errors from 'vs/base/common/errors'; import { Disposable, IDisposable, dispose, toDisposable, MutableDisposable, combinedDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { RunOnceScheduler } from 'vs/base/common/async'; import { FileChangeType, FileChangesEvent, IFileService, whenProviderRegistered, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; -import { ConfigurationModel, ConfigurationModelParser, UserSettings } from 'vs/platform/configuration/common/configurationModels'; +import { ConfigurationModel, ConfigurationModelParser, ConfigurationParseOptions, UserSettings } from 'vs/platform/configuration/common/configurationModels'; import { WorkspaceConfigurationModelParser, StandaloneConfigurationModelParser } from 'vs/workbench/services/configuration/common/configurationModels'; import { TASKS_CONFIGURATION_KEY, FOLDER_SETTINGS_NAME, LAUNCH_CONFIGURATION_KEY, IConfigurationCache, ConfigurationKey, REMOTE_MACHINE_SCOPES, FOLDER_SCOPES, WORKSPACE_SCOPES } from 'vs/workbench/services/configuration/common/configuration'; import { IStoredWorkspaceFolder, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; @@ -32,17 +32,20 @@ export class UserConfiguration extends Disposable { private readonly userConfiguration: MutableDisposable = this._register(new MutableDisposable()); private readonly reloadConfigurationScheduler: RunOnceScheduler; + private readonly configurationParseOptions: ConfigurationParseOptions; + get hasTasksLoaded(): boolean { return this.userConfiguration.value instanceof FileServiceBasedConfiguration; } constructor( private readonly userSettingsResource: URI, - private readonly scopes: ConfigurationScope[] | undefined, + scopes: ConfigurationScope[] | undefined, private readonly fileService: IFileService, private readonly uriIdentityService: IUriIdentityService, private readonly logService: ILogService, ) { super(); - this.userConfiguration.value = new UserSettings(this.userSettingsResource, this.scopes, uriIdentityService.extUri, this.fileService); + this.configurationParseOptions = { scopes, skipRestricted: false }; + this.userConfiguration.value = new UserSettings(this.userSettingsResource, scopes, uriIdentityService.extUri, this.fileService); this._register(this.userConfiguration.value.onDidChange(() => this.reloadConfigurationScheduler.schedule())); this.reloadConfigurationScheduler = this._register(new RunOnceScheduler(() => this.reload().then(configurationModel => this._onDidChangeConfiguration.fire(configurationModel)), 50)); } @@ -58,7 +61,7 @@ export class UserConfiguration extends Disposable { const folder = this.uriIdentityService.extUri.dirname(this.userSettingsResource); const standAloneConfigurationResources: [string, URI][] = [TASKS_CONFIGURATION_KEY].map(name => ([name, this.uriIdentityService.extUri.joinPath(folder, `${name}.json`)])); - const fileServiceBasedConfiguration = new FileServiceBasedConfiguration(folder.toString(), this.userSettingsResource, standAloneConfigurationResources, this.scopes, this.fileService, this.uriIdentityService, this.logService); + const fileServiceBasedConfiguration = new FileServiceBasedConfiguration(folder.toString(), this.userSettingsResource, standAloneConfigurationResources, this.configurationParseOptions, this.fileService, this.uriIdentityService, this.logService); const configurationModel = await fileServiceBasedConfiguration.loadConfiguration(); this.userConfiguration.value = fileServiceBasedConfiguration; @@ -70,8 +73,12 @@ export class UserConfiguration extends Disposable { return configurationModel; } - reprocess(): ConfigurationModel { - return this.userConfiguration.value!.reprocess(); + reparse(): ConfigurationModel { + return this.userConfiguration.value!.reparse(this.configurationParseOptions); + } + + getRestrictedSettings(): string[] { + return this.userConfiguration.value!.getRestrictedSettings(); } } @@ -79,6 +86,7 @@ class FileServiceBasedConfiguration extends Disposable { private readonly allResources: URI[]; private _folderSettingsModelParser: ConfigurationModelParser; + private _folderSettingsParseOptions: ConfigurationParseOptions; private _standAloneConfigurations: ConfigurationModel[]; private _cache: ConfigurationModel; @@ -89,7 +97,7 @@ class FileServiceBasedConfiguration extends Disposable { name: string, private readonly settingsResource: URI, private readonly standAloneConfigurationResources: [string, URI][], - private readonly scopes: ConfigurationScope[] | undefined, + configurationParseOptions: ConfigurationParseOptions, private readonly fileService: IFileService, private readonly uriIdentityService: IUriIdentityService, private readonly logService: ILogService, @@ -102,7 +110,8 @@ class FileServiceBasedConfiguration extends Disposable { this.fileService.watch(resource) )))); - this._folderSettingsModelParser = new ConfigurationModelParser(name, this.scopes); + this._folderSettingsModelParser = new ConfigurationModelParser(name); + this._folderSettingsParseOptions = configurationParseOptions; this._standAloneConfigurations = []; this._cache = new ConfigurationModel(); @@ -144,17 +153,17 @@ class FileServiceBasedConfiguration extends Disposable { // reset this._standAloneConfigurations = []; - this._folderSettingsModelParser.parseContent(''); + this._folderSettingsModelParser.parse('', this._folderSettingsParseOptions); // parse if (settingsContent !== undefined) { - this._folderSettingsModelParser.parseContent(settingsContent); + this._folderSettingsModelParser.parse(settingsContent, this._folderSettingsParseOptions); } for (let index = 0; index < standAloneConfigurationContents.length; index++) { const contents = standAloneConfigurationContents[index][1]; if (contents !== undefined) { const standAloneConfigurationModelParser = new StandaloneConfigurationModelParser(this.standAloneConfigurationResources[index][1].toString(), this.standAloneConfigurationResources[index][0]); - standAloneConfigurationModelParser.parseContent(contents); + standAloneConfigurationModelParser.parse(contents); this._standAloneConfigurations.push(standAloneConfigurationModelParser.configurationModel); } } @@ -165,9 +174,14 @@ class FileServiceBasedConfiguration extends Disposable { return this._cache; } - reprocess(): ConfigurationModel { + getRestrictedSettings(): string[] { + return this._folderSettingsModelParser.restrictedConfigurations; + } + + reparse(configurationParseOptions: ConfigurationParseOptions): ConfigurationModel { const oldContents = this._folderSettingsModelParser.configurationModel.contents; - this._folderSettingsModelParser.parse(); + this._folderSettingsParseOptions = configurationParseOptions; + this._folderSettingsModelParser.reparse(this._folderSettingsParseOptions); if (!equals(oldContents, this._folderSettingsModelParser.configurationModel.contents)) { this.consolidate(); } @@ -214,10 +228,10 @@ export class RemoteUserConfiguration extends Disposable { ) { super(); this._fileService = fileService; - this._userConfiguration = this._cachedConfiguration = new CachedRemoteUserConfiguration(remoteAuthority, configurationCache, REMOTE_MACHINE_SCOPES); + this._userConfiguration = this._cachedConfiguration = new CachedRemoteUserConfiguration(remoteAuthority, configurationCache, { scopes: REMOTE_MACHINE_SCOPES }); remoteAgentService.getEnvironment().then(async environment => { if (environment) { - const userConfiguration = this._register(new FileServiceBasedRemoteUserConfiguration(environment.settingsPath, REMOTE_MACHINE_SCOPES, this._fileService, uriIdentityService)); + const userConfiguration = this._register(new FileServiceBasedRemoteUserConfiguration(environment.settingsPath, { scopes: REMOTE_MACHINE_SCOPES }, this._fileService, uriIdentityService)); this._register(userConfiguration.onDidChangeConfiguration(configurationModel => this.onDidUserConfigurationChange(configurationModel))); this._userConfigurationInitializationPromise = userConfiguration.initialize(); const configurationModel = await this._userConfigurationInitializationPromise; @@ -249,8 +263,12 @@ export class RemoteUserConfiguration extends Disposable { return this._userConfiguration.reload(); } - reprocess(): ConfigurationModel { - return this._userConfiguration.reprocess(); + reparse(): ConfigurationModel { + return this._userConfiguration.reparse({ scopes: REMOTE_MACHINE_SCOPES }); + } + + getRestrictedSettings(): string[] { + return this._userConfiguration.getRestrictedSettings(); } private onDidUserConfigurationChange(configurationModel: ConfigurationModel): void { @@ -277,6 +295,7 @@ export class RemoteUserConfiguration extends Disposable { class FileServiceBasedRemoteUserConfiguration extends Disposable { private readonly parser: ConfigurationModelParser; + private parseOptions: ConfigurationParseOptions; private readonly reloadConfigurationScheduler: RunOnceScheduler; protected readonly _onDidChangeConfiguration: Emitter = this._register(new Emitter()); readonly onDidChangeConfiguration: Event = this._onDidChangeConfiguration.event; @@ -286,13 +305,14 @@ class FileServiceBasedRemoteUserConfiguration extends Disposable { constructor( private readonly configurationResource: URI, - private readonly scopes: ConfigurationScope[] | undefined, + configurationParseOptions: ConfigurationParseOptions, private readonly fileService: IFileService, private readonly uriIdentityService: IUriIdentityService, ) { super(); - this.parser = new ConfigurationModelParser(this.configurationResource.toString(), this.scopes); + this.parser = new ConfigurationModelParser(this.configurationResource.toString()); + this.parseOptions = configurationParseOptions; this._register(fileService.onDidFilesChange(e => this.handleFileEvents(e))); this.reloadConfigurationScheduler = this._register(new RunOnceScheduler(() => this.reload().then(configurationModel => this._onDidChangeConfiguration.fire(configurationModel)), 50)); this._register(toDisposable(() => { @@ -334,18 +354,23 @@ class FileServiceBasedRemoteUserConfiguration extends Disposable { async reload(): Promise { try { const content = await this.resolveContent(); - this.parser.parseContent(content); + this.parser.parse(content, this.parseOptions); return this.parser.configurationModel; } catch (e) { return new ConfigurationModel(); } } - reprocess(): ConfigurationModel { - this.parser.parse(); + reparse(configurationParseOptions: ConfigurationParseOptions): ConfigurationModel { + this.parseOptions = configurationParseOptions; + this.parser.reparse(this.parseOptions); return this.parser.configurationModel; } + getRestrictedSettings(): string[] { + return this.parser.restrictedConfigurations; + } + private async handleFileEvents(event: FileChangesEvent): Promise { // Find changes that affect the resource @@ -381,16 +406,18 @@ class CachedRemoteUserConfiguration extends Disposable { private readonly key: ConfigurationKey; private readonly parser: ConfigurationModelParser; + private parseOptions: ConfigurationParseOptions; private configurationModel: ConfigurationModel; constructor( remoteAuthority: string, private readonly configurationCache: IConfigurationCache, - scopes: ConfigurationScope[], + configurationParseOptions: ConfigurationParseOptions, ) { super(); this.key = { type: 'user', key: remoteAuthority }; - this.parser = new ConfigurationModelParser('CachedRemoteUserConfiguration', scopes); + this.parser = new ConfigurationModelParser('CachedRemoteUserConfiguration'); + this.parseOptions = configurationParseOptions; this.configurationModel = new ConfigurationModel(); } @@ -402,18 +429,23 @@ class CachedRemoteUserConfiguration extends Disposable { return this.reload(); } - reprocess(): ConfigurationModel { - this.parser.parse(); + reparse(configurationParseOptions: ConfigurationParseOptions): ConfigurationModel { + this.parseOptions = configurationParseOptions; + this.parser.reparse(this.parseOptions); this.configurationModel = this.parser.configurationModel; return this.configurationModel; } + getRestrictedSettings(): string[] { + return this.parser.restrictedConfigurations; + } + async reload(): Promise { try { const content = await this.configurationCache.read(this.key); const parsed: { content: string } = JSON.parse(content); if (parsed.content) { - this.parser.parseContent(parsed.content); + this.parser.parse(parsed.content, this.parseOptions); this.configurationModel = this.parser.configurationModel; } } catch (e) { /* Ignore error */ } @@ -436,9 +468,10 @@ export class WorkspaceConfiguration extends Disposable { private _workspaceConfiguration: CachedWorkspaceConfiguration | FileServiceBasedWorkspaceConfiguration; private _workspaceConfigurationDisposables = this._register(new DisposableStore()); private _workspaceIdentifier: IWorkspaceIdentifier | null = null; + private _isWorkspaceTrusted: boolean = false; - private readonly _onDidUpdateConfiguration: Emitter = this._register(new Emitter()); - public readonly onDidUpdateConfiguration: Event = this._onDidUpdateConfiguration.event; + private readonly _onDidUpdateConfiguration = this._register(new Emitter()); + public readonly onDidUpdateConfiguration = this._onDidUpdateConfiguration.event; private _initialized: boolean = false; get initialized(): boolean { return this._initialized; } @@ -451,8 +484,9 @@ export class WorkspaceConfiguration extends Disposable { this._workspaceConfiguration = this._cachedConfiguration = new CachedWorkspaceConfiguration(configurationCache); } - async initialize(workspaceIdentifier: IWorkspaceIdentifier): Promise { + async initialize(workspaceIdentifier: IWorkspaceIdentifier, workspaceTrusted: boolean): Promise { this._workspaceIdentifier = workspaceIdentifier; + this._isWorkspaceTrusted = workspaceTrusted; if (!this._initialized) { if (this.configurationCache.needsCaching(this._workspaceIdentifier.configPath)) { this._workspaceConfiguration = this._cachedConfiguration; @@ -466,7 +500,7 @@ export class WorkspaceConfiguration extends Disposable { async reload(): Promise { if (this._workspaceIdentifier) { - await this._workspaceConfiguration.load(this._workspaceIdentifier); + await this._workspaceConfiguration.load(this._workspaceIdentifier, { scopes: WORKSPACE_SCOPES, skipRestricted: this.isUntrusted() }); } } @@ -486,34 +520,47 @@ export class WorkspaceConfiguration extends Disposable { return this._workspaceConfiguration.getWorkspaceSettings(); } - reprocessWorkspaceSettings(): ConfigurationModel { - this._workspaceConfiguration.reprocessWorkspaceSettings(); + updateWorkspaceTrust(trusted: boolean): ConfigurationModel { + this._isWorkspaceTrusted = trusted; + return this.reparseWorkspaceSettings(); + } + + reparseWorkspaceSettings(): ConfigurationModel { + this._workspaceConfiguration.reparseWorkspaceSettings({ scopes: WORKSPACE_SCOPES, skipRestricted: this.isUntrusted() }); return this.getConfiguration(); } + getRestrictedSettings(): string[] { + return this._workspaceConfiguration.getRestrictedSettings(); + } + private async waitAndInitialize(workspaceIdentifier: IWorkspaceIdentifier): Promise { await whenProviderRegistered(workspaceIdentifier.configPath, this._fileService); if (!(this._workspaceConfiguration instanceof FileServiceBasedWorkspaceConfiguration)) { const fileServiceBasedWorkspaceConfiguration = this._register(new FileServiceBasedWorkspaceConfiguration(this._fileService)); - await fileServiceBasedWorkspaceConfiguration.load(workspaceIdentifier); + await fileServiceBasedWorkspaceConfiguration.load(workspaceIdentifier, { scopes: WORKSPACE_SCOPES, skipRestricted: this.isUntrusted() }); this.doInitialize(fileServiceBasedWorkspaceConfiguration); - this.onDidWorkspaceConfigurationChange(false); + this.onDidWorkspaceConfigurationChange(false, true); } } private doInitialize(fileServiceBasedWorkspaceConfiguration: FileServiceBasedWorkspaceConfiguration): void { this._workspaceConfigurationDisposables.clear(); this._workspaceConfiguration = this._workspaceConfigurationDisposables.add(fileServiceBasedWorkspaceConfiguration); - this._workspaceConfigurationDisposables.add(this._workspaceConfiguration.onDidChange(e => this.onDidWorkspaceConfigurationChange(true))); + this._workspaceConfigurationDisposables.add(this._workspaceConfiguration.onDidChange(e => this.onDidWorkspaceConfigurationChange(true, false))); this._initialized = true; } - private async onDidWorkspaceConfigurationChange(reload: boolean): Promise { + private isUntrusted(): boolean { + return !this._isWorkspaceTrusted; + } + + private async onDidWorkspaceConfigurationChange(reload: boolean, fromCache: boolean): Promise { if (reload) { await this.reload(); } this.updateCache(); - this._onDidUpdateConfiguration.fire(); + this._onDidUpdateConfiguration.fire(fromCache); } private async updateCache(): Promise { @@ -555,7 +602,7 @@ class FileServiceBasedWorkspaceConfiguration extends Disposable { return content.value.toString(); } - async load(workspaceIdentifier: IWorkspaceIdentifier): Promise { + async load(workspaceIdentifier: IWorkspaceIdentifier, configurationParseOptions: ConfigurationParseOptions): Promise { if (!this._workspaceIdentifier || this._workspaceIdentifier.id !== workspaceIdentifier.id) { this._workspaceIdentifier = workspaceIdentifier; this.workspaceConfigurationModelParser = new WorkspaceConfigurationModelParser(this._workspaceIdentifier.id); @@ -571,7 +618,7 @@ class FileServiceBasedWorkspaceConfiguration extends Disposable { errors.onUnexpectedError(error); } } - this.workspaceConfigurationModelParser.parseContent(contents); + this.workspaceConfigurationModelParser.parse(contents, configurationParseOptions); this.consolidate(); } @@ -587,12 +634,16 @@ class FileServiceBasedWorkspaceConfiguration extends Disposable { return this.workspaceSettings; } - reprocessWorkspaceSettings(): ConfigurationModel { - this.workspaceConfigurationModelParser.reprocessWorkspaceSettings(); + reparseWorkspaceSettings(configurationParseOptions: ConfigurationParseOptions): ConfigurationModel { + this.workspaceConfigurationModelParser.reparseWorkspaceSettings(configurationParseOptions); this.consolidate(); return this.getWorkspaceSettings(); } + getRestrictedSettings(): string[] { + return this.workspaceConfigurationModelParser.getRestrictedWorkspaceSettings(); + } + private consolidate(): void { this.workspaceSettings = this.workspaceConfigurationModelParser.settingsModel.merge(this.workspaceConfigurationModelParser.launchModel, this.workspaceConfigurationModelParser.tasksModel); } @@ -624,14 +675,14 @@ class CachedWorkspaceConfiguration { this.workspaceSettings = new ConfigurationModel(); } - async load(workspaceIdentifier: IWorkspaceIdentifier): Promise { + async load(workspaceIdentifier: IWorkspaceIdentifier, configurationParseOptions: ConfigurationParseOptions): Promise { try { const key = this.getKey(workspaceIdentifier); const contents = await this.configurationCache.read(key); const parsed: { content: string } = JSON.parse(contents); if (parsed.content) { this.workspaceConfigurationModelParser = new WorkspaceConfigurationModelParser(key.key); - this.workspaceConfigurationModelParser.parseContent(parsed.content); + this.workspaceConfigurationModelParser.parse(parsed.content, configurationParseOptions); this.consolidate(); } } catch (e) { @@ -654,12 +705,16 @@ class CachedWorkspaceConfiguration { return this.workspaceSettings; } - reprocessWorkspaceSettings(): ConfigurationModel { - this.workspaceConfigurationModelParser.reprocessWorkspaceSettings(); + reparseWorkspaceSettings(configurationParseOptions: ConfigurationParseOptions): ConfigurationModel { + this.workspaceConfigurationModelParser.reparseWorkspaceSettings(configurationParseOptions); this.consolidate(); return this.getWorkspaceSettings(); } + getRestrictedSettings(): string[] { + return this.workspaceConfigurationModelParser.getRestrictedWorkspaceSettings(); + } + private consolidate(): void { this.workspaceSettings = this.workspaceConfigurationModelParser.settingsModel.merge(this.workspaceConfigurationModelParser.launchModel, this.workspaceConfigurationModelParser.tasksModel); } @@ -689,6 +744,7 @@ class CachedFolderConfiguration { readonly onDidChange = Event.None; private _folderSettingsModelParser: ConfigurationModelParser; + private _folderSettingsParseOptions: ConfigurationParseOptions; private _standAloneConfigurations: ConfigurationModel[]; private configurationModel: ConfigurationModel; private readonly key: ConfigurationKey; @@ -696,11 +752,12 @@ class CachedFolderConfiguration { constructor( folder: URI, configFolderRelativePath: string, + configurationParseOptions: ConfigurationParseOptions, private readonly configurationCache: IConfigurationCache, - scopes: ConfigurationScope[], ) { this.key = { type: 'folder', key: hash(join(folder.path, configFolderRelativePath)).toString(16) }; - this._folderSettingsModelParser = new ConfigurationModelParser('CachedFolderConfiguration', scopes); + this._folderSettingsModelParser = new ConfigurationModelParser('CachedFolderConfiguration'); + this._folderSettingsParseOptions = configurationParseOptions; this._standAloneConfigurations = []; this.configurationModel = new ConfigurationModel(); } @@ -712,10 +769,10 @@ class CachedFolderConfiguration { if (configurationContents) { for (const key of Object.keys(configurationContents)) { if (key === FOLDER_SETTINGS_NAME) { - this._folderSettingsModelParser.parseContent(configurationContents[key]); + this._folderSettingsModelParser.parse(configurationContents[key], this._folderSettingsParseOptions); } else { const standAloneConfigurationModelParser = new StandaloneConfigurationModelParser(key, key); - standAloneConfigurationModelParser.parseContent(configurationContents[key]); + standAloneConfigurationModelParser.parse(configurationContents[key]); this._standAloneConfigurations.push(standAloneConfigurationModelParser.configurationModel); } } @@ -743,8 +800,13 @@ class CachedFolderConfiguration { } } - reprocess(): ConfigurationModel { - this._folderSettingsModelParser.parse(); + getRestrictedSettings(): string[] { + return this._folderSettingsModelParser.restrictedConfigurations; + } + + reparse(configurationParseOptions: ConfigurationParseOptions): ConfigurationModel { + this._folderSettingsParseOptions = configurationParseOptions; + this._folderSettingsModelParser.reparse(this._folderSettingsParseOptions); this.consolidate(); return this.configurationModel; } @@ -764,6 +826,7 @@ export class FolderConfiguration extends Disposable { readonly onDidChange: Event = this._onDidChange.event; private folderConfiguration: CachedFolderConfiguration | FileServiceBasedConfiguration; + private readonly scopes: ConfigurationScope[]; private readonly configurationFolder: URI; private cachedFolderConfiguration: CachedFolderConfiguration; @@ -771,6 +834,7 @@ export class FolderConfiguration extends Disposable { readonly workspaceFolder: IWorkspaceFolder, configFolderRelativePath: string, private readonly workbenchState: WorkbenchState, + private workspaceTrusted: boolean, fileService: IFileService, uriIdentityService: IUriIdentityService, logService: ILogService, @@ -778,19 +842,19 @@ export class FolderConfiguration extends Disposable { ) { super(); - const scopes = WorkbenchState.WORKSPACE === this.workbenchState ? FOLDER_SCOPES : WORKSPACE_SCOPES; + this.scopes = WorkbenchState.WORKSPACE === this.workbenchState ? FOLDER_SCOPES : WORKSPACE_SCOPES; this.configurationFolder = uriIdentityService.extUri.joinPath(workspaceFolder.uri, configFolderRelativePath); - this.cachedFolderConfiguration = new CachedFolderConfiguration(workspaceFolder.uri, configFolderRelativePath, configurationCache, scopes); + this.cachedFolderConfiguration = new CachedFolderConfiguration(workspaceFolder.uri, configFolderRelativePath, { scopes: this.scopes, skipRestricted: this.isUntrusted() }, configurationCache); if (this.configurationCache.needsCaching(workspaceFolder.uri)) { this.folderConfiguration = this.cachedFolderConfiguration; whenProviderRegistered(workspaceFolder.uri, fileService) .then(() => { - this.folderConfiguration = this._register(this.createFileServiceBasedConfiguration(scopes, fileService, uriIdentityService, logService)); + this.folderConfiguration = this._register(this.createFileServiceBasedConfiguration(fileService, uriIdentityService, logService)); this._register(this.folderConfiguration.onDidChange(e => this.onDidFolderConfigurationChange())); this.onDidFolderConfigurationChange(); }); } else { - this.folderConfiguration = this._register(this.createFileServiceBasedConfiguration(scopes, fileService, uriIdentityService, logService)); + this.folderConfiguration = this._register(this.createFileServiceBasedConfiguration(fileService, uriIdentityService, logService)); this._register(this.folderConfiguration.onDidChange(e => this.onDidFolderConfigurationChange())); } } @@ -799,21 +863,34 @@ export class FolderConfiguration extends Disposable { return this.folderConfiguration.loadConfiguration(); } - reprocess(): ConfigurationModel { - const configurationModel = this.folderConfiguration.reprocess(); + updateWorkspaceTrust(trusted: boolean): ConfigurationModel { + this.workspaceTrusted = trusted; + return this.reparse(); + } + + reparse(): ConfigurationModel { + const configurationModel = this.folderConfiguration.reparse({ scopes: this.scopes, skipRestricted: this.isUntrusted() }); this.updateCache(); return configurationModel; } + getRestrictedSettings(): string[] { + return this.folderConfiguration.getRestrictedSettings(); + } + + private isUntrusted(): boolean { + return !this.workspaceTrusted; + } + private onDidFolderConfigurationChange(): void { this.updateCache(); this._onDidChange.fire(); } - private createFileServiceBasedConfiguration(scopes: ConfigurationScope[], fileService: IFileService, uriIdentityService: IUriIdentityService, logService: ILogService) { + private createFileServiceBasedConfiguration(fileService: IFileService, uriIdentityService: IUriIdentityService, logService: ILogService) { const settingsResource = uriIdentityService.extUri.joinPath(this.configurationFolder, `${FOLDER_SETTINGS_NAME}.json`); const standAloneConfigurationResources: [string, URI][] = [TASKS_CONFIGURATION_KEY, LAUNCH_CONFIGURATION_KEY].map(name => ([name, uriIdentityService.extUri.joinPath(this.configurationFolder, `${name}.json`)])); - return new FileServiceBasedConfiguration(this.configurationFolder.toString(), settingsResource, standAloneConfigurationResources, scopes, fileService, uriIdentityService, logService); + return new FileServiceBasedConfiguration(this.configurationFolder.toString(), settingsResource, standAloneConfigurationResources, { scopes: this.scopes, skipRestricted: this.isUntrusted() }, fileService, uriIdentityService, logService); } private async updateCache(): Promise { diff --git a/src/vs/workbench/services/configuration/browser/configurationCache.ts b/src/vs/workbench/services/configuration/browser/configurationCache.ts index a05ffc4d..394e2bdb 100644 --- a/src/vs/workbench/services/configuration/browser/configurationCache.ts +++ b/src/vs/workbench/services/configuration/browser/configurationCache.ts @@ -11,7 +11,7 @@ export class ConfigurationCache implements IConfigurationCache { needsCaching(resource: URI): boolean { // Cache all non user data resources - return resource.scheme !== Schemas.userData; + return ![Schemas.file, Schemas.userData, Schemas.tmp].includes(resource.scheme); } async read(key: ConfigurationKey): Promise { diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index cd98feb3..43f2efcf 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -10,13 +10,13 @@ import { equals } from 'vs/base/common/objects'; import { Disposable } from 'vs/base/common/lifecycle'; import { Queue, Barrier, runWhenIdle, Promises } from 'vs/base/common/async'; import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; -import { IWorkspaceContextService, Workspace as BaseWorkspace, WorkbenchState, IWorkspaceFolder, IWorkspaceFoldersChangeEvent, WorkspaceFolder, toWorkspaceFolder, isWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; +import { IWorkspaceContextService, Workspace as BaseWorkspace, WorkbenchState, IWorkspaceFolder, IWorkspaceFoldersChangeEvent, WorkspaceFolder, toWorkspaceFolder, isWorkspaceFolder, IWorkspaceFoldersWillChangeEvent } from 'vs/platform/workspace/common/workspace'; import { ConfigurationModel, DefaultConfigurationModel, ConfigurationChangeEvent, AllKeysConfigurationChangeEvent, mergeChanges } from 'vs/platform/configuration/common/configurationModels'; import { IConfigurationChangeEvent, ConfigurationTarget, IConfigurationOverrides, keyFromOverrideIdentifier, isConfigurationOverrides, IConfigurationData, IConfigurationValue, IConfigurationChange, ConfigurationTargetToString } from 'vs/platform/configuration/common/configuration'; import { Configuration } from 'vs/workbench/services/configuration/common/configurationModels'; -import { FOLDER_CONFIG_FOLDER_NAME, defaultSettingsSchemaId, userSettingsSchemaId, workspaceSettingsSchemaId, folderSettingsSchemaId, IConfigurationCache, machineSettingsSchemaId, LOCAL_MACHINE_SCOPES, IWorkbenchConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; +import { FOLDER_CONFIG_FOLDER_NAME, defaultSettingsSchemaId, userSettingsSchemaId, workspaceSettingsSchemaId, folderSettingsSchemaId, IConfigurationCache, machineSettingsSchemaId, LOCAL_MACHINE_SCOPES, IWorkbenchConfigurationService, RestrictedSettings } from 'vs/workbench/services/configuration/common/configuration'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IConfigurationRegistry, Extensions, allSettings, windowSettings, resourceSettings, applicationSettings, machineSettings, machineOverridableSettings, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; +import { IConfigurationRegistry, Extensions, allSettings, windowSettings, resourceSettings, applicationSettings, machineSettings, machineOverridableSettings, ConfigurationScope, IConfigurationPropertySchema } from 'vs/platform/configuration/common/configurationRegistry'; import { IWorkspaceIdentifier, isWorkspaceIdentifier, IStoredWorkspaceFolder, isStoredWorkspaceFolder, IWorkspaceFolderCreationData, IWorkspaceInitializationPayload, IEmptyWorkspaceIdentifier, useSlashForPath, getStoredWorkspaceFolder, isSingleFolderWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, toWorkspaceFolders } from 'vs/platform/workspaces/common/workspaces'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ConfigurationEditingService, EditableConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditingService'; @@ -32,6 +32,9 @@ import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle import { ILogService } from 'vs/platform/log/common/log'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; +import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust'; +import { delta, distinct } from 'vs/base/common/arrays'; +import { forEach, IStringDictionary } from 'vs/base/common/collections'; class Workspace extends BaseWorkspace { initialized: boolean = false; @@ -58,18 +61,28 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat private readonly fileService: IFileService; private readonly uriIdentityService: IUriIdentityService; - protected readonly _onDidChangeConfiguration: Emitter = this._register(new Emitter()); + private readonly _onDidChangeConfiguration: Emitter = this._register(new Emitter()); public readonly onDidChangeConfiguration: Event = this._onDidChangeConfiguration.event; - protected readonly _onDidChangeWorkspaceFolders: Emitter = this._register(new Emitter()); + protected readonly _onWillChangeWorkspaceFolders: Emitter = this._register(new Emitter()); + public readonly onWillChangeWorkspaceFolders: Event = this._onWillChangeWorkspaceFolders.event; + + private readonly _onDidChangeWorkspaceFolders: Emitter = this._register(new Emitter()); public readonly onDidChangeWorkspaceFolders: Event = this._onDidChangeWorkspaceFolders.event; - protected readonly _onDidChangeWorkspaceName: Emitter = this._register(new Emitter()); + private readonly _onDidChangeWorkspaceName: Emitter = this._register(new Emitter()); public readonly onDidChangeWorkspaceName: Event = this._onDidChangeWorkspaceName.event; - protected readonly _onDidChangeWorkbenchState: Emitter = this._register(new Emitter()); + private readonly _onDidChangeWorkbenchState: Emitter = this._register(new Emitter()); public readonly onDidChangeWorkbenchState: Event = this._onDidChangeWorkbenchState.event; + private isWorkspaceTrusted: boolean = true; + + private _restrictedSettings: RestrictedSettings = { default: [] }; + get restrictedSettings() { return this._restrictedSettings; } + private readonly _onDidChangeRestrictedSettings = this._register(new Emitter()); + public readonly onDidChangeRestrictedSettings = this._onDidChangeRestrictedSettings.event; + private readonly configurationRegistry: IConfigurationRegistry; // TODO@sandeep debt with cyclic dependencies @@ -118,10 +131,10 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat } this.workspaceConfiguration = this._register(new WorkspaceConfiguration(configurationCache, fileService)); - this._register(this.workspaceConfiguration.onDidUpdateConfiguration(() => { - this.onWorkspaceConfigurationChanged().then(() => { + this._register(this.workspaceConfiguration.onDidUpdateConfiguration(fromCache => { + this.onWorkspaceConfigurationChanged(fromCache).then(() => { this.workspace.initialized = this.workspaceConfiguration.initialized; - this.checkAndMarkWorkspaceComplete(); + this.checkAndMarkWorkspaceComplete(fromCache); }); })); @@ -132,8 +145,9 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat // Workspace Context Service Impl - public getCompleteWorkspace(): Promise { - return this.completeWorkspaceBarrier.wait().then(() => this.getWorkspace()); + public async getCompleteWorkspace(): Promise { + await this.completeWorkspaceBarrier.wait(); + return this.getWorkspace(); } public getWorkspace(): Workspace { @@ -167,10 +181,9 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat return this.updateFolders([], foldersToRemove); } - public updateFolders(foldersToAdd: IWorkspaceFolderCreationData[], foldersToRemove: URI[], index?: number): Promise { - return this.cyclicDependency.then(() => { - return this.workspaceEditingQueue.queue(() => this.doUpdateFolders(foldersToAdd, foldersToRemove, index)); - }); + public async updateFolders(foldersToAdd: IWorkspaceFolderCreationData[], foldersToRemove: URI[], index?: number): Promise { + await this.cyclicDependency; + return this.workspaceEditingQueue.queue(() => this.doUpdateFolders(foldersToAdd, foldersToRemove, index)); } public isInsideWorkspace(resource: URI): boolean { @@ -265,11 +278,10 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat return Promise.resolve(undefined); } - private setFolders(folders: IStoredWorkspaceFolder[]): Promise { - return this.cyclicDependency.then(() => { - return this.workspaceConfiguration.setFolders(folders, this.jsonEditingService) - .then(() => this.onWorkspaceConfigurationChanged()); - }); + private async setFolders(folders: IStoredWorkspaceFolder[]): Promise { + await this.cyclicDependency; + await this.workspaceConfiguration.setFolders(folders, this.jsonEditingService); + return this.onWorkspaceConfigurationChanged(false); } private contains(resources: URI[], toCheck: URI): boolean { @@ -385,11 +397,54 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat const workspace = await this.createWorkspace(arg); await this.updateWorkspaceAndInitializeConfiguration(workspace); - this.checkAndMarkWorkspaceComplete(); + this.checkAndMarkWorkspaceComplete(false); mark('code/didInitWorkspaceService'); } + updateWorkspaceTrust(trusted: boolean): void { + if (this.isWorkspaceTrusted !== trusted) { + this.isWorkspaceTrusted = trusted; + const data = this._configuration.toData(); + const folderConfigurationModels: (ConfigurationModel | undefined)[] = []; + for (const folder of this.workspace.folders) { + const folderConfiguration = this.cachedFolderConfigs.get(folder.uri); + let configurationModel: ConfigurationModel | undefined; + if (folderConfiguration) { + configurationModel = folderConfiguration.updateWorkspaceTrust(this.isWorkspaceTrusted); + this._configuration.updateFolderConfiguration(folder.uri, configurationModel); + } + folderConfigurationModels.push(configurationModel); + } + if (this.getWorkbenchState() === WorkbenchState.FOLDER) { + if (folderConfigurationModels[0]) { + this._configuration.updateWorkspaceConfiguration(folderConfigurationModels[0]); + } + } else { + this._configuration.updateWorkspaceConfiguration(this.workspaceConfiguration.updateWorkspaceTrust(this.isWorkspaceTrusted)); + } + this.updateRestrictedSettings(); + + let keys: string[] = []; + if (this.restrictedSettings.userLocal) { + keys.push(...this.restrictedSettings.userLocal); + } + if (this.restrictedSettings.userRemote) { + keys.push(...this.restrictedSettings.userRemote); + } + if (this.restrictedSettings.workspace) { + keys.push(...this.restrictedSettings.workspace); + } + if (this.restrictedSettings.workspaceFolder) { + this.restrictedSettings.workspaceFolder.forEach((value) => keys.push(...value)); + } + keys = distinct(keys); + if (keys.length) { + this.triggerConfigurationChange({ keys, overrides: [] }, { data, workspace: this.workspace }, ConfigurationTarget.WORKSPACE); + } + } + } + acquireInstantiationService(instantiationService: IInstantiationService): void { this.configurationEditingService = instantiationService.createInstance(ConfigurationEditingService); this.jsonEditingService = instantiationService.createInstance(JSONEditingService); @@ -401,7 +456,7 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat } } - private createWorkspace(arg: IWorkspaceInitializationPayload): Promise { + private async createWorkspace(arg: IWorkspaceInitializationPayload): Promise { if (isWorkspaceIdentifier(arg)) { return this.createMultiFolderWorkspace(arg); } @@ -413,22 +468,20 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat return this.createEmptyWorkspace(arg); } - private createMultiFolderWorkspace(workspaceIdentifier: IWorkspaceIdentifier): Promise { - return this.workspaceConfiguration.initialize({ id: workspaceIdentifier.id, configPath: workspaceIdentifier.configPath }) - .then(() => { - const workspaceConfigPath = workspaceIdentifier.configPath; - const workspaceFolders = toWorkspaceFolders(this.workspaceConfiguration.getFolders(), workspaceConfigPath, this.uriIdentityService.extUri); - const workspaceId = workspaceIdentifier.id; - const workspace = new Workspace(workspaceId, workspaceFolders, workspaceConfigPath, uri => this.uriIdentityService.extUri.ignorePathCasing(uri)); - workspace.initialized = this.workspaceConfiguration.initialized; - return workspace; - }); + private async createMultiFolderWorkspace(workspaceIdentifier: IWorkspaceIdentifier): Promise { + await this.workspaceConfiguration.initialize({ id: workspaceIdentifier.id, configPath: workspaceIdentifier.configPath }, this.isWorkspaceTrusted); + const workspaceConfigPath = workspaceIdentifier.configPath; + const workspaceFolders = toWorkspaceFolders(this.workspaceConfiguration.getFolders(), workspaceConfigPath, this.uriIdentityService.extUri); + const workspaceId = workspaceIdentifier.id; + const workspace = new Workspace(workspaceId, workspaceFolders, workspaceConfigPath, uri => this.uriIdentityService.extUri.ignorePathCasing(uri)); + workspace.initialized = this.workspaceConfiguration.initialized; + return workspace; } - private createSingleFolderWorkspace(singleFolderWorkspaceIdentifier: ISingleFolderWorkspaceIdentifier): Promise { + private createSingleFolderWorkspace(singleFolderWorkspaceIdentifier: ISingleFolderWorkspaceIdentifier): Workspace { const workspace = new Workspace(singleFolderWorkspaceIdentifier.id, [toWorkspaceFolder(singleFolderWorkspaceIdentifier.uri)], null, uri => this.uriIdentityService.extUri.ignorePathCasing(uri)); workspace.initialized = true; - return Promise.resolve(workspace); + return workspace; } private createEmptyWorkspace(emptyWorkspaceIdentifier: IEmptyWorkspaceIdentifier): Promise { @@ -437,18 +490,18 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat return Promise.resolve(workspace); } - private checkAndMarkWorkspaceComplete(): void { + private checkAndMarkWorkspaceComplete(fromCache: boolean): void { if (!this.completeWorkspaceBarrier.isOpen() && this.workspace.initialized) { this.completeWorkspaceBarrier.open(); - this.validateWorkspaceFoldersAndReload(); + this.validateWorkspaceFoldersAndReload(fromCache); } } - private updateWorkspaceAndInitializeConfiguration(workspace: Workspace): Promise { + private async updateWorkspaceAndInitializeConfiguration(workspace: Workspace): Promise { const hasWorkspaceBefore = !!this.workspace; - let previousState: WorkbenchState; + let previousState: WorkbenchState | undefined; let previousWorkspacePath: string | undefined; - let previousFolders: WorkspaceFolder[]; + let previousFolders: WorkspaceFolder[] = []; if (hasWorkspaceBefore) { previousState = this.getWorkbenchState(); @@ -459,32 +512,31 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat this.workspace = workspace; } - return this.initializeConfiguration().then(() => { - - // Trigger changes after configuration initialization so that configuration is up to date. - if (hasWorkspaceBefore) { - const newState = this.getWorkbenchState(); - if (previousState && newState !== previousState) { - this._onDidChangeWorkbenchState.fire(newState); - } - - const newWorkspacePath = this.workspace.configuration ? this.workspace.configuration.fsPath : undefined; - if (previousWorkspacePath && newWorkspacePath !== previousWorkspacePath || newState !== previousState) { - this._onDidChangeWorkspaceName.fire(); - } - - const folderChanges = this.compareFolders(previousFolders, this.workspace.folders); - if (folderChanges && (folderChanges.added.length || folderChanges.removed.length || folderChanges.changed.length)) { - this._onDidChangeWorkspaceFolders.fire(folderChanges); - } + await this.initializeConfiguration(); + // Trigger changes after configuration initialization so that configuration is up to date. + if (hasWorkspaceBefore) { + const newState = this.getWorkbenchState(); + if (previousState && newState !== previousState) { + this._onDidChangeWorkbenchState.fire(newState); } - if (!this.localUserConfiguration.hasTasksLoaded) { - // Reload local user configuration again to load user tasks - this._register(runWhenIdle(() => this.reloadLocalUserConfiguration(), 5000)); + const newWorkspacePath = this.workspace.configuration ? this.workspace.configuration.fsPath : undefined; + if (previousWorkspacePath && newWorkspacePath !== previousWorkspacePath || newState !== previousState) { + this._onDidChangeWorkspaceName.fire(); } - }); + + const folderChanges = this.compareFolders(previousFolders, this.workspace.folders); + if (folderChanges && (folderChanges.added.length || folderChanges.removed.length || folderChanges.changed.length)) { + await this.handleWillChangeWorkspaceFolders(folderChanges, false); + this._onDidChangeWorkspaceFolders.fire(folderChanges); + } + } + + if (!this.localUserConfiguration.hasTasksLoaded) { + // Reload local user configuration again to load user tasks + this._register(runWhenIdle(() => this.reloadLocalUserConfiguration(), 5000)); + } } private compareFolders(currentFolders: IWorkspaceFolder[], newFolders: IWorkspaceFolder[]): IWorkspaceFoldersChangeEvent { @@ -505,18 +557,19 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat return result; } - private initializeConfiguration(): Promise { - return this.initializeUserConfiguration() - .then(({ local, remote }) => this.loadConfiguration(local, remote)); + private async initializeConfiguration(): Promise { + const { local, remote } = await this.initializeUserConfiguration(); + await this.loadConfiguration(local, remote); } - private initializeUserConfiguration(): Promise<{ local: ConfigurationModel, remote: ConfigurationModel }> { - return Promise.all([this.localUserConfiguration.initialize(), this.remoteUserConfiguration ? this.remoteUserConfiguration.initialize() : Promise.resolve(new ConfigurationModel())]) - .then(([local, remote]) => ({ local, remote })); + private async initializeUserConfiguration(): Promise<{ local: ConfigurationModel, remote: ConfigurationModel }> { + const [local, remote] = await Promise.all([this.localUserConfiguration.initialize(), this.remoteUserConfiguration ? this.remoteUserConfiguration.initialize() : Promise.resolve(new ConfigurationModel())]); + return { local, remote }; } - private reloadUserConfiguration(): Promise<{ local: ConfigurationModel, remote: ConfigurationModel }> { - return Promise.all([this.reloadLocalUserConfiguration(true), this.reloadRemoteUserConfiguration(true)]).then(([local, remote]) => ({ local, remote })); + private async reloadUserConfiguration(): Promise<{ local: ConfigurationModel, remote: ConfigurationModel }> { + const [local, remote] = await Promise.all([this.reloadLocalUserConfiguration(true), this.reloadRemoteUserConfiguration(true)]); + return { local, remote }; } async reloadLocalUserConfiguration(donotTrigger?: boolean): Promise { @@ -538,44 +591,43 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat return new ConfigurationModel(); } - private reloadWorkspaceConfiguration(): Promise { + private async reloadWorkspaceConfiguration(): Promise { const workbenchState = this.getWorkbenchState(); if (workbenchState === WorkbenchState.FOLDER) { return this.onWorkspaceFolderConfigurationChanged(this.workspace.folders[0]); } if (workbenchState === WorkbenchState.WORKSPACE) { - return this.workspaceConfiguration.reload().then(() => this.onWorkspaceConfigurationChanged()); + return this.workspaceConfiguration.reload().then(() => this.onWorkspaceConfigurationChanged(false)); } - return Promise.resolve(undefined); } private reloadWorkspaceFolderConfiguration(folder: IWorkspaceFolder): Promise { return this.onWorkspaceFolderConfigurationChanged(folder); } - private loadConfiguration(userConfigurationModel: ConfigurationModel, remoteUserConfigurationModel: ConfigurationModel): Promise { + private async loadConfiguration(userConfigurationModel: ConfigurationModel, remoteUserConfigurationModel: ConfigurationModel): Promise { // reset caches this.cachedFolderConfigs = new ResourceMap(); const folders = this.workspace.folders; - return this.loadFolderConfigurations(folders) - .then((folderConfigurations) => { + const folderConfigurations = await this.loadFolderConfigurations(folders); - let workspaceConfiguration = this.getWorkspaceConfigurationModel(folderConfigurations); - const folderConfigurationModels = new ResourceMap(); - folderConfigurations.forEach((folderConfiguration, index) => folderConfigurationModels.set(folders[index].uri, folderConfiguration)); + let workspaceConfiguration = this.getWorkspaceConfigurationModel(folderConfigurations); + const folderConfigurationModels = new ResourceMap(); + folderConfigurations.forEach((folderConfiguration, index) => folderConfigurationModels.set(folders[index].uri, folderConfiguration)); - const currentConfiguration = this._configuration; - this._configuration = new Configuration(this.defaultConfiguration, userConfigurationModel, remoteUserConfigurationModel, workspaceConfiguration, folderConfigurationModels, new ConfigurationModel(), new ResourceMap(), this.workspace); + const currentConfiguration = this._configuration; + this._configuration = new Configuration(this.defaultConfiguration, userConfigurationModel, remoteUserConfigurationModel, workspaceConfiguration, folderConfigurationModels, new ConfigurationModel(), new ResourceMap(), this.workspace); - if (this.initialized) { - const change = this._configuration.compare(currentConfiguration); - this.triggerConfigurationChange(change, { data: currentConfiguration.toData(), workspace: this.workspace }, ConfigurationTarget.WORKSPACE); - } else { - this._onDidChangeConfiguration.fire(new AllKeysConfigurationChangeEvent(this._configuration, this.workspace, ConfigurationTarget.WORKSPACE, this.getTargetConfiguration(ConfigurationTarget.WORKSPACE))); - this.initialized = true; - } - }); + if (this.initialized) { + const change = this._configuration.compare(currentConfiguration); + this.triggerConfigurationChange(change, { data: currentConfiguration.toData(), workspace: this.workspace }, ConfigurationTarget.WORKSPACE); + } else { + this._onDidChangeConfiguration.fire(new AllKeysConfigurationChangeEvent(this._configuration, this.workspace, ConfigurationTarget.WORKSPACE, this.getTargetConfiguration(ConfigurationTarget.WORKSPACE))); + this.initialized = true; + } + + this.updateRestrictedSettings(); } private getWorkspaceConfigurationModel(folderConfigurations: ConfigurationModel[]): ConfigurationModel { @@ -595,25 +647,26 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat const previousData = this._configuration.toData(); const change = this._configuration.compareAndUpdateDefaultConfiguration(this.defaultConfiguration, keys); if (this.remoteUserConfiguration) { - this._configuration.updateLocalUserConfiguration(this.localUserConfiguration.reprocess()); - this._configuration.updateRemoteUserConfiguration(this.remoteUserConfiguration.reprocess()); + this._configuration.updateLocalUserConfiguration(this.localUserConfiguration.reparse()); + this._configuration.updateRemoteUserConfiguration(this.remoteUserConfiguration.reparse()); } if (this.getWorkbenchState() === WorkbenchState.FOLDER) { const folderConfiguration = this.cachedFolderConfigs.get(this.workspace.folders[0].uri); if (folderConfiguration) { - this._configuration.updateWorkspaceConfiguration(folderConfiguration.reprocess()); - this._configuration.updateFolderConfiguration(this.workspace.folders[0].uri, folderConfiguration.reprocess()); + this._configuration.updateWorkspaceConfiguration(folderConfiguration.reparse()); + this._configuration.updateFolderConfiguration(this.workspace.folders[0].uri, folderConfiguration.reparse()); } } else { - this._configuration.updateWorkspaceConfiguration(this.workspaceConfiguration.reprocessWorkspaceSettings()); + this._configuration.updateWorkspaceConfiguration(this.workspaceConfiguration.reparseWorkspaceSettings()); for (const folder of this.workspace.folders) { const folderConfiguration = this.cachedFolderConfigs.get(folder.uri); if (folderConfiguration) { - this._configuration.updateFolderConfiguration(folder.uri, folderConfiguration.reprocess()); + this._configuration.updateFolderConfiguration(folder.uri, folderConfiguration.reparse()); } } } this.triggerConfigurationChange(change, { data: previousData, workspace: this.workspace }, ConfigurationTarget.DEFAULT); + this.updateRestrictedSettings(); } } @@ -629,7 +682,7 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat this.triggerConfigurationChange(change, previous, ConfigurationTarget.USER); } - private async onWorkspaceConfigurationChanged(): Promise { + private async onWorkspaceConfigurationChanged(fromCache: boolean): Promise { if (this.workspace && this.workspace.configuration) { let newFolders = toWorkspaceFolders(this.workspaceConfiguration.getFolders(), this.workspace.configuration, this.uriIdentityService.extUri); @@ -647,36 +700,94 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat } } - await this.updateWorkspaceConfiguration(newFolders, this.workspaceConfiguration.getConfiguration()); + await this.updateWorkspaceConfiguration(newFolders, this.workspaceConfiguration.getConfiguration(), fromCache); } } - private async updateWorkspaceConfiguration(workspaceFolders: WorkspaceFolder[], configuration: ConfigurationModel): Promise { + private updateRestrictedSettings(): void { + const changed: string[] = []; + + const allProperties = this.configurationRegistry.getConfigurationProperties(); + const defaultRestrictedSettings: string[] = Object.keys(allProperties).filter(key => allProperties[key].restricted).sort((a, b) => a.localeCompare(b)); + const defaultDelta = delta(defaultRestrictedSettings, this._restrictedSettings.default, (a, b) => a.localeCompare(b)); + changed.push(...defaultDelta.added, ...defaultDelta.removed); + + const userLocal = this.localUserConfiguration.getRestrictedSettings().sort((a, b) => a.localeCompare(b)); + const userLocalDelta = delta(userLocal, this._restrictedSettings.userLocal || [], (a, b) => a.localeCompare(b)); + changed.push(...userLocalDelta.added, ...userLocalDelta.removed); + + const userRemote = (this.remoteUserConfiguration?.getRestrictedSettings() || []).sort((a, b) => a.localeCompare(b)); + const userRemoteDelta = delta(userRemote, this._restrictedSettings.userRemote || [], (a, b) => a.localeCompare(b)); + changed.push(...userRemoteDelta.added, ...userRemoteDelta.removed); + + const workspaceFolderMap = new ResourceMap>(); + for (const workspaceFolder of this.workspace.folders) { + const cachedFolderConfig = this.cachedFolderConfigs.get(workspaceFolder.uri); + const folderRestrictedSettings = (cachedFolderConfig?.getRestrictedSettings() || []).sort((a, b) => a.localeCompare(b)); + if (folderRestrictedSettings.length) { + workspaceFolderMap.set(workspaceFolder.uri, folderRestrictedSettings); + } + const previous = this._restrictedSettings.workspaceFolder?.get(workspaceFolder.uri) || []; + const workspaceFolderDelta = delta(folderRestrictedSettings, previous, (a, b) => a.localeCompare(b)); + changed.push(...workspaceFolderDelta.added, ...workspaceFolderDelta.removed); + } + + const workspace = this.getWorkbenchState() === WorkbenchState.WORKSPACE ? this.workspaceConfiguration.getRestrictedSettings().sort((a, b) => a.localeCompare(b)) + : this.workspace.folders[0] ? (workspaceFolderMap.get(this.workspace.folders[0].uri) || []) : []; + const workspaceDelta = delta(workspace, this._restrictedSettings.workspace || [], (a, b) => a.localeCompare(b)); + changed.push(...workspaceDelta.added, ...workspaceDelta.removed); + + if (changed.length) { + this._restrictedSettings = { + default: defaultRestrictedSettings, + userLocal: userLocal.length ? userLocal : undefined, + userRemote: userRemote.length ? userRemote : undefined, + workspace: workspace.length ? workspace : undefined, + workspaceFolder: workspaceFolderMap.size ? workspaceFolderMap : undefined, + }; + this._onDidChangeRestrictedSettings.fire(this.restrictedSettings); + } + } + + private async updateWorkspaceConfiguration(workspaceFolders: WorkspaceFolder[], configuration: ConfigurationModel, fromCache: boolean): Promise { const previous = { data: this._configuration.toData(), workspace: this.workspace }; const change = this._configuration.compareAndUpdateWorkspaceConfiguration(configuration); const changes = this.compareFolders(this.workspace.folders, workspaceFolders); if (changes.added.length || changes.removed.length || changes.changed.length) { this.workspace.folders = workspaceFolders; const change = await this.onFoldersChanged(); + await this.handleWillChangeWorkspaceFolders(changes, fromCache); this.triggerConfigurationChange(change, previous, ConfigurationTarget.WORKSPACE_FOLDER); this._onDidChangeWorkspaceFolders.fire(changes); } else { this.triggerConfigurationChange(change, previous, ConfigurationTarget.WORKSPACE); } + this.updateRestrictedSettings(); } - private onWorkspaceFolderConfigurationChanged(folder: IWorkspaceFolder): Promise { - return this.loadFolderConfigurations([folder]) - .then(([folderConfiguration]) => { - const previous = { data: this._configuration.toData(), workspace: this.workspace }; - const folderConfiguraitonChange = this._configuration.compareAndUpdateFolderConfiguration(folder.uri, folderConfiguration); - if (this.getWorkbenchState() === WorkbenchState.FOLDER) { - const workspaceConfigurationChange = this._configuration.compareAndUpdateWorkspaceConfiguration(folderConfiguration); - this.triggerConfigurationChange(mergeChanges(folderConfiguraitonChange, workspaceConfigurationChange), previous, ConfigurationTarget.WORKSPACE); - } else { - this.triggerConfigurationChange(folderConfiguraitonChange, previous, ConfigurationTarget.WORKSPACE_FOLDER); - } - }); + private async handleWillChangeWorkspaceFolders(changes: IWorkspaceFoldersChangeEvent, fromCache: boolean): Promise { + const joiners: Promise[] = []; + this._onWillChangeWorkspaceFolders.fire({ + join(updateWorkspaceTrustStatePromise) { + joiners.push(updateWorkspaceTrustStatePromise); + }, + changes, + fromCache + }); + try { await Promises.settled(joiners); } catch (error) { /* Ignore */ } + } + + private async onWorkspaceFolderConfigurationChanged(folder: IWorkspaceFolder): Promise { + const [folderConfiguration] = await this.loadFolderConfigurations([folder]); + const previous = { data: this._configuration.toData(), workspace: this.workspace }; + const folderConfiguraitonChange = this._configuration.compareAndUpdateFolderConfiguration(folder.uri, folderConfiguration); + if (this.getWorkbenchState() === WorkbenchState.FOLDER) { + const workspaceConfigurationChange = this._configuration.compareAndUpdateWorkspaceConfiguration(folderConfiguration); + this.triggerConfigurationChange(mergeChanges(folderConfiguraitonChange, workspaceConfigurationChange), previous, ConfigurationTarget.WORKSPACE); + } else { + this.triggerConfigurationChange(folderConfiguraitonChange, previous, ConfigurationTarget.WORKSPACE_FOLDER); + } + this.updateRestrictedSettings(); } private async onFoldersChanged(): Promise { @@ -706,7 +817,7 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat return Promise.all([...folders.map(folder => { let folderConfiguration = this.cachedFolderConfigs.get(folder.uri); if (!folderConfiguration) { - folderConfiguration = new FolderConfiguration(folder, FOLDER_CONFIG_FOLDER_NAME, this.getWorkbenchState(), this.fileService, this.uriIdentityService, this.logService, this.configurationCache); + folderConfiguration = new FolderConfiguration(folder, FOLDER_CONFIG_FOLDER_NAME, this.getWorkbenchState(), this.isWorkspaceTrusted, this.fileService, this.uriIdentityService, this.logService, this.configurationCache); this._register(folderConfiguration.onDidChange(() => this.onWorkspaceFolderConfigurationChanged(folder))); this.cachedFolderConfigs.set(folder.uri, this._register(folderConfiguration)); } @@ -714,11 +825,11 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat })]); } - private async validateWorkspaceFoldersAndReload(): Promise { + private async validateWorkspaceFoldersAndReload(fromCache: boolean): Promise { const validWorkspaceFolders = await this.toValidWorkspaceFolders(this.workspace.folders); const { removed } = this.compareFolders(this.workspace.folders, validWorkspaceFolders); if (removed.length) { - await this.updateWorkspaceConfiguration(validWorkspaceFolders, this.workspaceConfiguration.getConfiguration()); + await this.updateWorkspaceConfiguration(validWorkspaceFolders, this.workspaceConfiguration.getConfiguration(), fromCache); } } @@ -740,44 +851,41 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat return validWorkspaceFolders; } - private writeConfigurationValue(key: string, value: any, target: ConfigurationTarget, overrides: IConfigurationOverrides | undefined, donotNotifyError: boolean): Promise { + private async writeConfigurationValue(key: string, value: any, target: ConfigurationTarget, overrides: IConfigurationOverrides | undefined, donotNotifyError: boolean): Promise { if (target === ConfigurationTarget.DEFAULT) { - return Promise.reject(new Error('Invalid configuration target')); + throw new Error('Invalid configuration target'); } if (target === ConfigurationTarget.MEMORY) { const previous = { data: this._configuration.toData(), workspace: this.workspace }; this._configuration.updateValue(key, value, overrides); this.triggerConfigurationChange({ keys: overrides?.overrideIdentifier ? [keyFromOverrideIdentifier(overrides.overrideIdentifier), key] : [key], overrides: overrides?.overrideIdentifier ? [[overrides?.overrideIdentifier, [key]]] : [] }, previous, target); - return Promise.resolve(undefined); + return; } const editableConfigurationTarget = this.toEditableConfigurationTarget(target, key); if (!editableConfigurationTarget) { - return Promise.reject(new Error('Invalid configuration target')); + throw new Error('Invalid configuration target'); } if (editableConfigurationTarget === EditableConfigurationTarget.USER_REMOTE && !this.remoteUserConfiguration) { - return Promise.reject(new Error('Invalid configuration target')); + throw new Error('Invalid configuration target'); } - return this.configurationEditingService.writeConfiguration(editableConfigurationTarget, { key, value }, { scopes: overrides, donotNotifyError }) - .then(() => { - switch (editableConfigurationTarget) { - case EditableConfigurationTarget.USER_LOCAL: - return this.reloadLocalUserConfiguration().then(() => undefined); - case EditableConfigurationTarget.USER_REMOTE: - return this.reloadRemoteUserConfiguration().then(() => undefined); - case EditableConfigurationTarget.WORKSPACE: - return this.reloadWorkspaceConfiguration(); - case EditableConfigurationTarget.WORKSPACE_FOLDER: - const workspaceFolder = overrides && overrides.resource ? this.workspace.getFolder(overrides.resource) : null; - if (workspaceFolder) { - return this.reloadWorkspaceFolderConfiguration(workspaceFolder); - } + await this.configurationEditingService.writeConfiguration(editableConfigurationTarget, { key, value }, { scopes: overrides, donotNotifyError }); + switch (editableConfigurationTarget) { + case EditableConfigurationTarget.USER_LOCAL: + return this.reloadLocalUserConfiguration().then(() => undefined); + case EditableConfigurationTarget.USER_REMOTE: + return this.reloadRemoteUserConfiguration().then(() => undefined); + case EditableConfigurationTarget.WORKSPACE: + return this.reloadWorkspaceConfiguration(); + case EditableConfigurationTarget.WORKSPACE_FOLDER: + const workspaceFolder = overrides && overrides.resource ? this.workspace.getFolder(overrides.resource) : null; + if (workspaceFolder) { + return this.reloadWorkspaceFolderConfiguration(workspaceFolder); } - return Promise.resolve(); - }); + } } private deriveConfigurationTargets(key: string, value: any, inspect: IConfigurationValue): ConfigurationTarget[] { @@ -864,24 +972,81 @@ class RegisterConfigurationSchemasContribution extends Disposable implements IWo constructor( @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, + @IWorkspaceTrustManagementService private readonly workspaceTrustManagementService: IWorkspaceTrustManagementService, ) { super(); this.registerConfigurationSchemas(); const configurationRegistry = Registry.as(Extensions.Configuration); this._register(configurationRegistry.onDidUpdateConfiguration(e => this.registerConfigurationSchemas())); this._register(configurationRegistry.onDidSchemaChange(e => this.registerConfigurationSchemas())); + this._register(workspaceTrustManagementService.onDidChangeTrust(() => this.registerConfigurationSchemas())); } private registerConfigurationSchemas(): void { const jsonRegistry = Registry.as(JSONExtensions.JSONContribution); - const allSettingsSchema: IJSONSchema = { properties: allSettings.properties, patternProperties: allSettings.patternProperties, additionalProperties: true, allowTrailingCommas: true, allowComments: true }; - const userSettingsSchema: IJSONSchema = this.environmentService.remoteAuthority ? { properties: { ...applicationSettings.properties, ...windowSettings.properties, ...resourceSettings.properties }, patternProperties: allSettings.patternProperties, additionalProperties: true, allowTrailingCommas: true, allowComments: true } : allSettingsSchema; - const machineSettingsSchema: IJSONSchema = { properties: { ...machineSettings.properties, ...machineOverridableSettings.properties, ...windowSettings.properties, ...resourceSettings.properties }, patternProperties: allSettings.patternProperties, additionalProperties: true, allowTrailingCommas: true, allowComments: true }; - const workspaceSettingsSchema: IJSONSchema = { properties: { ...machineOverridableSettings.properties, ...windowSettings.properties, ...resourceSettings.properties }, patternProperties: allSettings.patternProperties, additionalProperties: true, allowTrailingCommas: true, allowComments: true }; + + const allSettingsSchema: IJSONSchema = { + properties: allSettings.properties, + patternProperties: allSettings.patternProperties, + additionalProperties: true, + allowTrailingCommas: true, + allowComments: true + }; + + const userSettingsSchema: IJSONSchema = this.environmentService.remoteAuthority ? + { + properties: { + ...applicationSettings.properties, + ...windowSettings.properties, + ...resourceSettings.properties + }, + patternProperties: allSettings.patternProperties, + additionalProperties: true, + allowTrailingCommas: true, + allowComments: true + } + : allSettingsSchema; + + const machineSettingsSchema: IJSONSchema = { + properties: { + ...machineSettings.properties, + ...machineOverridableSettings.properties, + ...windowSettings.properties, + ...resourceSettings.properties + }, + patternProperties: allSettings.patternProperties, + additionalProperties: true, + allowTrailingCommas: true, + allowComments: true + }; + + const workspaceSettingsSchema: IJSONSchema = { + properties: { + ...this.checkAndFilterPropertiesRequiringTrust(machineOverridableSettings.properties), + ...this.checkAndFilterPropertiesRequiringTrust(windowSettings.properties), + ...this.checkAndFilterPropertiesRequiringTrust(resourceSettings.properties) + }, + patternProperties: allSettings.patternProperties, + additionalProperties: true, + allowTrailingCommas: true, + allowComments: true + }; jsonRegistry.registerSchema(defaultSettingsSchemaId, { - properties: Object.keys(allSettings.properties).reduce((result, key) => { result[key] = { ...allSettings.properties[key], deprecationMessage: undefined }; return result; }, {}), - patternProperties: Object.keys(allSettings.patternProperties).reduce((result, key) => { result[key] = { ...allSettings.patternProperties[key], deprecationMessage: undefined }; return result; }, {}), + properties: Object.keys(allSettings.properties).reduce((result, key) => { + result[key] = { + ...allSettings.properties[key], + deprecationMessage: undefined + }; + return result; + }, {}), + patternProperties: Object.keys(allSettings.patternProperties).reduce((result, key) => { + result[key] = { + ...allSettings.patternProperties[key], + deprecationMessage: undefined + }; + return result; + }, {}), additionalProperties: true, allowTrailingCommas: true, allowComments: true @@ -890,7 +1055,16 @@ class RegisterConfigurationSchemasContribution extends Disposable implements IWo jsonRegistry.registerSchema(machineSettingsSchemaId, machineSettingsSchema); if (WorkbenchState.WORKSPACE === this.workspaceContextService.getWorkbenchState()) { - const folderSettingsSchema: IJSONSchema = { properties: { ...machineOverridableSettings.properties, ...resourceSettings.properties }, patternProperties: allSettings.patternProperties, additionalProperties: true, allowTrailingCommas: true, allowComments: true }; + const folderSettingsSchema: IJSONSchema = { + properties: { + ...this.checkAndFilterPropertiesRequiringTrust(machineOverridableSettings.properties), + ...this.checkAndFilterPropertiesRequiringTrust(resourceSettings.properties) + }, + patternProperties: allSettings.patternProperties, + additionalProperties: true, + allowTrailingCommas: true, + allowComments: true + }; jsonRegistry.registerSchema(workspaceSettingsSchemaId, workspaceSettingsSchema); jsonRegistry.registerSchema(folderSettingsSchemaId, folderSettingsSchema); } else { @@ -898,6 +1072,21 @@ class RegisterConfigurationSchemasContribution extends Disposable implements IWo jsonRegistry.registerSchema(folderSettingsSchemaId, workspaceSettingsSchema); } } + + private checkAndFilterPropertiesRequiringTrust(properties: IStringDictionary): IStringDictionary { + if (this.workspaceTrustManagementService.isWorkpaceTrusted()) { + return properties; + } + + const result: IStringDictionary = {}; + forEach(properties, ({ key, value }) => { + if (!value.restricted) { + result[key] = value; + } + }); + return result; + } } -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(RegisterConfigurationSchemasContribution, LifecyclePhase.Restored); +const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); +workbenchContributionsRegistry.registerWorkbenchContribution(RegisterConfigurationSchemasContribution, LifecyclePhase.Restored); diff --git a/src/vs/workbench/services/configuration/common/configuration.ts b/src/vs/workbench/services/configuration/common/configuration.ts index e54d141f..9cff2844 100644 --- a/src/vs/workbench/services/configuration/common/configuration.ts +++ b/src/vs/workbench/services/configuration/common/configuration.ts @@ -3,10 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; +import { ConfigurationScope, Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { URI } from 'vs/base/common/uri'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { refineServiceDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { Event } from 'vs/base/common/event'; +import { ResourceMap } from 'vs/base/common/map'; +import { Registry } from 'vs/platform/registry/common/platform'; export const FOLDER_CONFIG_FOLDER_NAME = '.vscode'; export const FOLDER_SETTINGS_NAME = 'settings'; @@ -45,8 +48,34 @@ export interface IConfigurationCache { } -export const IWorkbenchConfigurationService = createDecorator('configurationService'); +export function filterSettingsRequireWorkspaceTrust(settings: ReadonlyArray): ReadonlyArray { + const configurationRegistry = Registry.as(Extensions.Configuration); + return settings.filter(key => { + const property = configurationRegistry.getConfigurationProperties()[key]; + return property.restricted && property.scope !== ConfigurationScope.APPLICATION && property.scope !== ConfigurationScope.MACHINE; + }); +} + +export type RestrictedSettings = { + default: ReadonlyArray; + userLocal?: ReadonlyArray; + userRemote?: ReadonlyArray; + workspace?: ReadonlyArray; + workspaceFolder?: ResourceMap>; +}; + +export const IWorkbenchConfigurationService = refineServiceDecorator(IConfigurationService); export interface IWorkbenchConfigurationService extends IConfigurationService { + /** + * Restricted settings defined in each configuraiton target + */ + readonly restrictedSettings: RestrictedSettings; + + /** + * Event that triggers when the restricted settings changes + */ + readonly onDidChangeRestrictedSettings: Event; + /** * A promise that resolves when the remote configuration is loaded in a remote window. * The promise is resolved immediately if the window is not remote. diff --git a/src/vs/workbench/services/configuration/common/configurationModels.ts b/src/vs/workbench/services/configuration/common/configurationModels.ts index ad60a05a..6006c999 100644 --- a/src/vs/workbench/services/configuration/common/configurationModels.ts +++ b/src/vs/workbench/services/configuration/common/configurationModels.ts @@ -5,12 +5,11 @@ import { equals } from 'vs/base/common/objects'; import { toValuesTree, IConfigurationModel, IConfigurationOverrides, IConfigurationValue, IConfigurationChange } from 'vs/platform/configuration/common/configuration'; -import { Configuration as BaseConfiguration, ConfigurationModelParser, ConfigurationModel } from 'vs/platform/configuration/common/configurationModels'; +import { Configuration as BaseConfiguration, ConfigurationModelParser, ConfigurationModel, ConfigurationParseOptions } from 'vs/platform/configuration/common/configurationModels'; import { IStoredWorkspaceFolder } from 'vs/platform/workspaces/common/workspaces'; import { Workspace } from 'vs/platform/workspace/common/workspace'; import { ResourceMap } from 'vs/base/common/map'; import { URI } from 'vs/base/common/uri'; -import { WORKSPACE_SCOPES } from 'vs/workbench/services/configuration/common/configuration'; import { OVERRIDE_PROPERTY_PATTERN, overrideIdentifierFromKey } from 'vs/platform/configuration/common/configurationRegistry'; export class WorkspaceConfigurationModelParser extends ConfigurationModelParser { @@ -22,7 +21,7 @@ export class WorkspaceConfigurationModelParser extends ConfigurationModelParser constructor(name: string) { super(name); - this._settingsModelParser = new ConfigurationModelParser(name, WORKSPACE_SCOPES); + this._settingsModelParser = new ConfigurationModelParser(name); this._launchModel = new ConfigurationModel(); this._tasksModel = new ConfigurationModel(); } @@ -43,16 +42,20 @@ export class WorkspaceConfigurationModelParser extends ConfigurationModelParser return this._tasksModel; } - reprocessWorkspaceSettings(): void { - this._settingsModelParser.parse(); + reparseWorkspaceSettings(configurationParseOptions: ConfigurationParseOptions): void { + this._settingsModelParser.reparse(configurationParseOptions); } - protected doParseRaw(raw: any): IConfigurationModel { + getRestrictedWorkspaceSettings(): string[] { + return this._settingsModelParser.restrictedConfigurations; + } + + protected override doParseRaw(raw: any, configurationParseOptions?: ConfigurationParseOptions): IConfigurationModel { this._folders = (raw['folders'] || []) as IStoredWorkspaceFolder[]; - this._settingsModelParser.parseRaw(raw['settings']); + this._settingsModelParser.parseRaw(raw['settings'], configurationParseOptions); this._launchModel = this.createConfigurationModelFrom(raw, 'launch'); this._tasksModel = this.createConfigurationModelFrom(raw, 'tasks'); - return super.doParseRaw(raw); + return super.doParseRaw(raw, configurationParseOptions); } private createConfigurationModelFrom(raw: any, key: string): ConfigurationModel { @@ -74,7 +77,7 @@ export class StandaloneConfigurationModelParser extends ConfigurationModelParser super(name); } - protected doParseRaw(raw: any): IConfigurationModel { + protected override doParseRaw(raw: any, configurationParseOptions?: ConfigurationParseOptions): IConfigurationModel { const contents = toValuesTree(raw, message => console.error(`Conflict in settings file ${this._name}: ${message}`)); const scopedContents = Object.create(null); scopedContents[this.scope] = contents; @@ -98,15 +101,15 @@ export class Configuration extends BaseConfiguration { super(defaults, localUser, remoteUser, workspaceConfiguration, folders, memoryConfiguration, memoryConfigurationByResource); } - getValue(key: string | undefined, overrides: IConfigurationOverrides = {}): any { + override getValue(key: string | undefined, overrides: IConfigurationOverrides = {}): any { return super.getValue(key, overrides, this._workspace); } - inspect(key: string, overrides: IConfigurationOverrides = {}): IConfigurationValue { + override inspect(key: string, overrides: IConfigurationOverrides = {}): IConfigurationValue { return super.inspect(key, overrides, this._workspace); } - keys(): { + override keys(): { default: string[]; user: string[]; workspace: string[]; @@ -115,7 +118,7 @@ export class Configuration extends BaseConfiguration { return super.keys(this._workspace); } - compareAndDeleteFolderConfiguration(folder: URI): IConfigurationChange { + override compareAndDeleteFolderConfiguration(folder: URI): IConfigurationChange { if (this._workspace && this._workspace.folders.length > 0 && this._workspace.folders[0].uri.toString() === folder.toString()) { // Do not remove workspace configuration return { keys: [], overrides: [] }; diff --git a/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts b/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts index d5423db1..22a7e65d 100644 --- a/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts +++ b/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts @@ -110,7 +110,7 @@ suite('ConfigurationEditingService', () => { await testObject.writeConfiguration(EditableConfigurationTarget.WORKSPACE, { key: 'unknown.key', value: 'value' }); assert.fail('Should fail with ERROR_UNKNOWN_KEY'); } catch (error) { - assert.equal(error.code, ConfigurationEditingErrorCode.ERROR_UNKNOWN_KEY); + assert.strictEqual(error.code, ConfigurationEditingErrorCode.ERROR_UNKNOWN_KEY); } }); @@ -120,7 +120,7 @@ suite('ConfigurationEditingService', () => { await testObject.writeConfiguration(EditableConfigurationTarget.WORKSPACE, { key: 'configurationEditing.service.testSetting', value: 'value' }); assert.fail('Should fail with ERROR_NO_WORKSPACE_OPENED'); } catch (error) { - assert.equal(error.code, ConfigurationEditingErrorCode.ERROR_NO_WORKSPACE_OPENED); + assert.strictEqual(error.code, ConfigurationEditingErrorCode.ERROR_NO_WORKSPACE_OPENED); } }); @@ -130,7 +130,7 @@ suite('ConfigurationEditingService', () => { await testObject.writeConfiguration(EditableConfigurationTarget.USER_LOCAL, { key: 'configurationEditing.service.testSetting', value: 'value' }); assert.fail('Should fail with ERROR_INVALID_CONFIGURATION'); } catch (error) { - assert.equal(error.code, ConfigurationEditingErrorCode.ERROR_INVALID_CONFIGURATION); + assert.strictEqual(error.code, ConfigurationEditingErrorCode.ERROR_INVALID_CONFIGURATION); } }); @@ -141,7 +141,7 @@ suite('ConfigurationEditingService', () => { await testObject.writeConfiguration(EditableConfigurationTarget.USER_LOCAL, { key: 'tasks.configurationEditing.service.testSetting', value: 'value' }); assert.fail('Should fail with ERROR_INVALID_CONFIGURATION'); } catch (error) { - assert.equal(error.code, ConfigurationEditingErrorCode.ERROR_INVALID_CONFIGURATION); + assert.strictEqual(error.code, ConfigurationEditingErrorCode.ERROR_INVALID_CONFIGURATION); } }); @@ -151,7 +151,7 @@ suite('ConfigurationEditingService', () => { await testObject.writeConfiguration(EditableConfigurationTarget.USER_LOCAL, { key: 'configurationEditing.service.testSetting', value: 'value' }); assert.fail('Should fail with ERROR_CONFIGURATION_FILE_DIRTY error.'); } catch (error) { - assert.equal(error.code, ConfigurationEditingErrorCode.ERROR_CONFIGURATION_FILE_DIRTY); + assert.strictEqual(error.code, ConfigurationEditingErrorCode.ERROR_CONFIGURATION_FILE_DIRTY); } }); @@ -168,8 +168,8 @@ suite('ConfigurationEditingService', () => { await testObject.writeConfiguration(EditableConfigurationTarget.USER_LOCAL, { key: 'configurationEditing.service.testSetting', value: 'value' }, { donotNotifyError: true }); assert.fail('Should fail with ERROR_CONFIGURATION_FILE_DIRTY error.'); } catch (error) { - assert.equal(false, target.calledOnce); - assert.equal(error.code, ConfigurationEditingErrorCode.ERROR_CONFIGURATION_FILE_DIRTY); + assert.strictEqual(false, target.calledOnce); + assert.strictEqual(error.code, ConfigurationEditingErrorCode.ERROR_CONFIGURATION_FILE_DIRTY); } }); @@ -177,7 +177,7 @@ suite('ConfigurationEditingService', () => { await testObject.writeConfiguration(EditableConfigurationTarget.USER_LOCAL, { key: 'configurationEditing.service.testSetting', value: 'value' }); const contents = await fileService.readFile(environmentService.settingsResource); const parsed = json.parse(contents.value.toString()); - assert.equal(parsed['configurationEditing.service.testSetting'], 'value'); + assert.strictEqual(parsed['configurationEditing.service.testSetting'], 'value'); }); test('write one setting - existing file', async () => { @@ -186,8 +186,8 @@ suite('ConfigurationEditingService', () => { const contents = await fileService.readFile(environmentService.settingsResource); const parsed = json.parse(contents.value.toString()); - assert.equal(parsed['configurationEditing.service.testSetting'], 'value'); - assert.equal(parsed['my.super.setting'], 'my.super.value'); + assert.strictEqual(parsed['configurationEditing.service.testSetting'], 'value'); + assert.strictEqual(parsed['my.super.setting'], 'my.super.value'); }); test('remove an existing setting - existing file', async () => { @@ -196,8 +196,8 @@ suite('ConfigurationEditingService', () => { const contents = await fileService.readFile(environmentService.settingsResource); const parsed = json.parse(contents.value.toString()); - assert.deepEqual(Object.keys(parsed), ['my.super.setting']); - assert.equal(parsed['my.super.setting'], 'my.super.value'); + assert.deepStrictEqual(Object.keys(parsed), ['my.super.setting']); + assert.strictEqual(parsed['my.super.setting'], 'my.super.value'); }); test('remove non existing setting - existing file', async () => { @@ -206,8 +206,8 @@ suite('ConfigurationEditingService', () => { const contents = await fileService.readFile(environmentService.settingsResource); const parsed = json.parse(contents.value.toString()); - assert.deepEqual(Object.keys(parsed), ['my.super.setting']); - assert.equal(parsed['my.super.setting'], 'my.super.value'); + assert.deepStrictEqual(Object.keys(parsed), ['my.super.setting']); + assert.strictEqual(parsed['my.super.setting'], 'my.super.value'); }); test('write overridable settings to user settings', async () => { @@ -217,7 +217,7 @@ suite('ConfigurationEditingService', () => { const contents = await fileService.readFile(environmentService.settingsResource); const parsed = json.parse(contents.value.toString()); - assert.deepEqual(parsed[key], value); + assert.deepStrictEqual(parsed[key], value); }); test('write overridable settings to workspace settings', async () => { @@ -227,7 +227,7 @@ suite('ConfigurationEditingService', () => { const contents = await fileService.readFile(joinPath(workspaceService.getWorkspace().folders[0].uri, FOLDER_SETTINGS_PATH)); const parsed = json.parse(contents.value.toString()); - assert.deepEqual(parsed[key], value); + assert.deepStrictEqual(parsed[key], value); }); test('write overridable settings to workspace folder settings', async () => { @@ -238,7 +238,7 @@ suite('ConfigurationEditingService', () => { const contents = await fileService.readFile(folderSettingsFile); const parsed = json.parse(contents.value.toString()); - assert.deepEqual(parsed[key], value); + assert.deepStrictEqual(parsed[key], value); }); test('write workspace standalone setting - empty file', async () => { @@ -247,7 +247,7 @@ suite('ConfigurationEditingService', () => { const contents = await fileService.readFile(target); const parsed = json.parse(contents.value.toString()); - assert.equal(parsed['service.testSetting'], 'value'); + assert.strictEqual(parsed['service.testSetting'], 'value'); }); test('write user standalone setting - empty file', async () => { @@ -256,7 +256,7 @@ suite('ConfigurationEditingService', () => { const contents = await fileService.readFile(target); const parsed = json.parse(contents.value.toString()); - assert.equal(parsed['service.testSetting'], 'value'); + assert.strictEqual(parsed['service.testSetting'], 'value'); }); test('write workspace standalone setting - existing file', async () => { @@ -267,8 +267,8 @@ suite('ConfigurationEditingService', () => { const contents = await fileService.readFile(target); const parsed = json.parse(contents.value.toString()); - assert.equal(parsed['service.testSetting'], 'value'); - assert.equal(parsed['my.super.setting'], 'my.super.value'); + assert.strictEqual(parsed['service.testSetting'], 'value'); + assert.strictEqual(parsed['my.super.setting'], 'my.super.value'); }); test('write user standalone setting - existing file', async () => { @@ -279,8 +279,8 @@ suite('ConfigurationEditingService', () => { const contents = await fileService.readFile(target); const parsed = json.parse(contents.value.toString()); - assert.equal(parsed['service.testSetting'], 'value'); - assert.equal(parsed['my.super.setting'], 'my.super.value'); + assert.strictEqual(parsed['service.testSetting'], 'value'); + assert.strictEqual(parsed['my.super.setting'], 'my.super.value'); }); test('write workspace standalone setting - empty file - full JSON', async () => { @@ -289,8 +289,8 @@ suite('ConfigurationEditingService', () => { const target = joinPath(workspaceService.getWorkspace().folders[0].uri, WORKSPACE_STANDALONE_CONFIGURATIONS['tasks']); const contents = await fileService.readFile(target); const parsed = json.parse(contents.value.toString()); - assert.equal(parsed['version'], '1.0.0'); - assert.equal(parsed['tasks'][0]['taskName'], 'myTask'); + assert.strictEqual(parsed['version'], '1.0.0'); + assert.strictEqual(parsed['tasks'][0]['taskName'], 'myTask'); }); test('write user standalone setting - empty file - full JSON', async () => { @@ -299,8 +299,8 @@ suite('ConfigurationEditingService', () => { const target = joinPath(environmentService.userRoamingDataHome, USER_STANDALONE_CONFIGURATIONS['tasks']); const contents = await fileService.readFile(target); const parsed = json.parse(contents.value.toString()); - assert.equal(parsed['version'], '1.0.0'); - assert.equal(parsed['tasks'][0]['taskName'], 'myTask'); + assert.strictEqual(parsed['version'], '1.0.0'); + assert.strictEqual(parsed['tasks'][0]['taskName'], 'myTask'); }); test('write workspace standalone setting - existing file - full JSON', async () => { @@ -311,8 +311,8 @@ suite('ConfigurationEditingService', () => { const contents = await fileService.readFile(target); const parsed = json.parse(contents.value.toString()); - assert.equal(parsed['version'], '1.0.0'); - assert.equal(parsed['tasks'][0]['taskName'], 'myTask'); + assert.strictEqual(parsed['version'], '1.0.0'); + assert.strictEqual(parsed['tasks'][0]['taskName'], 'myTask'); }); test('write user standalone setting - existing file - full JSON', async () => { @@ -323,8 +323,8 @@ suite('ConfigurationEditingService', () => { const contents = await fileService.readFile(target); const parsed = json.parse(contents.value.toString()); - assert.equal(parsed['version'], '1.0.0'); - assert.equal(parsed['tasks'][0]['taskName'], 'myTask'); + assert.strictEqual(parsed['version'], '1.0.0'); + assert.strictEqual(parsed['tasks'][0]['taskName'], 'myTask'); }); test('write workspace standalone setting - existing file with JSON errors - full JSON', async () => { @@ -335,8 +335,8 @@ suite('ConfigurationEditingService', () => { const contents = await fileService.readFile(target); const parsed = json.parse(contents.value.toString()); - assert.equal(parsed['version'], '1.0.0'); - assert.equal(parsed['tasks'][0]['taskName'], 'myTask'); + assert.strictEqual(parsed['version'], '1.0.0'); + assert.strictEqual(parsed['tasks'][0]['taskName'], 'myTask'); }); test('write user standalone setting - existing file with JSON errors - full JSON', async () => { @@ -347,8 +347,8 @@ suite('ConfigurationEditingService', () => { const contents = await fileService.readFile(target); const parsed = json.parse(contents.value.toString()); - assert.equal(parsed['version'], '1.0.0'); - assert.equal(parsed['tasks'][0]['taskName'], 'myTask'); + assert.strictEqual(parsed['version'], '1.0.0'); + assert.strictEqual(parsed['tasks'][0]['taskName'], 'myTask'); }); test('write workspace standalone setting should replace complete file', async () => { @@ -369,7 +369,7 @@ suite('ConfigurationEditingService', () => { const actual = await fileService.readFile(target); const expected = JSON.stringify({ 'version': '1.0.0', tasks: [{ 'taskName': 'myTask1' }] }, null, '\t'); - assert.equal(actual.value.toString(), expected); + assert.strictEqual(actual.value.toString(), expected); }); test('write user standalone setting should replace complete file', async () => { @@ -390,6 +390,6 @@ suite('ConfigurationEditingService', () => { const actual = await fileService.readFile(target); const expected = JSON.stringify({ 'version': '1.0.0', tasks: [{ 'taskName': 'myTask1' }] }, null, '\t'); - assert.equal(actual.value.toString(), expected); + assert.strictEqual(actual.value.toString(), expected); }); }); 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 76642f56..129bf5d3 100644 --- a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts +++ b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts @@ -24,7 +24,7 @@ import { IJSONEditingService } from 'vs/workbench/services/configuration/common/ import { JSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditingService'; import { Schemas } from 'vs/base/common/network'; import { joinPath, dirname, basename } from 'vs/base/common/resources'; -import { isLinux } from 'vs/base/common/platform'; +import { isLinux, isMacintosh } from 'vs/base/common/platform'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { FileService } from 'vs/platform/files/common/fileService'; import { NullLogService } from 'vs/platform/log/common/log'; @@ -54,7 +54,7 @@ function convertToWorkspacePayload(folder: URI): ISingleFolderWorkspaceIdentifie } class ConfigurationCache extends BrowserConfigurationCache { - needsCaching() { return false; } + override needsCaching() { return false; } } const ROOT = URI.file('tests').with({ scheme: 'vscode-tests' }); @@ -84,23 +84,23 @@ suite('WorkspaceContextService - Folder', () => { test('getWorkspace()', () => { const actual = testObject.getWorkspace(); - assert.equal(actual.folders.length, 1); - assert.equal(actual.folders[0].uri.path, folder.path); - assert.equal(actual.folders[0].name, folderName); - assert.equal(actual.folders[0].index, 0); + assert.strictEqual(actual.folders.length, 1); + assert.strictEqual(actual.folders[0].uri.path, folder.path); + assert.strictEqual(actual.folders[0].name, folderName); + assert.strictEqual(actual.folders[0].index, 0); assert.ok(!actual.configuration); }); test('getWorkbenchState()', () => { const actual = testObject.getWorkbenchState(); - assert.equal(actual, WorkbenchState.FOLDER); + assert.strictEqual(actual, WorkbenchState.FOLDER); }); test('getWorkspaceFolder()', () => { const actual = testObject.getWorkspaceFolder(joinPath(folder, 'a')); - assert.equal(actual, testObject.getWorkspace().folders[0]); + assert.strictEqual(actual, testObject.getWorkspace().folders[0]); }); test('isCurrentWorkspace() => true', () => { @@ -156,15 +156,15 @@ suite('WorkspaceContextService - Workspace', () => { test('workspace folders', () => { const actual = testObject.getWorkspace().folders; - assert.equal(actual.length, 2); - assert.equal(basename(actual[0].uri), 'a'); - assert.equal(basename(actual[1].uri), 'b'); + assert.strictEqual(actual.length, 2); + assert.strictEqual(basename(actual[0].uri), 'a'); + assert.strictEqual(basename(actual[1].uri), 'b'); }); test('getWorkbenchState()', () => { const actual = testObject.getWorkbenchState(); - assert.equal(actual, WorkbenchState.WORKSPACE); + assert.strictEqual(actual, WorkbenchState.WORKSPACE); }); @@ -218,81 +218,83 @@ suite('WorkspaceContextService - Workspace Editing', () => { await testObject.addFolders([{ uri: joinPath(ROOT, 'd') }, { uri: joinPath(ROOT, 'c') }]); const actual = testObject.getWorkspace().folders; - assert.equal(actual.length, 4); - assert.equal(basename(actual[0].uri), 'a'); - assert.equal(basename(actual[1].uri), 'b'); - assert.equal(basename(actual[2].uri), 'd'); - assert.equal(basename(actual[3].uri), 'c'); + assert.strictEqual(actual.length, 4); + assert.strictEqual(basename(actual[0].uri), 'a'); + assert.strictEqual(basename(actual[1].uri), 'b'); + assert.strictEqual(basename(actual[2].uri), 'd'); + assert.strictEqual(basename(actual[3].uri), 'c'); }); test('add folders (at specific index)', async () => { await testObject.addFolders([{ uri: joinPath(ROOT, 'd') }, { uri: joinPath(ROOT, 'c') }], 0); const actual = testObject.getWorkspace().folders; - assert.equal(actual.length, 4); - assert.equal(basename(actual[0].uri), 'd'); - assert.equal(basename(actual[1].uri), 'c'); - assert.equal(basename(actual[2].uri), 'a'); - assert.equal(basename(actual[3].uri), 'b'); + assert.strictEqual(actual.length, 4); + assert.strictEqual(basename(actual[0].uri), 'd'); + assert.strictEqual(basename(actual[1].uri), 'c'); + assert.strictEqual(basename(actual[2].uri), 'a'); + assert.strictEqual(basename(actual[3].uri), 'b'); }); test('add folders (at specific wrong index)', async () => { await testObject.addFolders([{ uri: joinPath(ROOT, 'd') }, { uri: joinPath(ROOT, 'c') }], 10); const actual = testObject.getWorkspace().folders; - assert.equal(actual.length, 4); - assert.equal(basename(actual[0].uri), 'a'); - assert.equal(basename(actual[1].uri), 'b'); - assert.equal(basename(actual[2].uri), 'd'); - assert.equal(basename(actual[3].uri), 'c'); + assert.strictEqual(actual.length, 4); + assert.strictEqual(basename(actual[0].uri), 'a'); + assert.strictEqual(basename(actual[1].uri), 'b'); + assert.strictEqual(basename(actual[2].uri), 'd'); + assert.strictEqual(basename(actual[3].uri), 'c'); }); test('add folders (with name)', async () => { await testObject.addFolders([{ uri: joinPath(ROOT, 'd'), name: 'DDD' }, { uri: joinPath(ROOT, 'c'), name: 'CCC' }]); const actual = testObject.getWorkspace().folders; - assert.equal(actual.length, 4); - assert.equal(basename(actual[0].uri), 'a'); - assert.equal(basename(actual[1].uri), 'b'); - assert.equal(basename(actual[2].uri), 'd'); - assert.equal(basename(actual[3].uri), 'c'); - assert.equal(actual[2].name, 'DDD'); - assert.equal(actual[3].name, 'CCC'); + assert.strictEqual(actual.length, 4); + assert.strictEqual(basename(actual[0].uri), 'a'); + assert.strictEqual(basename(actual[1].uri), 'b'); + assert.strictEqual(basename(actual[2].uri), 'd'); + assert.strictEqual(basename(actual[3].uri), 'c'); + assert.strictEqual(actual[2].name, 'DDD'); + assert.strictEqual(actual[3].name, 'CCC'); }); test('add folders triggers change event', async () => { const target = sinon.spy(); + testObject.onWillChangeWorkspaceFolders(target); testObject.onDidChangeWorkspaceFolders(target); const addedFolders = [{ uri: joinPath(ROOT, 'd') }, { uri: joinPath(ROOT, 'c') }]; await testObject.addFolders(addedFolders); - assert.equal(target.callCount, 1, `Should be called only once but called ${target.callCount} times`); - const actual_1 = (target.args[0][0]); - assert.deepEqual(actual_1.added.map(r => r.uri.toString()), addedFolders.map(a => a.uri.toString())); - assert.deepEqual(actual_1.removed, []); - assert.deepEqual(actual_1.changed, []); + assert.strictEqual(target.callCount, 2, `Should be called only once but called ${target.callCount} times`); + const actual_1 = (target.args[1][0]); + assert.deepStrictEqual(actual_1.added.map(r => r.uri.toString()), addedFolders.map(a => a.uri.toString())); + assert.deepStrictEqual(actual_1.removed, []); + assert.deepStrictEqual(actual_1.changed, []); }); test('remove folders', async () => { await testObject.removeFolders([testObject.getWorkspace().folders[0].uri]); const actual = testObject.getWorkspace().folders; - assert.equal(actual.length, 1); - assert.equal(basename(actual[0].uri), 'b'); + assert.strictEqual(actual.length, 1); + assert.strictEqual(basename(actual[0].uri), 'b'); }); test('remove folders triggers change event', async () => { const target = sinon.spy(); + testObject.onWillChangeWorkspaceFolders(target); testObject.onDidChangeWorkspaceFolders(target); const removedFolder = testObject.getWorkspace().folders[0]; await testObject.removeFolders([removedFolder.uri]); - assert.equal(target.callCount, 1, `Should be called only once but called ${target.callCount} times`); - const actual_1 = (target.args[0][0]); - assert.deepEqual(actual_1.added, []); - assert.deepEqual(actual_1.removed.map(r => r.uri.toString()), [removedFolder.uri.toString()]); - assert.deepEqual(actual_1.changed.map(c => c.uri.toString()), [testObject.getWorkspace().folders[0].uri.toString()]); + assert.strictEqual(target.callCount, 2, `Should be called only once but called ${target.callCount} times`); + const actual_1 = (target.args[1][0]); + assert.deepStrictEqual(actual_1.added, []); + assert.deepStrictEqual(actual_1.removed.map(r => r.uri.toString()), [removedFolder.uri.toString()]); + assert.deepStrictEqual(actual_1.changed.map(c => c.uri.toString()), [testObject.getWorkspace().folders[0].uri.toString()]); }); test('remove folders and add them back by writing into the file', async () => { @@ -302,7 +304,7 @@ suite('WorkspaceContextService - Workspace Editing', () => { const promise = new Promise((resolve, reject) => { testObject.onDidChangeWorkspaceFolders(actual => { try { - assert.deepEqual(actual.added.map(r => r.uri.toString()), [folders[0].uri.toString()]); + assert.deepStrictEqual(actual.added.map(r => r.uri.toString()), [folders[0].uri.toString()]); resolve(); } catch (error) { reject(error); @@ -317,73 +319,78 @@ suite('WorkspaceContextService - Workspace Editing', () => { test('update folders (remove last and add to end)', async () => { const target = sinon.spy(); + testObject.onWillChangeWorkspaceFolders(target); testObject.onDidChangeWorkspaceFolders(target); const addedFolders = [{ uri: joinPath(ROOT, 'd') }, { uri: joinPath(ROOT, 'c') }]; const removedFolders = [testObject.getWorkspace().folders[1]].map(f => f.uri); await testObject.updateFolders(addedFolders, removedFolders); - assert.equal(target.callCount, 1, `Should be called only once but called ${target.callCount} times`); - const actual_1 = (target.args[0][0]); - assert.deepEqual(actual_1.added.map(r => r.uri.toString()), addedFolders.map(a => a.uri.toString())); - assert.deepEqual(actual_1.removed.map(r_1 => r_1.uri.toString()), removedFolders.map(a_1 => a_1.toString())); - assert.deepEqual(actual_1.changed, []); + assert.strictEqual(target.callCount, 2, `Should be called only once but called ${target.callCount} times`); + const actual_1 = (target.args[1][0]); + assert.deepStrictEqual(actual_1.added.map(r => r.uri.toString()), addedFolders.map(a => a.uri.toString())); + assert.deepStrictEqual(actual_1.removed.map(r_1 => r_1.uri.toString()), removedFolders.map(a_1 => a_1.toString())); + assert.deepStrictEqual(actual_1.changed, []); }); test('update folders (rename first via add and remove)', async () => { const target = sinon.spy(); + testObject.onWillChangeWorkspaceFolders(target); testObject.onDidChangeWorkspaceFolders(target); const addedFolders = [{ uri: joinPath(ROOT, 'a'), name: 'The Folder' }]; const removedFolders = [testObject.getWorkspace().folders[0]].map(f => f.uri); await testObject.updateFolders(addedFolders, removedFolders, 0); - assert.equal(target.callCount, 1, `Should be called only once but called ${target.callCount} times`); - const actual_1 = (target.args[0][0]); - assert.deepEqual(actual_1.added, []); - assert.deepEqual(actual_1.removed, []); - assert.deepEqual(actual_1.changed.map(r => r.uri.toString()), removedFolders.map(a => a.toString())); + assert.strictEqual(target.callCount, 2, `Should be called only once but called ${target.callCount} times`); + const actual_1 = (target.args[1][0]); + assert.deepStrictEqual(actual_1.added, []); + assert.deepStrictEqual(actual_1.removed, []); + assert.deepStrictEqual(actual_1.changed.map(r => r.uri.toString()), removedFolders.map(a => a.toString())); }); test('update folders (remove first and add to end)', async () => { const target = sinon.spy(); + testObject.onWillChangeWorkspaceFolders(target); testObject.onDidChangeWorkspaceFolders(target); const addedFolders = [{ uri: joinPath(ROOT, 'd') }, { uri: joinPath(ROOT, 'c') }]; const removedFolders = [testObject.getWorkspace().folders[0]].map(f => f.uri); const changedFolders = [testObject.getWorkspace().folders[1]].map(f => f.uri); await testObject.updateFolders(addedFolders, removedFolders); - assert.equal(target.callCount, 1, `Should be called only once but called ${target.callCount} times`); - const actual_1 = (target.args[0][0]); - assert.deepEqual(actual_1.added.map(r => r.uri.toString()), addedFolders.map(a => a.uri.toString())); - assert.deepEqual(actual_1.removed.map(r_1 => r_1.uri.toString()), removedFolders.map(a_1 => a_1.toString())); - assert.deepEqual(actual_1.changed.map(r_2 => r_2.uri.toString()), changedFolders.map(a_2 => a_2.toString())); + assert.strictEqual(target.callCount, 2, `Should be called only once but called ${target.callCount} times`); + const actual_1 = (target.args[1][0]); + assert.deepStrictEqual(actual_1.added.map(r => r.uri.toString()), addedFolders.map(a => a.uri.toString())); + assert.deepStrictEqual(actual_1.removed.map(r_1 => r_1.uri.toString()), removedFolders.map(a_1 => a_1.toString())); + assert.deepStrictEqual(actual_1.changed.map(r_2 => r_2.uri.toString()), changedFolders.map(a_2 => a_2.toString())); }); test('reorder folders trigger change event', async () => { const target = sinon.spy(); + testObject.onWillChangeWorkspaceFolders(target); testObject.onDidChangeWorkspaceFolders(target); const workspace = { folders: [{ path: testObject.getWorkspace().folders[1].uri.path }, { path: testObject.getWorkspace().folders[0].uri.path }] }; await fileService.writeFile(testObject.getWorkspace().configuration!, VSBuffer.fromString(JSON.stringify(workspace, null, '\t'))); await testObject.reloadConfiguration(); - assert.equal(target.callCount, 1, `Should be called only once but called ${target.callCount} times`); - const actual_1 = (target.args[0][0]); - assert.deepEqual(actual_1.added, []); - assert.deepEqual(actual_1.removed, []); - assert.deepEqual(actual_1.changed.map(c => c.uri.toString()), testObject.getWorkspace().folders.map(f => f.uri.toString()).reverse()); + assert.strictEqual(target.callCount, 2, `Should be called only once but called ${target.callCount} times`); + const actual_1 = (target.args[1][0]); + assert.deepStrictEqual(actual_1.added, []); + assert.deepStrictEqual(actual_1.removed, []); + assert.deepStrictEqual(actual_1.changed.map(c => c.uri.toString()), testObject.getWorkspace().folders.map(f => f.uri.toString()).reverse()); }); test('rename folders trigger change event', async () => { const target = sinon.spy(); + testObject.onWillChangeWorkspaceFolders(target); testObject.onDidChangeWorkspaceFolders(target); const workspace = { folders: [{ path: testObject.getWorkspace().folders[0].uri.path, name: '1' }, { path: testObject.getWorkspace().folders[1].uri.path }] }; fileService.writeFile(testObject.getWorkspace().configuration!, VSBuffer.fromString(JSON.stringify(workspace, null, '\t'))); await testObject.reloadConfiguration(); - assert.equal(target.callCount, 1, `Should be called only once but called ${target.callCount} times`); - const actual_1 = (target.args[0][0]); - assert.deepEqual(actual_1.added, []); - assert.deepEqual(actual_1.removed, []); - assert.deepEqual(actual_1.changed.map(c => c.uri.toString()), [testObject.getWorkspace().folders[0].uri.toString()]); + assert.strictEqual(target.callCount, 2, `Should be called only once but called ${target.callCount} times`); + const actual_1 = (target.args[1][0]); + assert.deepStrictEqual(actual_1.added, []); + assert.deepStrictEqual(actual_1.removed, []); + assert.deepStrictEqual(actual_1.changed.map(c => c.uri.toString()), [testObject.getWorkspace().folders[0].uri.toString()]); }); }); @@ -449,7 +456,7 @@ suite('WorkspaceService - Initialization', () => { teardown(() => disposables.clear()); - test('initialize a folder workspace from an empty workspace with no configuration changes', async () => { + (isMacintosh ? test.skip : test)('initialize a folder workspace from an empty workspace with no configuration changes', async () => { await fileService.writeFile(environmentService.settingsResource, VSBuffer.fromString('{ "initialization.testSetting1": "userValue" }')); @@ -457,23 +464,24 @@ suite('WorkspaceService - Initialization', () => { const target = sinon.spy(); testObject.onDidChangeWorkbenchState(target); testObject.onDidChangeWorkspaceName(target); + testObject.onWillChangeWorkspaceFolders(target); testObject.onDidChangeWorkspaceFolders(target); testObject.onDidChangeConfiguration(target); const folder = joinPath(ROOT, 'a'); await testObject.initialize(convertToWorkspacePayload(folder)); - assert.equal(testObject.getValue('initialization.testSetting1'), 'userValue'); - assert.equal(target.callCount, 3); - assert.deepEqual(target.args[0], [WorkbenchState.FOLDER]); - assert.deepEqual(target.args[1], [undefined]); - assert.deepEqual((target.args[2][0]).added.map(f => f.uri.toString()), [folder.toString()]); - assert.deepEqual((target.args[2][0]).removed, []); - assert.deepEqual((target.args[2][0]).changed, []); + assert.strictEqual(testObject.getValue('initialization.testSetting1'), 'userValue'); + assert.strictEqual(target.callCount, 4); + assert.deepStrictEqual(target.args[0], [WorkbenchState.FOLDER]); + assert.deepStrictEqual(target.args[1], [undefined]); + assert.deepStrictEqual((target.args[3][0]).added.map(f => f.uri.toString()), [folder.toString()]); + assert.deepStrictEqual((target.args[3][0]).removed, []); + assert.deepStrictEqual((target.args[3][0]).changed, []); }); - test('initialize a folder workspace from an empty workspace with configuration changes', async () => { + (isMacintosh ? test.skip : test)('initialize a folder workspace from an empty workspace with configuration changes', async () => { await fileService.writeFile(environmentService.settingsResource, VSBuffer.fromString('{ "initialization.testSetting1": "userValue" }')); @@ -481,6 +489,7 @@ suite('WorkspaceService - Initialization', () => { const target = sinon.spy(); testObject.onDidChangeWorkbenchState(target); testObject.onDidChangeWorkspaceName(target); + testObject.onWillChangeWorkspaceFolders(target); testObject.onDidChangeWorkspaceFolders(target); testObject.onDidChangeConfiguration(target); @@ -488,18 +497,18 @@ suite('WorkspaceService - Initialization', () => { await fileService.writeFile(joinPath(folder, '.vscode', 'settings.json'), VSBuffer.fromString('{ "initialization.testSetting1": "workspaceValue" }')); await testObject.initialize(convertToWorkspacePayload(folder)); - assert.equal(testObject.getValue('initialization.testSetting1'), 'workspaceValue'); - assert.equal(target.callCount, 4); - assert.deepEqual((target.args[0][0]).affectedKeys, ['initialization.testSetting1']); - assert.deepEqual(target.args[1], [WorkbenchState.FOLDER]); - assert.deepEqual(target.args[2], [undefined]); - assert.deepEqual((target.args[3][0]).added.map(f => f.uri.toString()), [folder.toString()]); - assert.deepEqual((target.args[3][0]).removed, []); - assert.deepEqual((target.args[3][0]).changed, []); + assert.strictEqual(testObject.getValue('initialization.testSetting1'), 'workspaceValue'); + assert.strictEqual(target.callCount, 5); + assert.deepStrictEqual((target.args[0][0]).affectedKeys, ['initialization.testSetting1']); + assert.deepStrictEqual(target.args[1], [WorkbenchState.FOLDER]); + assert.deepStrictEqual(target.args[2], [undefined]); + assert.deepStrictEqual((target.args[4][0]).added.map(f => f.uri.toString()), [folder.toString()]); + assert.deepStrictEqual((target.args[4][0]).removed, []); + assert.deepStrictEqual((target.args[4][0]).changed, []); }); - test('initialize a multi root workspace from an empty workspace with no configuration changes', async () => { + (isMacintosh ? test.skip : test)('initialize a multi root workspace from an empty workspace with no configuration changes', async () => { await fileService.writeFile(environmentService.settingsResource, VSBuffer.fromString('{ "initialization.testSetting1": "userValue" }')); @@ -507,21 +516,22 @@ suite('WorkspaceService - Initialization', () => { const target = sinon.spy(); testObject.onDidChangeWorkbenchState(target); testObject.onDidChangeWorkspaceName(target); + testObject.onWillChangeWorkspaceFolders(target); testObject.onDidChangeWorkspaceFolders(target); testObject.onDidChangeConfiguration(target); await testObject.initialize(getWorkspaceIdentifier(configResource)); - assert.equal(target.callCount, 3); - assert.deepEqual(target.args[0], [WorkbenchState.WORKSPACE]); - assert.deepEqual(target.args[1], [undefined]); - assert.deepEqual((target.args[2][0]).added.map(folder => folder.uri.toString()), [joinPath(ROOT, 'a').toString(), joinPath(ROOT, 'b').toString()]); - assert.deepEqual((target.args[2][0]).removed, []); - assert.deepEqual((target.args[2][0]).changed, []); + assert.strictEqual(target.callCount, 4); + assert.deepStrictEqual(target.args[0], [WorkbenchState.WORKSPACE]); + assert.deepStrictEqual(target.args[1], [undefined]); + assert.deepStrictEqual((target.args[3][0]).added.map(folder => folder.uri.toString()), [joinPath(ROOT, 'a').toString(), joinPath(ROOT, 'b').toString()]); + assert.deepStrictEqual((target.args[3][0]).removed, []); + assert.deepStrictEqual((target.args[3][0]).changed, []); }); - test('initialize a multi root workspace from an empty workspace with configuration changes', async () => { + (isMacintosh ? test.skip : test)('initialize a multi root workspace from an empty workspace with configuration changes', async () => { await fileService.writeFile(environmentService.settingsResource, VSBuffer.fromString('{ "initialization.testSetting1": "userValue" }')); @@ -529,6 +539,7 @@ suite('WorkspaceService - Initialization', () => { const target = sinon.spy(); testObject.onDidChangeWorkbenchState(target); testObject.onDidChangeWorkspaceName(target); + testObject.onWillChangeWorkspaceFolders(target); testObject.onDidChangeWorkspaceFolders(target); testObject.onDidChangeConfiguration(target); @@ -536,17 +547,17 @@ suite('WorkspaceService - Initialization', () => { await fileService.writeFile(joinPath(ROOT, 'b', '.vscode', 'settings.json'), VSBuffer.fromString('{ "initialization.testSetting2": "workspaceValue2" }')); await testObject.initialize(getWorkspaceIdentifier(configResource)); - assert.equal(target.callCount, 4); - assert.deepEqual((target.args[0][0]).affectedKeys, ['initialization.testSetting1', 'initialization.testSetting2']); - assert.deepEqual(target.args[1], [WorkbenchState.WORKSPACE]); - assert.deepEqual(target.args[2], [undefined]); - assert.deepEqual((target.args[3][0]).added.map(folder => folder.uri.toString()), [joinPath(ROOT, 'a').toString(), joinPath(ROOT, 'b').toString()]); - assert.deepEqual((target.args[3][0]).removed, []); - assert.deepEqual((target.args[3][0]).changed, []); + assert.strictEqual(target.callCount, 5); + assert.deepStrictEqual((target.args[0][0]).affectedKeys, ['initialization.testSetting1', 'initialization.testSetting2']); + assert.deepStrictEqual(target.args[1], [WorkbenchState.WORKSPACE]); + assert.deepStrictEqual(target.args[2], [undefined]); + assert.deepStrictEqual((target.args[4][0]).added.map(folder => folder.uri.toString()), [joinPath(ROOT, 'a').toString(), joinPath(ROOT, 'b').toString()]); + assert.deepStrictEqual((target.args[4][0]).removed, []); + assert.deepStrictEqual((target.args[4][0]).changed, []); }); - test('initialize a folder workspace from a folder workspace with no configuration changes', async () => { + (isMacintosh ? test.skip : test)('initialize a folder workspace from a folder workspace with no configuration changes', async () => { await testObject.initialize(convertToWorkspacePayload(joinPath(ROOT, 'a'))); await fileService.writeFile(environmentService.settingsResource, VSBuffer.fromString('{ "initialization.testSetting1": "userValue" }')); @@ -554,65 +565,68 @@ suite('WorkspaceService - Initialization', () => { const target = sinon.spy(); testObject.onDidChangeWorkbenchState(target); testObject.onDidChangeWorkspaceName(target); + testObject.onWillChangeWorkspaceFolders(target); testObject.onDidChangeWorkspaceFolders(target); testObject.onDidChangeConfiguration(target); await testObject.initialize(convertToWorkspacePayload(joinPath(ROOT, 'b'))); - assert.equal(testObject.getValue('initialization.testSetting1'), 'userValue'); - assert.equal(target.callCount, 1); - assert.deepEqual((target.args[0][0]).added.map(folder_1 => folder_1.uri.toString()), [joinPath(ROOT, 'b').toString()]); - assert.deepEqual((target.args[0][0]).removed.map(folder_2 => folder_2.uri.toString()), [joinPath(ROOT, 'a').toString()]); - assert.deepEqual((target.args[0][0]).changed, []); + assert.strictEqual(testObject.getValue('initialization.testSetting1'), 'userValue'); + assert.strictEqual(target.callCount, 2); + assert.deepStrictEqual((target.args[1][0]).added.map(folder_1 => folder_1.uri.toString()), [joinPath(ROOT, 'b').toString()]); + assert.deepStrictEqual((target.args[1][0]).removed.map(folder_2 => folder_2.uri.toString()), [joinPath(ROOT, 'a').toString()]); + assert.deepStrictEqual((target.args[1][0]).changed, []); }); - test('initialize a folder workspace from a folder workspace with configuration changes', async () => { + (isMacintosh ? test.skip : test)('initialize a folder workspace from a folder workspace with configuration changes', async () => { await testObject.initialize(convertToWorkspacePayload(joinPath(ROOT, 'a'))); const target = sinon.spy(); testObject.onDidChangeWorkbenchState(target); testObject.onDidChangeWorkspaceName(target); + testObject.onWillChangeWorkspaceFolders(target); testObject.onDidChangeWorkspaceFolders(target); testObject.onDidChangeConfiguration(target); await fileService.writeFile(joinPath(ROOT, 'b', '.vscode', 'settings.json'), VSBuffer.fromString('{ "initialization.testSetting1": "workspaceValue2" }')); await testObject.initialize(convertToWorkspacePayload(joinPath(ROOT, 'b'))); - assert.equal(testObject.getValue('initialization.testSetting1'), 'workspaceValue2'); - assert.equal(target.callCount, 2); - assert.deepEqual((target.args[0][0]).affectedKeys, ['initialization.testSetting1']); - assert.deepEqual((target.args[1][0]).added.map(folder_1 => folder_1.uri.toString()), [joinPath(ROOT, 'b').toString()]); - assert.deepEqual((target.args[1][0]).removed.map(folder_2 => folder_2.uri.toString()), [joinPath(ROOT, 'a').toString()]); - assert.deepEqual((target.args[1][0]).changed, []); + assert.strictEqual(testObject.getValue('initialization.testSetting1'), 'workspaceValue2'); + assert.strictEqual(target.callCount, 3); + assert.deepStrictEqual((target.args[0][0]).affectedKeys, ['initialization.testSetting1']); + assert.deepStrictEqual((target.args[2][0]).added.map(folder_1 => folder_1.uri.toString()), [joinPath(ROOT, 'b').toString()]); + assert.deepStrictEqual((target.args[2][0]).removed.map(folder_2 => folder_2.uri.toString()), [joinPath(ROOT, 'a').toString()]); + assert.deepStrictEqual((target.args[2][0]).changed, []); }); - test('initialize a multi folder workspace from a folder workspacce triggers change events in the right order', async () => { + (isMacintosh ? test.skip : test)('initialize a multi folder workspace from a folder workspacce triggers change events in the right order', async () => { await testObject.initialize(convertToWorkspacePayload(joinPath(ROOT, 'a'))); const target = sinon.spy(); testObject.onDidChangeWorkbenchState(target); testObject.onDidChangeWorkspaceName(target); + testObject.onWillChangeWorkspaceFolders(target); testObject.onDidChangeWorkspaceFolders(target); testObject.onDidChangeConfiguration(target); await fileService.writeFile(joinPath(ROOT, 'a', '.vscode', 'settings.json'), VSBuffer.fromString('{ "initialization.testSetting1": "workspaceValue2" }')); await testObject.initialize(getWorkspaceIdentifier(configResource)); - assert.equal(target.callCount, 4); - assert.deepEqual((target.args[0][0]).affectedKeys, ['initialization.testSetting1']); - assert.deepEqual(target.args[1], [WorkbenchState.WORKSPACE]); - assert.deepEqual(target.args[2], [undefined]); - assert.deepEqual((target.args[3][0]).added.map(folder_1 => folder_1.uri.toString()), [joinPath(ROOT, 'b').toString()]); - assert.deepEqual((target.args[3][0]).removed, []); - assert.deepEqual((target.args[3][0]).changed, []); + assert.strictEqual(target.callCount, 5); + assert.deepStrictEqual((target.args[0][0]).affectedKeys, ['initialization.testSetting1']); + assert.deepStrictEqual(target.args[1], [WorkbenchState.WORKSPACE]); + assert.deepStrictEqual(target.args[2], [undefined]); + assert.deepStrictEqual((target.args[4][0]).added.map(folder_1 => folder_1.uri.toString()), [joinPath(ROOT, 'b').toString()]); + assert.deepStrictEqual((target.args[4][0]).removed, []); + assert.deepStrictEqual((target.args[4][0]).changed, []); }); }); suite('WorkspaceConfigurationService - Folder', () => { - let testObject: IConfigurationService, workspaceService: WorkspaceService, fileService: IFileService, environmentService: BrowserWorkbenchEnvironmentService; + let testObject: WorkspaceService, workspaceService: WorkspaceService, fileService: IFileService, environmentService: BrowserWorkbenchEnvironmentService; const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); const disposables: DisposableStore = new DisposableStore(); @@ -645,7 +659,12 @@ suite('WorkspaceConfigurationService - Folder', () => { 'type': 'string', 'default': 'isSet', scope: ConfigurationScope.LANGUAGE_OVERRIDABLE - } + }, + 'configurationService.folder.restrictedSetting': { + 'type': 'string', + 'default': 'isSet', + restricted: true + }, } }); }); @@ -680,39 +699,39 @@ suite('WorkspaceConfigurationService - Folder', () => { teardown(() => disposables.clear()); test('defaults', () => { - assert.deepEqual(testObject.getValue('configurationService'), { 'folder': { 'applicationSetting': 'isSet', 'machineSetting': 'isSet', 'machineOverridableSetting': 'isSet', 'testSetting': 'isSet', 'languageSetting': 'isSet' } }); + assert.deepStrictEqual(testObject.getValue('configurationService'), { 'folder': { 'applicationSetting': 'isSet', 'machineSetting': 'isSet', 'machineOverridableSetting': 'isSet', 'testSetting': 'isSet', 'languageSetting': 'isSet', 'restrictedSetting': 'isSet' } }); }); test('globals override defaults', async () => { await fileService.writeFile(environmentService.settingsResource, VSBuffer.fromString('{ "configurationService.folder.testSetting": "userValue" }')); await testObject.reloadConfiguration(); - assert.equal(testObject.getValue('configurationService.folder.testSetting'), 'userValue'); + assert.strictEqual(testObject.getValue('configurationService.folder.testSetting'), 'userValue'); }); test('globals', async () => { await fileService.writeFile(environmentService.settingsResource, VSBuffer.fromString('{ "testworkbench.editor.tabs": true }')); await testObject.reloadConfiguration(); - assert.equal(testObject.getValue('testworkbench.editor.tabs'), true); + assert.strictEqual(testObject.getValue('testworkbench.editor.tabs'), true); }); test('workspace settings', async () => { await fileService.writeFile(joinPath(workspaceService.getWorkspace().folders[0].uri, '.vscode', 'settings.json'), VSBuffer.fromString('{ "testworkbench.editor.icons": true }')); await testObject.reloadConfiguration(); - assert.equal(testObject.getValue('testworkbench.editor.icons'), true); + assert.strictEqual(testObject.getValue('testworkbench.editor.icons'), true); }); test('workspace settings override user settings', async () => { await fileService.writeFile(environmentService.settingsResource, VSBuffer.fromString('{ "configurationService.folder.testSetting": "userValue" }')); await fileService.writeFile(joinPath(workspaceService.getWorkspace().folders[0].uri, '.vscode', 'settings.json'), VSBuffer.fromString('{ "configurationService.folder.testSetting": "workspaceValue" }')); await testObject.reloadConfiguration(); - assert.equal(testObject.getValue('configurationService.folder.testSetting'), 'workspaceValue'); + assert.strictEqual(testObject.getValue('configurationService.folder.testSetting'), 'workspaceValue'); }); test('machine overridable settings override user Settings', async () => { await fileService.writeFile(environmentService.settingsResource, VSBuffer.fromString('{ "configurationService.folder.machineOverridableSetting": "userValue" }')); await fileService.writeFile(joinPath(workspaceService.getWorkspace().folders[0].uri, '.vscode', 'settings.json'), VSBuffer.fromString('{ "configurationService.folder.machineOverridableSetting": "workspaceValue" }')); await testObject.reloadConfiguration(); - assert.equal(testObject.getValue('configurationService.folder.machineOverridableSetting'), 'workspaceValue'); + assert.strictEqual(testObject.getValue('configurationService.folder.machineOverridableSetting'), 'workspaceValue'); }); test('workspace settings override user settings after defaults are registered ', async () => { @@ -729,7 +748,7 @@ suite('WorkspaceConfigurationService - Folder', () => { } } }); - assert.equal(testObject.getValue('configurationService.folder.newSetting'), 'workspaceValue'); + assert.strictEqual(testObject.getValue('configurationService.folder.newSetting'), 'workspaceValue'); }); test('machine overridable settings override user settings after defaults are registered ', async () => { @@ -747,7 +766,7 @@ suite('WorkspaceConfigurationService - Folder', () => { } } }); - assert.equal(testObject.getValue('configurationService.folder.newMachineOverridableSetting'), 'workspaceValue'); + assert.strictEqual(testObject.getValue('configurationService.folder.newMachineOverridableSetting'), 'workspaceValue'); }); test('application settings are not read from workspace', async () => { @@ -756,7 +775,7 @@ suite('WorkspaceConfigurationService - Folder', () => { await testObject.reloadConfiguration(); - assert.equal(testObject.getValue('configurationService.folder.applicationSetting'), 'userValue'); + assert.strictEqual(testObject.getValue('configurationService.folder.applicationSetting'), 'userValue'); }); test('application settings are not read from workspace when workspace folder uri is passed', async () => { @@ -765,7 +784,7 @@ suite('WorkspaceConfigurationService - Folder', () => { await testObject.reloadConfiguration(); - assert.equal(testObject.getValue('configurationService.folder.applicationSetting', { resource: workspaceService.getWorkspace().folders[0].uri }), 'userValue'); + assert.strictEqual(testObject.getValue('configurationService.folder.applicationSetting', { resource: workspaceService.getWorkspace().folders[0].uri }), 'userValue'); }); test('machine settings are not read from workspace', async () => { @@ -774,7 +793,7 @@ suite('WorkspaceConfigurationService - Folder', () => { await testObject.reloadConfiguration(); - assert.equal(testObject.getValue('configurationService.folder.machineSetting', { resource: workspaceService.getWorkspace().folders[0].uri }), 'userValue'); + assert.strictEqual(testObject.getValue('configurationService.folder.machineSetting', { resource: workspaceService.getWorkspace().folders[0].uri }), 'userValue'); }); test('machine settings are not read from workspace when workspace folder uri is passed', async () => { @@ -783,7 +802,7 @@ suite('WorkspaceConfigurationService - Folder', () => { await testObject.reloadConfiguration(); - assert.equal(testObject.getValue('configurationService.folder.machineSetting', { resource: workspaceService.getWorkspace().folders[0].uri }), 'userValue'); + assert.strictEqual(testObject.getValue('configurationService.folder.machineSetting', { resource: workspaceService.getWorkspace().folders[0].uri }), 'userValue'); }); test('get application scope settings are not loaded after defaults are registered', async () => { @@ -791,7 +810,7 @@ suite('WorkspaceConfigurationService - Folder', () => { await fileService.writeFile(joinPath(workspaceService.getWorkspace().folders[0].uri, '.vscode', 'settings.json'), VSBuffer.fromString('{ "configurationService.folder.applicationSetting-2": "workspaceValue" }')); await testObject.reloadConfiguration(); - assert.equal(testObject.getValue('configurationService.folder.applicationSetting-2'), 'workspaceValue'); + assert.strictEqual(testObject.getValue('configurationService.folder.applicationSetting-2'), 'workspaceValue'); configurationRegistry.registerConfiguration({ 'id': '_test', @@ -805,10 +824,10 @@ suite('WorkspaceConfigurationService - Folder', () => { } }); - assert.equal(testObject.getValue('configurationService.folder.applicationSetting-2'), 'userValue'); + assert.strictEqual(testObject.getValue('configurationService.folder.applicationSetting-2'), 'userValue'); await testObject.reloadConfiguration(); - assert.equal(testObject.getValue('configurationService.folder.applicationSetting-2'), 'userValue'); + assert.strictEqual(testObject.getValue('configurationService.folder.applicationSetting-2'), 'userValue'); }); test('get application scope settings are not loaded after defaults are registered when workspace folder uri is passed', async () => { @@ -816,7 +835,7 @@ suite('WorkspaceConfigurationService - Folder', () => { await fileService.writeFile(joinPath(workspaceService.getWorkspace().folders[0].uri, '.vscode', 'settings.json'), VSBuffer.fromString('{ "configurationService.folder.applicationSetting-3": "workspaceValue" }')); await testObject.reloadConfiguration(); - assert.equal(testObject.getValue('configurationService.folder.applicationSetting-3', { resource: workspaceService.getWorkspace().folders[0].uri }), 'workspaceValue'); + assert.strictEqual(testObject.getValue('configurationService.folder.applicationSetting-3', { resource: workspaceService.getWorkspace().folders[0].uri }), 'workspaceValue'); configurationRegistry.registerConfiguration({ 'id': '_test', @@ -830,10 +849,10 @@ suite('WorkspaceConfigurationService - Folder', () => { } }); - assert.equal(testObject.getValue('configurationService.folder.applicationSetting-3', { resource: workspaceService.getWorkspace().folders[0].uri }), 'userValue'); + assert.strictEqual(testObject.getValue('configurationService.folder.applicationSetting-3', { resource: workspaceService.getWorkspace().folders[0].uri }), 'userValue'); await testObject.reloadConfiguration(); - assert.equal(testObject.getValue('configurationService.folder.applicationSetting-3', { resource: workspaceService.getWorkspace().folders[0].uri }), 'userValue'); + assert.strictEqual(testObject.getValue('configurationService.folder.applicationSetting-3', { resource: workspaceService.getWorkspace().folders[0].uri }), 'userValue'); }); test('get machine scope settings are not loaded after defaults are registered', async () => { @@ -841,7 +860,7 @@ suite('WorkspaceConfigurationService - Folder', () => { await fileService.writeFile(joinPath(workspaceService.getWorkspace().folders[0].uri, '.vscode', 'settings.json'), VSBuffer.fromString('{ "configurationService.folder.machineSetting-2": "workspaceValue" }')); await testObject.reloadConfiguration(); - assert.equal(testObject.getValue('configurationService.folder.machineSetting-2'), 'workspaceValue'); + assert.strictEqual(testObject.getValue('configurationService.folder.machineSetting-2'), 'workspaceValue'); configurationRegistry.registerConfiguration({ 'id': '_test', @@ -855,10 +874,10 @@ suite('WorkspaceConfigurationService - Folder', () => { } }); - assert.equal(testObject.getValue('configurationService.folder.machineSetting-2'), 'userValue'); + assert.strictEqual(testObject.getValue('configurationService.folder.machineSetting-2'), 'userValue'); await testObject.reloadConfiguration(); - assert.equal(testObject.getValue('configurationService.folder.machineSetting-2'), 'userValue'); + assert.strictEqual(testObject.getValue('configurationService.folder.machineSetting-2'), 'userValue'); }); test('get machine scope settings are not loaded after defaults are registered when workspace folder uri is passed', async () => { @@ -866,7 +885,7 @@ suite('WorkspaceConfigurationService - Folder', () => { await fileService.writeFile(joinPath(workspaceService.getWorkspace().folders[0].uri, '.vscode', 'settings.json'), VSBuffer.fromString('{ "configurationService.folder.machineSetting-3": "workspaceValue" }')); await testObject.reloadConfiguration(); - assert.equal(testObject.getValue('configurationService.folder.machineSetting-3', { resource: workspaceService.getWorkspace().folders[0].uri }), 'workspaceValue'); + assert.strictEqual(testObject.getValue('configurationService.folder.machineSetting-3', { resource: workspaceService.getWorkspace().folders[0].uri }), 'workspaceValue'); configurationRegistry.registerConfiguration({ 'id': '_test', @@ -880,10 +899,10 @@ suite('WorkspaceConfigurationService - Folder', () => { } }); - assert.equal(testObject.getValue('configurationService.folder.machineSetting-3', { resource: workspaceService.getWorkspace().folders[0].uri }), 'userValue'); + assert.strictEqual(testObject.getValue('configurationService.folder.machineSetting-3', { resource: workspaceService.getWorkspace().folders[0].uri }), 'userValue'); await testObject.reloadConfiguration(); - assert.equal(testObject.getValue('configurationService.folder.machineSetting-3', { resource: workspaceService.getWorkspace().folders[0].uri }), 'userValue'); + assert.strictEqual(testObject.getValue('configurationService.folder.machineSetting-3', { resource: workspaceService.getWorkspace().folders[0].uri }), 'userValue'); }); test('reload configuration emits events after global configuraiton changes', async () => { @@ -914,95 +933,95 @@ suite('WorkspaceConfigurationService - Folder', () => { test('inspect', async () => { let actual = testObject.inspect('something.missing'); - assert.equal(actual.defaultValue, undefined); - assert.equal(actual.userValue, undefined); - assert.equal(actual.workspaceValue, undefined); - assert.equal(actual.workspaceFolderValue, undefined); - assert.equal(actual.value, undefined); + assert.strictEqual(actual.defaultValue, undefined); + assert.strictEqual(actual.userValue, undefined); + assert.strictEqual(actual.workspaceValue, undefined); + assert.strictEqual(actual.workspaceFolderValue, undefined); + assert.strictEqual(actual.value, undefined); actual = testObject.inspect('configurationService.folder.testSetting'); - assert.equal(actual.defaultValue, 'isSet'); - assert.equal(actual.userValue, undefined); - assert.equal(actual.workspaceValue, undefined); - assert.equal(actual.workspaceFolderValue, undefined); - assert.equal(actual.value, 'isSet'); + assert.strictEqual(actual.defaultValue, 'isSet'); + assert.strictEqual(actual.userValue, undefined); + assert.strictEqual(actual.workspaceValue, undefined); + assert.strictEqual(actual.workspaceFolderValue, undefined); + assert.strictEqual(actual.value, 'isSet'); await fileService.writeFile(environmentService.settingsResource, VSBuffer.fromString('{ "configurationService.folder.testSetting": "userValue" }')); await testObject.reloadConfiguration(); actual = testObject.inspect('configurationService.folder.testSetting'); - assert.equal(actual.defaultValue, 'isSet'); - assert.equal(actual.userValue, 'userValue'); - assert.equal(actual.workspaceValue, undefined); - assert.equal(actual.workspaceFolderValue, undefined); - assert.equal(actual.value, 'userValue'); + assert.strictEqual(actual.defaultValue, 'isSet'); + assert.strictEqual(actual.userValue, 'userValue'); + assert.strictEqual(actual.workspaceValue, undefined); + assert.strictEqual(actual.workspaceFolderValue, undefined); + assert.strictEqual(actual.value, 'userValue'); await fileService.writeFile(joinPath(workspaceService.getWorkspace().folders[0].uri, '.vscode', 'settings.json'), VSBuffer.fromString('{ "configurationService.folder.testSetting": "workspaceValue" }')); await testObject.reloadConfiguration(); actual = testObject.inspect('configurationService.folder.testSetting'); - assert.equal(actual.defaultValue, 'isSet'); - assert.equal(actual.userValue, 'userValue'); - assert.equal(actual.workspaceValue, 'workspaceValue'); - assert.equal(actual.workspaceFolderValue, undefined); - assert.equal(actual.value, 'workspaceValue'); + assert.strictEqual(actual.defaultValue, 'isSet'); + assert.strictEqual(actual.userValue, 'userValue'); + assert.strictEqual(actual.workspaceValue, 'workspaceValue'); + assert.strictEqual(actual.workspaceFolderValue, undefined); + assert.strictEqual(actual.value, 'workspaceValue'); }); test('keys', async () => { let actual = testObject.keys(); assert.ok(actual.default.indexOf('configurationService.folder.testSetting') !== -1); - assert.deepEqual(actual.user, []); - assert.deepEqual(actual.workspace, []); - assert.deepEqual(actual.workspaceFolder, []); + assert.deepStrictEqual(actual.user, []); + assert.deepStrictEqual(actual.workspace, []); + assert.deepStrictEqual(actual.workspaceFolder, []); await fileService.writeFile(environmentService.settingsResource, VSBuffer.fromString('{ "configurationService.folder.testSetting": "userValue" }')); await testObject.reloadConfiguration(); actual = testObject.keys(); assert.ok(actual.default.indexOf('configurationService.folder.testSetting') !== -1); - assert.deepEqual(actual.user, ['configurationService.folder.testSetting']); - assert.deepEqual(actual.workspace, []); - assert.deepEqual(actual.workspaceFolder, []); + assert.deepStrictEqual(actual.user, ['configurationService.folder.testSetting']); + assert.deepStrictEqual(actual.workspace, []); + assert.deepStrictEqual(actual.workspaceFolder, []); await fileService.writeFile(joinPath(workspaceService.getWorkspace().folders[0].uri, '.vscode', 'settings.json'), VSBuffer.fromString('{ "configurationService.folder.testSetting": "workspaceValue" }')); await testObject.reloadConfiguration(); actual = testObject.keys(); assert.ok(actual.default.indexOf('configurationService.folder.testSetting') !== -1); - assert.deepEqual(actual.user, ['configurationService.folder.testSetting']); - assert.deepEqual(actual.workspace, ['configurationService.folder.testSetting']); - assert.deepEqual(actual.workspaceFolder, []); + assert.deepStrictEqual(actual.user, ['configurationService.folder.testSetting']); + assert.deepStrictEqual(actual.workspace, ['configurationService.folder.testSetting']); + assert.deepStrictEqual(actual.workspaceFolder, []); }); test('update user configuration', () => { return testObject.updateValue('configurationService.folder.testSetting', 'value', ConfigurationTarget.USER) - .then(() => assert.equal(testObject.getValue('configurationService.folder.testSetting'), 'value')); + .then(() => assert.strictEqual(testObject.getValue('configurationService.folder.testSetting'), 'value')); }); test('update workspace configuration', () => { return testObject.updateValue('tasks.service.testSetting', 'value', ConfigurationTarget.WORKSPACE) - .then(() => assert.equal(testObject.getValue('tasks.service.testSetting'), 'value')); + .then(() => assert.strictEqual(testObject.getValue('tasks.service.testSetting'), 'value')); }); test('update resource configuration', () => { return testObject.updateValue('configurationService.folder.testSetting', 'value', { resource: workspaceService.getWorkspace().folders[0].uri }, ConfigurationTarget.WORKSPACE_FOLDER) - .then(() => assert.equal(testObject.getValue('configurationService.folder.testSetting'), 'value')); + .then(() => assert.strictEqual(testObject.getValue('configurationService.folder.testSetting'), 'value')); }); test('update resource language configuration', () => { return testObject.updateValue('configurationService.folder.languageSetting', 'value', { resource: workspaceService.getWorkspace().folders[0].uri }, ConfigurationTarget.WORKSPACE_FOLDER) - .then(() => assert.equal(testObject.getValue('configurationService.folder.languageSetting'), 'value')); + .then(() => assert.strictEqual(testObject.getValue('configurationService.folder.languageSetting'), 'value')); }); test('update application setting into workspace configuration in a workspace is not supported', () => { return testObject.updateValue('configurationService.folder.applicationSetting', 'workspaceValue', {}, ConfigurationTarget.WORKSPACE, true) - .then(() => assert.fail('Should not be supported'), (e) => assert.equal(e.code, ConfigurationEditingErrorCode.ERROR_INVALID_WORKSPACE_CONFIGURATION_APPLICATION)); + .then(() => assert.fail('Should not be supported'), (e) => assert.strictEqual(e.code, ConfigurationEditingErrorCode.ERROR_INVALID_WORKSPACE_CONFIGURATION_APPLICATION)); }); test('update machine setting into workspace configuration in a workspace is not supported', () => { return testObject.updateValue('configurationService.folder.machineSetting', 'workspaceValue', {}, ConfigurationTarget.WORKSPACE, true) - .then(() => assert.fail('Should not be supported'), (e) => assert.equal(e.code, ConfigurationEditingErrorCode.ERROR_INVALID_WORKSPACE_CONFIGURATION_MACHINE)); + .then(() => assert.fail('Should not be supported'), (e) => assert.strictEqual(e.code, ConfigurationEditingErrorCode.ERROR_INVALID_WORKSPACE_CONFIGURATION_MACHINE)); }); test('update tasks configuration', () => { return testObject.updateValue('tasks', { 'version': '1.0.0', tasks: [{ 'taskName': 'myTask' }] }, ConfigurationTarget.WORKSPACE) - .then(() => assert.deepEqual(testObject.getValue('tasks'), { 'version': '1.0.0', tasks: [{ 'taskName': 'myTask' }] })); + .then(() => assert.deepStrictEqual(testObject.getValue('tasks'), { 'version': '1.0.0', tasks: [{ 'taskName': 'myTask' }] })); }); test('update user configuration should trigger change event before promise is resolve', () => { @@ -1021,7 +1040,7 @@ suite('WorkspaceConfigurationService - Folder', () => { test('update memory configuration', () => { return testObject.updateValue('configurationService.folder.testSetting', 'memoryValue', ConfigurationTarget.MEMORY) - .then(() => assert.equal(testObject.getValue('configurationService.folder.testSetting'), 'memoryValue')); + .then(() => assert.strictEqual(testObject.getValue('configurationService.folder.testSetting'), 'memoryValue')); }); test('update memory configuration should trigger change event before promise is resolve', () => { @@ -1040,21 +1059,21 @@ suite('WorkspaceConfigurationService - Folder', () => { await testObject.reloadConfiguration(); const actual = testObject.inspect(key, { resource: workspaceService.getWorkspace().folders[0].uri }); - assert.equal(actual.userValue, undefined); - assert.equal(actual.workspaceValue, undefined); - assert.equal(actual.workspaceFolderValue, undefined); + assert.strictEqual(actual.userValue, undefined); + assert.strictEqual(actual.workspaceValue, undefined); + assert.strictEqual(actual.workspaceFolderValue, undefined); }); test('update user configuration to default value when target is not passed', async () => { await testObject.updateValue('configurationService.folder.testSetting', 'value', ConfigurationTarget.USER); await testObject.updateValue('configurationService.folder.testSetting', 'isSet'); - assert.equal(testObject.inspect('configurationService.folder.testSetting').userValue, undefined); + assert.strictEqual(testObject.inspect('configurationService.folder.testSetting').userValue, undefined); }); test('update user configuration to default value when target is passed', async () => { await testObject.updateValue('configurationService.folder.testSetting', 'value', ConfigurationTarget.USER); await testObject.updateValue('configurationService.folder.testSetting', 'isSet', ConfigurationTarget.USER); - assert.equal(testObject.inspect('configurationService.folder.testSetting').userValue, 'isSet'); + assert.strictEqual(testObject.inspect('configurationService.folder.testSetting').userValue, 'isSet'); }); test('update task configuration should trigger change event before promise is resolve', () => { @@ -1082,7 +1101,7 @@ suite('WorkspaceConfigurationService - Folder', () => { await new Promise(async (c) => { const disposable = testObject.onDidChangeConfiguration(e => { assert.ok(e.affectsConfiguration('configurationService.folder.testSetting')); - assert.equal(testObject.getValue('configurationService.folder.testSetting'), 'workspaceValue'); + assert.strictEqual(testObject.getValue('configurationService.folder.testSetting'), 'workspaceValue'); disposable.dispose(); c(); }); @@ -1100,13 +1119,121 @@ suite('WorkspaceConfigurationService - Folder', () => { await fileService.del(workspaceSettingsResource); }); assert.ok(e.affectsConfiguration('configurationService.folder.testSetting')); - assert.equal(testObject.getValue('configurationService.folder.testSetting'), 'userValue'); + assert.strictEqual(testObject.getValue('configurationService.folder.testSetting'), 'userValue'); + }); + + test('restricted setting is read from workspace when workspace is trusted', async () => { + testObject.updateWorkspaceTrust(true); + + await fileService.writeFile(environmentService.settingsResource, VSBuffer.fromString('{ "configurationService.folder.restrictedSetting": "userValue" }')); + await fileService.writeFile(joinPath(workspaceService.getWorkspace().folders[0].uri, '.vscode', 'settings.json'), VSBuffer.fromString('{ "configurationService.folder.restrictedSetting": "workspaceValue" }')); + await testObject.reloadConfiguration(); + + assert.strictEqual(testObject.getValue('configurationService.folder.restrictedSetting', { resource: workspaceService.getWorkspace().folders[0].uri }), 'workspaceValue'); + assert.ok(testObject.restrictedSettings.default.includes('configurationService.folder.restrictedSetting')); + assert.strictEqual(testObject.restrictedSettings.userLocal, undefined); + assert.strictEqual(testObject.restrictedSettings.userRemote, undefined); + assert.deepStrictEqual(testObject.restrictedSettings.workspace, ['configurationService.folder.restrictedSetting']); + assert.strictEqual(testObject.restrictedSettings.workspaceFolder?.size, 1); + assert.deepStrictEqual(testObject.restrictedSettings.workspaceFolder?.get(workspaceService.getWorkspace().folders[0].uri), ['configurationService.folder.restrictedSetting']); + }); + + test('restricted setting is not read from workspace when workspace is changed to trusted', async () => { + testObject.updateWorkspaceTrust(true); + + await fileService.writeFile(environmentService.settingsResource, VSBuffer.fromString('{ "configurationService.folder.restrictedSetting": "userValue" }')); + await fileService.writeFile(joinPath(workspaceService.getWorkspace().folders[0].uri, '.vscode', 'settings.json'), VSBuffer.fromString('{ "configurationService.folder.restrictedSetting": "workspaceValue" }')); + await testObject.reloadConfiguration(); + + testObject.updateWorkspaceTrust(false); + + assert.strictEqual(testObject.getValue('configurationService.folder.restrictedSetting', { resource: workspaceService.getWorkspace().folders[0].uri }), 'userValue'); + assert.ok(testObject.restrictedSettings.default.includes('configurationService.folder.restrictedSetting')); + assert.strictEqual(testObject.restrictedSettings.userLocal, undefined); + assert.strictEqual(testObject.restrictedSettings.userRemote, undefined); + assert.deepStrictEqual(testObject.restrictedSettings.workspace, ['configurationService.folder.restrictedSetting']); + assert.strictEqual(testObject.restrictedSettings.workspaceFolder?.size, 1); + assert.deepStrictEqual(testObject.restrictedSettings.workspaceFolder?.get(workspaceService.getWorkspace().folders[0].uri), ['configurationService.folder.restrictedSetting']); + }); + + test('change event is triggered when workspace is changed to untrusted', async () => { + testObject.updateWorkspaceTrust(true); + + await fileService.writeFile(environmentService.settingsResource, VSBuffer.fromString('{ "configurationService.folder.restrictedSetting": "userValue" }')); + await fileService.writeFile(joinPath(workspaceService.getWorkspace().folders[0].uri, '.vscode', 'settings.json'), VSBuffer.fromString('{ "configurationService.folder.restrictedSetting": "workspaceValue" }')); + await testObject.reloadConfiguration(); + + const promise = Event.toPromise(testObject.onDidChangeConfiguration); + testObject.updateWorkspaceTrust(false); + + const event = await promise; + assert.ok(event.affectedKeys.includes('configurationService.folder.restrictedSetting')); + assert.ok(event.affectsConfiguration('configurationService.folder.restrictedSetting')); + }); + + test('restricted setting is not read from workspace when workspace is not trusted', async () => { + testObject.updateWorkspaceTrust(false); + + await fileService.writeFile(environmentService.settingsResource, VSBuffer.fromString('{ "configurationService.folder.restrictedSetting": "userValue" }')); + await fileService.writeFile(joinPath(workspaceService.getWorkspace().folders[0].uri, '.vscode', 'settings.json'), VSBuffer.fromString('{ "configurationService.folder.restrictedSetting": "workspaceValue" }')); + await testObject.reloadConfiguration(); + + assert.strictEqual(testObject.getValue('configurationService.folder.restrictedSetting', { resource: workspaceService.getWorkspace().folders[0].uri }), 'userValue'); + assert.ok(testObject.restrictedSettings.default.includes('configurationService.folder.restrictedSetting')); + assert.strictEqual(testObject.restrictedSettings.userLocal, undefined); + assert.strictEqual(testObject.restrictedSettings.userRemote, undefined); + assert.deepStrictEqual(testObject.restrictedSettings.workspace, ['configurationService.folder.restrictedSetting']); + assert.strictEqual(testObject.restrictedSettings.workspaceFolder?.size, 1); + assert.deepStrictEqual(testObject.restrictedSettings.workspaceFolder?.get(workspaceService.getWorkspace().folders[0].uri), ['configurationService.folder.restrictedSetting']); + }); + + test('restricted setting is read when workspace is changed to trusted', async () => { + testObject.updateWorkspaceTrust(false); + + await fileService.writeFile(environmentService.settingsResource, VSBuffer.fromString('{ "configurationService.folder.restrictedSetting": "userValue" }')); + await fileService.writeFile(joinPath(workspaceService.getWorkspace().folders[0].uri, '.vscode', 'settings.json'), VSBuffer.fromString('{ "configurationService.folder.restrictedSetting": "workspaceValue" }')); + await testObject.reloadConfiguration(); + + testObject.updateWorkspaceTrust(true); + + assert.strictEqual(testObject.getValue('configurationService.folder.restrictedSetting', { resource: workspaceService.getWorkspace().folders[0].uri }), 'workspaceValue'); + assert.ok(testObject.restrictedSettings.default.includes('configurationService.folder.restrictedSetting')); + assert.strictEqual(testObject.restrictedSettings.userLocal, undefined); + assert.strictEqual(testObject.restrictedSettings.userRemote, undefined); + assert.deepStrictEqual(testObject.restrictedSettings.workspace, ['configurationService.folder.restrictedSetting']); + assert.strictEqual(testObject.restrictedSettings.workspaceFolder?.size, 1); + assert.deepStrictEqual(testObject.restrictedSettings.workspaceFolder?.get(workspaceService.getWorkspace().folders[0].uri), ['configurationService.folder.restrictedSetting']); + }); + + test('change event is triggered when workspace is changed to trusted', async () => { + testObject.updateWorkspaceTrust(false); + + await fileService.writeFile(environmentService.settingsResource, VSBuffer.fromString('{ "configurationService.folder.restrictedSetting": "userValue" }')); + await fileService.writeFile(joinPath(workspaceService.getWorkspace().folders[0].uri, '.vscode', 'settings.json'), VSBuffer.fromString('{ "configurationService.folder.restrictedSetting": "workspaceValue" }')); + await testObject.reloadConfiguration(); + + const promise = Event.toPromise(testObject.onDidChangeConfiguration); + testObject.updateWorkspaceTrust(true); + + const event = await promise; + assert.ok(event.affectedKeys.includes('configurationService.folder.restrictedSetting')); + assert.ok(event.affectsConfiguration('configurationService.folder.restrictedSetting')); + }); + + test('adding an restricted setting triggers change event', async () => { + await fileService.writeFile(environmentService.settingsResource, VSBuffer.fromString('{ "configurationService.folder.restrictedSetting": "userValue" }')); + testObject.updateWorkspaceTrust(false); + + const promise = Event.toPromise(testObject.onDidChangeRestrictedSettings); + await fileService.writeFile(joinPath(workspaceService.getWorkspace().folders[0].uri, '.vscode', 'settings.json'), VSBuffer.fromString('{ "configurationService.folder.restrictedSetting": "workspaceValue" }')); + + return promise; }); }); suite('WorkspaceConfigurationService-Multiroot', () => { - let workspaceContextService: IWorkspaceContextService, jsonEditingServce: IJSONEditingService, testObject: IConfigurationService, fileService: IFileService, environmentService: BrowserWorkbenchEnvironmentService; + let workspaceContextService: IWorkspaceContextService, jsonEditingServce: IJSONEditingService, testObject: WorkspaceService, fileService: IFileService, environmentService: BrowserWorkbenchEnvironmentService; const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); const disposables = new DisposableStore(); @@ -1143,6 +1270,18 @@ suite('WorkspaceConfigurationService-Multiroot', () => { 'type': 'string', 'default': 'isSet', scope: ConfigurationScope.LANGUAGE_OVERRIDABLE + }, + 'configurationService.workspace.testRestrictedSetting1': { + 'type': 'string', + 'default': 'isSet', + restricted: true, + scope: ConfigurationScope.RESOURCE + }, + 'configurationService.workspace.testRestrictedSetting2': { + 'type': 'string', + 'default': 'isSet', + restricted: true, + scope: ConfigurationScope.RESOURCE } } }); @@ -1197,7 +1336,7 @@ suite('WorkspaceConfigurationService-Multiroot', () => { await testObject.reloadConfiguration(); - assert.equal(testObject.getValue('configurationService.folder.applicationSetting'), 'userValue'); + assert.strictEqual(testObject.getValue('configurationService.folder.applicationSetting'), 'userValue'); }); test('application settings are not read from workspace when folder is passed', async () => { @@ -1206,7 +1345,7 @@ suite('WorkspaceConfigurationService-Multiroot', () => { await testObject.reloadConfiguration(); - assert.equal(testObject.getValue('configurationService.folder.applicationSetting', { resource: workspaceContextService.getWorkspace().folders[0].uri }), 'userValue'); + assert.strictEqual(testObject.getValue('configurationService.folder.applicationSetting', { resource: workspaceContextService.getWorkspace().folders[0].uri }), 'userValue'); }); test('machine settings are not read from workspace', async () => { @@ -1215,7 +1354,7 @@ suite('WorkspaceConfigurationService-Multiroot', () => { await testObject.reloadConfiguration(); - assert.equal(testObject.getValue('configurationService.folder.machineSetting'), 'userValue'); + assert.strictEqual(testObject.getValue('configurationService.folder.machineSetting'), 'userValue'); }); test('machine settings are not read from workspace when folder is passed', async () => { @@ -1224,7 +1363,7 @@ suite('WorkspaceConfigurationService-Multiroot', () => { await testObject.reloadConfiguration(); - assert.equal(testObject.getValue('configurationService.folder.machineSetting', { resource: workspaceContextService.getWorkspace().folders[0].uri }), 'userValue'); + assert.strictEqual(testObject.getValue('configurationService.folder.machineSetting', { resource: workspaceContextService.getWorkspace().folders[0].uri }), 'userValue'); }); test('get application scope settings are not loaded after defaults are registered', async () => { @@ -1232,7 +1371,7 @@ suite('WorkspaceConfigurationService-Multiroot', () => { await jsonEditingServce.write(workspaceContextService.getWorkspace().configuration!, [{ path: ['settings'], value: { 'configurationService.workspace.newSetting': 'workspaceValue' } }], true); await testObject.reloadConfiguration(); - assert.equal(testObject.getValue('configurationService.workspace.newSetting'), 'workspaceValue'); + assert.strictEqual(testObject.getValue('configurationService.workspace.newSetting'), 'workspaceValue'); configurationRegistry.registerConfiguration({ 'id': '_test', @@ -1246,10 +1385,10 @@ suite('WorkspaceConfigurationService-Multiroot', () => { } }); - assert.equal(testObject.getValue('configurationService.workspace.newSetting'), 'userValue'); + assert.strictEqual(testObject.getValue('configurationService.workspace.newSetting'), 'userValue'); await testObject.reloadConfiguration(); - assert.equal(testObject.getValue('configurationService.workspace.newSetting'), 'userValue'); + assert.strictEqual(testObject.getValue('configurationService.workspace.newSetting'), 'userValue'); }); test('get application scope settings are not loaded after defaults are registered when workspace folder is passed', async () => { @@ -1257,7 +1396,7 @@ suite('WorkspaceConfigurationService-Multiroot', () => { await jsonEditingServce.write(workspaceContextService.getWorkspace().configuration!, [{ path: ['settings'], value: { 'configurationService.workspace.newSetting-2': 'workspaceValue' } }], true); await testObject.reloadConfiguration(); - assert.equal(testObject.getValue('configurationService.workspace.newSetting-2', { resource: workspaceContextService.getWorkspace().folders[0].uri }), 'workspaceValue'); + assert.strictEqual(testObject.getValue('configurationService.workspace.newSetting-2', { resource: workspaceContextService.getWorkspace().folders[0].uri }), 'workspaceValue'); configurationRegistry.registerConfiguration({ 'id': '_test', @@ -1271,10 +1410,10 @@ suite('WorkspaceConfigurationService-Multiroot', () => { } }); - assert.equal(testObject.getValue('configurationService.workspace.newSetting-2', { resource: workspaceContextService.getWorkspace().folders[0].uri }), 'userValue'); + assert.strictEqual(testObject.getValue('configurationService.workspace.newSetting-2', { resource: workspaceContextService.getWorkspace().folders[0].uri }), 'userValue'); await testObject.reloadConfiguration(); - assert.equal(testObject.getValue('configurationService.workspace.newSetting-2', { resource: workspaceContextService.getWorkspace().folders[0].uri }), 'userValue'); + assert.strictEqual(testObject.getValue('configurationService.workspace.newSetting-2', { resource: workspaceContextService.getWorkspace().folders[0].uri }), 'userValue'); }); test('workspace settings override user settings after defaults are registered for machine overridable settings ', async () => { @@ -1282,7 +1421,7 @@ suite('WorkspaceConfigurationService-Multiroot', () => { await jsonEditingServce.write(workspaceContextService.getWorkspace().configuration!, [{ path: ['settings'], value: { 'configurationService.workspace.newMachineOverridableSetting': 'workspaceValue' } }], true); await testObject.reloadConfiguration(); - assert.equal(testObject.getValue('configurationService.workspace.newMachineOverridableSetting'), 'workspaceValue'); + assert.strictEqual(testObject.getValue('configurationService.workspace.newMachineOverridableSetting'), 'workspaceValue'); configurationRegistry.registerConfiguration({ 'id': '_test', @@ -1296,10 +1435,10 @@ suite('WorkspaceConfigurationService-Multiroot', () => { } }); - assert.equal(testObject.getValue('configurationService.workspace.newMachineOverridableSetting'), 'workspaceValue'); + assert.strictEqual(testObject.getValue('configurationService.workspace.newMachineOverridableSetting'), 'workspaceValue'); await testObject.reloadConfiguration(); - assert.equal(testObject.getValue('configurationService.workspace.newMachineOverridableSetting'), 'workspaceValue'); + assert.strictEqual(testObject.getValue('configurationService.workspace.newMachineOverridableSetting'), 'workspaceValue'); }); @@ -1309,7 +1448,7 @@ suite('WorkspaceConfigurationService-Multiroot', () => { await testObject.reloadConfiguration(); - assert.equal(testObject.getValue('configurationService.workspace.applicationSetting'), 'userValue'); + assert.strictEqual(testObject.getValue('configurationService.workspace.applicationSetting'), 'userValue'); }); test('application settings are not read from workspace folder when workspace folder is passed', async () => { @@ -1318,7 +1457,7 @@ suite('WorkspaceConfigurationService-Multiroot', () => { await testObject.reloadConfiguration(); - assert.equal(testObject.getValue('configurationService.workspace.applicationSetting', { resource: workspaceContextService.getWorkspace().folders[0].uri }), 'userValue'); + assert.strictEqual(testObject.getValue('configurationService.workspace.applicationSetting', { resource: workspaceContextService.getWorkspace().folders[0].uri }), 'userValue'); }); test('machine settings are not read from workspace folder', async () => { @@ -1327,7 +1466,7 @@ suite('WorkspaceConfigurationService-Multiroot', () => { await testObject.reloadConfiguration(); - assert.equal(testObject.getValue('configurationService.workspace.machineSetting'), 'userValue'); + assert.strictEqual(testObject.getValue('configurationService.workspace.machineSetting'), 'userValue'); }); test('machine settings are not read from workspace folder when workspace folder is passed', async () => { @@ -1336,7 +1475,7 @@ suite('WorkspaceConfigurationService-Multiroot', () => { await testObject.reloadConfiguration(); - assert.equal(testObject.getValue('configurationService.workspace.machineSetting', { resource: workspaceContextService.getWorkspace().folders[0].uri }), 'userValue'); + assert.strictEqual(testObject.getValue('configurationService.workspace.machineSetting', { resource: workspaceContextService.getWorkspace().folders[0].uri }), 'userValue'); }); test('application settings are not read from workspace folder after defaults are registered', async () => { @@ -1344,7 +1483,7 @@ suite('WorkspaceConfigurationService-Multiroot', () => { await fileService.writeFile(workspaceContextService.getWorkspace().folders[0].toResource('.vscode/settings.json'), VSBuffer.fromString('{ "configurationService.workspace.testNewApplicationSetting": "workspaceFolderValue" }')); await testObject.reloadConfiguration(); - assert.equal(testObject.getValue('configurationService.workspace.testNewApplicationSetting', { resource: workspaceContextService.getWorkspace().folders[0].uri }), 'workspaceFolderValue'); + assert.strictEqual(testObject.getValue('configurationService.workspace.testNewApplicationSetting', { resource: workspaceContextService.getWorkspace().folders[0].uri }), 'workspaceFolderValue'); configurationRegistry.registerConfiguration({ 'id': '_test', @@ -1358,10 +1497,10 @@ suite('WorkspaceConfigurationService-Multiroot', () => { } }); - assert.equal(testObject.getValue('configurationService.workspace.testNewApplicationSetting', { resource: workspaceContextService.getWorkspace().folders[0].uri }), 'userValue'); + assert.strictEqual(testObject.getValue('configurationService.workspace.testNewApplicationSetting', { resource: workspaceContextService.getWorkspace().folders[0].uri }), 'userValue'); await testObject.reloadConfiguration(); - assert.equal(testObject.getValue('configurationService.workspace.testNewApplicationSetting', { resource: workspaceContextService.getWorkspace().folders[0].uri }), 'userValue'); + assert.strictEqual(testObject.getValue('configurationService.workspace.testNewApplicationSetting', { resource: workspaceContextService.getWorkspace().folders[0].uri }), 'userValue'); }); test('application settings are not read from workspace folder after defaults are registered', async () => { @@ -1369,7 +1508,7 @@ suite('WorkspaceConfigurationService-Multiroot', () => { await fileService.writeFile(workspaceContextService.getWorkspace().folders[0].toResource('.vscode/settings.json'), VSBuffer.fromString('{ "configurationService.workspace.testNewMachineSetting": "workspaceFolderValue" }')); await testObject.reloadConfiguration(); - assert.equal(testObject.getValue('configurationService.workspace.testNewMachineSetting', { resource: workspaceContextService.getWorkspace().folders[0].uri }), 'workspaceFolderValue'); + assert.strictEqual(testObject.getValue('configurationService.workspace.testNewMachineSetting', { resource: workspaceContextService.getWorkspace().folders[0].uri }), 'workspaceFolderValue'); configurationRegistry.registerConfiguration({ 'id': '_test', @@ -1383,10 +1522,10 @@ suite('WorkspaceConfigurationService-Multiroot', () => { } }); - assert.equal(testObject.getValue('configurationService.workspace.testNewMachineSetting', { resource: workspaceContextService.getWorkspace().folders[0].uri }), 'userValue'); + assert.strictEqual(testObject.getValue('configurationService.workspace.testNewMachineSetting', { resource: workspaceContextService.getWorkspace().folders[0].uri }), 'userValue'); await testObject.reloadConfiguration(); - assert.equal(testObject.getValue('configurationService.workspace.testNewMachineSetting', { resource: workspaceContextService.getWorkspace().folders[0].uri }), 'userValue'); + assert.strictEqual(testObject.getValue('configurationService.workspace.testNewMachineSetting', { resource: workspaceContextService.getWorkspace().folders[0].uri }), 'userValue'); }); test('resource setting in folder is read after it is registered later', async () => { @@ -1404,7 +1543,7 @@ suite('WorkspaceConfigurationService-Multiroot', () => { } } }); - assert.equal(testObject.getValue('configurationService.workspace.testNewResourceSetting2', { resource: workspaceContextService.getWorkspace().folders[0].uri }), 'workspaceFolderValue'); + assert.strictEqual(testObject.getValue('configurationService.workspace.testNewResourceSetting2', { resource: workspaceContextService.getWorkspace().folders[0].uri }), 'workspaceFolderValue'); }); test('resource language setting in folder is read after it is registered later', async () => { @@ -1422,7 +1561,7 @@ suite('WorkspaceConfigurationService-Multiroot', () => { } } }); - assert.equal(testObject.getValue('configurationService.workspace.testNewResourceLanguageSetting2', { resource: workspaceContextService.getWorkspace().folders[0].uri }), 'workspaceFolderValue'); + assert.strictEqual(testObject.getValue('configurationService.workspace.testNewResourceLanguageSetting2', { resource: workspaceContextService.getWorkspace().folders[0].uri }), 'workspaceFolderValue'); }); test('machine overridable setting in folder is read after it is registered later', async () => { @@ -1440,50 +1579,50 @@ suite('WorkspaceConfigurationService-Multiroot', () => { } } }); - assert.equal(testObject.getValue('configurationService.workspace.testNewMachineOverridableSetting2', { resource: workspaceContextService.getWorkspace().folders[0].uri }), 'workspaceFolderValue'); + assert.strictEqual(testObject.getValue('configurationService.workspace.testNewMachineOverridableSetting2', { resource: workspaceContextService.getWorkspace().folders[0].uri }), 'workspaceFolderValue'); }); test('inspect', async () => { let actual = testObject.inspect('something.missing'); - assert.equal(actual.defaultValue, undefined); - assert.equal(actual.userValue, undefined); - assert.equal(actual.workspaceValue, undefined); - assert.equal(actual.workspaceFolderValue, undefined); - assert.equal(actual.value, undefined); + assert.strictEqual(actual.defaultValue, undefined); + assert.strictEqual(actual.userValue, undefined); + assert.strictEqual(actual.workspaceValue, undefined); + assert.strictEqual(actual.workspaceFolderValue, undefined); + assert.strictEqual(actual.value, undefined); actual = testObject.inspect('configurationService.workspace.testResourceSetting'); - assert.equal(actual.defaultValue, 'isSet'); - assert.equal(actual.userValue, undefined); - assert.equal(actual.workspaceValue, undefined); - assert.equal(actual.workspaceFolderValue, undefined); - assert.equal(actual.value, 'isSet'); + assert.strictEqual(actual.defaultValue, 'isSet'); + assert.strictEqual(actual.userValue, undefined); + assert.strictEqual(actual.workspaceValue, undefined); + assert.strictEqual(actual.workspaceFolderValue, undefined); + assert.strictEqual(actual.value, 'isSet'); await fileService.writeFile(environmentService.settingsResource, VSBuffer.fromString('{ "configurationService.workspace.testResourceSetting": "userValue" }')); await testObject.reloadConfiguration(); actual = testObject.inspect('configurationService.workspace.testResourceSetting'); - assert.equal(actual.defaultValue, 'isSet'); - assert.equal(actual.userValue, 'userValue'); - assert.equal(actual.workspaceValue, undefined); - assert.equal(actual.workspaceFolderValue, undefined); - assert.equal(actual.value, 'userValue'); + assert.strictEqual(actual.defaultValue, 'isSet'); + assert.strictEqual(actual.userValue, 'userValue'); + assert.strictEqual(actual.workspaceValue, undefined); + assert.strictEqual(actual.workspaceFolderValue, undefined); + assert.strictEqual(actual.value, 'userValue'); await jsonEditingServce.write((workspaceContextService.getWorkspace().configuration!), [{ path: ['settings'], value: { 'configurationService.workspace.testResourceSetting': 'workspaceValue' } }], true); await testObject.reloadConfiguration(); actual = testObject.inspect('configurationService.workspace.testResourceSetting'); - assert.equal(actual.defaultValue, 'isSet'); - assert.equal(actual.userValue, 'userValue'); - assert.equal(actual.workspaceValue, 'workspaceValue'); - assert.equal(actual.workspaceFolderValue, undefined); - assert.equal(actual.value, 'workspaceValue'); + assert.strictEqual(actual.defaultValue, 'isSet'); + assert.strictEqual(actual.userValue, 'userValue'); + assert.strictEqual(actual.workspaceValue, 'workspaceValue'); + assert.strictEqual(actual.workspaceFolderValue, undefined); + assert.strictEqual(actual.value, 'workspaceValue'); await fileService.writeFile(workspaceContextService.getWorkspace().folders[0].toResource('.vscode/settings.json'), VSBuffer.fromString('{ "configurationService.workspace.testResourceSetting": "workspaceFolderValue" }')); await testObject.reloadConfiguration(); actual = testObject.inspect('configurationService.workspace.testResourceSetting', { resource: workspaceContextService.getWorkspace().folders[0].uri }); - assert.equal(actual.defaultValue, 'isSet'); - assert.equal(actual.userValue, 'userValue'); - assert.equal(actual.workspaceValue, 'workspaceValue'); - assert.equal(actual.workspaceFolderValue, 'workspaceFolderValue'); - assert.equal(actual.value, 'workspaceFolderValue'); + assert.strictEqual(actual.defaultValue, 'isSet'); + assert.strictEqual(actual.userValue, 'userValue'); + assert.strictEqual(actual.workspaceValue, 'workspaceValue'); + assert.strictEqual(actual.workspaceFolderValue, 'workspaceFolderValue'); + assert.strictEqual(actual.value, 'workspaceFolderValue'); }); test('get launch configuration', async () => { @@ -1506,7 +1645,7 @@ suite('WorkspaceConfigurationService-Multiroot', () => { await jsonEditingServce.write((workspaceContextService.getWorkspace().configuration!), [{ path: ['launch'], value: expectedLaunchConfiguration }], true); await testObject.reloadConfiguration(); const actual = testObject.getValue('launch'); - assert.deepEqual(actual, expectedLaunchConfiguration); + assert.deepStrictEqual(actual, expectedLaunchConfiguration); }); test('inspect launch configuration', async () => { @@ -1529,7 +1668,7 @@ suite('WorkspaceConfigurationService-Multiroot', () => { await jsonEditingServce.write((workspaceContextService.getWorkspace().configuration!), [{ path: ['launch'], value: expectedLaunchConfiguration }], true); await testObject.reloadConfiguration(); const actual = testObject.inspect('launch').workspaceValue; - assert.deepEqual(actual, expectedLaunchConfiguration); + assert.deepStrictEqual(actual, expectedLaunchConfiguration); }); @@ -1551,7 +1690,7 @@ suite('WorkspaceConfigurationService-Multiroot', () => { await jsonEditingServce.write((workspaceContextService.getWorkspace().configuration!), [{ path: ['tasks'], value: expectedTasksConfiguration }], true); await testObject.reloadConfiguration(); const actual = testObject.getValue('tasks'); - assert.deepEqual(actual, expectedTasksConfiguration); + assert.deepStrictEqual(actual, expectedTasksConfiguration); }); test('inspect tasks configuration', async () => { @@ -1572,12 +1711,12 @@ suite('WorkspaceConfigurationService-Multiroot', () => { await jsonEditingServce.write(workspaceContextService.getWorkspace().configuration!, [{ path: ['tasks'], value: expectedTasksConfiguration }], true); await testObject.reloadConfiguration(); const actual = testObject.inspect('tasks').workspaceValue; - assert.deepEqual(actual, expectedTasksConfiguration); + assert.deepStrictEqual(actual, expectedTasksConfiguration); }); test('update user configuration', async () => { await testObject.updateValue('configurationService.workspace.testSetting', 'userValue', ConfigurationTarget.USER); - assert.equal(testObject.getValue('configurationService.workspace.testSetting'), 'userValue'); + assert.strictEqual(testObject.getValue('configurationService.workspace.testSetting'), 'userValue'); }); test('update user configuration should trigger change event before promise is resolve', async () => { @@ -1589,7 +1728,7 @@ suite('WorkspaceConfigurationService-Multiroot', () => { test('update workspace configuration', async () => { await testObject.updateValue('configurationService.workspace.testSetting', 'workspaceValue', ConfigurationTarget.WORKSPACE); - assert.equal(testObject.getValue('configurationService.workspace.testSetting'), 'workspaceValue'); + assert.strictEqual(testObject.getValue('configurationService.workspace.testSetting'), 'workspaceValue'); }); test('update workspace configuration should trigger change event before promise is resolve', async () => { @@ -1601,24 +1740,24 @@ suite('WorkspaceConfigurationService-Multiroot', () => { test('update application setting into workspace configuration in a workspace is not supported', () => { return testObject.updateValue('configurationService.workspace.applicationSetting', 'workspaceValue', {}, ConfigurationTarget.WORKSPACE, true) - .then(() => assert.fail('Should not be supported'), (e) => assert.equal(e.code, ConfigurationEditingErrorCode.ERROR_INVALID_WORKSPACE_CONFIGURATION_APPLICATION)); + .then(() => assert.fail('Should not be supported'), (e) => assert.strictEqual(e.code, ConfigurationEditingErrorCode.ERROR_INVALID_WORKSPACE_CONFIGURATION_APPLICATION)); }); test('update machine setting into workspace configuration in a workspace is not supported', () => { return testObject.updateValue('configurationService.workspace.machineSetting', 'workspaceValue', {}, ConfigurationTarget.WORKSPACE, true) - .then(() => assert.fail('Should not be supported'), (e) => assert.equal(e.code, ConfigurationEditingErrorCode.ERROR_INVALID_WORKSPACE_CONFIGURATION_MACHINE)); + .then(() => assert.fail('Should not be supported'), (e) => assert.strictEqual(e.code, ConfigurationEditingErrorCode.ERROR_INVALID_WORKSPACE_CONFIGURATION_MACHINE)); }); test('update workspace folder configuration', () => { const workspace = workspaceContextService.getWorkspace(); return testObject.updateValue('configurationService.workspace.testResourceSetting', 'workspaceFolderValue', { resource: workspace.folders[0].uri }, ConfigurationTarget.WORKSPACE_FOLDER) - .then(() => assert.equal(testObject.getValue('configurationService.workspace.testResourceSetting', { resource: workspace.folders[0].uri }), 'workspaceFolderValue')); + .then(() => assert.strictEqual(testObject.getValue('configurationService.workspace.testResourceSetting', { resource: workspace.folders[0].uri }), 'workspaceFolderValue')); }); test('update resource language configuration in workspace folder', async () => { const workspace = workspaceContextService.getWorkspace(); await testObject.updateValue('configurationService.workspace.testLanguageSetting', 'workspaceFolderValue', { resource: workspace.folders[0].uri }, ConfigurationTarget.WORKSPACE_FOLDER); - assert.equal(testObject.getValue('configurationService.workspace.testLanguageSetting', { resource: workspace.folders[0].uri }), 'workspaceFolderValue'); + assert.strictEqual(testObject.getValue('configurationService.workspace.testLanguageSetting', { resource: workspace.folders[0].uri }), 'workspaceFolderValue'); }); test('update workspace folder configuration should trigger change event before promise is resolve', async () => { @@ -1640,7 +1779,7 @@ suite('WorkspaceConfigurationService-Multiroot', () => { test('update memory configuration', async () => { await testObject.updateValue('configurationService.workspace.testSetting', 'memoryValue', ConfigurationTarget.MEMORY); - assert.equal(testObject.getValue('configurationService.workspace.testSetting'), 'memoryValue'); + assert.strictEqual(testObject.getValue('configurationService.workspace.testSetting'), 'memoryValue'); }); test('update memory configuration should trigger change event before promise is resolve', async () => { @@ -1661,28 +1800,28 @@ suite('WorkspaceConfigurationService-Multiroot', () => { await testObject.reloadConfiguration(); const actual = testObject.inspect(key, { resource: workspace.folders[0].uri }); - assert.equal(actual.userValue, undefined); - assert.equal(actual.workspaceValue, undefined); - assert.equal(actual.workspaceFolderValue, undefined); + assert.strictEqual(actual.userValue, undefined); + assert.strictEqual(actual.workspaceValue, undefined); + assert.strictEqual(actual.workspaceFolderValue, undefined); }); test('update tasks configuration in a folder', async () => { const workspace = workspaceContextService.getWorkspace(); await testObject.updateValue('tasks', { 'version': '1.0.0', tasks: [{ 'taskName': 'myTask' }] }, { resource: workspace.folders[0].uri }, ConfigurationTarget.WORKSPACE_FOLDER); - assert.deepEqual(testObject.getValue('tasks', { resource: workspace.folders[0].uri }), { 'version': '1.0.0', tasks: [{ 'taskName': 'myTask' }] }); + assert.deepStrictEqual(testObject.getValue('tasks', { resource: workspace.folders[0].uri }), { 'version': '1.0.0', tasks: [{ 'taskName': 'myTask' }] }); }); test('update launch configuration in a workspace', async () => { const workspace = workspaceContextService.getWorkspace(); await testObject.updateValue('launch', { 'version': '1.0.0', configurations: [{ 'name': 'myLaunch' }] }, { resource: workspace.folders[0].uri }, ConfigurationTarget.WORKSPACE, true); - assert.deepEqual(testObject.getValue('launch'), { 'version': '1.0.0', configurations: [{ 'name': 'myLaunch' }] }); + assert.deepStrictEqual(testObject.getValue('launch'), { 'version': '1.0.0', configurations: [{ 'name': 'myLaunch' }] }); }); test('update tasks configuration in a workspace', async () => { const workspace = workspaceContextService.getWorkspace(); const tasks = { 'version': '2.0.0', tasks: [{ 'label': 'myTask' }] }; await testObject.updateValue('tasks', tasks, { resource: workspace.folders[0].uri }, ConfigurationTarget.WORKSPACE, true); - assert.deepEqual(testObject.getValue('tasks'), tasks); + assert.deepStrictEqual(testObject.getValue('tasks'), tasks); }); test('configuration of newly added folder is available on configuration change event', async () => { @@ -1694,7 +1833,7 @@ suite('WorkspaceConfigurationService-Multiroot', () => { return new Promise((c, e) => { testObject.onDidChangeConfiguration(() => { try { - assert.equal(testObject.getValue('configurationService.workspace.testResourceSetting', { resource: uri }), 'workspaceFolderValue'); + assert.strictEqual(testObject.getValue('configurationService.workspace.testResourceSetting', { resource: uri }), 'workspaceFolderValue'); c(); } catch (error) { e(error); @@ -1703,6 +1842,47 @@ suite('WorkspaceConfigurationService-Multiroot', () => { workspaceService.addFolders([{ uri }]); }); }); + + test('restricted setting is read from workspace folders when workspace is trusted', async () => { + testObject.updateWorkspaceTrust(true); + + await fileService.writeFile(environmentService.settingsResource, VSBuffer.fromString('{ "configurationService.workspace.testRestrictedSetting1": "userValue", "configurationService.workspace.testRestrictedSetting2": "userValue" }')); + await jsonEditingServce.write((workspaceContextService.getWorkspace().configuration!), [{ path: ['settings'], value: { 'configurationService.workspace.testRestrictedSetting1': 'workspaceValue' } }], true); + await fileService.writeFile(joinPath(testObject.getWorkspace().folders[1].uri, '.vscode', 'settings.json'), VSBuffer.fromString('{ "configurationService.workspace.testRestrictedSetting2": "workspaceFolder2Value" }')); + await testObject.reloadConfiguration(); + + assert.strictEqual(testObject.getValue('configurationService.workspace.testRestrictedSetting1', { resource: testObject.getWorkspace().folders[0].uri }), 'workspaceValue'); + assert.strictEqual(testObject.getValue('configurationService.workspace.testRestrictedSetting2', { resource: testObject.getWorkspace().folders[1].uri }), 'workspaceFolder2Value'); + assert.ok(testObject.restrictedSettings.default.includes('configurationService.workspace.testRestrictedSetting1')); + assert.ok(testObject.restrictedSettings.default.includes('configurationService.workspace.testRestrictedSetting2')); + assert.strictEqual(testObject.restrictedSettings.userLocal, undefined); + assert.strictEqual(testObject.restrictedSettings.userRemote, undefined); + assert.deepStrictEqual(testObject.restrictedSettings.workspace, ['configurationService.workspace.testRestrictedSetting1']); + assert.strictEqual(testObject.restrictedSettings.workspaceFolder?.size, 1); + assert.strictEqual(testObject.restrictedSettings.workspaceFolder?.get(testObject.getWorkspace().folders[0].uri), undefined); + assert.deepStrictEqual(testObject.restrictedSettings.workspaceFolder?.get(testObject.getWorkspace().folders[1].uri), ['configurationService.workspace.testRestrictedSetting2']); + }); + + test('restricted setting is not read from workspace when workspace is not trusted', async () => { + testObject.updateWorkspaceTrust(false); + + await fileService.writeFile(environmentService.settingsResource, VSBuffer.fromString('{ "configurationService.workspace.testRestrictedSetting1": "userValue", "configurationService.workspace.testRestrictedSetting2": "userValue" }')); + await jsonEditingServce.write((workspaceContextService.getWorkspace().configuration!), [{ path: ['settings'], value: { 'configurationService.workspace.testRestrictedSetting1': 'workspaceValue' } }], true); + await fileService.writeFile(joinPath(testObject.getWorkspace().folders[1].uri, '.vscode', 'settings.json'), VSBuffer.fromString('{ "configurationService.workspace.testRestrictedSetting2": "workspaceFolder2Value" }')); + await testObject.reloadConfiguration(); + + assert.strictEqual(testObject.getValue('configurationService.workspace.testRestrictedSetting1', { resource: testObject.getWorkspace().folders[0].uri }), 'userValue'); + assert.strictEqual(testObject.getValue('configurationService.workspace.testRestrictedSetting2', { resource: testObject.getWorkspace().folders[1].uri }), 'userValue'); + assert.ok(testObject.restrictedSettings.default.includes('configurationService.workspace.testRestrictedSetting1')); + assert.ok(testObject.restrictedSettings.default.includes('configurationService.workspace.testRestrictedSetting2')); + assert.strictEqual(testObject.restrictedSettings.userLocal, undefined); + assert.strictEqual(testObject.restrictedSettings.userRemote, undefined); + assert.deepStrictEqual(testObject.restrictedSettings.workspace, ['configurationService.workspace.testRestrictedSetting1']); + assert.strictEqual(testObject.restrictedSettings.workspaceFolder?.size, 1); + assert.strictEqual(testObject.restrictedSettings.workspaceFolder?.get(testObject.getWorkspace().folders[0].uri), undefined); + assert.deepStrictEqual(testObject.restrictedSettings.workspaceFolder?.get(testObject.getWorkspace().folders[1].uri), ['configurationService.workspace.testRestrictedSetting2']); + }); + }); suite('WorkspaceConfigurationService - Remote Folder', () => { @@ -1796,7 +1976,7 @@ suite('WorkspaceConfigurationService - Remote Folder', () => { registerRemoteFileSystemProvider(); resolveRemoteEnvironment(); await initialize(); - assert.equal(testObject.getValue('configurationService.remote.machineSetting'), 'remoteValue'); + assert.strictEqual(testObject.getValue('configurationService.remote.machineSetting'), 'remoteValue'); }); test('remote settings override globals after remote provider is registered on activation', async () => { @@ -1804,7 +1984,7 @@ suite('WorkspaceConfigurationService - Remote Folder', () => { resolveRemoteEnvironment(); registerRemoteFileSystemProviderOnActivation(); await initialize(); - assert.equal(testObject.getValue('configurationService.remote.machineSetting'), 'remoteValue'); + assert.strictEqual(testObject.getValue('configurationService.remote.machineSetting'), 'remoteValue'); }); test('remote settings override globals after remote environment is resolved', async () => { @@ -1814,9 +1994,9 @@ suite('WorkspaceConfigurationService - Remote Folder', () => { const promise = new Promise((c, e) => { testObject.onDidChangeConfiguration(event => { try { - assert.equal(event.source, ConfigurationTarget.USER); - assert.deepEqual(event.affectedKeys, ['configurationService.remote.machineSetting']); - assert.equal(testObject.getValue('configurationService.remote.machineSetting'), 'remoteValue'); + assert.strictEqual(event.source, ConfigurationTarget.USER); + assert.deepStrictEqual(event.affectedKeys, ['configurationService.remote.machineSetting']); + assert.strictEqual(testObject.getValue('configurationService.remote.machineSetting'), 'remoteValue'); c(); } catch (error) { e(error); @@ -1834,9 +2014,9 @@ suite('WorkspaceConfigurationService - Remote Folder', () => { const promise = new Promise((c, e) => { testObject.onDidChangeConfiguration(event => { try { - assert.equal(event.source, ConfigurationTarget.USER); - assert.deepEqual(event.affectedKeys, ['configurationService.remote.machineSetting']); - assert.equal(testObject.getValue('configurationService.remote.machineSetting'), 'remoteValue'); + assert.strictEqual(event.source, ConfigurationTarget.USER); + assert.deepStrictEqual(event.affectedKeys, ['configurationService.remote.machineSetting']); + assert.strictEqual(testObject.getValue('configurationService.remote.machineSetting'), 'remoteValue'); c(); } catch (error) { e(error); @@ -1852,7 +2032,7 @@ suite('WorkspaceConfigurationService - Remote Folder', () => { registerRemoteFileSystemProvider(); resolveRemoteEnvironment(); await initialize(); - assert.equal(testObject.getValue('configurationService.remote.machineSetting'), 'isSet'); + assert.strictEqual(testObject.getValue('configurationService.remote.machineSetting'), 'isSet'); }); test('machine overridable settings in local user settings does not override defaults', async () => { @@ -1860,7 +2040,7 @@ suite('WorkspaceConfigurationService - Remote Folder', () => { registerRemoteFileSystemProvider(); resolveRemoteEnvironment(); await initialize(); - assert.equal(testObject.getValue('configurationService.remote.machineOverridableSetting'), 'isSet'); + assert.strictEqual(testObject.getValue('configurationService.remote.machineOverridableSetting'), 'isSet'); }); test('non machine setting is written in local settings', async () => { @@ -1869,7 +2049,7 @@ suite('WorkspaceConfigurationService - Remote Folder', () => { await initialize(); await testObject.updateValue('configurationService.remote.applicationSetting', 'applicationValue'); await testObject.reloadConfiguration(); - assert.equal(testObject.inspect('configurationService.remote.applicationSetting').userLocalValue, 'applicationValue'); + assert.strictEqual(testObject.inspect('configurationService.remote.applicationSetting').userLocalValue, 'applicationValue'); }); test('machine setting is written in remote settings', async () => { @@ -1878,7 +2058,7 @@ suite('WorkspaceConfigurationService - Remote Folder', () => { await initialize(); await testObject.updateValue('configurationService.remote.machineSetting', 'machineValue'); await testObject.reloadConfiguration(); - assert.equal(testObject.inspect('configurationService.remote.machineSetting').userRemoteValue, 'machineValue'); + assert.strictEqual(testObject.inspect('configurationService.remote.machineSetting').userRemoteValue, 'machineValue'); }); test('machine overridable setting is written in remote settings', async () => { @@ -1887,7 +2067,7 @@ suite('WorkspaceConfigurationService - Remote Folder', () => { await initialize(); await testObject.updateValue('configurationService.remote.machineOverridableSetting', 'machineValue'); await testObject.reloadConfiguration(); - assert.equal(testObject.inspect('configurationService.remote.machineOverridableSetting').userRemoteValue, 'machineValue'); + assert.strictEqual(testObject.inspect('configurationService.remote.machineOverridableSetting').userRemoteValue, 'machineValue'); }); test('machine settings in local user settings does not override defaults after defalts are registered ', async () => { @@ -1906,7 +2086,7 @@ suite('WorkspaceConfigurationService - Remote Folder', () => { } } }); - assert.equal(testObject.getValue('configurationService.remote.newMachineSetting'), 'isSet'); + assert.strictEqual(testObject.getValue('configurationService.remote.newMachineSetting'), 'isSet'); }); test('machine overridable settings in local user settings does not override defaults after defaults are registered ', async () => { @@ -1925,7 +2105,7 @@ suite('WorkspaceConfigurationService - Remote Folder', () => { } } }); - assert.equal(testObject.getValue('configurationService.remote.newMachineOverridableSetting'), 'isSet'); + assert.strictEqual(testObject.getValue('configurationService.remote.newMachineOverridableSetting'), 'isSet'); }); }); @@ -1951,12 +2131,12 @@ suite('ConfigurationService - Configuration Defaults', () => { test('when default value is not overriden', () => { const testObject = createConfigurationService({}); - assert.deepEqual(testObject.getValue('configurationService.defaultOverridesSetting'), 'isSet'); + assert.deepStrictEqual(testObject.getValue('configurationService.defaultOverridesSetting'), 'isSet'); }); test('when default value is overriden', () => { const testObject = createConfigurationService({ 'configurationService.defaultOverridesSetting': 'overriddenValue' }); - assert.deepEqual(testObject.getValue('configurationService.defaultOverridesSetting'), 'overriddenValue'); + assert.deepStrictEqual(testObject.getValue('configurationService.defaultOverridesSetting'), 'overriddenValue'); }); function createConfigurationService(configurationDefaults: Record): IConfigurationService { diff --git a/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts b/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts index ef4976ce..228cc131 100644 --- a/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts +++ b/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import { Registry } from 'vs/platform/registry/common/platform'; import { StandaloneConfigurationModelParser, Configuration } from 'vs/workbench/services/configuration/common/configurationModels'; -import { ConfigurationModelParser, ConfigurationModel } from 'vs/platform/configuration/common/configurationModels'; +import { ConfigurationModelParser, ConfigurationModel, ConfigurationParseOptions } from 'vs/platform/configuration/common/configurationModels'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { ResourceMap } from 'vs/base/common/map'; import { WorkspaceFolder } from 'vs/platform/workspace/common/workspace'; @@ -49,35 +49,51 @@ suite('FolderSettingsModelParser', () => { }); test('parse all folder settings', () => { - const testObject = new ConfigurationModelParser('settings', [ConfigurationScope.RESOURCE, ConfigurationScope.WINDOW]); + const testObject = new ConfigurationModelParser('settings'); - testObject.parseContent(JSON.stringify({ 'FolderSettingsModelParser.window': 'window', 'FolderSettingsModelParser.resource': 'resource', 'FolderSettingsModelParser.application': 'application', 'FolderSettingsModelParser.machine': 'executable' })); + testObject.parse(JSON.stringify({ 'FolderSettingsModelParser.window': 'window', 'FolderSettingsModelParser.resource': 'resource', 'FolderSettingsModelParser.application': 'application', 'FolderSettingsModelParser.machine': 'executable' }), { scopes: [ConfigurationScope.RESOURCE, ConfigurationScope.WINDOW] }); - assert.deepEqual(testObject.configurationModel.contents, { 'FolderSettingsModelParser': { 'window': 'window', 'resource': 'resource' } }); + const expected = Object.create(null); + expected['FolderSettingsModelParser'] = Object.create(null); + expected['FolderSettingsModelParser']['window'] = 'window'; + expected['FolderSettingsModelParser']['resource'] = 'resource'; + assert.deepStrictEqual(testObject.configurationModel.contents, expected); }); test('parse resource folder settings', () => { - const testObject = new ConfigurationModelParser('settings', [ConfigurationScope.RESOURCE]); + const testObject = new ConfigurationModelParser('settings'); - testObject.parseContent(JSON.stringify({ 'FolderSettingsModelParser.window': 'window', 'FolderSettingsModelParser.resource': 'resource', 'FolderSettingsModelParser.application': 'application', 'FolderSettingsModelParser.machine': 'executable' })); + testObject.parse(JSON.stringify({ 'FolderSettingsModelParser.window': 'window', 'FolderSettingsModelParser.resource': 'resource', 'FolderSettingsModelParser.application': 'application', 'FolderSettingsModelParser.machine': 'executable' }), { scopes: [ConfigurationScope.RESOURCE] }); - assert.deepEqual(testObject.configurationModel.contents, { 'FolderSettingsModelParser': { 'resource': 'resource' } }); + const expected = Object.create(null); + expected['FolderSettingsModelParser'] = Object.create(null); + expected['FolderSettingsModelParser']['resource'] = 'resource'; + assert.deepStrictEqual(testObject.configurationModel.contents, expected); }); test('parse resource and resource language settings', () => { - const testObject = new ConfigurationModelParser('settings', [ConfigurationScope.RESOURCE, ConfigurationScope.LANGUAGE_OVERRIDABLE]); + const testObject = new ConfigurationModelParser('settings'); - testObject.parseContent(JSON.stringify({ '[json]': { 'FolderSettingsModelParser.window': 'window', 'FolderSettingsModelParser.resource': 'resource', 'FolderSettingsModelParser.resourceLanguage': 'resourceLanguage', 'FolderSettingsModelParser.application': 'application', 'FolderSettingsModelParser.machine': 'executable' } })); + testObject.parse(JSON.stringify({ '[json]': { 'FolderSettingsModelParser.window': 'window', 'FolderSettingsModelParser.resource': 'resource', 'FolderSettingsModelParser.resourceLanguage': 'resourceLanguage', 'FolderSettingsModelParser.application': 'application', 'FolderSettingsModelParser.machine': 'executable' } }), { scopes: [ConfigurationScope.RESOURCE, ConfigurationScope.LANGUAGE_OVERRIDABLE] }); - assert.deepEqual(testObject.configurationModel.overrides, [{ 'contents': { 'FolderSettingsModelParser': { 'resource': 'resource', 'resourceLanguage': 'resourceLanguage' } }, 'identifiers': ['json'], 'keys': ['FolderSettingsModelParser.resource', 'FolderSettingsModelParser.resourceLanguage'] }]); + const expected = Object.create(null); + expected['FolderSettingsModelParser'] = Object.create(null); + expected['FolderSettingsModelParser']['resource'] = 'resource'; + expected['FolderSettingsModelParser']['resourceLanguage'] = 'resourceLanguage'; + assert.deepStrictEqual(testObject.configurationModel.overrides, [{ 'contents': expected, 'identifiers': ['json'], 'keys': ['FolderSettingsModelParser.resource', 'FolderSettingsModelParser.resourceLanguage'] }]); }); - test('reprocess folder settings excludes application and machine setting', () => { - const testObject = new ConfigurationModelParser('settings', [ConfigurationScope.RESOURCE, ConfigurationScope.WINDOW]); + test('reparse folder settings excludes application and machine setting', () => { + const parseOptions: ConfigurationParseOptions = { scopes: [ConfigurationScope.RESOURCE, ConfigurationScope.WINDOW] }; + const testObject = new ConfigurationModelParser('settings'); - testObject.parseContent(JSON.stringify({ 'FolderSettingsModelParser.resource': 'resource', 'FolderSettingsModelParser.anotherApplicationSetting': 'executable' })); + testObject.parse(JSON.stringify({ 'FolderSettingsModelParser.resource': 'resource', 'FolderSettingsModelParser.anotherApplicationSetting': 'executable' }), parseOptions); - assert.deepEqual(testObject.configurationModel.contents, { 'FolderSettingsModelParser': { 'resource': 'resource', 'anotherApplicationSetting': 'executable' } }); + let expected = Object.create(null); + expected['FolderSettingsModelParser'] = Object.create(null); + expected['FolderSettingsModelParser']['resource'] = 'resource'; + expected['FolderSettingsModelParser']['anotherApplicationSetting'] = 'executable'; + assert.deepStrictEqual(testObject.configurationModel.contents, expected); const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); configurationRegistry.registerConfiguration({ @@ -97,8 +113,12 @@ suite('FolderSettingsModelParser', () => { } }); - testObject.parse(); - assert.deepEqual(testObject.configurationModel.contents, { 'FolderSettingsModelParser': { 'resource': 'resource' } }); + testObject.reparse(parseOptions); + + expected = Object.create(null); + expected['FolderSettingsModelParser'] = Object.create(null); + expected['FolderSettingsModelParser']['resource'] = 'resource'; + assert.deepStrictEqual(testObject.configurationModel.contents, expected); }); }); @@ -108,9 +128,13 @@ suite('StandaloneConfigurationModelParser', () => { test('parse tasks stand alone configuration model', () => { const testObject = new StandaloneConfigurationModelParser('tasks', 'tasks'); - testObject.parseContent(JSON.stringify({ 'version': '1.1.1', 'tasks': [] })); + testObject.parse(JSON.stringify({ 'version': '1.1.1', 'tasks': [] })); - assert.deepEqual(testObject.configurationModel.contents, { 'tasks': { 'version': '1.1.1', 'tasks': [] } }); + const expected = Object.create(null); + expected['tasks'] = Object.create(null); + expected['tasks']['version'] = '1.1.1'; + expected['tasks']['tasks'] = []; + assert.deepStrictEqual(testObject.configurationModel.contents, expected); }); }); @@ -147,7 +171,7 @@ suite('Workspace Configuration', () => { const actual = configuration2.compare(configuration1); - assert.deepEqual(actual, { keys: [], overrides: [] }); + assert.deepStrictEqual(actual, { keys: [], overrides: [] }); }); test('Test compare different configurations', () => { @@ -173,7 +197,7 @@ suite('Workspace Configuration', () => { const actual = configuration2.compare(configuration1); - assert.deepEqual(actual, { keys: ['editor.wordWrap', 'editor.fontSize', '[markdown]', 'window.title', 'workbench.enableTabs', '[typescript]'], overrides: [['markdown', ['editor.lineNumbers', 'editor.wordWrap']], ['typescript', ['editor.insertSpaces']]] }); + assert.deepStrictEqual(actual, { keys: ['editor.wordWrap', 'editor.fontSize', '[markdown]', 'window.title', 'workbench.enableTabs', '[typescript]'], overrides: [['markdown', ['editor.lineNumbers', 'editor.wordWrap']], ['typescript', ['editor.insertSpaces']]] }); }); @@ -181,6 +205,6 @@ suite('Workspace Configuration', () => { function toConfigurationModel(obj: any): ConfigurationModel { const parser = new ConfigurationModelParser('test'); - parser.parseContent(JSON.stringify(obj)); + parser.parse(JSON.stringify(obj)); return parser.configurationModel; } diff --git a/src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts b/src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts index 1f775814..1b64e412 100644 --- a/src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts +++ b/src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts @@ -9,7 +9,6 @@ import * as Types from 'vs/base/common/types'; import { Schemas } from 'vs/base/common/network'; import { SideBySideEditor, EditorResourceAccessor } from 'vs/workbench/common/editor'; import { IStringDictionary, forEach, fromMap } from 'vs/base/common/collections'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IConfigurationService, IConfigurationOverrides, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IWorkspaceFolder, IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; @@ -30,7 +29,7 @@ export abstract class BaseConfigurationResolverService extends AbstractVariableR getAppRoot: () => string | undefined, getExecPath: () => string | undefined }, - envVariables: IProcessEnvironment, + envVariablesPromise: Promise, editorService: IEditorService, private readonly configurationService: IConfigurationService, private readonly commandService: ICommandService, @@ -101,12 +100,12 @@ export abstract class BaseConfigurationResolverService extends AbstractVariableR } return undefined; } - }, labelService, envVariables); + }, labelService, envVariablesPromise); } - public async resolveWithInteractionReplace(folder: IWorkspaceFolder | undefined, config: any, section?: string, variables?: IStringDictionary, target?: ConfigurationTarget): Promise { + public override async resolveWithInteractionReplace(folder: IWorkspaceFolder | undefined, config: any, section?: string, variables?: IStringDictionary, target?: ConfigurationTarget): Promise { // resolve any non-interactive variables and any contributed variables - config = this.resolveAny(folder, config); + config = await this.resolveAnyAsync(folder, config); // resolve input variables in the order in which they are encountered return this.resolveWithInteraction(folder, config, section, variables, target).then(mapping => { @@ -114,14 +113,14 @@ export abstract class BaseConfigurationResolverService extends AbstractVariableR if (!mapping) { return null; } else if (mapping.size > 0) { - return this.resolveAny(folder, config, fromMap(mapping)); + return this.resolveAnyAsync(folder, config, fromMap(mapping)); } else { return config; } }); } - public async resolveWithInteraction(folder: IWorkspaceFolder | undefined, config: any, section?: string, variables?: IStringDictionary, target?: ConfigurationTarget): Promise | undefined> { + public override async resolveWithInteraction(folder: IWorkspaceFolder | undefined, config: any, section?: string, variables?: IStringDictionary, target?: ConfigurationTarget): Promise | undefined> { // resolve any non-interactive variables and any contributed variables const resolved = await this.resolveAnyMap(folder, config); config = resolved.newConfig; @@ -362,13 +361,14 @@ export class ConfigurationResolverService extends BaseConfigurationResolverServi constructor( @IEditorService editorService: IEditorService, - @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, @IConfigurationService configurationService: IConfigurationService, @ICommandService commandService: ICommandService, @IWorkspaceContextService workspaceContextService: IWorkspaceContextService, @IQuickInputService quickInputService: IQuickInputService, - @ILabelService labelService: ILabelService + @ILabelService labelService: ILabelService, ) { - super({ getAppRoot: () => undefined, getExecPath: () => undefined }, Object.create(null), editorService, configurationService, commandService, workspaceContextService, quickInputService, labelService); + super({ getAppRoot: () => undefined, getExecPath: () => undefined }, + Promise.resolve(Object.create(null)), editorService, configurationService, + commandService, workspaceContextService, quickInputService, labelService); } } diff --git a/src/vs/workbench/services/configurationResolver/common/configurationResolver.ts b/src/vs/workbench/services/configurationResolver/common/configurationResolver.ts index 0bd4c9e6..68a71a2b 100644 --- a/src/vs/workbench/services/configurationResolver/common/configurationResolver.ts +++ b/src/vs/workbench/services/configurationResolver/common/configurationResolver.ts @@ -7,21 +7,24 @@ import { IStringDictionary } from 'vs/base/common/collections'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; +import { IProcessEnvironment } from 'vs/base/common/platform'; export const IConfigurationResolverService = createDecorator('configurationResolverService'); export interface IConfigurationResolverService { readonly _serviceBrand: undefined; - resolve(folder: IWorkspaceFolder | undefined, value: string): string; - resolve(folder: IWorkspaceFolder | undefined, value: string[]): string[]; - resolve(folder: IWorkspaceFolder | undefined, value: IStringDictionary): IStringDictionary; + resolveWithEnvironment(environment: IProcessEnvironment, folder: IWorkspaceFolder | undefined, value: string): string; + + resolveAsync(folder: IWorkspaceFolder | undefined, value: string): Promise; + resolveAsync(folder: IWorkspaceFolder | undefined, value: string[]): Promise; + resolveAsync(folder: IWorkspaceFolder | undefined, value: IStringDictionary): Promise>; /** * Recursively resolves all variables in the given config and returns a copy of it with substituted values. * Command variables are only substituted if a "commandValueMapping" dictionary is given and if it contains an entry for the command. */ - resolveAny(folder: IWorkspaceFolder | undefined, config: any, commandValueMapping?: IStringDictionary): any; + resolveAnyAsync(folder: IWorkspaceFolder | undefined, config: any, commandValueMapping?: IStringDictionary): Promise; /** * Recursively resolves all variables (including commands and user input) in the given config and returns a copy of it with substituted values. diff --git a/src/vs/workbench/services/configurationResolver/common/variableResolver.ts b/src/vs/workbench/services/configurationResolver/common/variableResolver.ts index 507dd859..cb4efa5e 100644 --- a/src/vs/workbench/services/configurationResolver/common/variableResolver.ts +++ b/src/vs/workbench/services/configurationResolver/common/variableResolver.ts @@ -37,35 +37,43 @@ export class AbstractVariableResolverService implements IConfigurationResolverSe private _context: IVariableResolveContext; private _labelService?: ILabelService; - private _envVariables?: IProcessEnvironment; + private _envVariablesPromise?: Promise; protected _contributedVariables: Map Promise> = new Map(); - - constructor(_context: IVariableResolveContext, _labelService?: ILabelService, _envVariables?: IProcessEnvironment) { + constructor(_context: IVariableResolveContext, _labelService?: ILabelService, _envVariablesPromise?: Promise) { this._context = _context; this._labelService = _labelService; - if (_envVariables) { - if (isWindows) { - // windows env variables are case insensitive - const ev: IProcessEnvironment = Object.create(null); - this._envVariables = ev; - Object.keys(_envVariables).forEach(key => { - ev[key.toLowerCase()] = _envVariables[key]; - }); - } else { - this._envVariables = _envVariables; - } + if (_envVariablesPromise) { + this._envVariablesPromise = _envVariablesPromise.then(envVariables => { + return this.prepareEnv(envVariables); + }); } } - public resolve(root: IWorkspaceFolder | undefined, value: string): string; - public resolve(root: IWorkspaceFolder | undefined, value: string[]): string[]; - public resolve(root: IWorkspaceFolder | undefined, value: IStringDictionary): IStringDictionary; - public resolve(root: IWorkspaceFolder | undefined, value: any): any { - return this.recursiveResolve(root ? root.uri : undefined, value); + private prepareEnv(envVariables: IProcessEnvironment): IProcessEnvironment { + // windows env variables are case insensitive + if (isWindows) { + const ev: IProcessEnvironment = Object.create(null); + Object.keys(envVariables).forEach(key => { + ev[key.toLowerCase()] = envVariables[key]; + }); + return ev; + } + return envVariables; } - public resolveAnyBase(workspaceFolder: IWorkspaceFolder | undefined, config: any, commandValueMapping?: IStringDictionary, resolvedVariables?: Map): any { + public resolveWithEnvironment(environment: IProcessEnvironment, root: IWorkspaceFolder | undefined, value: string): string { + return this.recursiveResolve(this.prepareEnv(environment), root ? root.uri : undefined, value); + } + + public async resolveAsync(root: IWorkspaceFolder | undefined, value: string): Promise; + public async resolveAsync(root: IWorkspaceFolder | undefined, value: string[]): Promise; + public async resolveAsync(root: IWorkspaceFolder | undefined, value: IStringDictionary): Promise>; + public async resolveAsync(root: IWorkspaceFolder | undefined, value: any): Promise { + return this.recursiveResolve(await this._envVariablesPromise, root ? root.uri : undefined, value); + } + + private async resolveAnyBase(workspaceFolder: IWorkspaceFolder | undefined, config: any, commandValueMapping?: IStringDictionary, resolvedVariables?: Map): Promise { const result = objects.deepClone(config) as any; @@ -84,16 +92,16 @@ export class AbstractVariableResolverService implements IConfigurationResolverSe delete result.linux; // substitute all variables recursively in string values - return this.recursiveResolve(workspaceFolder ? workspaceFolder.uri : undefined, result, commandValueMapping, resolvedVariables); + return this.recursiveResolve(await this._envVariablesPromise, workspaceFolder ? workspaceFolder.uri : undefined, result, commandValueMapping, resolvedVariables); } - public resolveAny(workspaceFolder: IWorkspaceFolder | undefined, config: any, commandValueMapping?: IStringDictionary): any { + public async resolveAnyAsync(workspaceFolder: IWorkspaceFolder | undefined, config: any, commandValueMapping?: IStringDictionary): Promise { return this.resolveAnyBase(workspaceFolder, config, commandValueMapping); } - public resolveAnyMap(workspaceFolder: IWorkspaceFolder | undefined, config: any, commandValueMapping?: IStringDictionary): { newConfig: any, resolvedVariables: Map } { + protected async resolveAnyMap(workspaceFolder: IWorkspaceFolder | undefined, config: any, commandValueMapping?: IStringDictionary): Promise<{ newConfig: any, resolvedVariables: Map }> { const resolvedVariables = new Map(); - const newConfig = this.resolveAnyBase(workspaceFolder, config, commandValueMapping, resolvedVariables); + const newConfig = await this.resolveAnyBase(workspaceFolder, config, commandValueMapping, resolvedVariables); return { newConfig, resolvedVariables }; } @@ -113,23 +121,23 @@ export class AbstractVariableResolverService implements IConfigurationResolverSe } } - private recursiveResolve(folderUri: uri | undefined, value: any, commandValueMapping?: IStringDictionary, resolvedVariables?: Map): any { + private recursiveResolve(environment: IProcessEnvironment | undefined, folderUri: uri | undefined, value: any, commandValueMapping?: IStringDictionary, resolvedVariables?: Map): any { if (types.isString(value)) { - return this.resolveString(folderUri, value, commandValueMapping, resolvedVariables); + return this.resolveString(environment, folderUri, value, commandValueMapping, resolvedVariables); } else if (types.isArray(value)) { - return value.map(s => this.recursiveResolve(folderUri, s, commandValueMapping, resolvedVariables)); + return value.map(s => this.recursiveResolve(environment, folderUri, s, commandValueMapping, resolvedVariables)); } else if (types.isObject(value)) { let result: IStringDictionary | string[]> = Object.create(null); Object.keys(value).forEach(key => { - const replaced = this.resolveString(folderUri, key, commandValueMapping, resolvedVariables); - result[replaced] = this.recursiveResolve(folderUri, value[key], commandValueMapping, resolvedVariables); + const replaced = this.resolveString(environment, folderUri, key, commandValueMapping, resolvedVariables); + result[replaced] = this.recursiveResolve(environment, folderUri, value[key], commandValueMapping, resolvedVariables); }); return result; } return value; } - private resolveString(folderUri: uri | undefined, value: string, commandValueMapping: IStringDictionary | undefined, resolvedVariables?: Map): string { + private resolveString(environment: IProcessEnvironment | undefined, folderUri: uri | undefined, value: string, commandValueMapping: IStringDictionary | undefined, resolvedVariables?: Map): string { // loop through all variables occurrences in 'value' const replaced = value.replace(AbstractVariableResolverService.VARIABLE_REGEXP, (match: string, variable: string) => { @@ -138,7 +146,7 @@ export class AbstractVariableResolverService implements IConfigurationResolverSe return match; } - let resolvedValue = this.evaluateSingleVariable(match, variable, folderUri, commandValueMapping); + let resolvedValue = this.evaluateSingleVariable(environment, match, variable, folderUri, commandValueMapping); if (resolvedVariables) { resolvedVariables.set(variable, resolvedValue); @@ -154,7 +162,7 @@ export class AbstractVariableResolverService implements IConfigurationResolverSe return this._labelService ? this._labelService.getUriLabel(displayUri, { noPrefix: true }) : displayUri.fsPath; } - private evaluateSingleVariable(match: string, variable: string, folderUri: uri | undefined, commandValueMapping: IStringDictionary | undefined): string { + private evaluateSingleVariable(environment: IProcessEnvironment | undefined, match: string, variable: string, folderUri: uri | undefined, commandValueMapping: IStringDictionary | undefined): string { // try to separate variable arguments from variable name let argument: string | undefined; @@ -213,8 +221,9 @@ export class AbstractVariableResolverService implements IConfigurationResolverSe case 'env': if (argument) { - if (this._envVariables) { - const env = this._envVariables[isWindows ? argument.toLowerCase() : argument]; + if (environment) { + // Depending on the source of the environment, on Windows, the values may all be lowercase. + const env = environment[isWindows ? argument.toLowerCase() : argument]; if (types.isString(env)) { return env; } diff --git a/src/vs/workbench/services/configurationResolver/electron-sandbox/configurationResolverService.ts b/src/vs/workbench/services/configurationResolver/electron-sandbox/configurationResolverService.ts index 5ff57578..cf99451c 100644 --- a/src/vs/workbench/services/configurationResolver/electron-sandbox/configurationResolverService.ts +++ b/src/vs/workbench/services/configurationResolver/electron-sandbox/configurationResolverService.ts @@ -12,8 +12,8 @@ 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 { BaseConfigurationResolverService } from 'vs/workbench/services/configurationResolver/browser/configurationResolverService'; -import { env } from 'vs/base/common/process'; import { ILabelService } from 'vs/platform/label/common/label'; +import { IShellEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/shellEnvironmentService'; export class ConfigurationResolverService extends BaseConfigurationResolverService { @@ -24,7 +24,8 @@ export class ConfigurationResolverService extends BaseConfigurationResolverServi @ICommandService commandService: ICommandService, @IWorkspaceContextService workspaceContextService: IWorkspaceContextService, @IQuickInputService quickInputService: IQuickInputService, - @ILabelService labelService: ILabelService + @ILabelService labelService: ILabelService, + @IShellEnvironmentService shellEnvironmentService: IShellEnvironmentService ) { super({ getAppRoot: (): string | undefined => { @@ -33,7 +34,7 @@ export class ConfigurationResolverService extends BaseConfigurationResolverServi getExecPath: (): string | undefined => { return environmentService.execPath; } - }, env, editorService, configurationService, commandService, workspaceContextService, quickInputService, labelService); + }, shellEnvironmentService.getShellEnv(), 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 37dc83af..c1b218f8 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 @@ -8,6 +8,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { normalize } from 'vs/base/common/path'; import * as platform from 'vs/base/common/platform'; +import { isObject } from 'vs/base/common/types'; import { URI as uri } from 'vs/base/common/uri'; import { Selection } from 'vs/editor/common/core/selection'; import { EditorType } from 'vs/editor/common/editorCommon'; @@ -23,11 +24,11 @@ import { IConfigurationResolverService } from 'vs/workbench/services/configurati 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, TestEnvironmentPaths } from 'vs/workbench/test/electron-browser/workbenchTestServices'; +import { TestWorkbenchConfiguration } from 'vs/workbench/test/electron-browser/workbenchTestServices'; const mockLineNumber = 10; class TestEditorServiceWithActiveEditor extends TestEditorService { - get activeTextEditorControl(): any { + override get activeTextEditorControl(): any { return { getEditorType() { return EditorType.ICodeEditor; @@ -37,7 +38,7 @@ class TestEditorServiceWithActiveEditor extends TestEditorService { } }; } - get activeEditor(): any { + override get activeEditor(): any { return { get resource(): any { return uri.parse('file:///VSCode/workspaceLocation/file'); @@ -74,111 +75,111 @@ suite('Configuration Resolver Service', () => { labelService = new MockLabelService(); containingWorkspace = testWorkspace(uri.parse('file:///VSCode/workspaceLocation')); workspace = containingWorkspace.folders[0]; - configurationResolverService = new TestConfigurationResolverService(nullContext, environmentService.userEnv, editorService, new MockInputsConfigurationService(), mockCommandService, new TestContextService(containingWorkspace), quickInputService, labelService); + configurationResolverService = new TestConfigurationResolverService(nullContext, Promise.resolve(environmentService.userEnv), editorService, new MockInputsConfigurationService(), mockCommandService, new TestContextService(containingWorkspace), quickInputService, labelService); }); teardown(() => { configurationResolverService = null; }); - test('substitute one', () => { + test('substitute one', async () => { if (platform.isWindows) { - assert.strictEqual(configurationResolverService!.resolve(workspace, 'abc ${workspaceFolder} xyz'), 'abc \\VSCode\\workspaceLocation xyz'); + assert.strictEqual(await configurationResolverService!.resolveAsync(workspace, 'abc ${workspaceFolder} xyz'), 'abc \\VSCode\\workspaceLocation xyz'); } else { - assert.strictEqual(configurationResolverService!.resolve(workspace, 'abc ${workspaceFolder} xyz'), 'abc /VSCode/workspaceLocation xyz'); + assert.strictEqual(await configurationResolverService!.resolveAsync(workspace, 'abc ${workspaceFolder} xyz'), 'abc /VSCode/workspaceLocation xyz'); } }); - test('workspace folder with argument', () => { + test('workspace folder with argument', async () => { if (platform.isWindows) { - assert.strictEqual(configurationResolverService!.resolve(workspace, 'abc ${workspaceFolder:workspaceLocation} xyz'), 'abc \\VSCode\\workspaceLocation xyz'); + assert.strictEqual(await configurationResolverService!.resolveAsync(workspace, 'abc ${workspaceFolder:workspaceLocation} xyz'), 'abc \\VSCode\\workspaceLocation xyz'); } else { - assert.strictEqual(configurationResolverService!.resolve(workspace, 'abc ${workspaceFolder:workspaceLocation} xyz'), 'abc /VSCode/workspaceLocation xyz'); + assert.strictEqual(await configurationResolverService!.resolveAsync(workspace, 'abc ${workspaceFolder:workspaceLocation} xyz'), 'abc /VSCode/workspaceLocation xyz'); } }); test('workspace folder with invalid argument', () => { - assert.throws(() => configurationResolverService!.resolve(workspace, 'abc ${workspaceFolder:invalidLocation} xyz')); + assert.rejects(async () => await configurationResolverService!.resolveAsync(workspace, 'abc ${workspaceFolder:invalidLocation} xyz')); }); test('workspace folder with undefined workspace folder', () => { - assert.throws(() => configurationResolverService!.resolve(undefined, 'abc ${workspaceFolder} xyz')); + assert.rejects(async () => await configurationResolverService!.resolveAsync(undefined, 'abc ${workspaceFolder} xyz')); }); - test('workspace folder with argument and undefined workspace folder', () => { + test('workspace folder with argument and undefined workspace folder', async () => { if (platform.isWindows) { - assert.strictEqual(configurationResolverService!.resolve(undefined, 'abc ${workspaceFolder:workspaceLocation} xyz'), 'abc \\VSCode\\workspaceLocation xyz'); + assert.strictEqual(await configurationResolverService!.resolveAsync(undefined, 'abc ${workspaceFolder:workspaceLocation} xyz'), 'abc \\VSCode\\workspaceLocation xyz'); } else { - assert.strictEqual(configurationResolverService!.resolve(undefined, 'abc ${workspaceFolder:workspaceLocation} xyz'), 'abc /VSCode/workspaceLocation xyz'); + assert.strictEqual(await configurationResolverService!.resolveAsync(undefined, 'abc ${workspaceFolder:workspaceLocation} xyz'), 'abc /VSCode/workspaceLocation xyz'); } }); test('workspace folder with invalid argument and undefined workspace folder', () => { - assert.throws(() => configurationResolverService!.resolve(undefined, 'abc ${workspaceFolder:invalidLocation} xyz')); + assert.rejects(async () => await configurationResolverService!.resolveAsync(undefined, 'abc ${workspaceFolder:invalidLocation} xyz')); }); - test('workspace root folder name', () => { - assert.strictEqual(configurationResolverService!.resolve(workspace, 'abc ${workspaceRootFolderName} xyz'), 'abc workspaceLocation xyz'); + test('workspace root folder name', async () => { + assert.strictEqual(await configurationResolverService!.resolveAsync(workspace, 'abc ${workspaceRootFolderName} xyz'), 'abc workspaceLocation xyz'); }); - test('current selected line number', () => { - assert.strictEqual(configurationResolverService!.resolve(workspace, 'abc ${lineNumber} xyz'), `abc ${mockLineNumber} xyz`); + test('current selected line number', async () => { + assert.strictEqual(await configurationResolverService!.resolveAsync(workspace, 'abc ${lineNumber} xyz'), `abc ${mockLineNumber} xyz`); }); - test('relative file', () => { - assert.strictEqual(configurationResolverService!.resolve(workspace, 'abc ${relativeFile} xyz'), 'abc file xyz'); + test('relative file', async () => { + assert.strictEqual(await configurationResolverService!.resolveAsync(workspace, 'abc ${relativeFile} xyz'), 'abc file xyz'); }); - test('relative file with argument', () => { - assert.strictEqual(configurationResolverService!.resolve(workspace, 'abc ${relativeFile:workspaceLocation} xyz'), 'abc file xyz'); + test('relative file with argument', async () => { + assert.strictEqual(await configurationResolverService!.resolveAsync(workspace, 'abc ${relativeFile:workspaceLocation} xyz'), 'abc file xyz'); }); test('relative file with invalid argument', () => { - assert.throws(() => configurationResolverService!.resolve(workspace, 'abc ${relativeFile:invalidLocation} xyz')); + assert.rejects(async () => await configurationResolverService!.resolveAsync(workspace, 'abc ${relativeFile:invalidLocation} xyz')); }); - test('relative file with undefined workspace folder', () => { + test('relative file with undefined workspace folder', async () => { if (platform.isWindows) { - assert.strictEqual(configurationResolverService!.resolve(undefined, 'abc ${relativeFile} xyz'), 'abc \\VSCode\\workspaceLocation\\file xyz'); + assert.strictEqual(await configurationResolverService!.resolveAsync(undefined, 'abc ${relativeFile} xyz'), 'abc \\VSCode\\workspaceLocation\\file xyz'); } else { - assert.strictEqual(configurationResolverService!.resolve(undefined, 'abc ${relativeFile} xyz'), 'abc /VSCode/workspaceLocation/file xyz'); + assert.strictEqual(await configurationResolverService!.resolveAsync(undefined, 'abc ${relativeFile} xyz'), 'abc /VSCode/workspaceLocation/file xyz'); } }); - test('relative file with argument and undefined workspace folder', () => { - assert.strictEqual(configurationResolverService!.resolve(undefined, 'abc ${relativeFile:workspaceLocation} xyz'), 'abc file xyz'); + test('relative file with argument and undefined workspace folder', async () => { + assert.strictEqual(await configurationResolverService!.resolveAsync(undefined, 'abc ${relativeFile:workspaceLocation} xyz'), 'abc file xyz'); }); test('relative file with invalid argument and undefined workspace folder', () => { - assert.throws(() => configurationResolverService!.resolve(undefined, 'abc ${relativeFile:invalidLocation} xyz')); + assert.rejects(async () => await configurationResolverService!.resolveAsync(undefined, 'abc ${relativeFile:invalidLocation} xyz')); }); - test('substitute many', () => { + test('substitute many', async () => { if (platform.isWindows) { - assert.strictEqual(configurationResolverService!.resolve(workspace, '${workspaceFolder} - ${workspaceFolder}'), '\\VSCode\\workspaceLocation - \\VSCode\\workspaceLocation'); + assert.strictEqual(await configurationResolverService!.resolveAsync(workspace, '${workspaceFolder} - ${workspaceFolder}'), '\\VSCode\\workspaceLocation - \\VSCode\\workspaceLocation'); } else { - assert.strictEqual(configurationResolverService!.resolve(workspace, '${workspaceFolder} - ${workspaceFolder}'), '/VSCode/workspaceLocation - /VSCode/workspaceLocation'); + assert.strictEqual(await configurationResolverService!.resolveAsync(workspace, '${workspaceFolder} - ${workspaceFolder}'), '/VSCode/workspaceLocation - /VSCode/workspaceLocation'); } }); - test('substitute one env variable', () => { + test('substitute one env variable', async () => { if (platform.isWindows) { - assert.strictEqual(configurationResolverService!.resolve(workspace, 'abc ${workspaceFolder} ${env:key1} xyz'), 'abc \\VSCode\\workspaceLocation Value for key1 xyz'); + assert.strictEqual(await configurationResolverService!.resolveAsync(workspace, 'abc ${workspaceFolder} ${env:key1} xyz'), 'abc \\VSCode\\workspaceLocation Value for key1 xyz'); } else { - assert.strictEqual(configurationResolverService!.resolve(workspace, 'abc ${workspaceFolder} ${env:key1} xyz'), 'abc /VSCode/workspaceLocation Value for key1 xyz'); + assert.strictEqual(await configurationResolverService!.resolveAsync(workspace, 'abc ${workspaceFolder} ${env:key1} xyz'), 'abc /VSCode/workspaceLocation Value for key1 xyz'); } }); - test('substitute many env variable', () => { + test('substitute many env variable', async () => { if (platform.isWindows) { - assert.strictEqual(configurationResolverService!.resolve(workspace, '${workspaceFolder} - ${workspaceFolder} ${env:key1} - ${env:key2}'), '\\VSCode\\workspaceLocation - \\VSCode\\workspaceLocation Value for key1 - Value for key2'); + assert.strictEqual(await configurationResolverService!.resolveAsync(workspace, '${workspaceFolder} - ${workspaceFolder} ${env:key1} - ${env:key2}'), '\\VSCode\\workspaceLocation - \\VSCode\\workspaceLocation Value for key1 - Value for key2'); } else { - assert.strictEqual(configurationResolverService!.resolve(workspace, '${workspaceFolder} - ${workspaceFolder} ${env:key1} - ${env:key2}'), '/VSCode/workspaceLocation - /VSCode/workspaceLocation Value for key1 - Value for key2'); + assert.strictEqual(await configurationResolverService!.resolveAsync(workspace, '${workspaceFolder} - ${workspaceFolder} ${env:key1} - ${env:key2}'), '/VSCode/workspaceLocation - /VSCode/workspaceLocation Value for key1 - Value for key2'); } }); - test('disallows nested keys (#77289)', () => { - assert.strictEqual(configurationResolverService!.resolve(workspace, '${env:key1} ${env:key1${env:key2}}'), 'Value for key1 ${env:key1${env:key2}}'); + test('disallows nested keys (#77289)', async () => { + assert.strictEqual(await configurationResolverService!.resolveAsync(workspace, '${env:key1} ${env:key1${env:key2}}'), 'Value for key1 ${env:key1${env:key2}}'); }); // test('substitute keys and values in object', () => { @@ -186,22 +187,22 @@ suite('Configuration Resolver Service', () => { // '${workspaceRootFolderName}': '${lineNumber}', // 'hey ${env:key1} ': '${workspaceRootFolderName}' // }; - // assert.deepEqual(configurationResolverService!.resolve(workspace, myObject), { + // assert.deepStrictEqual(configurationResolverService!.resolveAsync(workspace, myObject), { // 'workspaceLocation': `${editorService.mockLineNumber}`, // 'hey Value for key1 ': 'workspaceLocation' // }); // }); - test('substitute one env variable using platform case sensitivity', () => { + test('substitute one env variable using platform case sensitivity', async () => { if (platform.isWindows) { - assert.strictEqual(configurationResolverService!.resolve(workspace, '${env:key1} - ${env:Key1}'), 'Value for key1 - Value for key1'); + assert.strictEqual(await configurationResolverService!.resolveAsync(workspace, '${env:key1} - ${env:Key1}'), 'Value for key1 - Value for key1'); } else { - assert.strictEqual(configurationResolverService!.resolve(workspace, '${env:key1} - ${env:Key1}'), 'Value for key1 - '); + assert.strictEqual(await configurationResolverService!.resolveAsync(workspace, '${env:key1} - ${env:Key1}'), 'Value for key1 - '); } }); - test('substitute one configuration variable', () => { + test('substitute one configuration variable', async () => { let configurationService: IConfigurationService = new TestConfigurationService({ editor: { fontFamily: 'foo' @@ -213,22 +214,22 @@ suite('Configuration Resolver Service', () => { } }); - let service = new TestConfigurationResolverService(nullContext, environmentService.userEnv, new TestEditorServiceWithActiveEditor(), configurationService, mockCommandService, new TestContextService(), quickInputService, labelService); - assert.strictEqual(service.resolve(workspace, 'abc ${config:editor.fontFamily} xyz'), 'abc foo xyz'); + let service = new TestConfigurationResolverService(nullContext, Promise.resolve(environmentService.userEnv), new TestEditorServiceWithActiveEditor(), configurationService, mockCommandService, new TestContextService(), quickInputService, labelService); + assert.strictEqual(await service.resolveAsync(workspace, 'abc ${config:editor.fontFamily} xyz'), 'abc foo xyz'); }); - test('substitute configuration variable with undefined workspace folder', () => { + test('substitute configuration variable with undefined workspace folder', async () => { let configurationService: IConfigurationService = new TestConfigurationService({ editor: { fontFamily: 'foo' } }); - let service = new TestConfigurationResolverService(nullContext, environmentService.userEnv, new TestEditorServiceWithActiveEditor(), configurationService, mockCommandService, new TestContextService(), quickInputService, labelService); - assert.strictEqual(service.resolve(undefined, 'abc ${config:editor.fontFamily} xyz'), 'abc foo xyz'); + let service = new TestConfigurationResolverService(nullContext, Promise.resolve(environmentService.userEnv), new TestEditorServiceWithActiveEditor(), configurationService, mockCommandService, new TestContextService(), quickInputService, labelService); + assert.strictEqual(await service.resolveAsync(undefined, 'abc ${config:editor.fontFamily} xyz'), 'abc foo xyz'); }); - test('substitute many configuration variables', () => { + test('substitute many configuration variables', async () => { let configurationService: IConfigurationService; configurationService = new TestConfigurationService({ editor: { @@ -241,11 +242,11 @@ suite('Configuration Resolver Service', () => { } }); - let service = new TestConfigurationResolverService(nullContext, environmentService.userEnv, new TestEditorServiceWithActiveEditor(), configurationService, mockCommandService, new TestContextService(), quickInputService, labelService); - assert.strictEqual(service.resolve(workspace, 'abc ${config:editor.fontFamily} ${config:terminal.integrated.fontFamily} xyz'), 'abc foo bar xyz'); + let service = new TestConfigurationResolverService(nullContext, Promise.resolve(environmentService.userEnv), new TestEditorServiceWithActiveEditor(), configurationService, mockCommandService, new TestContextService(), quickInputService, labelService); + assert.strictEqual(await service.resolveAsync(workspace, 'abc ${config:editor.fontFamily} ${config:terminal.integrated.fontFamily} xyz'), 'abc foo bar xyz'); }); - test('substitute one env variable and a configuration variable', () => { + test('substitute one env variable and a configuration variable', async () => { let configurationService: IConfigurationService; configurationService = new TestConfigurationService({ editor: { @@ -258,15 +259,15 @@ suite('Configuration Resolver Service', () => { } }); - let service = new TestConfigurationResolverService(nullContext, environmentService.userEnv, new TestEditorServiceWithActiveEditor(), configurationService, mockCommandService, new TestContextService(), quickInputService, labelService); + let service = new TestConfigurationResolverService(nullContext, Promise.resolve(environmentService.userEnv), new TestEditorServiceWithActiveEditor(), configurationService, mockCommandService, new TestContextService(), quickInputService, labelService); if (platform.isWindows) { - assert.strictEqual(service.resolve(workspace, 'abc ${config:editor.fontFamily} ${workspaceFolder} ${env:key1} xyz'), 'abc foo \\VSCode\\workspaceLocation Value for key1 xyz'); + assert.strictEqual(await service.resolveAsync(workspace, 'abc ${config:editor.fontFamily} ${workspaceFolder} ${env:key1} xyz'), 'abc foo \\VSCode\\workspaceLocation Value for key1 xyz'); } else { - assert.strictEqual(service.resolve(workspace, 'abc ${config:editor.fontFamily} ${workspaceFolder} ${env:key1} xyz'), 'abc foo /VSCode/workspaceLocation Value for key1 xyz'); + assert.strictEqual(await service.resolveAsync(workspace, 'abc ${config:editor.fontFamily} ${workspaceFolder} ${env:key1} xyz'), 'abc foo /VSCode/workspaceLocation Value for key1 xyz'); } }); - test('substitute many env variable and a configuration variable', () => { + test('substitute many env variable and a configuration variable', async () => { let configurationService: IConfigurationService; configurationService = new TestConfigurationService({ editor: { @@ -279,15 +280,15 @@ suite('Configuration Resolver Service', () => { } }); - let service = new TestConfigurationResolverService(nullContext, environmentService.userEnv, new TestEditorServiceWithActiveEditor(), configurationService, mockCommandService, new TestContextService(), quickInputService, labelService); + let service = new TestConfigurationResolverService(nullContext, Promise.resolve(environmentService.userEnv), new TestEditorServiceWithActiveEditor(), configurationService, mockCommandService, new TestContextService(), quickInputService, labelService); if (platform.isWindows) { - assert.strictEqual(service.resolve(workspace, '${config:editor.fontFamily} ${config:terminal.integrated.fontFamily} ${workspaceFolder} - ${workspaceFolder} ${env:key1} - ${env:key2}'), 'foo bar \\VSCode\\workspaceLocation - \\VSCode\\workspaceLocation Value for key1 - Value for key2'); + assert.strictEqual(await service.resolveAsync(workspace, '${config:editor.fontFamily} ${config:terminal.integrated.fontFamily} ${workspaceFolder} - ${workspaceFolder} ${env:key1} - ${env:key2}'), 'foo bar \\VSCode\\workspaceLocation - \\VSCode\\workspaceLocation Value for key1 - Value for key2'); } else { - assert.strictEqual(service.resolve(workspace, '${config:editor.fontFamily} ${config:terminal.integrated.fontFamily} ${workspaceFolder} - ${workspaceFolder} ${env:key1} - ${env:key2}'), 'foo bar /VSCode/workspaceLocation - /VSCode/workspaceLocation Value for key1 - Value for key2'); + assert.strictEqual(await service.resolveAsync(workspace, '${config:editor.fontFamily} ${config:terminal.integrated.fontFamily} ${workspaceFolder} - ${workspaceFolder} ${env:key1} - ${env:key2}'), 'foo bar /VSCode/workspaceLocation - /VSCode/workspaceLocation Value for key1 - Value for key2'); } }); - test('mixed types of configuration variables', () => { + test('mixed types of configuration variables', async () => { let configurationService: IConfigurationService; configurationService = new TestConfigurationService({ editor: { @@ -313,19 +314,19 @@ suite('Configuration Resolver Service', () => { } }); - let service = new TestConfigurationResolverService(nullContext, environmentService.userEnv, new TestEditorServiceWithActiveEditor(), configurationService, mockCommandService, new TestContextService(), quickInputService, labelService); - assert.strictEqual(service.resolve(workspace, 'abc ${config:editor.fontFamily} ${config:editor.lineNumbers} ${config:editor.insertSpaces} xyz'), 'abc foo 123 false xyz'); + let service = new TestConfigurationResolverService(nullContext, Promise.resolve(environmentService.userEnv), new TestEditorServiceWithActiveEditor(), configurationService, mockCommandService, new TestContextService(), quickInputService, labelService); + assert.strictEqual(await service.resolveAsync(workspace, 'abc ${config:editor.fontFamily} ${config:editor.lineNumbers} ${config:editor.insertSpaces} xyz'), 'abc foo 123 false xyz'); }); - test('uses original variable as fallback', () => { + test('uses original variable as fallback', async () => { let configurationService: IConfigurationService; configurationService = new TestConfigurationService({ editor: {} }); - let service = new TestConfigurationResolverService(nullContext, environmentService.userEnv, new TestEditorServiceWithActiveEditor(), configurationService, mockCommandService, new TestContextService(), quickInputService, labelService); - assert.strictEqual(service.resolve(workspace, 'abc ${unknownVariable} xyz'), 'abc ${unknownVariable} xyz'); - assert.strictEqual(service.resolve(workspace, 'abc ${env:unknownVariable} xyz'), 'abc xyz'); + let service = new TestConfigurationResolverService(nullContext, Promise.resolve(environmentService.userEnv), new TestEditorServiceWithActiveEditor(), configurationService, mockCommandService, new TestContextService(), quickInputService, labelService); + assert.strictEqual(await service.resolveAsync(workspace, 'abc ${unknownVariable} xyz'), 'abc ${unknownVariable} xyz'); + assert.strictEqual(await service.resolveAsync(workspace, 'abc ${env:unknownVariable} xyz'), 'abc xyz'); }); test('configuration variables with invalid accessor', () => { @@ -336,15 +337,15 @@ suite('Configuration Resolver Service', () => { } }); - let service = new TestConfigurationResolverService(nullContext, environmentService.userEnv, new TestEditorServiceWithActiveEditor(), configurationService, mockCommandService, new TestContextService(), quickInputService, labelService); + let service = new TestConfigurationResolverService(nullContext, Promise.resolve(environmentService.userEnv), new TestEditorServiceWithActiveEditor(), configurationService, mockCommandService, new TestContextService(), quickInputService, labelService); - assert.throws(() => service.resolve(workspace, 'abc ${env} xyz')); - assert.throws(() => service.resolve(workspace, 'abc ${env:} xyz')); - assert.throws(() => service.resolve(workspace, 'abc ${config} xyz')); - assert.throws(() => service.resolve(workspace, 'abc ${config:} xyz')); - assert.throws(() => service.resolve(workspace, 'abc ${config:editor} xyz')); - assert.throws(() => service.resolve(workspace, 'abc ${config:editor..fontFamily} xyz')); - assert.throws(() => service.resolve(workspace, 'abc ${config:editor.none.none2} xyz')); + assert.rejects(async () => await service.resolveAsync(workspace, 'abc ${env} xyz')); + assert.rejects(async () => await service.resolveAsync(workspace, 'abc ${env:} xyz')); + assert.rejects(async () => await service.resolveAsync(workspace, 'abc ${config} xyz')); + assert.rejects(async () => await service.resolveAsync(workspace, 'abc ${config:} xyz')); + assert.rejects(async () => await service.resolveAsync(workspace, 'abc ${config:editor} xyz')); + assert.rejects(async () => await service.resolveAsync(workspace, 'abc ${config:editor..fontFamily} xyz')); + assert.rejects(async () => await service.resolveAsync(workspace, 'abc ${config:editor.none.none2} xyz')); }); test('a single command variable', () => { @@ -360,8 +361,7 @@ suite('Configuration Resolver Service', () => { }; return configurationResolverService!.resolveWithInteractionReplace(undefined, configuration).then(result => { - - assert.deepEqual(result, { + assert.deepStrictEqual({ ...result }, { 'name': 'Attach to Process', 'type': 'node', 'request': 'attach', @@ -371,7 +371,7 @@ suite('Configuration Resolver Service', () => { 'outDir': null }); - assert.equal(1, mockCommandService.callCount); + assert.strictEqual(1, mockCommandService.callCount); }); }); @@ -389,8 +389,7 @@ suite('Configuration Resolver Service', () => { commandVariables['commandVariable1'] = 'command1'; return configurationResolverService!.resolveWithInteractionReplace(undefined, configuration, undefined, commandVariables).then(result => { - - assert.deepEqual(result, { + assert.deepStrictEqual({ ...result }, { 'name': 'Attach to Process', 'type': 'node', 'request': 'attach', @@ -400,7 +399,7 @@ suite('Configuration Resolver Service', () => { 'outDir': null }); - assert.equal(1, mockCommandService.callCount); + assert.strictEqual(1, mockCommandService.callCount); }); }); @@ -422,8 +421,7 @@ suite('Configuration Resolver Service', () => { commandVariables['commandVariable1'] = 'command1'; return configurationResolverService!.resolveWithInteractionReplace(undefined, configuration, undefined, commandVariables).then(result => { - - assert.deepEqual(result, { + const expected = { 'name': 'Attach to Process', 'type': 'node', 'request': 'attach', @@ -434,9 +432,18 @@ suite('Configuration Resolver Service', () => { 'env': { 'processId': '__command2-result__', } - }); + }; - assert.equal(2, mockCommandService.callCount); + assert.deepStrictEqual(Object.keys(result), Object.keys(expected)); + Object.keys(result).forEach(property => { + const expectedProperty = (expected)[property]; + if (isObject(result[property])) { + assert.deepStrictEqual({ ...result[property] }, expectedProperty); + } else { + assert.deepStrictEqual(result[property], expectedProperty); + } + }); + assert.strictEqual(2, mockCommandService.callCount); }); }); @@ -454,7 +461,7 @@ suite('Configuration Resolver Service', () => { return configurationResolverService!.resolveWithInteractionReplace(undefined, configuration, undefined, commandVariables).then(result => { - assert.deepEqual(result, { + assert.deepStrictEqual({ ...result }, { 'name': 'Attach to Process', 'type': 'node', 'request': 'attach', @@ -462,7 +469,7 @@ suite('Configuration Resolver Service', () => { 'value': 'Value for key1' }); - assert.equal(1, mockCommandService.callCount); + assert.strictEqual(1, mockCommandService.callCount); }); }); @@ -480,7 +487,7 @@ suite('Configuration Resolver Service', () => { return configurationResolverService!.resolveWithInteractionReplace(workspace, configuration, 'tasks').then(result => { - assert.deepEqual(result, { + assert.deepStrictEqual({ ...result }, { 'name': 'Attach to Process', 'type': 'node', 'request': 'attach', @@ -490,7 +497,7 @@ suite('Configuration Resolver Service', () => { 'outDir': null }); - assert.equal(0, mockCommandService.callCount); + assert.strictEqual(0, mockCommandService.callCount); }); }); @@ -508,7 +515,7 @@ suite('Configuration Resolver Service', () => { return configurationResolverService!.resolveWithInteractionReplace(workspace, configuration, 'tasks').then(result => { - assert.deepEqual(result, { + assert.deepStrictEqual({ ...result }, { 'name': 'Attach to Process', 'type': 'node', 'request': 'attach', @@ -518,7 +525,7 @@ suite('Configuration Resolver Service', () => { 'outDir': null }); - assert.equal(0, mockCommandService.callCount); + assert.strictEqual(0, mockCommandService.callCount); }); }); @@ -536,7 +543,7 @@ suite('Configuration Resolver Service', () => { return configurationResolverService!.resolveWithInteractionReplace(workspace, configuration, 'tasks').then(result => { - assert.deepEqual(result, { + assert.deepStrictEqual({ ...result }, { 'name': 'Attach to Process', 'type': 'node', 'request': 'attach', @@ -546,7 +553,7 @@ suite('Configuration Resolver Service', () => { 'outDir': null }); - assert.equal(1, mockCommandService.callCount); + assert.strictEqual(1, mockCommandService.callCount); }); }); @@ -565,7 +572,7 @@ suite('Configuration Resolver Service', () => { return configurationResolverService!.resolveWithInteractionReplace(workspace, configuration, 'tasks').then(result => { - assert.deepEqual(result, { + assert.deepStrictEqual({ ...result }, { 'name': 'resolvedEnterinput3', 'type': 'command1-result', 'request': 'resolvedEnterinput1', @@ -576,7 +583,7 @@ suite('Configuration Resolver Service', () => { 'outDir': null }); - assert.equal(2, mockCommandService.callCount); + assert.strictEqual(2, mockCommandService.callCount); }); }); @@ -594,7 +601,7 @@ suite('Configuration Resolver Service', () => { return configurationResolverService!.resolveWithInteractionReplace(undefined, configuration, 'tasks').then(result => { - assert.deepEqual(result, { + assert.deepStrictEqual({ ...result }, { 'name': 'Attach to Process', 'type': 'node', 'request': 'attach', @@ -604,7 +611,7 @@ suite('Configuration Resolver Service', () => { 'outDir': null }); - assert.equal(0, mockCommandService.callCount); + assert.strictEqual(0, mockCommandService.callCount); }); }); @@ -616,7 +623,7 @@ suite('Configuration Resolver Service', () => { }; configurationResolverService!.contributeVariable(variable, async () => { return buildTask; }); return configurationResolverService!.resolveWithInteractionReplace(workspace, configuration).then(result => { - assert.deepEqual(result, { + assert.deepStrictEqual({ ...result }, { 'name': `${buildTask}` }); }); @@ -669,7 +676,7 @@ class MockLabelService implements ILabelService { } class MockInputsConfigurationService extends TestConfigurationService { - public getValue(arg1?: any, arg2?: any): any { + public override getValue(arg1?: any, arg2?: any): any { let configuration; if (arg1 === 'tasks') { configuration = { @@ -712,6 +719,6 @@ class MockInputsConfigurationService extends TestConfigurationService { class MockWorkbenchEnvironmentService extends NativeWorkbenchEnvironmentService { constructor(public userEnv: platform.IProcessEnvironment) { - super({ ...TestWorkbenchConfiguration, userEnv }, TestEnvironmentPaths, TestProductService); + super({ ...TestWorkbenchConfiguration, userEnv }, TestProductService); } } diff --git a/src/vs/workbench/services/decorations/browser/decorations.ts b/src/vs/workbench/services/decorations/browser/decorations.ts index 45842b7f..525b0fdb 100644 --- a/src/vs/workbench/services/decorations/browser/decorations.ts +++ b/src/vs/workbench/services/decorations/browser/decorations.ts @@ -9,13 +9,14 @@ import { Event } from 'vs/base/common/event'; import { ColorIdentifier } from 'vs/platform/theme/common/colorRegistry'; import { IDisposable } from 'vs/base/common/lifecycle'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { ThemeIcon } from 'vs/platform/theme/common/themeService'; export const IDecorationsService = createDecorator('IFileDecorationsService'); export interface IDecorationData { readonly weight?: number; readonly color?: ColorIdentifier; - readonly letter?: string; + readonly letter?: string | ThemeIcon; readonly tooltip?: string; readonly bubble?: boolean; } @@ -24,6 +25,7 @@ export interface IDecoration extends IDisposable { readonly tooltip: string; readonly labelClassName: string; readonly badgeClassName: string; + readonly iconClassName: string; } export interface IDecorationsProvider { diff --git a/src/vs/workbench/services/decorations/browser/decorationsService.ts b/src/vs/workbench/services/decorations/browser/decorationsService.ts index 7cfea530..6ece1521 100644 --- a/src/vs/workbench/services/decorations/browser/decorationsService.ts +++ b/src/vs/workbench/services/decorations/browser/decorationsService.ts @@ -11,7 +11,7 @@ import { IDisposable, toDisposable, DisposableStore } from 'vs/base/common/lifec import { isThenable } from 'vs/base/common/async'; import { LinkedList } from 'vs/base/common/linkedList'; import { createStyleSheet, createCSSRule, removeCSSRulesContainingSelector } from 'vs/base/browser/dom'; -import { IThemeService, IColorTheme } from 'vs/platform/theme/common/themeService'; +import { IThemeService, IColorTheme, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { isFalsyOrWhitespace } from 'vs/base/common/strings'; import { localize } from 'vs/nls'; import { isPromiseCanceledError } from 'vs/base/common/errors'; @@ -19,6 +19,7 @@ import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { hash } from 'vs/base/common/hash'; import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; +import { iconRegistry } from 'vs/base/common/codicons'; class DecorationRule { @@ -27,7 +28,11 @@ class DecorationRule { return data.map(DecorationRule.keyOf).join(','); } else { const { color, letter } = data; - return `${color}/${letter}`; + if (ThemeIcon.isThemeIcon(letter)) { + return `${color}+${letter.id}`; + } else { + return `${color}/${letter}`; + } } } @@ -36,6 +41,7 @@ class DecorationRule { readonly data: IDecorationData | IDecorationData[]; readonly itemColorClassName: string; readonly itemBadgeClassName: string; + readonly iconBadgeClassName: string; readonly bubbleBadgeClassName: string; private _refCounter: number = 0; @@ -46,6 +52,7 @@ class DecorationRule { this.itemColorClassName = `${DecorationRule._classNamesPrefix}-itemColor-${suffix}`; this.itemBadgeClassName = `${DecorationRule._classNamesPrefix}-itemBadge-${suffix}`; this.bubbleBadgeClassName = `${DecorationRule._classNamesPrefix}-bubbleBadge-${suffix}`; + this.iconBadgeClassName = `${DecorationRule._classNamesPrefix}-iconBadge-${suffix}`; } acquire(): void { @@ -68,8 +75,12 @@ class DecorationRule { const { color, letter } = data; // label createCSSRule(`.${this.itemColorClassName}`, `color: ${getColor(theme, color)};`, element); + // icon + if (ThemeIcon.isThemeIcon(letter)) { + this._createIconCSSRule(letter, color, element, theme); + } // letter - if (letter) { + else if (letter) { createCSSRule(`.${this.itemBadgeClassName}::after`, `content: "${letter}"; color: ${getColor(theme, color)};`, element); } } @@ -79,17 +90,36 @@ class DecorationRule { const { color } = data[0]; createCSSRule(`.${this.itemColorClassName}`, `color: ${getColor(theme, color)};`, element); - // badge - const letters = data.filter(d => !isFalsyOrWhitespace(d.letter)).map(d => d.letter); - if (letters.length) { - createCSSRule(`.${this.itemBadgeClassName}::after`, `content: "${letters.join(', ')}"; color: ${getColor(theme, color)};`, element); - } + // icon (only show first) + const icon = data.find(d => ThemeIcon.isThemeIcon(d.letter))?.letter as ThemeIcon | undefined; + if (icon) { + this._createIconCSSRule(icon, color, element, theme); + } else { + // badge + const letters = data.filter(d => !isFalsyOrWhitespace(d.letter as string | undefined)).map(d => d.letter); + if (letters.length) { + createCSSRule(`.${this.itemBadgeClassName}::after`, `content: "${letters.join(', ')}"; color: ${getColor(theme, color)};`, element); + } - // bubble badge - // TODO @misolori update bubble badge to use class name instead of unicode + // bubble badge + // TODO @misolori update bubble badge to adopt letter: ThemeIcon instead of unicode + createCSSRule( + `.${this.bubbleBadgeClassName}::after`, + `content: "\uea71"; color: ${getColor(theme, color)}; font-family: codicon; font-size: 14px; padding-right: 14px; opacity: 0.4;`, + element + ); + } + } + + private _createIconCSSRule(icon: ThemeIcon, color: string | undefined, element: HTMLStyleElement, theme: IColorTheme) { + const codicon = iconRegistry.get(icon.id); + if (!codicon || !('fontCharacter' in codicon.definition)) { + return; + } + const charCode = parseInt(codicon.definition.fontCharacter.substr(1), 16); createCSSRule( - `.${this.bubbleBadgeClassName}::after`, - `content: "\uea71"; color: ${getColor(theme, color)}; font-family: codicon; font-size: 14px; padding-right: 14px; opacity: 0.4;`, + `.${this.iconBadgeClassName}::after`, + `content: "${String.fromCharCode(charCode)}"; color: ${getColor(theme, color)}; font-family: codicon; font-size: 16px; padding-right: 14px; font-weight: normal`, element ); } @@ -98,6 +128,7 @@ class DecorationRule { removeCSSRulesContainingSelector(this.itemColorClassName, element); removeCSSRulesContainingSelector(this.itemBadgeClassName, element); removeCSSRulesContainingSelector(this.bubbleBadgeClassName, element); + removeCSSRulesContainingSelector(this.iconBadgeClassName, element); } } @@ -135,6 +166,7 @@ class DecorationStyles { let labelClassName = rule.itemColorClassName; let badgeClassName = rule.itemBadgeClassName; + let iconClassName = rule.iconBadgeClassName; let tooltip = data.filter(d => !isFalsyOrWhitespace(d.tooltip)).map(d => d.tooltip).join(' • '); if (onlyChildren) { @@ -146,6 +178,7 @@ class DecorationStyles { return { labelClassName, badgeClassName, + iconClassName, tooltip, dispose: () => { if (rule?.release()) { diff --git a/src/vs/workbench/services/decorations/test/browser/decorationsService.test.ts b/src/vs/workbench/services/decorations/test/browser/decorationsService.test.ts index 5a4b76eb..834ffca6 100644 --- a/src/vs/workbench/services/decorations/test/browser/decorationsService.test.ts +++ b/src/vs/workbench/services/decorations/test/browser/decorationsService.test.ts @@ -25,7 +25,7 @@ suite('DecorationsService', function () { service = new DecorationsService( new TestThemeService(), new class extends mock() { - extUri = resources.extUri; + override extUri = resources.extUri; } ); }); @@ -50,16 +50,16 @@ suite('DecorationsService', function () { }); // trigger -> async - assert.equal(service.getDecoration(uri, false), undefined); - assert.equal(callCounter, 1); + assert.strictEqual(service.getDecoration(uri, false), undefined); + assert.strictEqual(callCounter, 1); // event when result is computed return Event.toPromise(service.onDidChangeDecorations).then(e => { - assert.equal(e.affectsResource(uri), true); + assert.strictEqual(e.affectsResource(uri), true); // sync result - assert.deepEqual(service.getDecoration(uri, false)!.tooltip, 'T'); - assert.equal(callCounter, 1); + assert.deepStrictEqual(service.getDecoration(uri, false)!.tooltip, 'T'); + assert.strictEqual(callCounter, 1); }); }); @@ -78,8 +78,8 @@ suite('DecorationsService', function () { }); // trigger -> sync - assert.deepEqual(service.getDecoration(uri, false)!.tooltip, 'Z'); - assert.equal(callCounter, 1); + assert.deepStrictEqual(service.getDecoration(uri, false)!.tooltip, 'Z'); + assert.strictEqual(callCounter, 1); }); test('Clear decorations on provider dispose', async function () { @@ -96,23 +96,23 @@ suite('DecorationsService', function () { }); // trigger -> sync - assert.deepEqual(service.getDecoration(uri, false)!.tooltip, 'J'); - assert.equal(callCounter, 1); + assert.deepStrictEqual(service.getDecoration(uri, false)!.tooltip, 'J'); + assert.strictEqual(callCounter, 1); // un-register -> ensure good event let didSeeEvent = false; let p = new Promise(resolve => { service.onDidChangeDecorations(e => { - assert.equal(e.affectsResource(uri), true); - assert.deepEqual(service.getDecoration(uri, false), undefined); - assert.equal(callCounter, 1); + assert.strictEqual(e.affectsResource(uri), true); + assert.deepStrictEqual(service.getDecoration(uri, false), undefined); + assert.strictEqual(callCounter, 1); didSeeEvent = true; resolve(); }); }); reg.dispose(); // will clear all data await p; - assert.equal(didSeeEvent, true); + assert.strictEqual(didSeeEvent, true); }); test('No default bubbling', function () { @@ -130,10 +130,10 @@ suite('DecorationsService', function () { let childUri = URI.parse('file:///some/path/some/file.txt'); let deco = service.getDecoration(childUri, false)!; - assert.equal(deco.tooltip, '.txt'); + assert.strictEqual(deco.tooltip, '.txt'); deco = service.getDecoration(childUri.with({ path: 'some/path/' }), true)!; - assert.equal(deco, undefined); + assert.strictEqual(deco, undefined); reg.dispose(); // bubble @@ -148,10 +148,10 @@ suite('DecorationsService', function () { }); deco = service.getDecoration(childUri, false)!; - assert.equal(deco.tooltip, '.txt.bubble'); + assert.strictEqual(deco.tooltip, '.txt.bubble'); deco = service.getDecoration(childUri.with({ path: 'some/path/' }), true)!; - assert.equal(typeof deco.tooltip, 'string'); + assert.strictEqual(typeof deco.tooltip, 'string'); }); test('Decorations not showing up for second root folder #48502', async function () { @@ -190,9 +190,9 @@ suite('DecorationsService', function () { provider._onDidChange.fire([uri]); service.getDecoration(uri, false); - assert.equal(cancelCount, 1); - assert.equal(winjsCancelCount, 0); - assert.equal(callCount, 2); + assert.strictEqual(cancelCount, 1); + assert.strictEqual(winjsCancelCount, 0); + assert.strictEqual(callCount, 2); reg.dispose(); }); @@ -242,7 +242,7 @@ suite('DecorationsService', function () { let uri = URI.parse('foo:/folder/file.ts'); let uri2 = URI.parse('foo:/folder/'); let data = service.getDecoration(uri, true)!; - assert.equal(data.tooltip, 'FOO'); + assert.strictEqual(data.tooltip, 'FOO'); data = service.getDecoration(uri2, true)!; assert.ok(data.tooltip); // emphazied items... @@ -251,10 +251,10 @@ suite('DecorationsService', function () { emitter.fire([uri]); data = service.getDecoration(uri, true)!; - assert.equal(data, undefined); + assert.strictEqual(data, undefined); data = service.getDecoration(uri2, true)!; - assert.equal(data, undefined); + assert.strictEqual(data, undefined); reg.dispose(); }); @@ -277,7 +277,7 @@ suite('DecorationsService', function () { let uri = URI.parse('foo:/folder/file.ts'); let uri2 = URI.parse('foo:/folder/'); let data = service.getDecoration(uri, true)!; - assert.equal(data.tooltip, 'FOO'); + assert.strictEqual(data.tooltip, 'FOO'); data = service.getDecoration(uri2, true)!; assert.ok(data.tooltip); // emphazied items... diff --git a/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts b/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts index 268b195e..e488e6df 100644 --- a/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts +++ b/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts @@ -163,7 +163,7 @@ export abstract class AbstractFileDialogService implements IFileDialogService { } if (stat.isDirectory || options.forceNewWindow || preferNewWindow) { - return this.hostService.openWindow([toOpen], { forceNewWindow: options.forceNewWindow }); + return this.hostService.openWindow([toOpen], { forceNewWindow: options.forceNewWindow, remoteAuthority: options.remoteAuthority }); } else { return this.openerService.open(uri, { fromUserGesture: true, editorOptions: { pinned: true } }); } @@ -180,7 +180,7 @@ export abstract class AbstractFileDialogService implements IFileDialogService { this.workspacesService.addRecentlyOpened([{ fileUri: uri, label: this.labelService.getUriLabel(uri) }]); if (options.forceNewWindow || preferNewWindow) { - return this.hostService.openWindow([{ fileUri: uri }], { forceNewWindow: options.forceNewWindow }); + return this.hostService.openWindow([{ fileUri: uri }], { forceNewWindow: options.forceNewWindow, remoteAuthority: options.remoteAuthority }); } else { return this.openerService.open(uri, { fromUserGesture: true, editorOptions: { pinned: true } }); } @@ -193,7 +193,7 @@ export abstract class AbstractFileDialogService implements IFileDialogService { const uri = await this.pickResource({ canSelectFiles: false, canSelectFolders: true, canSelectMany: false, defaultUri: options.defaultUri, title, availableFileSystems }); if (uri) { - return this.hostService.openWindow([{ folderUri: uri }], { forceNewWindow: options.forceNewWindow }); + return this.hostService.openWindow([{ folderUri: uri }], { forceNewWindow: options.forceNewWindow, remoteAuthority: options.remoteAuthority }); } } @@ -204,7 +204,7 @@ export abstract class AbstractFileDialogService implements IFileDialogService { const uri = await this.pickResource({ canSelectFiles: true, canSelectFolders: false, canSelectMany: false, defaultUri: options.defaultUri, title, filters, availableFileSystems }); if (uri) { - return this.hostService.openWindow([{ workspaceUri: uri }], { forceNewWindow: options.forceNewWindow }); + return this.hostService.openWindow([{ workspaceUri: uri }], { forceNewWindow: options.forceNewWindow, remoteAuthority: options.remoteAuthority }); } } diff --git a/src/vs/workbench/services/dialogs/browser/fileDialogService.ts b/src/vs/workbench/services/dialogs/browser/fileDialogService.ts index a6eb25f5..bf6260cc 100644 --- a/src/vs/workbench/services/dialogs/browser/fileDialogService.ts +++ b/src/vs/workbench/services/dialogs/browser/fileDialogService.ts @@ -8,9 +8,18 @@ import { URI } from 'vs/base/common/uri'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { AbstractFileDialogService } from 'vs/workbench/services/dialogs/browser/abstractFileDialogService'; import { Schemas } from 'vs/base/common/network'; +import { memoize } from 'vs/base/common/decorators'; +import { HTMLFileSystemProvider } from 'vs/platform/files/browser/htmlFileSystemProvider'; +import { generateUuid } from 'vs/base/common/uuid'; +import { localize } from 'vs/nls'; export class FileDialogService extends AbstractFileDialogService implements IFileDialogService { + @memoize + private get fileSystemProvider(): HTMLFileSystemProvider { + return this.fileService.getProvider(Schemas.file) as HTMLFileSystemProvider; + } + async pickFileFolderAndOpen(options: IPickAndOpenOptions): Promise { const schema = this.getFileSystemSchema(options); @@ -18,7 +27,11 @@ export class FileDialogService extends AbstractFileDialogService implements IFil options.defaultUri = await this.defaultFilePath(schema); } - return this.pickFileFolderAndOpenSimplified(schema, options, false); + if (this.shouldUseSimplified(schema)) { + return this.pickFileFolderAndOpenSimplified(schema, options, false); + } + + throw new Error(localize('pickFolderAndOpen', "Can't open folders, try adding a folder to the workspace instead.")); } async pickFileAndOpen(options: IPickAndOpenOptions): Promise { @@ -28,7 +41,17 @@ export class FileDialogService extends AbstractFileDialogService implements IFil options.defaultUri = await this.defaultFilePath(schema); } - return this.pickFileAndOpenSimplified(schema, options, false); + if (this.shouldUseSimplified(schema)) { + return this.pickFileAndOpenSimplified(schema, options, false); + } + + const [handle] = await window.showOpenFilePicker({ multiple: false }); + const uuid = generateUuid(); + const uri = URI.from({ scheme: Schemas.file, path: `/${uuid}/${handle.name}` }); + + this.fileSystemProvider.registerFileHandle(uuid, handle); + + await this.openerService.open(uri, { fromUserGesture: true, editorOptions: { pinned: true } }); } async pickFolderAndOpen(options: IPickAndOpenOptions): Promise { @@ -38,7 +61,11 @@ export class FileDialogService extends AbstractFileDialogService implements IFil options.defaultUri = await this.defaultFolderPath(schema); } - return this.pickFolderAndOpenSimplified(schema, options); + if (this.shouldUseSimplified(schema)) { + return this.pickFolderAndOpenSimplified(schema, options); + } + + throw new Error(localize('pickFolderAndOpen', "Can't open folders, try adding a folder to the workspace instead.")); } async pickWorkspaceAndOpen(options: IPickAndOpenOptions): Promise { @@ -48,27 +75,68 @@ export class FileDialogService extends AbstractFileDialogService implements IFil options.defaultUri = await this.defaultWorkspacePath(schema); } - return this.pickWorkspaceAndOpenSimplified(schema, options); + if (this.shouldUseSimplified(schema)) { + return this.pickWorkspaceAndOpenSimplified(schema, options); + } + + throw new Error(localize('pickWorkspaceAndOpen', "Can't open workspaces, try adding a folder to the workspace instead.")); } async pickFileToSave(defaultUri: URI, availableFileSystems?: string[]): Promise { const schema = this.getFileSystemSchema({ defaultUri, availableFileSystems }); - return this.pickFileToSaveSimplified(schema, this.getPickFileToSaveDialogOptions(defaultUri, availableFileSystems)); + + if (this.shouldUseSimplified(schema)) { + return this.pickFileToSaveSimplified(schema, this.getPickFileToSaveDialogOptions(defaultUri, availableFileSystems)); + } + + const handle = await window.showSaveFilePicker(); + const uuid = generateUuid(); + const uri = URI.from({ scheme: Schemas.file, path: `/${uuid}/${handle.name}` }); + + this.fileSystemProvider.registerFileHandle(uuid, handle); + + return uri; } async showSaveDialog(options: ISaveDialogOptions): Promise { const schema = this.getFileSystemSchema(options); - return this.showSaveDialogSimplified(schema, options); + + if (this.shouldUseSimplified(schema)) { + return this.showSaveDialogSimplified(schema, options); + } + + const handle = await window.showSaveFilePicker(); + const uuid = generateUuid(); + const uri = URI.from({ scheme: Schemas.file, path: `/${uuid}/${handle.name}` }); + + this.fileSystemProvider.registerFileHandle(uuid, handle); + + return uri; } async showOpenDialog(options: IOpenDialogOptions): Promise { const schema = this.getFileSystemSchema(options); - return this.showOpenDialogSimplified(schema, options); + + if (this.shouldUseSimplified(schema)) { + return this.showOpenDialogSimplified(schema, options); + } + + const handle = await window.showDirectoryPicker(); + const uuid = generateUuid(); + const uri = URI.from({ scheme: Schemas.file, path: `/${uuid}/${handle.name}` }); + + this.fileSystemProvider.registerDirectoryHandle(uuid, handle); + + return [uri]; } protected addFileSchemaIfNeeded(schema: string): string[] { return schema === Schemas.untitled ? [Schemas.file] : [schema]; } + + private shouldUseSimplified(scheme: string): boolean { + return ![Schemas.file, Schemas.userData, Schemas.tmp].includes(scheme); + } } registerSingleton(IFileDialogService, FileDialogService, true); diff --git a/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts b/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts index 2db31b9a..6ddbb5a1 100644 --- a/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts +++ b/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts @@ -92,6 +92,7 @@ interface FileQuickPickItem extends IQuickPickItem { enum UpdateResult { Updated, + UpdatedWithTrailing, Updating, NotUpdated, InvalidPath @@ -119,7 +120,7 @@ export class SimpleFileDialog { private remoteAgentEnvironment: IRemoteAgentEnvironment | null | undefined; private separator: string = '/'; private readonly onBusyChangeEmitter = new Emitter(); - private updatingPromise: CancelablePromise | undefined; + private updatingPromise: CancelablePromise | undefined; protected disposables: IDisposable[] = [ this.onBusyChangeEmitter @@ -416,7 +417,7 @@ export class SimpleFileDialog { if (!resources.extUriIgnorePathCase.isEqual(this.currentFolder, filePickBoxUri)) { updated = await this.tryUpdateItems(value, filePickBoxUri); } - if (updated === UpdateResult.NotUpdated) { + if ((updated === UpdateResult.NotUpdated) || (updated === UpdateResult.UpdatedWithTrailing)) { this.setActiveItems(value); } } else { @@ -538,19 +539,24 @@ export class SimpleFileDialog { return dir; } + private tildaReplace(value: string): URI { + const home = this.userHome; + if ((value[0] === '~') && (value.length > 1)) { + return resources.joinPath(home, value.substring(1)); + } else if (value[value.length - 1] === '~') { + return home; + } + return this.remoteUriFrom(value); + } + private async tryUpdateItems(value: string, valueUri: URI): Promise { if ((value.length > 0) && ((value[value.length - 1] === '~') || (value[0] === '~'))) { - let newDir = this.userHome; - if ((value[0] === '~') && (value.length > 1)) { - newDir = resources.joinPath(newDir, value.substring(1)); - } - await this.updateItems(newDir, true); - return UpdateResult.Updated; + let newDir = this.tildaReplace(value); + return await this.updateItems(newDir, true) ? UpdateResult.UpdatedWithTrailing : UpdateResult.Updated; } else if (value === '\\') { valueUri = this.root(this.currentFolder); value = this.pathFromUri(valueUri); - await this.updateItems(valueUri, true); - return UpdateResult.Updated; + return await this.updateItems(valueUri, true) ? UpdateResult.UpdatedWithTrailing : UpdateResult.Updated; } else if (!resources.extUriIgnorePathCase.isEqual(this.currentFolder, valueUri) && (this.endsWithSlash(value) || (!resources.extUriIgnorePathCase.isEqual(this.currentFolder, resources.dirname(valueUri)) && resources.extUriIgnorePathCase.isEqualOrParent(this.currentFolder, resources.dirname(valueUri))))) { let stat: IFileStat | undefined; try { @@ -559,8 +565,7 @@ export class SimpleFileDialog { // do nothing } if (stat && stat.isDirectory && (resources.basename(valueUri) !== '.') && this.endsWithSlash(value)) { - await this.updateItems(valueUri); - return UpdateResult.Updated; + return await this.updateItems(valueUri) ? UpdateResult.UpdatedWithTrailing : UpdateResult.Updated; } else if (this.endsWithSlash(value)) { // The input box contains a path that doesn't exist on the system. this.filePickBox.validationMessage = nls.localize('remoteFileDialog.badPath', 'The path does not exist.'); @@ -579,9 +584,8 @@ export class SimpleFileDialog { // do nothing } if (statWithoutTrailing && statWithoutTrailing.isDirectory) { - await this.updateItems(inputUriDirname, false, resources.basename(valueUri)); this.badPath = undefined; - return UpdateResult.Updated; + return await this.updateItems(inputUriDirname, false, resources.basename(valueUri)) ? UpdateResult.UpdatedWithTrailing : UpdateResult.Updated; } } } @@ -591,6 +595,7 @@ export class SimpleFileDialog { } private setActiveItems(value: string) { + value = this.pathFromUri(this.tildaReplace(value)); const inputBasename = resources.basename(this.remoteUriFrom(value)); const userPath = this.constructFullUserPath(); // Make sure that the folder whose children we are currently viewing matches the path in the input @@ -798,31 +803,46 @@ export class SimpleFileDialog { return Promise.resolve(true); } - private async updateItems(newFolder: URI, force: boolean = false, trailing?: string) { + // Returns true if there is a file at the end of the URI. + private async updateItems(newFolder: URI, force: boolean = false, trailing?: string): Promise { this.busy = true; - this.userEnteredPathSegment = trailing ? trailing : ''; this.autoCompletePathSegment = ''; - const newValue = trailing ? this.pathAppend(newFolder, trailing) : this.pathFromUri(newFolder, true); - this.currentFolder = resources.addTrailingPathSeparator(newFolder, this.separator); + const isSave = !!trailing; + let result = false; const updatingPromise = createCancelablePromise(async token => { - return this.createItems(this.currentFolder, token).then(items => { + let folderStat: IFileStat | undefined; + try { + folderStat = await this.fileService.resolve(newFolder); + if (!folderStat.isDirectory) { + trailing = resources.basename(newFolder); + newFolder = resources.dirname(newFolder); + folderStat = undefined; + result = true; + } + } catch (e) { + // The file/directory doesn't exist + } + const newValue = trailing ? this.pathAppend(newFolder, trailing) : this.pathFromUri(newFolder, true); + this.currentFolder = resources.addTrailingPathSeparator(newFolder, this.separator); + this.userEnteredPathSegment = trailing ? trailing : ''; + + return this.createItems(folderStat, this.currentFolder, token).then(items => { if (token.isCancellationRequested) { this.busy = false; - return; + return false; } this.filePickBox.items = items; this.filePickBox.activeItems = [this.filePickBox.items[0]]; - if (this.allowFolderSelection) { - this.filePickBox.activeItems = []; - } - // the user might have continued typing while we were updating. Only update the input box if it doesn't matche directory. + this.filePickBox.activeItems = []; + + // the user might have continued typing while we were updating. Only update the input box if it doesn't match the directory. if (!equalsIgnoreCase(this.filePickBox.value, newValue) && force) { this.filePickBox.valueSelection = [0, this.filePickBox.value.length]; this.insertText(newValue, newValue); } - if (force && trailing) { + if (force && trailing && isSave) { // Keep the cursor position in front of the save as name. this.filePickBox.valueSelection = [this.filePickBox.value.length - trailing.length, this.filePickBox.value.length - trailing.length]; } else if (!trailing) { @@ -831,6 +851,7 @@ export class SimpleFileDialog { } this.busy = false; this.updatingPromise = undefined; + return result; }); }); @@ -893,12 +914,14 @@ export class SimpleFileDialog { return null; } - private async createItems(currentFolder: URI, token: CancellationToken): Promise { + private async createItems(folder: IFileStat | undefined, currentFolder: URI, token: CancellationToken): Promise { const result: FileQuickPickItem[] = []; const backDir = this.createBackItem(currentFolder); try { - const folder = await this.fileService.resolve(currentFolder); + if (!folder) { + folder = await this.fileService.resolve(currentFolder); + } const items = folder.children ? await Promise.all(folder.children.map(child => this.createItem(child, currentFolder, token))) : []; for (let item of items) { if (item) { diff --git a/src/vs/workbench/services/dialogs/electron-sandbox/fileDialogService.ts b/src/vs/workbench/services/dialogs/electron-sandbox/fileDialogService.ts index 3398150a..368c1619 100644 --- a/src/vs/workbench/services/dialogs/electron-sandbox/fileDialogService.ts +++ b/src/vs/workbench/services/dialogs/electron-sandbox/fileDialogService.ts @@ -25,8 +25,6 @@ import { IPathService } from 'vs/workbench/services/path/common/pathService'; export class FileDialogService extends AbstractFileDialogService implements IFileDialogService { - declare readonly _serviceBrand: undefined; - constructor( @IHostService hostService: IHostService, @IWorkspaceContextService contextService: IWorkspaceContextService, diff --git a/src/vs/workbench/services/editor/browser/editorOverrideService.ts b/src/vs/workbench/services/editor/browser/editorOverrideService.ts new file mode 100644 index 00000000..ebef7e13 --- /dev/null +++ b/src/vs/workbench/services/editor/browser/editorOverrideService.ts @@ -0,0 +1,564 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as glob from 'vs/base/common/glob'; +import { distinct, firstOrDefault, flatten, insert } from 'vs/base/common/arrays'; +import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { basename, extname, isEqual } from 'vs/base/common/resources'; +import { URI } from 'vs/base/common/uri'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { EditorActivation, EditorOverride, IEditorOptions, ITextEditorOptions } from 'vs/platform/editor/common/editor'; +import { EditorResourceAccessor, IEditorInput, IEditorInputWithOptions, IEditorInputWithOptionsAndGroup } from 'vs/workbench/common/editor'; +import { IEditorGroup, IEditorGroupsService, preferredSideBySideGroupDirection } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { Schemas } from 'vs/base/common/network'; +import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; +import { ContributedEditorInfo, ContributedEditorPriority, ContributionPointOptions, DEFAULT_EDITOR_ASSOCIATION, DiffEditorInputFactoryFunction, EditorAssociation, EditorAssociations, EditorInputFactoryFunction, editorsAssociationsSettingId, globMatchesResource, IEditorOverrideService, priorityToRank } from 'vs/workbench/services/editor/common/editorOverrideService'; +import { IKeyMods, IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; +import { localize } from 'vs/nls'; +import { Codicon } from 'vs/base/common/codicons'; +import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; + +interface IContributedEditorInput extends IEditorInput { + viewType?: string; +} + +interface ContributionPoint { + globPattern: string | glob.IRelativePattern, + editorInfo: ContributedEditorInfo, + options?: ContributionPointOptions, + createEditorInput: EditorInputFactoryFunction + createDiffEditorInput?: DiffEditorInputFactoryFunction +} + +type ContributionPoints = Array; + +export class EditorOverrideService extends Disposable implements IEditorOverrideService { + readonly _serviceBrand: undefined; + + private _contributionPoints: Map = new Map(); + private static readonly overrideCacheStorageID = 'editorOverrideService.cache'; + private cache: Set | undefined; + + constructor( + @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @IQuickInputService private readonly quickInputService: IQuickInputService, + @INotificationService private readonly notificationService: INotificationService, + @ITelemetryService private readonly telemetryService: ITelemetryService, + @IStorageService private readonly storageService: IStorageService, + @IExtensionService private readonly extensionService: IExtensionService + ) { + super(); + // Read in the cache on statup + this.cache = new Set(JSON.parse(this.storageService.get(EditorOverrideService.overrideCacheStorageID, StorageScope.GLOBAL, JSON.stringify([])))); + this.storageService.remove(EditorOverrideService.overrideCacheStorageID, StorageScope.GLOBAL); + + this._register(this.storageService.onWillSaveState(() => { + // We want to store the glob patterns we would activate on, this allows us to know if we need to await the ext host on startup for opening a resource + this.cacheContributionPoints(); + })); + + // When extensions have registered we no longer need the cache + this.extensionService.onDidRegisterExtensions(() => { + this.cache = undefined; + }); + } + + async resolveEditorOverride(editor: IEditorInput, options: IEditorOptions | ITextEditorOptions | undefined, group: IEditorGroup): Promise { + // If it was an override before we await for the extensions to activate and then proceed with overriding or else they won't be registered + if (this.cache && editor.resource && this.resourceMatchesCache(editor.resource)) { + await this.extensionService.whenInstalledExtensionsRegistered(); + } + + if (options?.override === EditorOverride.DISABLED) { + throw new Error(`Calling resolve editor override when override is explicitly disabled!`); + } + + // Always ensure inputs have populated resource fields + if (editor instanceof DiffEditorInput) { + if ((!editor.modifiedInput.resource || !editor.originalInput.resource)) { + return { editor, options, group }; + } + } else if (!editor.resource) { + return { editor, options, group }; + } + + let override = typeof options?.override === 'string' ? options.override : undefined; + // If the editor passed in already has a type and the user didn't explicitly override the editor choice, use the editor type. + override = override ?? (editor as IContributedEditorInput).viewType; + + if (options?.override === EditorOverride.PICK) { + const picked = await this.doPickEditorOverride(editor, options, group); + // If the picker was cancelled we will stop resolving the override + if (!picked) { + return; + } + // Deconstruct the return picked options and overrides if the user selected something + override = picked[0].override as string | undefined; + options = picked[0]; + group = picked[1] ?? group; + } + + // Resolved the override as much as possible, now find a given contribution + const { contributionPoint, conflictingDefault } = this.getContributionPoint(editor instanceof DiffEditorInput ? editor.modifiedInput.resource! : editor.resource!, override); + const selectedContribution = contributionPoint; + if (!selectedContribution) { + return { editor, options, group }; + } + + const handlesDiff = typeof selectedContribution.options?.canHandleDiff === 'function' ? selectedContribution.options.canHandleDiff() : selectedContribution.options?.canHandleDiff; + if (editor instanceof DiffEditorInput && handlesDiff === false) { + return { editor, options, group }; + } + + // If it's the currently active editor we shouldn't do anything + if (selectedContribution.editorInfo.describes(editor)) { + return; + } + const input = await this.doOverrideEditorInput(editor, options, group, selectedContribution); + if (conflictingDefault && input) { + // Wait one second to give the user ample time to see the current editor then ask them to configure a default + setTimeout(() => { + this.doHandleConflictingDefaults(selectedContribution.editorInfo.label, input.editor, input.options ?? options, group); + }, 1200); + } + // Add the group as we might've changed it with the quickpick + if (input) { + this.sendOverrideTelemetry(input.editor); + return { ...input, group }; + } + return input; + } + + registerContributionPoint( + globPattern: string | glob.IRelativePattern, + editorInfo: ContributedEditorInfo, + options: ContributionPointOptions, + createEditorInput: EditorInputFactoryFunction, + createDiffEditorInput?: DiffEditorInputFactoryFunction + ): IDisposable { + if (this._contributionPoints.get(globPattern) === undefined) { + this._contributionPoints.set(globPattern, []); + } + const remove = insert(this._contributionPoints.get(globPattern)!, { + globPattern, + editorInfo, + options, + createEditorInput, + createDiffEditorInput + }); + return toDisposable(() => remove()); + } + + hasContributionPoint(schemeOrGlob: string): boolean { + return this._contributionPoints.has(schemeOrGlob); + } + + getAssociationsForResource(resource: URI): EditorAssociations { + const rawAssociations = this.configurationService.getValue(editorsAssociationsSettingId) || []; + return rawAssociations.filter(association => association.filenamePattern && globMatchesResource(association.filenamePattern, resource)); + } + + updateUserAssociations(globPattern: string, editorID: string): void { + const newAssociation: EditorAssociation = { viewType: editorID, filenamePattern: globPattern }; + const currentAssociations = [...this.configurationService.getValue(editorsAssociationsSettingId)]; + + // First try updating existing association + for (let i = 0; i < currentAssociations.length; ++i) { + const existing = currentAssociations[i]; + if (existing.filenamePattern === newAssociation.filenamePattern) { + currentAssociations.splice(i, 1, newAssociation); + this.configurationService.updateValue(editorsAssociationsSettingId, currentAssociations); + return; + } + } + + // Otherwise, create a new one + currentAssociations.unshift(newAssociation); + this.configurationService.updateValue(editorsAssociationsSettingId, currentAssociations); + } + + private findMatchingContributions(resource: URI): ContributionPoint[] { + let contributions: ContributionPoint[] = []; + // Then all glob patterns + for (const key of this._contributionPoints.keys()) { + const contributionPoints = this._contributionPoints.get(key)!; + for (const contributionPoint of contributionPoints) { + if (globMatchesResource(key, resource)) { + contributions.push(contributionPoint); + } + } + } + // Return the contributions sorted by their priority + return contributions.sort((a, b) => priorityToRank(b.editorInfo.priority) - priorityToRank(a.editorInfo.priority)); + } + + /** + * Given a resource and an override selects the best possible contribution point + * @returns The contribution point and whether there was another default which conflicted with it + */ + private getContributionPoint(resource: URI, override: string | undefined): { contributionPoint: ContributionPoint | undefined, conflictingDefault: boolean } { + const findMatchingContribPoint = (contributionPoints: ContributionPoints, viewType: string) => { + return contributionPoints.find((contribPoint) => { + if (contribPoint.options && contribPoint.options.canSupportResource !== undefined) { + return contribPoint.editorInfo.id === viewType && contribPoint.options.canSupportResource(resource); + } + return contribPoint.editorInfo.id === viewType; + }); + }; + if (override) { + // Specific overried passed in doesn't have to match the reosurce, it can be anything + const contributionPoints = flatten(Array.from(this._contributionPoints.values())); + return { + contributionPoint: findMatchingContribPoint(contributionPoints, override), + conflictingDefault: false + }; + } + + let contributionPoints = this.findMatchingContributions(resource); + + const associationsFromSetting = this.getAssociationsForResource(resource); + // We only want built-in+ if no user defined setting is found, else we won't override + const possibleContributionPoints = contributionPoints.filter(contribPoint => priorityToRank(contribPoint.editorInfo.priority) >= priorityToRank(ContributedEditorPriority.builtin) && contribPoint.editorInfo.id !== DEFAULT_EDITOR_ASSOCIATION.id); + // If the user has a setting we use that, else choose the highest priority editor that is built-in+ + const selectedViewType = associationsFromSetting[0]?.viewType || possibleContributionPoints[0]?.editorInfo.id; + + let conflictingDefault = false; + if (associationsFromSetting.length === 0 && possibleContributionPoints.length > 1) { + conflictingDefault = true; + } + + return { + contributionPoint: findMatchingContribPoint(contributionPoints, selectedViewType), + conflictingDefault + }; + } + + private async doOverrideEditorInput(editor: IEditorInput, options: IEditorOptions | ITextEditorOptions | undefined, group: IEditorGroup, selectedContribution: ContributionPoint): Promise { + + // If no activation option is provided, populate it. + if (options && typeof options.activation === 'undefined') { + options = { ...options, activation: options.preserveFocus ? EditorActivation.RESTORE : undefined }; + } + + // If it's a diff editor we trigger the create diff editor input + if (editor instanceof DiffEditorInput) { + if (!selectedContribution.createDiffEditorInput) { + return; + } + const inputWithOptions = selectedContribution.createDiffEditorInput(editor, options, group); + return inputWithOptions; + } + + // We only call this function from one place and there we do the check to ensure editor.resource is not undefined + const resource = editor.resource!; + + // Respect options passed back + const inputWithOptions = selectedContribution.createEditorInput(resource, options, group); + options = inputWithOptions.options ?? options; + const input = inputWithOptions.editor; + + // If the editor states it can only be opened once per resource we must close all existing ones first + const singleEditorPerResource = typeof selectedContribution.options?.singlePerResource === 'function' ? selectedContribution.options.singlePerResource() : selectedContribution.options?.singlePerResource; + if (singleEditorPerResource) { + this.closeExistingEditorsForResource(resource, selectedContribution.editorInfo.id, group); + } + + return { editor: input, options }; + } + + private closeExistingEditorsForResource( + resource: URI, + viewType: string, + targetGroup: IEditorGroup, + ): void { + const editorInfoForResource = this.findExistingEditorsForResource(resource, viewType); + if (!editorInfoForResource.length) { + return; + } + + const editorToUse = editorInfoForResource[0]; + + // Replace all other editors + for (const { editor, group } of editorInfoForResource) { + if (editor !== editorToUse.editor) { + group.closeEditor(editor); + } + } + + if (targetGroup.id !== editorToUse.group.id) { + editorToUse.group.closeEditor(editorToUse.editor); + } + return; + } + + /** + * Given a resource and a viewType, returns all editors open for that resouce and viewType. + * @param resource The resource specified + * @param viewType The viewtype + * @returns A list of editors + */ + private findExistingEditorsForResource( + resource: URI, + viewType: string, + ): Array<{ editor: IEditorInput, group: IEditorGroup }> { + const out: Array<{ editor: IEditorInput, group: IEditorGroup }> = []; + const orderedGroups = distinct([ + ...this.editorGroupService.groups, + ]); + + for (const group of orderedGroups) { + for (const editor of group.editors) { + if (isEqual(editor.resource, resource) && (editor as IContributedEditorInput).viewType === viewType) { + out.push({ editor, group }); + } + } + } + return out; + } + + private async doHandleConflictingDefaults(editorName: string, currentEditor: IContributedEditorInput, options: IEditorOptions | undefined, group: IEditorGroup) { + const makeCurrentEditorDefault = () => { + const viewType = currentEditor.viewType; + if (viewType) { + this.updateUserAssociations(`*${extname(currentEditor.resource!)}`, viewType); + } + }; + + const handle = this.notificationService.prompt(Severity.Warning, + localize('editorOverride.conflictingDefaults', 'Multiple editors want to be your default editor for this resource.'), + [{ + label: localize('editorOverride.configureDefault', 'Configure Default'), + run: async () => { + // Show the picker and tell it to update the setting to whatever the user selected + const picked = await this.doPickEditorOverride(currentEditor, options, group, true); + if (!picked) { + return; + } + const replacementEditor = await this.resolveEditorOverride(currentEditor, picked[0], picked[1] ?? group); + if (!replacementEditor) { + return; + } + // Replace the current editor with the picked one + (replacementEditor.group ?? picked[1] ?? group).replaceEditors([ + { + editor: currentEditor, + replacement: replacementEditor.editor, + options: replacementEditor.options ?? picked[0], + } + ]); + } + }, + { + label: localize('editorOverride.keepDefault', 'Keep {0}', editorName), + run: makeCurrentEditorDefault + } + ]); + // If the user pressed X we assume they want to keep the current editor as default + const onCloseListener = handle.onDidClose(() => { + makeCurrentEditorDefault(); + onCloseListener.dispose(); + }); + } + + private mapContributionsToQuickPickEntry(resource: URI, group: IEditorGroup, alwaysUpdateSetting?: boolean) { + const currentEditor = firstOrDefault(group.findEditors(resource)); + // If untitled, we want all contribution points + let contributionPoints = resource.scheme === Schemas.untitled ? distinct(flatten(Array.from(this._contributionPoints.values())), (contrib) => contrib.editorInfo.id) : this.findMatchingContributions(resource); + + // Not the most efficient way to do this, but we want to ensure the text editor is at the top of the quickpick + contributionPoints = contributionPoints.sort((a, b) => { + if (a.editorInfo.id === DEFAULT_EDITOR_ASSOCIATION.id) { + return -1; + } else if (b.editorInfo.id === DEFAULT_EDITOR_ASSOCIATION.id) { + return 1; + } else { + return priorityToRank(b.editorInfo.priority) - priorityToRank(a.editorInfo.priority); + } + }); + const contribGroups: { defaults: Array, optional: Array } = { + defaults: [ + { type: 'separator', label: localize('editorOverride.picker.default', 'Defaults') } + ], + optional: [ + { type: 'separator', label: localize('editorOverride.picker.optional', 'Optional') } + ], + }; + // Get the matching contribtuions and call resolve whether they're active for the picker + contributionPoints.forEach(contribPoint => { + const isActive = currentEditor ? contribPoint.editorInfo.describes(currentEditor) : false; + const quickPickEntry = { + id: contribPoint.editorInfo.id, + label: contribPoint.editorInfo.label, + description: isActive ? localize('promptOpenWith.currentlyActive', "Currently Active") : undefined, + detail: contribPoint.editorInfo.detail ?? contribPoint.editorInfo.priority, + buttons: alwaysUpdateSetting ? [] : [{ + iconClass: Codicon.gear.classNames, + tooltip: localize('promptOpenWith.setDefaultTooltip', "Set as default editor for '{0}' files", extname(resource)) + }], + }; + if (contribPoint.editorInfo.priority === ContributedEditorPriority.option) { + contribGroups.optional.push(quickPickEntry); + } else { + contribGroups.defaults.push(quickPickEntry); + } + }); + return [...contribGroups.defaults, ...contribGroups.optional]; + } + + private async doPickEditorOverride(editor: IEditorInput, options: IEditorOptions | undefined, group: IEditorGroup, alwaysUpdateSetting?: boolean): Promise<[IEditorOptions, IEditorGroup | undefined] | undefined> { + + type EditorOverridePick = { + readonly item: IQuickPickItem; + readonly keyMods?: IKeyMods; + readonly openInBackground: boolean; + }; + + const resource = EditorResourceAccessor.getOriginalUri(editor); + + if (!resource) { + return; + } + + // Text editor has the lowest priority because we + const editorOverridePicks = this.mapContributionsToQuickPickEntry(resource, group, alwaysUpdateSetting); + + // Create editor override picker + const editorOverridePicker = this.quickInputService.createQuickPick(); + const placeHolderMessage = alwaysUpdateSetting ? + localize('prompOpenWith.updateDefaultPlaceHolder', "Select new default editor for '{0}'", basename(resource)) : + localize('promptOpenWith.placeHolder', "Select editor for '{0}'", basename(resource)); + editorOverridePicker.placeholder = placeHolderMessage; + editorOverridePicker.canAcceptInBackground = true; + editorOverridePicker.items = editorOverridePicks; + const firstItem = editorOverridePicker.items.find(item => item.type === 'item') as IQuickPickItem | undefined; + if (firstItem) { + editorOverridePicker.selectedItems = [firstItem]; + } + + // Prompt the user to select an override + const picked: EditorOverridePick | undefined = await new Promise(resolve => { + editorOverridePicker.onDidAccept(e => { + let result: EditorOverridePick | undefined = undefined; + + if (editorOverridePicker.selectedItems.length === 1) { + result = { + item: editorOverridePicker.selectedItems[0], + keyMods: editorOverridePicker.keyMods, + openInBackground: e.inBackground + }; + } + + // If asked to always update the setting then update it even if the gear isn't clicked + if (alwaysUpdateSetting && result?.item.id) { + this.updateUserAssociations(`*${extname(resource)}`, result.item.id,); + } + + resolve(result); + }); + + editorOverridePicker.onDidTriggerItemButton(e => { + + // Trigger opening and close picker + resolve({ item: e.item, openInBackground: false }); + + // Persist setting + if (resource && e.item && e.item.id) { + this.updateUserAssociations(`*${extname(resource)}`, e.item.id,); + } + }); + + editorOverridePicker.show(); + }); + + // Close picker + editorOverridePicker.dispose(); + + // If the user picked an override, look at how the picker was + // used (e.g. modifier keys, open in background) and create the + // options and group to use accordingly + if (picked) { + + // Figure out target group + let targetGroup: IEditorGroup | undefined; + if (picked.keyMods?.alt || picked.keyMods?.ctrlCmd) { + const direction = preferredSideBySideGroupDirection(this.configurationService); + targetGroup = this.editorGroupService.findGroup({ direction }, group.id); + targetGroup = targetGroup ?? this.editorGroupService.addGroup(group, direction); + } + + // Figure out options + const targetOptions: IEditorOptions = { + ...options, + override: picked.item.id, + preserveFocus: picked.openInBackground || options?.preserveFocus, + }; + + return [targetOptions, targetGroup]; + } + + return undefined; + } + + private sendOverrideTelemetry(chosenInput: IContributedEditorInput): void { + type editorOverrideClassification = { + viewType: { classification: 'PublicNonPersonalData', purpose: 'FeatureInsight' }; + }; + type editorOverrideEvent = { + viewType: string + }; + if (chosenInput.viewType) { + this.telemetryService.publicLog2('override.viewType', { viewType: chosenInput.viewType }); + } + } + + private cacheContributionPoints() { + // Create a set to store contributed glob patterns + const cacheStorage: Set = new Set(); + + // Store just the relative pattern pieces without any path info + for (const globPattern of this._contributionPoints.keys()) { + const contribPoint = this._contributionPoints.get(globPattern)!; + const nonOptional = !!contribPoint.find(c => c.editorInfo.priority !== ContributedEditorPriority.option && c.editorInfo.id !== DEFAULT_EDITOR_ASSOCIATION.id); + // Don't keep a cache of the optional ones as those wouldn't be opened on start anyways + if (!nonOptional) { + continue; + } + if (glob.isRelativePattern(globPattern)) { + cacheStorage.add(`${globPattern.pattern}`); + } else { + cacheStorage.add(globPattern); + } + } + + // Also store the users settings as those would have to activate on startup as well + const userAssociations = this.configurationService.getValue(editorsAssociationsSettingId) || []; + for (const association of userAssociations) { + if (association.filenamePattern) { + cacheStorage.add(association.filenamePattern); + } + } + this.storageService.store(EditorOverrideService.overrideCacheStorageID, JSON.stringify(Array.from(cacheStorage)), StorageScope.GLOBAL, StorageTarget.MACHINE); + } + + private resourceMatchesCache(resource: URI): boolean { + if (!this.cache) { + return false; + } + + for (const cacheEntry of this.cache) { + if (globMatchesResource(cacheEntry, resource)) { + return true; + } + } + return false; + } +} + +registerSingleton(IEditorOverrideService, EditorOverrideService); diff --git a/src/vs/workbench/services/editor/browser/editorService.ts b/src/vs/workbench/services/editor/browser/editorService.ts index 80f834db..63b976e3 100644 --- a/src/vs/workbench/services/editor/browser/editorService.ts +++ b/src/vs/workbench/services/editor/browser/editorService.ts @@ -3,11 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from 'vs/nls'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IResourceEditorInput, ITextEditorOptions, IEditorOptions, EditorActivation, EditorOverride } from 'vs/platform/editor/common/editor'; -import { SideBySideEditor, IEditorInput, IEditorPane, GroupIdentifier, IFileEditorInput, IUntitledTextResourceEditorInput, IResourceDiffEditorInput, IEditorInputFactoryRegistry, Extensions as EditorExtensions, EditorInput, SideBySideEditorInput, IEditorInputWithOptions, isEditorInputWithOptions, EditorOptions, TextEditorOptions, IEditorIdentifier, IEditorCloseEvent, ITextEditorPane, ITextDiffEditorPane, IRevertOptions, SaveReason, EditorsOrder, isTextEditorPane, IWorkbenchEditorConfiguration, EditorResourceAccessor, IVisibleEditorPane } from 'vs/workbench/common/editor'; -import { DEFAULT_EDITOR_ASSOCIATION, EditorAssociation, EditorsAssociations, editorsAssociationsSettingId } from 'vs/workbench/browser/editor'; +import { IResourceEditorInput, ITextEditorOptions, IEditorOptions, EditorActivation, EditorOverride, IResourceEditorInputIdentifier } from 'vs/platform/editor/common/editor'; +import { SideBySideEditor, IEditorInput, IEditorPane, GroupIdentifier, IFileEditorInput, IUntitledTextResourceEditorInput, IResourceDiffEditorInput, IEditorInputFactoryRegistry, EditorExtensions, EditorInput, SideBySideEditorInput, IEditorInputWithOptions, isEditorInputWithOptions, EditorOptions, TextEditorOptions, IEditorIdentifier, IEditorCloseEvent, ITextEditorPane, ITextDiffEditorPane, IRevertOptions, SaveReason, EditorsOrder, isTextEditorPane, IWorkbenchEditorConfiguration, EditorResourceAccessor, IVisibleEditorPane, IEditorInputWithOptionsAndGroup } from 'vs/workbench/common/editor'; import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; import { Registry } from 'vs/platform/registry/common/platform'; import { ResourceMap } from 'vs/base/common/map'; @@ -16,17 +14,17 @@ import { IFileService, FileOperationEvent, FileOperation, FileChangesEvent, File import { Schemas } from 'vs/base/common/network'; import { Event, Emitter } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; -import { basename, joinPath, isEqual, extname } from 'vs/base/common/resources'; +import { basename, joinPath, isEqual } from 'vs/base/common/resources'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; -import { IEditorGroupsService, IEditorGroup, GroupsOrder, IEditorReplacement, GroupChangeKind, preferredSideBySideGroupDirection, OpenEditorContext } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IEditorGroupsService, IEditorGroup, GroupsOrder, IEditorReplacement, GroupChangeKind, preferredSideBySideGroupDirection } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IResourceEditorInputType, SIDE_GROUP, IResourceEditorReplacement, IOpenEditorOverrideHandler, IEditorService, SIDE_GROUP_TYPE, ACTIVE_GROUP_TYPE, ISaveEditorsOptions, ISaveAllEditorsOptions, IRevertAllEditorsOptions, IBaseSaveRevertAllEditorOptions, IOpenEditorOverrideEntry } from 'vs/workbench/services/editor/common/editorService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { Disposable, IDisposable, dispose, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { coalesce, distinct, firstOrDefault, insert } from 'vs/base/common/arrays'; import { isCodeEditor, isDiffEditor, ICodeEditor, IDiffEditor, isCompositeEditor } from 'vs/editor/browser/editorBrowser'; -import { IEditorGroupView, IEditorOpeningEvent, EditorServiceImpl } from 'vs/workbench/browser/parts/editor/editor'; +import { IEditorGroupView, EditorServiceImpl } from 'vs/workbench/browser/parts/editor/editor'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { withNullAsUndefined } from 'vs/base/common/types'; +import { isUndefined, withNullAsUndefined } from 'vs/base/common/types'; import { EditorsObserver } from 'vs/workbench/browser/parts/editor/editorsObserver'; import { IEditorViewState } from 'vs/editor/common/editorCommon'; import { IUntitledTextEditorModel } from 'vs/workbench/services/untitled/common/untitledTextEditorModel'; @@ -34,12 +32,10 @@ import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/u import { Promises, timeout } from 'vs/base/common/async'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { indexOfPath } from 'vs/base/common/extpath'; -import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; import { IModelService } from 'vs/editor/common/services/modelService'; import { ILogService } from 'vs/platform/log/common/log'; -import { IKeyMods, IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; -import { Codicon } from 'vs/base/common/codicons'; +import { ContributedEditorPriority, DEFAULT_EDITOR_ASSOCIATION, IEditorOverrideService } from 'vs/workbench/services/editor/common/editorOverrideService'; type CachedEditorInput = ResourceEditorInput | IFileEditorInput | UntitledTextEditorInput; type OpenInEditorGroup = IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE; @@ -76,22 +72,25 @@ export class EditorService extends Disposable implements EditorServiceImpl { @IFileService private readonly fileService: IFileService, @IConfigurationService private readonly configurationService: IConfigurationService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, - @IWorkingCopyService private readonly workingCopyService: IWorkingCopyService, @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, @ILogService private readonly logService: ILogService, - @IQuickInputService private readonly quickInputService: IQuickInputService + @IEditorOverrideService private readonly editorOverrideService: IEditorOverrideService, ) { super(); this.onConfigurationUpdated(configurationService.getValue()); this.registerListeners(); + + // Register the default editor to the override service + // so that it shows up in the editors picker + this.registerDefaultOverride(); } private registerListeners(): void { // Editor & group changes - this.editorGroupService.whenRestored.then(() => this.onEditorsRestored()); + this.editorGroupService.whenReady.then(() => this.onEditorGroupsReady()); this.editorGroupService.onDidChangeActiveGroup(group => this.handleActiveEditorChange(group)); this.editorGroupService.onDidAddGroup(group => this.registerGroupListeners(group as IEditorGroupView)); this.editorsObserver.onDidMostRecentlyActiveEditorsChange(() => this._onDidMostRecentlyActiveEditorsChange.fire()); @@ -115,10 +114,12 @@ export class EditorService extends Disposable implements EditorServiceImpl { private lastActiveEditor: IEditorInput | undefined = undefined; - private onEditorsRestored(): void { + private onEditorGroupsReady(): void { // Register listeners to each opened group - this.editorGroupService.groups.forEach(group => this.registerGroupListeners(group as IEditorGroupView)); + for (const group of this.editorGroupService.groups) { + this.registerGroupListeners(group as IEditorGroupView); + } // Fire initial set of editor events if there is an active editor if (this.activeEditor) { @@ -163,10 +164,6 @@ export class EditorService extends Disposable implements EditorServiceImpl { this._onDidCloseEditor.fire(event); })); - groupDisposables.add(group.onWillOpenEditor(event => { - this.onGroupWillOpenEditor(group, event); - })); - groupDisposables.add(group.onDidOpenEditorFail(editor => { this._onDidOpenEditorFail.fire({ editor, groupId: group.id }); })); @@ -199,20 +196,20 @@ export class EditorService extends Disposable implements EditorServiceImpl { } // Handle no longer visible out of workspace resources - [...this.activeOutOfWorkspaceWatchers.keys()].forEach(resource => { + for (const resource of this.activeOutOfWorkspaceWatchers.keys()) { if (!visibleOutOfWorkspaceResources.get(resource)) { dispose(this.activeOutOfWorkspaceWatchers.get(resource)); this.activeOutOfWorkspaceWatchers.delete(resource); } - }); + } // Handle newly visible out of workspace resources - visibleOutOfWorkspaceResources.forEach(resource => { + for (const resource of visibleOutOfWorkspaceResources.keys()) { if (!this.activeOutOfWorkspaceWatchers.get(resource)) { const disposable = this.fileService.watch(resource); this.activeOutOfWorkspaceWatchers.set(resource, disposable); } - }); + } } //#endregion @@ -437,7 +434,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { return this.getEditors(EditorsOrder.SEQUENTIAL).map(({ editor }) => editor); } - getEditors(order: EditorsOrder, options?: { excludeSticky?: boolean }): ReadonlyArray { + getEditors(order: EditorsOrder, options?: { excludeSticky?: boolean }): readonly IEditorIdentifier[] { switch (order) { // MRU @@ -452,9 +449,9 @@ export class EditorService extends Disposable implements EditorServiceImpl { case EditorsOrder.SEQUENTIAL: const editors: IEditorIdentifier[] = []; - this.editorGroupService.getGroups(GroupsOrder.GRID_APPEARANCE).forEach(group => { + for (const group of this.editorGroupService.getGroups(GroupsOrder.GRID_APPEARANCE)) { editors.push(...group.getEditors(EditorsOrder.SEQUENTIAL, options).map(editor => ({ editor, groupId: group.id }))); - }); + } return editors; } @@ -490,10 +487,10 @@ export class EditorService extends Disposable implements EditorServiceImpl { //#region editor overrides - private readonly openEditorHandlers: IOpenEditorOverrideHandler[] = []; + private readonly openEditorOverrides: IOpenEditorOverrideHandler[] = []; overrideOpenEditor(handler: IOpenEditorOverrideHandler): IDisposable { - const remove = insert(this.openEditorHandlers, handler); + const remove = insert(this.openEditorOverrides, handler); return toDisposable(() => remove()); } @@ -502,10 +499,10 @@ export class EditorService extends Disposable implements EditorServiceImpl { const overrides: [IOpenEditorOverrideHandler, IOpenEditorOverrideEntry][] = []; // Collect contributed editor open overrides - for (const handler of this.openEditorHandlers) { - if (typeof handler.getEditorOverrides === 'function') { + for (const openEditorOverride of this.openEditorOverrides) { + if (typeof openEditorOverride.getEditorOverrides === 'function') { try { - overrides.push(...handler.getEditorOverrides(resource, options, group).map(val => [handler, val] as [IOpenEditorOverrideHandler, IOpenEditorOverrideEntry])); + overrides.push(...openEditorOverride.getEditorOverrides(resource, options, group).map(val => [openEditorOverride, val] as [IOpenEditorOverrideHandler, IOpenEditorOverrideEntry])); } catch (error) { this.logService.error(`Unexpected error getting editor overrides: ${error}`); } @@ -520,6 +517,22 @@ export class EditorService extends Disposable implements EditorServiceImpl { return overrides; } + private registerDefaultOverride(): void { + this._register(this.editorOverrideService.registerContributionPoint( + '*', + { + id: DEFAULT_EDITOR_ASSOCIATION.id, + label: DEFAULT_EDITOR_ASSOCIATION.displayName, + detail: DEFAULT_EDITOR_ASSOCIATION.providerDisplayName, + describes: (currentEditor) => currentEditor.matches(this.activeEditor), + priority: ContributedEditorPriority.builtin + }, + {}, + resource => ({ editor: this.createEditorInput({ resource }) }), + diffEditor => ({ editor: diffEditor }) + )); + } + private getDefaultEditorOverride(resource: URI): [IOpenEditorOverrideHandler, IOpenEditorOverrideEntry] { return [ { @@ -559,19 +572,16 @@ export class EditorService extends Disposable implements EditorServiceImpl { ]; } - private onGroupWillOpenEditor(group: IEditorGroup, event: IEditorOpeningEvent): void { - if (event.options?.override === EditorOverride.DISABLED) { - return; // return early when overrides are explicitly disabled - } - - for (const handler of this.openEditorHandlers) { - const result = handler.open(event.editor, event.options, group, event.context ?? OpenEditorContext.NEW_EDITOR); + private doOverrideOpenEditor(editor: IEditorInput, options: IEditorOptions | undefined, group: IEditorGroup): Promise | undefined { + for (const openEditorOverride of this.openEditorOverrides) { + const result = openEditorOverride.open(editor, options, group); const override = result?.override; if (override) { - event.prevent((() => override.then(editor => withNullAsUndefined(editor)))); - break; + return override; } } + + return; } //#endregion @@ -586,13 +596,34 @@ export class EditorService extends Disposable implements EditorServiceImpl { if (result) { const [resolvedGroup, resolvedEditor, resolvedOptions] = result; - // If the override option is provided we want to open that specific editor or show a picker + // Override handling: pick editor or open specific if (resolvedOptions?.override === EditorOverride.PICK || typeof resolvedOptions?.override === 'string') { - return this.openEditorWith(resolvedOptions.override, resolvedEditor, resolvedOptions, resolvedGroup); + const resolvedInputWithOptionsAndGroup = await this.editorOverrideService.resolveEditorOverride(resolvedEditor, resolvedOptions, resolvedGroup); + if (!resolvedInputWithOptionsAndGroup) { + return undefined; // no editor was picked or registered for the identifier + } + + return (resolvedInputWithOptionsAndGroup.group ?? resolvedGroup).openEditor(resolvedInputWithOptionsAndGroup.editor, resolvedInputWithOptionsAndGroup.options ?? resolvedOptions); } - // Otherwise proceed to open normally - return withNullAsUndefined(await resolvedGroup.openEditor(resolvedEditor, resolvedOptions)); + // Override handling: ask providers to override + if (resolvedOptions?.override !== EditorOverride.DISABLED) { + // TODO@lramos15 this will get cleaned up soon, but since the override + // service no longer uses the override flow we must check that + const resolvedInputWithOptionsAndGroup = await this.editorOverrideService.resolveEditorOverride(resolvedEditor, resolvedOptions, resolvedGroup); + // If we didn't override try the legacy overrides + if (!resolvedInputWithOptionsAndGroup || resolvedEditor.matches(resolvedInputWithOptionsAndGroup.editor)) { + const override = this.doOverrideOpenEditor(resolvedEditor, resolvedOptions, resolvedGroup); + if (override) { + return override; + } + } else { + return (resolvedInputWithOptionsAndGroup.group ?? resolvedGroup).openEditor(resolvedInputWithOptionsAndGroup.editor, resolvedInputWithOptionsAndGroup.options ?? resolvedOptions); + } + } + + // Override handling: disabled + return resolvedGroup.openEditor(resolvedEditor, resolvedOptions); } return undefined; @@ -616,7 +647,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { // Untyped Text Editor Support else { - const textInput = editor; + const textInput = editor as IResourceEditorInputType; typedEditor = this.createEditorInput(textInput); if (typedEditor) { typedOptions = TextEditorOptions.from(textInput); @@ -652,158 +683,6 @@ export class EditorService extends Disposable implements EditorServiceImpl { return undefined; } - private async openEditorWith(override: EditorOverride.PICK | string, editor: IEditorInput, options: IEditorOptions | undefined, group: IEditorGroup): Promise { - const editorOverride = await this.findEditorOverride(override, editor, options, group); - if (!editorOverride) { - return undefined; - } - - const [editorOverrideHandler, , targetOptions, targetGroup] = editorOverride; - - return editorOverrideHandler.open(editor, targetOptions ?? options, targetGroup ?? group, OpenEditorContext.NEW_EDITOR)?.override; - } - - private async findEditorOverride(override: EditorOverride.PICK | string, editor: IEditorInput, options: IEditorOptions | undefined, group: IEditorGroup): Promise<[IOpenEditorOverrideHandler, IOpenEditorOverrideEntry, IEditorOptions?, IEditorGroup?] | undefined> { - - // We need a resource at least - const resource = editor.resource; - if (!resource) { - return undefined; - } - - // Collect all overrides for resource - const allEditorOverrides = this.getEditorOverrides(resource, undefined, undefined); - if (!allEditorOverrides.length) { - return undefined; - } - - // Return early for a specific override or we have just 1 in total - if (typeof override === 'string') { - const overrideToUse = allEditorOverrides.find(([, entry]) => entry.id === override); - if (overrideToUse) { - return overrideToUse; - } - } else if (allEditorOverrides.length === 1) { - return allEditorOverrides[0]; - } - - // Otherwise find via picker - return this.doPickEditorOverride(allEditorOverrides, editor, options, group); - } - - private async doPickEditorOverride(allEditorOverrides: [IOpenEditorOverrideHandler, IOpenEditorOverrideEntry][], editor: IEditorInput, options: IEditorOptions | undefined, group: IEditorGroup): Promise<[IOpenEditorOverrideHandler, IOpenEditorOverrideEntry, IEditorOptions?, IEditorGroup?] | undefined> { - - type EditorOverrideQuickPickItem = IQuickPickItem & { - readonly overrideHandler: IOpenEditorOverrideHandler; - readonly overrideEntry: IOpenEditorOverrideEntry; - }; - - type EditorOverridePick = { - readonly item: EditorOverrideQuickPickItem; - readonly keyMods?: IKeyMods; - readonly openInBackground: boolean; - }; - - const resource = EditorResourceAccessor.getOriginalUri(editor); - - const editorOverridePicks = allEditorOverrides.map(([overrideHandler, overrideEntry]) => { - return { - id: overrideEntry.id, - label: overrideEntry.label, - description: overrideEntry.active ? localize('promptOpenWith.currentlyActive', "Currently Active") : undefined, - detail: overrideEntry.detail, - buttons: resource && extname(resource) ? [{ - iconClass: Codicon.gear.classNames, - tooltip: localize('promptOpenWith.setDefaultTooltip', "Set as default editor for '{0}' files", extname(resource)) - }] : undefined, - overrideHandler, - overrideEntry - }; - }); - - // Create editor override picker - const editorOverridePicker = this.quickInputService.createQuickPick(); - editorOverridePicker.placeholder = resource ? localize('promptOpenWith.placeHolder', "Select editor for '{0}'", basename(resource)) : localize('promptOpenWith.placeHolderGeneric', "Select editor"); - editorOverridePicker.canAcceptInBackground = true; - editorOverridePicker.items = editorOverridePicks; - if (editorOverridePicks.length) { - editorOverridePicker.selectedItems = [editorOverridePicks[0]]; - } - - // Prompt the user to select an override - const picked: EditorOverridePick | undefined = await new Promise(resolve => { - editorOverridePicker.onDidAccept(e => { - let result: EditorOverridePick | undefined = undefined; - - if (editorOverridePicker.selectedItems.length === 1) { - result = { - item: editorOverridePicker.selectedItems[0], - keyMods: editorOverridePicker.keyMods, - openInBackground: e.inBackground - }; - } - - resolve(result); - }); - - editorOverridePicker.onDidTriggerItemButton(e => { - - // Trigger opening and close picker - resolve({ item: e.item, openInBackground: false }); - - // Persist setting - if (resource && e.item && e.item.id) { - const newAssociation: EditorAssociation = { viewType: e.item.id, filenamePattern: `*${extname(resource)}` }; - const currentAssociations = [...this.configurationService.getValue(editorsAssociationsSettingId)]; - - // First try updating existing association - for (let i = 0; i < currentAssociations.length; ++i) { - const existing = currentAssociations[i]; - if (existing.filenamePattern === newAssociation.filenamePattern) { - currentAssociations.splice(i, 1, newAssociation); - this.configurationService.updateValue(editorsAssociationsSettingId, currentAssociations); - return; - } - } - - // Otherwise, create a new one - currentAssociations.unshift(newAssociation); - this.configurationService.updateValue(editorsAssociationsSettingId, currentAssociations); - } - }); - - editorOverridePicker.show(); - }); - - // Close picker - editorOverridePicker.dispose(); - - // If the user picked an override, look at how the picker was - // used (e.g. modifier keys, open in background) and create the - // options and group to use accordingly - if (picked) { - - // Figure out target group - let targetGroup: IEditorGroup | undefined; - if (picked.keyMods?.alt || picked.keyMods?.ctrlCmd) { - const direction = preferredSideBySideGroupDirection(this.configurationService); - targetGroup = this.editorGroupService.findGroup({ direction }, group.id); - targetGroup = targetGroup ?? this.editorGroupService.addGroup(group, direction); - } - - // Figure out options - const targetOptions: IEditorOptions = { - ...options, - override: picked.item.overrideEntry.id, - preserveFocus: picked.openInBackground || options?.preserveFocus, - }; - - return [picked.item.overrideHandler, picked.item.overrideEntry, targetOptions, targetGroup]; - } - - return undefined; - } - private findTargetGroup(editor: IEditorInput, options?: IEditorOptions, group?: OpenInEditorGroup): IEditorGroup { let targetGroup: IEditorGroup | undefined; @@ -844,7 +723,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { let groupWithInputOpened: IEditorGroup | undefined = undefined; for (const group of groupsByLastActive) { - if (group.isOpened(editor)) { + if (group.contains(editor)) { if (!groupWithInputOpened) { groupWithInputOpened = group; } @@ -906,78 +785,153 @@ export class EditorService extends Disposable implements EditorServiceImpl { async openEditors(editors: Array, group?: OpenInEditorGroup): Promise { // Convert to typed editors and options - const typedEditors = editors.map(editor => { + const typedEditors: IEditorInputWithOptions[] = editors.map(editor => { if (isEditorInputWithOptions(editor)) { return editor; } - const editorInput: IEditorInputWithOptions = { editor: this.createEditorInput(editor), options: TextEditorOptions.from(editor) }; - return editorInput; + return { + editor: this.createEditorInput(editor), + options: TextEditorOptions.from(editor) + }; }); // Find target groups to open - const mapGroupToEditors = new Map(); + const mapGroupToEditorsCandidates = new Map(); if (group === SIDE_GROUP) { - mapGroupToEditors.set(this.findSideBySideGroup(), typedEditors); + mapGroupToEditorsCandidates.set(this.findSideBySideGroup(), typedEditors); } else { - typedEditors.forEach(typedEditor => { + for (const typedEditor of typedEditors) { const targetGroup = this.findTargetGroup(typedEditor.editor, typedEditor.options, group); + let targetGroupEditors = mapGroupToEditorsCandidates.get(targetGroup); + if (!targetGroupEditors) { + targetGroupEditors = []; + mapGroupToEditorsCandidates.set(targetGroup, targetGroupEditors); + } + + targetGroupEditors.push(typedEditor); + } + } + + // Resolve overrides + const mapGroupToEditors = new Map(); + for (const [group, editorsWithOptions] of mapGroupToEditorsCandidates) { + for (const { editor, options } of editorsWithOptions) { + let editorOverride: IEditorInputWithOptionsAndGroup | undefined; + if (options?.override !== EditorOverride.DISABLED) { + editorOverride = await this.editorOverrideService.resolveEditorOverride(editor, options, group); + } + + const targetGroup = editorOverride?.group ?? group; let targetGroupEditors = mapGroupToEditors.get(targetGroup); if (!targetGroupEditors) { targetGroupEditors = []; mapGroupToEditors.set(targetGroup, targetGroupEditors); } - targetGroupEditors.push(typedEditor); - }); + targetGroupEditors.push(editorOverride ?? { editor, options }); + } } // Open in target groups const result: Promise[] = []; - mapGroupToEditors.forEach((editorsWithOptions, group) => { + for (const [group, editorsWithOptions] of mapGroupToEditors) { result.push(group.openEditors(editorsWithOptions)); - }); + } return coalesce(await Promises.settled(result)); } //#endregion - //#region isOpen() + //#region isOpened() - isOpen(editor: IEditorInput): boolean; - isOpen(editor: IResourceEditorInput): boolean; - isOpen(editor: IEditorInput | IResourceEditorInput): boolean { - if (editor instanceof EditorInput) { - return this.editorGroupService.groups.some(group => group.isOpened(editor)); - } - - if (editor.resource) { - return this.editorsObserver.hasEditor(this.asCanonicalEditorResource(editor.resource)); - } - - return false; + isOpened(editor: IResourceEditorInputIdentifier): boolean { + return this.editorsObserver.hasEditor({ + resource: this.asCanonicalEditorResource(editor.resource), + typeId: editor.typeId + }); } //#endregion - //#region findEditor() + //#region findEditors() - findEditors(resource: URI, group: IEditorGroup | GroupIdentifier): IEditorInput[] { - if (!this.isOpen({ resource })) { - return []; + findEditors(resource: URI): readonly IEditorIdentifier[]; + findEditors(editor: IResourceEditorInputIdentifier): readonly IEditorIdentifier[]; + findEditors(resource: URI, group: IEditorGroup | GroupIdentifier): readonly IEditorInput[]; + findEditors(editor: IResourceEditorInputIdentifier, group: IEditorGroup | GroupIdentifier): IEditorInput | undefined; + findEditors(arg1: URI | IResourceEditorInputIdentifier, arg2?: IEditorGroup | GroupIdentifier): readonly IEditorIdentifier[] | readonly IEditorInput[] | IEditorInput | undefined; + findEditors(arg1: URI | IResourceEditorInputIdentifier, arg2?: IEditorGroup | GroupIdentifier): readonly IEditorIdentifier[] | readonly IEditorInput[] | IEditorInput | undefined { + const resource = URI.isUri(arg1) ? arg1 : arg1.resource; + const typeId = URI.isUri(arg1) ? undefined : arg1.typeId; + + // Do a quick check for the resource via the editor observer + // which is a very efficient way to find an editor by resource + if (!this.editorsObserver.hasEditors(resource)) { + if (URI.isUri(arg1) || isUndefined(arg2)) { + return []; + } + + return undefined; } - const canonicalResource = this.asCanonicalEditorResource(resource); - const targetGroup = typeof group === 'number' ? this.editorGroupService.getGroup(group) : group; - if (!targetGroup) { - return []; + // Search only in specific group + if (!isUndefined(arg2)) { + const targetGroup = typeof arg2 === 'number' ? this.editorGroupService.getGroup(arg2) : arg2; + + // Resource provided: result is an array + if (URI.isUri(arg1)) { + if (!targetGroup) { + return []; + } + + return targetGroup.findEditors(resource); + } + + // Editor identifier provided, result is single + else { + if (!targetGroup) { + return undefined; + } + + const editors = targetGroup.findEditors(resource); + for (const editor of editors) { + if (editor.typeId === typeId) { + return editor; + } + } + + return undefined; + } } - return targetGroup.getEditors(EditorsOrder.SEQUENTIAL).filter(editor => { - return editor.resource && isEqual(editor.resource, canonicalResource); - }); + // Search across all groups in MRU order + else { + const result: IEditorIdentifier[] = []; + + for (const group of this.editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE)) { + const editors: IEditorInput[] = []; + + // Resource provided: result is an array + if (URI.isUri(arg1)) { + editors.push(...this.findEditors(arg1, group)); + } + + // Editor identifier provided, result is single + else { + const editor = this.findEditors(arg1, group); + if (editor) { + editors.push(editor); + } + } + + result.push(...editors.map(editor => ({ editor, groupId: group.id }))); + } + + return result; + } } //#endregion @@ -988,11 +942,16 @@ export class EditorService extends Disposable implements EditorServiceImpl { async replaceEditors(editors: IEditorReplacement[], group: IEditorGroup | GroupIdentifier): Promise; async replaceEditors(editors: Array, group: IEditorGroup | GroupIdentifier): Promise { const typedEditors: IEditorReplacement[] = []; + const targetGroup = typeof group === 'number' ? this.editorGroupService.getGroup(group) : group; - editors.forEach(replaceEditorArg => { + for (const replaceEditorArg of editors) { if (replaceEditorArg.editor instanceof EditorInput) { const replacementArg = replaceEditorArg as IEditorReplacement; - + if (replacementArg.options?.override !== EditorOverride.DISABLED && targetGroup) { + const override = await this.editorOverrideService.resolveEditorOverride(replacementArg.replacement, replacementArg.options, targetGroup); + replacementArg.options = override?.options ?? replacementArg.options; + replacementArg.replacement = override?.editor ?? replacementArg.replacement; + } typedEditors.push({ editor: replacementArg.editor, replacement: replacementArg.replacement, @@ -1008,9 +967,8 @@ export class EditorService extends Disposable implements EditorServiceImpl { options: this.toOptions(replacementArg.replacement.options) }); } - }); + } - const targetGroup = typeof group === 'number' ? this.editorGroupService.getGroup(group) : group; if (targetGroup) { return targetGroup.replaceEditors(typedEditors); } @@ -1080,7 +1038,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { // created the model initially, the lifecycle for // untitled is tightly coupled with the editor // lifecycle for now. - Event.once(input.onDispose)(() => untitledModel.dispose()); + Event.once(input.onWillDispose)(() => untitledModel.dispose()); return input; }) as EditorInput; @@ -1196,7 +1154,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { // Otherwise create and add to cache input = factoryFn(); this.editorInputCache.set(resource, input); - Event.once(input.onDispose)(() => this.editorInputCache.delete(resource)); + Event.once(input.onWillDispose)(() => this.editorInputCache.delete(resource)); return input; } @@ -1352,68 +1310,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { //#endregion - - //#region Editor Tracking - - whenClosed(editors: IResourceEditorInput[], options?: { waitForSaved: boolean }): Promise { - let remainingEditors = [...editors]; - - return new Promise(resolve => { - const listener = this.onDidCloseEditor(async event => { - const primaryResource = EditorResourceAccessor.getOriginalUri(event.editor, { supportSideBySide: SideBySideEditor.PRIMARY }); - const secondaryResource = EditorResourceAccessor.getOriginalUri(event.editor, { supportSideBySide: SideBySideEditor.SECONDARY }); - - // Remove from resources to wait for being closed based on the - // resources from editors that got closed - remainingEditors = remainingEditors.filter(({ resource }) => { - if (this.uriIdentityService.extUri.isEqual(resource, primaryResource) || this.uriIdentityService.extUri.isEqual(resource, secondaryResource)) { - return false; // remove - the closing editor matches this resource - } - - return true; // keep - not yet closed - }); - - // All resources to wait for being closed are closed - if (remainingEditors.length === 0) { - if (options?.waitForSaved) { - // If auto save is configured with the default delay (1s) it is possible - // to close the editor while the save still continues in the background. As such - // we have to also check if the editors to track for are dirty and if so wait - // for them to get saved. - const dirtyResources = editors.filter(({ resource }) => this.workingCopyService.isDirty(resource)).map(({ resource }) => resource); - if (dirtyResources.length > 0) { - await Promises.settled(dirtyResources.map(async resource => await this.whenSaved(resource))); - } - } - - listener.dispose(); - - resolve(); - } - }); - }); - } - - private whenSaved(resource: URI): Promise { - return new Promise(resolve => { - if (!this.workingCopyService.isDirty(resource)) { - return resolve(); // return early if resource is not dirty - } - - // Otherwise resolve promise when resource is saved - const listener = this.workingCopyService.onDidChangeDirty(workingCopy => { - if (!workingCopy.isDirty() && this.uriIdentityService.extUri.isEqual(resource, workingCopy.resource)) { - listener.dispose(); - - resolve(); - } - }); - }); - } - - //#endregion - - dispose(): void { + override dispose(): void { super.dispose(); // Dispose remaining watchers if any @@ -1467,13 +1364,13 @@ export class DelegatingEditorService implements IEditorService { get activeEditorPane(): IVisibleEditorPane | undefined { return this.editorService.activeEditorPane; } get activeTextEditorControl(): ICodeEditor | IDiffEditor | undefined { return this.editorService.activeTextEditorControl; } get activeTextEditorMode(): string | undefined { return this.editorService.activeTextEditorMode; } - get visibleEditors(): ReadonlyArray { return this.editorService.visibleEditors; } - get visibleEditorPanes(): ReadonlyArray { return this.editorService.visibleEditorPanes; } - get visibleTextEditorControls(): ReadonlyArray { return this.editorService.visibleTextEditorControls; } - get editors(): ReadonlyArray { return this.editorService.editors; } + get visibleEditors(): readonly IEditorInput[] { return this.editorService.visibleEditors; } + get visibleEditorPanes(): readonly IVisibleEditorPane[] { return this.editorService.visibleEditorPanes; } + get visibleTextEditorControls(): readonly (ICodeEditor | IDiffEditor)[] { return this.editorService.visibleTextEditorControls; } + get editors(): readonly IEditorInput[] { return this.editorService.editors; } get count(): number { return this.editorService.count; } - getEditors(order: EditorsOrder, options?: { excludeSticky?: boolean }): ReadonlyArray { return this.editorService.getEditors(order, options); } + getEditors(order: EditorsOrder, options?: { excludeSticky?: boolean }): readonly IEditorIdentifier[] { return this.editorService.getEditors(order, options); } openEditors(editors: IEditorInputWithOptions[], group?: OpenInEditorGroup): Promise; openEditors(editors: IResourceEditorInputType[], group?: OpenInEditorGroup): Promise; @@ -1484,14 +1381,16 @@ export class DelegatingEditorService implements IEditorService { replaceEditors(editors: IResourceEditorReplacement[], group: IEditorGroup | GroupIdentifier): Promise; replaceEditors(editors: IEditorReplacement[], group: IEditorGroup | GroupIdentifier): Promise; replaceEditors(editors: Array, group: IEditorGroup | GroupIdentifier): Promise { - return this.editorService.replaceEditors(editors as IResourceEditorReplacement[] /* TS fail */, group); + return this.editorService.replaceEditors(editors, group); } - isOpen(editor: IEditorInput): boolean; - isOpen(editor: IResourceEditorInput): boolean; - isOpen(editor: IEditorInput | IResourceEditorInput): boolean { return this.editorService.isOpen(editor as IResourceEditorInput /* TS fail */); } + isOpened(editor: IResourceEditorInputIdentifier): boolean { return this.editorService.isOpened(editor); } - findEditors(resource: URI, group: IEditorGroup | GroupIdentifier) { return this.editorService.findEditors(resource, group); } + findEditors(resource: URI): readonly IEditorIdentifier[]; + findEditors(resource: IResourceEditorInputIdentifier): readonly IEditorIdentifier[]; + findEditors(resource: URI, group: IEditorGroup | GroupIdentifier): readonly IEditorInput[]; + findEditors(resource: IResourceEditorInputIdentifier, group: IEditorGroup | GroupIdentifier): IEditorInput | undefined; + findEditors(arg1: URI | IResourceEditorInputIdentifier, arg2?: IEditorGroup | GroupIdentifier): readonly IEditorIdentifier[] | readonly IEditorInput[] | IEditorInput | undefined { return this.editorService.findEditors(arg1, arg2); } overrideOpenEditor(handler: IOpenEditorOverrideHandler): IDisposable { return this.editorService.overrideOpenEditor(handler); } getEditorOverrides(resource: URI, options: IEditorOptions | undefined, group: IEditorGroup | undefined) { return this.editorService.getEditorOverrides(resource, options, group); } @@ -1504,8 +1403,6 @@ export class DelegatingEditorService implements IEditorService { revert(editors: IEditorIdentifier | IEditorIdentifier[], options?: IRevertOptions): Promise { return this.editorService.revert(editors, options); } revertAll(options?: IRevertAllEditorsOptions): Promise { return this.editorService.revertAll(options); } - whenClosed(editors: IResourceEditorInput[]): Promise { return this.editorService.whenClosed(editors); } - //#endregion } diff --git a/src/vs/workbench/services/editor/common/editorGroupsService.ts b/src/vs/workbench/services/editor/common/editorGroupsService.ts index 0447d4f1..f5ebf288 100644 --- a/src/vs/workbench/services/editor/common/editorGroupsService.ts +++ b/src/vs/workbench/services/editor/common/editorGroupsService.ts @@ -5,12 +5,13 @@ import { Event } from 'vs/base/common/event'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IEditorInput, IEditorPane, GroupIdentifier, IEditorInputWithOptions, CloseDirection, IEditorPartOptions, IEditorPartOptionsChangeEvent, EditorsOrder, IVisibleEditorPane, IEditorCloseEvent } from 'vs/workbench/common/editor'; +import { IEditorInput, IEditorPane, GroupIdentifier, IEditorInputWithOptions, CloseDirection, IEditorPartOptions, IEditorPartOptionsChangeEvent, EditorsOrder, IVisibleEditorPane, IEditorCloseEvent, IEditorMoveEvent } from 'vs/workbench/common/editor'; import { IEditorOptions, ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IDimension } from 'vs/editor/common/editorCommon'; import { IDisposable } from 'vs/base/common/lifecycle'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { URI } from 'vs/base/common/uri'; export const IEditorGroupsService = createDecorator('editorGroupsService'); @@ -68,14 +69,6 @@ export interface EditorGroupLayout { groups: GroupLayoutArgument[]; } -export interface IMoveEditorOptions { - index?: number; - inactive?: boolean; - preserveFocus?: boolean; -} - -export interface ICopyEditorOptions extends IMoveEditorOptions { } - export interface IAddGroupOptions { activate?: boolean; } @@ -108,9 +101,13 @@ export interface ICloseAllEditorsOptions { export interface IEditorReplacement { editor: IEditorInput; replacement: IEditorInput; - /** Skips asking the user for confirmation and doesn't save the document. Only use this if you really need to! */ - forceReplaceDirty?: boolean; options?: IEditorOptions | ITextEditorOptions; + + /** + * Skips asking the user for confirmation and doesn't + * save the document. Only use this if you really need to! + */ + forceReplaceDirty?: boolean; } export const enum GroupsOrder { @@ -185,7 +182,7 @@ export interface IEditorGroupsService { * All groups that are currently visible in the editor area in the * order of their creation (oldest first). */ - readonly groups: ReadonlyArray; + readonly groups: readonly IEditorGroup[]; /** * The number of editor groups that are currently opened. @@ -197,22 +194,46 @@ export interface IEditorGroupsService { */ readonly orientation: GroupOrientation; + /** + * A promise that resolves when groups have been created + * and are ready to be used. + * + * Await this promise to safely work on the editor groups model + * (for example, install editor group listeners). + * + * Use the `whenRestored` property to await visible editors + * having fully resolved. + */ + readonly whenReady: Promise; + /** * A promise that resolves when groups have been restored. + * + * For groups with active editor, the promise will resolve + * when the visible editor has finished to resolve. + * + * Use the `whenReady` property to not await editors to + * resolve. */ readonly whenRestored: Promise; /** - * Find out if the editor group service has editors to restore from a previous session. + * Will return `true` as soon as `whenRestored` is resolved. */ - readonly willRestoreEditors: boolean; + isRestored(): boolean; + + /** + * Find out if the editor group service has UI state to restore + * from a previous session. + */ + readonly hasRestorableState: boolean; /** * Get all groups that are currently visible in the editor area. * * @param order the order of the editors to use */ - getGroups(order: GroupsOrder): ReadonlyArray; + getGroups(order: GroupsOrder): readonly IEditorGroup[]; /** * Allows to convert a group identifier to a group. @@ -390,6 +411,12 @@ export interface IEditorGroup { */ readonly onWillCloseEditor: Event; + /** + * An event that is fired when an editor is about to move to + * a different group. + */ + readonly onWillMoveEditor: Event; + /** * A unique identifier of this group that remains identical even if the * group is moved to different locations. @@ -438,6 +465,11 @@ export interface IEditorGroup { */ readonly count: number; + /** + * Whether the group has editors or not. + */ + readonly isEmpty: boolean; + /** * The number of sticky editors in this group. */ @@ -446,7 +478,7 @@ export interface IEditorGroup { /** * All opened editors in the group in sequential order of their appearance. */ - readonly editors: ReadonlyArray; + readonly editors: readonly IEditorInput[]; /** * The scoped context key service for this group. @@ -459,7 +491,17 @@ export interface IEditorGroup { * @param order the order of the editors to use * @param options options to select only specific editors as instructed */ - getEditors(order: EditorsOrder, options?: { excludeSticky?: boolean }): ReadonlyArray; + getEditors(order: EditorsOrder, options?: { excludeSticky?: boolean }): readonly IEditorInput[]; + + /** + * Finds all editors for the given resource that are currently + * opened in the group. This method will return an entry for + * each editor that reports a `resource` that matches the + * provided one. + * + * @param resource The resource of the editor to find + */ + findEditors(resource: URI): readonly IEditorInput[]; /** * Returns the editor at a specific index of the group. @@ -477,7 +519,7 @@ export interface IEditorGroup { * @returns a promise that resolves around an IEditor instance unless * the call failed, or the editor was not opened as active editor. */ - openEditor(editor: IEditorInput, options?: IEditorOptions | ITextEditorOptions, context?: OpenEditorContext): Promise; + openEditor(editor: IEditorInput, options?: IEditorOptions | ITextEditorOptions): Promise; /** * Opens editors in this group. @@ -489,13 +531,6 @@ export interface IEditorGroup { */ openEditors(editors: IEditorInputWithOptions[]): Promise; - /** - * Find out if the provided editor is opened in the group. - * - * Note: An editor can be opened but not actively visible. - */ - isOpened(editor: IEditorInput): boolean; - /** * Find out if the provided editor is pinned in the group. */ @@ -511,17 +546,24 @@ export interface IEditorGroup { */ isActive(editor: IEditorInput): boolean; + /** + * Find out if a certain editor is included in the group. + * + * @param candidate the editor to find + */ + contains(candidate: IEditorInput): boolean; + /** * Move an editor from this group either within this group or to another group. */ - moveEditor(editor: IEditorInput, target: IEditorGroup, options?: IMoveEditorOptions): void; + moveEditor(editor: IEditorInput, target: IEditorGroup, options?: IEditorOptions | ITextEditorOptions): void; /** * Copy an editor from this group to another group. * * Note: It is currently not supported to show the same editor more than once in the same group. */ - copyEditor(editor: IEditorInput, target: IEditorGroup, options?: ICopyEditorOptions): void; + copyEditor(editor: IEditorInput, target: IEditorGroup, options?: IEditorOptions | ITextEditorOptions): void; /** * Close an editor from the group. This may trigger a confirmation dialog if diff --git a/src/vs/workbench/services/editor/common/editorOverrideService.ts b/src/vs/workbench/services/editor/common/editorOverrideService.ts new file mode 100644 index 00000000..0ee1031c --- /dev/null +++ b/src/vs/workbench/services/editor/common/editorOverrideService.ts @@ -0,0 +1,266 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as glob from 'vs/base/common/glob'; +import { Event } from 'vs/base/common/event'; +import { IJSONSchema } from 'vs/base/common/jsonSchema'; +import { IDisposable } from 'vs/base/common/lifecycle'; +import { Schemas } from 'vs/base/common/network'; +import { posix } from 'vs/base/common/path'; +import { basename } from 'vs/base/common/resources'; +import { URI } from 'vs/base/common/uri'; +import { localize } from 'vs/nls'; +import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration'; +import { Extensions as ConfigurationExtensions, IConfigurationNode, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; +import { IEditorOptions, ITextEditorOptions } from 'vs/platform/editor/common/editor'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { EditorExtensions, IEditorInput, IEditorInputWithOptions, IEditorInputWithOptionsAndGroup } from 'vs/workbench/common/editor'; +import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; +import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; + +export const IEditorOverrideService = createDecorator('editorOverrideService'); + +//#region Editor Associations + +// Static values for editor contributions + +export type EditorAssociation = { + readonly viewType: string; + readonly filenamePattern?: string; +}; + +export type EditorAssociations = readonly EditorAssociation[]; + +export const editorsAssociationsSettingId = 'workbench.editorAssociations'; + +export const DEFAULT_EDITOR_ASSOCIATION: IEditorType = { + id: 'default', + displayName: localize('promptOpenWith.defaultEditor.displayName', "Text Editor"), + providerDisplayName: localize('builtinProviderDisplayName', "Built-in") +}; + +const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); + +const editorTypeSchemaAddition: IJSONSchema = { + type: 'string', + enum: [] +}; + +const editorAssociationsConfigurationNode: IConfigurationNode = { + ...workbenchConfigurationNodeBase, + properties: { + 'workbench.editorAssociations': { + type: 'array', + markdownDescription: localize('editor.editorAssociations', "Configure which editor to use for specific file types."), + items: { + type: 'object', + defaultSnippets: [{ + body: { + 'viewType': '$1', + 'filenamePattern': '$2' + } + }], + properties: { + 'viewType': { + anyOf: [ + { + type: 'string', + description: localize('editor.editorAssociations.viewType', "The unique id of the editor to use."), + }, + editorTypeSchemaAddition + ] + }, + 'filenamePattern': { + type: 'string', + description: localize('editor.editorAssociations.filenamePattern', "Glob pattern specifying which files the editor should be used for."), + } + } + } + } + } +}; + +export interface IEditorType { + readonly id: string; + readonly displayName: string; + readonly providerDisplayName: string; +} + +export interface IEditorTypesHandler { + readonly onDidChangeEditorTypes: Event; + + getEditorTypes(): IEditorType[]; +} + +export interface IEditorAssociationsRegistry { + + /** + * Register handlers for editor types + */ + registerEditorTypesHandler(id: string, handler: IEditorTypesHandler): IDisposable; +} + +class EditorAssociationsRegistry implements IEditorAssociationsRegistry { + + private readonly editorTypesHandlers = new Map(); + + registerEditorTypesHandler(id: string, handler: IEditorTypesHandler): IDisposable { + if (this.editorTypesHandlers.has(id)) { + throw new Error(`An editor type handler with ${id} was already registered.`); + } + + this.editorTypesHandlers.set(id, handler); + this.updateEditorAssociationsSchema(); + + const editorTypeChangeEvent = handler.onDidChangeEditorTypes(() => { + this.updateEditorAssociationsSchema(); + }); + + return { + dispose: () => { + editorTypeChangeEvent.dispose(); + this.editorTypesHandlers.delete(id); + this.updateEditorAssociationsSchema(); + } + }; + } + + private updateEditorAssociationsSchema() { + const enumValues: string[] = []; + const enumDescriptions: string[] = []; + + const editorTypes: IEditorType[] = [DEFAULT_EDITOR_ASSOCIATION]; + + for (const [, handler] of this.editorTypesHandlers) { + editorTypes.push(...handler.getEditorTypes()); + } + + for (const { id, providerDisplayName } of editorTypes) { + enumValues.push(id); + enumDescriptions.push(localize('editorAssociations.viewType.sourceDescription', "Source: {0}", providerDisplayName)); + } + + editorTypeSchemaAddition.enum = enumValues; + editorTypeSchemaAddition.enumDescriptions = enumDescriptions; + + configurationRegistry.notifyConfigurationSchemaUpdated(editorAssociationsConfigurationNode); + } +} + +Registry.add(EditorExtensions.Associations, new EditorAssociationsRegistry()); +configurationRegistry.registerConfiguration(editorAssociationsConfigurationNode); +//#endregion + +//#region EditorOverrideService types +export enum ContributedEditorPriority { + builtin = 'builtin', + option = 'option', + exclusive = 'exclusive', + default = 'default' +} + +export type ContributionPointOptions = { + /** + * If your editor cannot be opened in multiple groups for the same resource + */ + singlePerResource?: boolean | (() => boolean); + /** + * If your editor supports diffs + */ + canHandleDiff?: boolean | (() => boolean); + + /** + * Whether or not you can support opening the given resource. + * If omitted we assume you can open everything + */ + canSupportResource?: (resource: URI) => boolean; +}; + +export type ContributedEditorInfo = { + id: string; + describes: (currentEditor: IEditorInput) => boolean; + label: string; + detail?: string; + priority: ContributedEditorPriority; +}; + +export type EditorInputFactoryFunction = (resource: URI, options: IEditorOptions | ITextEditorOptions | undefined, group: IEditorGroup) => IEditorInputWithOptions; + +export type DiffEditorInputFactoryFunction = (diffEditorInput: DiffEditorInput, options: IEditorOptions | ITextEditorOptions | undefined, group: IEditorGroup) => IEditorInputWithOptions; + +export interface IEditorOverrideService { + readonly _serviceBrand: undefined; + /** + * Given a resource finds the editor associations that match it from the user's settings + * @param resource The resource to match + * @return The matching associations + */ + getAssociationsForResource(resource: URI): EditorAssociations; + + /** + * Updates the user's association to include a specific editor ID as a default for the given glob pattern + * @param globPattern The glob pattern (must be a string as settings don't support relative glob) + * @param editorID The ID of the editor to make a user default + */ + updateUserAssociations(globPattern: string, editorID: string): void; + + /** + * Registers a specific editor contribution. + * @param globPattern The glob pattern for this contribution point + * @param editorInfo Information about the contribution point + * @param options Specific options which apply to this contribution + * @param createEditorInput The factory method for creating inputs + */ + registerContributionPoint( + globPattern: string | glob.IRelativePattern, + editorInfo: ContributedEditorInfo, + options: ContributionPointOptions, + createEditorInput: EditorInputFactoryFunction, + createDiffEditorInput?: DiffEditorInputFactoryFunction + ): IDisposable; + + /** + * Given an editor determines if there's a suitable override for it, if so returns an IEditorInputWithOptions for opening + * @param editor The editor to override + * @param options The current options for the editor + * @param group The current group + * @returns An IEditorInputWithOptionsAndGroup if there is an available override or undefined if there is not + */ + resolveEditorOverride(editor: IEditorInput, options: IEditorOptions | ITextEditorOptions | undefined, group: IEditorGroup): Promise; +} + +//#endregion + +//#region Util functions +export function priorityToRank(priority: ContributedEditorPriority): number { + switch (priority) { + case ContributedEditorPriority.exclusive: + return 5; + case ContributedEditorPriority.default: + return 4; + case ContributedEditorPriority.builtin: + return 3; + // Text editor is priority 2 + case ContributedEditorPriority.option: + default: + return 1; + } +} + +export function globMatchesResource(globPattern: string | glob.IRelativePattern, resource: URI): boolean { + const excludedSchemes = new Set([ + Schemas.extension, + Schemas.webviewPanel, + ]); + // We want to say that the above schemes match no glob patterns + if (excludedSchemes.has(resource.scheme)) { + return false; + } + const matchOnPath = typeof globPattern === 'string' && globPattern.indexOf(posix.sep) >= 0; + const target = matchOnPath ? resource.path : basename(resource); + return glob.match(globPattern, target.toLowerCase()); +} +//#endregion diff --git a/src/vs/workbench/services/editor/common/editorService.ts b/src/vs/workbench/services/editor/common/editorService.ts index f948aaec..c3b0d1c2 100644 --- a/src/vs/workbench/services/editor/common/editorService.ts +++ b/src/vs/workbench/services/editor/common/editorService.ts @@ -4,11 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IResourceEditorInput, IEditorOptions, ITextEditorOptions } from 'vs/platform/editor/common/editor'; +import { IResourceEditorInput, IEditorOptions, ITextEditorOptions, IResourceEditorInputIdentifier } from 'vs/platform/editor/common/editor'; import { IEditorInput, IEditorPane, GroupIdentifier, IEditorInputWithOptions, IUntitledTextResourceEditorInput, IResourceDiffEditorInput, ITextEditorPane, ITextDiffEditorPane, IEditorIdentifier, ISaveOptions, IRevertOptions, EditorsOrder, IVisibleEditorPane, IEditorCloseEvent } from 'vs/workbench/common/editor'; import { Event } from 'vs/base/common/event'; import { IEditor, IDiffEditor } from 'vs/editor/common/editorCommon'; -import { IEditorGroup, IEditorReplacement, OpenEditorContext } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IEditorGroup, IEditorReplacement } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; @@ -35,7 +35,7 @@ export interface IOpenEditorOverrideEntry { } export interface IOpenEditorOverrideHandler { - open(editor: IEditorInput, options: IEditorOptions | ITextEditorOptions | undefined, group: IEditorGroup, context: OpenEditorContext): IOpenEditorOverride | undefined; + open(editor: IEditorInput, options: IEditorOptions | ITextEditorOptions | undefined, group: IEditorGroup): IOpenEditorOverride | undefined; getEditorOverrides?(resource: URI, options: IEditorOptions | undefined, group: IEditorGroup | undefined): IOpenEditorOverrideEntry[]; } @@ -131,19 +131,19 @@ export interface IEditorService { * * @see `IEditorService.visibleEditors` for access to the visible editor inputs */ - readonly visibleEditorPanes: ReadonlyArray; + readonly visibleEditorPanes: readonly IVisibleEditorPane[]; /** * All editors that are currently visible. An editor is visible when it is opened in an * editor group and active in that group. Multiple editor groups can be opened at the same time. */ - readonly visibleEditors: ReadonlyArray; + readonly visibleEditors: readonly IEditorInput[]; /** * All text editor widgets that are currently visible across all editor groups. A text editor * widget is either a text or a diff editor. */ - readonly visibleTextEditorControls: ReadonlyArray; + readonly visibleTextEditorControls: readonly (IEditor | IDiffEditor)[]; /** * All editors that are opened across all editor groups in sequential order @@ -151,7 +151,7 @@ export interface IEditorService { * * This includes active as well as inactive editors in each editor group. */ - readonly editors: ReadonlyArray; + readonly editors: readonly IEditorInput[]; /** * The total number of editors that are opened either inactive or active. @@ -165,7 +165,7 @@ export interface IEditorService { * @param order the order of the editors to use * @param options whether to exclude sticky editors or not */ - getEditors(order: EditorsOrder, options?: { excludeSticky?: boolean }): ReadonlyArray; + getEditors(order: EditorsOrder, options?: { excludeSticky?: boolean }): readonly IEditorIdentifier[]; /** * Open an editor in an editor group. @@ -194,8 +194,8 @@ export interface IEditorService { * @returns the editors that opened. The array can be empty or have less elements for editors * that failed to open or were instructed to open as inactive. */ - openEditors(editors: IEditorInputWithOptions[], group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise>; - openEditors(editors: IResourceEditorInputType[], group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise>; + openEditors(editors: IEditorInputWithOptions[], group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; + openEditors(editors: IResourceEditorInputType[], group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; /** * Replaces editors in an editor group with the provided replacement. @@ -214,18 +214,24 @@ export interface IEditorService { * * Note: An editor can be opened but not actively visible. * - * @param editor the editor to check for being opened. If a - * `IResourceEditorInput` is passed in, the resource is checked on - * all opened editors. In case of a side by side editor, the - * right hand side resource is considered only. + * Note: This method will return `true` if a side by side editor + * is opened where the `primary` editor matches too. */ - isOpen(editor: IResourceEditorInput): boolean; - isOpen(editor: IEditorInput): boolean; + isOpened(editor: IResourceEditorInputIdentifier): boolean; /** - * Find the existing editors for a given resource. + * This method will return an entry for each editor that reports + * a `resource` that matches the provided one in the group or + * across all groups. + * + * It is possible that multiple editors are returned in case the + * same resource is opened in different editors. To find the specific + * editor, use the `IResourceEditorInputIdentifier` as input. */ - findEditors(resource: URI, group: IEditorGroup | GroupIdentifier): IEditorInput[]; + findEditors(resource: URI): readonly IEditorIdentifier[]; + findEditors(resource: IResourceEditorInputIdentifier): readonly IEditorIdentifier[]; + findEditors(resource: URI, group: IEditorGroup | GroupIdentifier): readonly IEditorInput[]; + findEditors(resource: IResourceEditorInputIdentifier, group: IEditorGroup | GroupIdentifier): IEditorInput | undefined; /** * Get all available editor overrides for the editor input. @@ -271,13 +277,4 @@ export interface IEditorService { * @returns `true` if all editors reverted and `false` otherwise. */ revertAll(options?: IRevertAllEditorsOptions): Promise; - - /** - * Track the provided editors until all have been closed. - * - * @param options use `waitForSaved: true` to wait for the resources - * being saved. If auto-save is enabled, it may be possible to close - * an editor while the save continues in the background. - */ - whenClosed(editors: IResourceEditorInput[], options?: { waitForSaved: boolean }): Promise; } diff --git a/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts b/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts index 5a13a21e..46301315 100644 --- a/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts @@ -4,9 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { Event } from 'vs/base/common/event'; import { workbenchInstantiationService, registerTestEditor, TestFileEditorInput, TestEditorPart, ITestInstantiationService, TestServiceAccessor, createEditorPart } from 'vs/workbench/test/browser/workbenchTestServices'; -import { GroupDirection, GroupsOrder, MergeGroupMode, GroupOrientation, GroupChangeKind, GroupLocation, OpenEditorContext } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { GroupDirection, GroupsOrder, MergeGroupMode, GroupOrientation, GroupChangeKind, GroupLocation } from 'vs/workbench/services/editor/common/editorGroupsService'; import { EditorOptions, CloseDirection, IEditorPartOptions, EditorsOrder } from 'vs/workbench/common/editor'; import { URI } from 'vs/base/common/uri'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; @@ -29,15 +28,15 @@ suite('EditorGroupsService', () => { disposables.clear(); }); - function createPart(instantiationService = workbenchInstantiationService()): [TestEditorPart, ITestInstantiationService] { - const part = createEditorPart(instantiationService, disposables); + async function createPart(instantiationService = workbenchInstantiationService()): Promise<[TestEditorPart, ITestInstantiationService]> { + const part = await createEditorPart(instantiationService, disposables); return [part, instantiationService]; } test('groups basics', async function () { const instantiationService = workbenchInstantiationService({ contextKeyService: instantiationService => instantiationService.createInstance(MockScopableContextKeyService) }); - const [part] = createPart(instantiationService); + const [part] = await createPart(instantiationService); let activeGroupChangeCounter = 0; const activeGroupChangeListener = part.onDidChangeActiveGroup(() => { @@ -190,7 +189,7 @@ suite('EditorGroupsService', () => { }); test('save & restore state', async function () { - let [part, instantiationService] = createPart(); + let [part, instantiationService] = await createPart(); const rootGroup = part.groups[0]; const rightGroup = part.addGroup(rootGroup, GroupDirection.RIGHT); @@ -207,7 +206,7 @@ suite('EditorGroupsService', () => { part.saveState(); part.dispose(); - let [restoredPart] = createPart(instantiationService); + let [restoredPart] = await createPart(instantiationService); assert.strictEqual(restoredPart.groups.length, 3); assert.ok(restoredPart.getGroup(rootGroup.id)); @@ -217,8 +216,8 @@ suite('EditorGroupsService', () => { restoredPart.clearState(); }); - test('groups index / labels', function () { - const [part] = createPart(); + test('groups index / labels', async function () { + const [part] = await createPart(); const rootGroup = part.groups[0]; const rightGroup = part.addGroup(rootGroup, GroupDirection.RIGHT); @@ -274,7 +273,7 @@ suite('EditorGroupsService', () => { }); test('copy/merge groups', async () => { - const [part] = createPart(); + const [part] = await createPart(); let groupAddedCounter = 0; const groupAddedListener = part.onDidAddGroup(() => { @@ -316,7 +315,7 @@ suite('EditorGroupsService', () => { }); test('merge all groups', async () => { - const [part] = createPart(); + const [part] = await createPart(); const rootGroup = part.groups[0]; @@ -343,15 +342,17 @@ suite('EditorGroupsService', () => { part.dispose(); }); - test('whenRestored', async () => { - const [part] = createPart(); + test('whenReady / whenRestored', async () => { + const [part] = await createPart(); + await part.whenReady; await part.whenRestored; - assert.ok(true); + + assert.strictEqual(part.isRestored(), true); }); - test('options', () => { - const [part] = createPart(); + test('options', async () => { + const [part] = await createPart(); let oldOptions!: IEditorPartOptions; let newOptions!: IEditorPartOptions; @@ -370,17 +371,10 @@ suite('EditorGroupsService', () => { }); test('editor basics', async function () { - const [part] = createPart(); + const [part] = await createPart(); const group = part.activeGroup; assert.strictEqual(group.isEmpty, true); - await part.whenRestored; - - let editorWillOpenCounter = 0; - const editorWillOpenListener = group.onWillOpenEditor(() => { - editorWillOpenCounter++; - }); - let activeEditorChangeCounter = 0; let editorDidOpenCounter = 0; let editorCloseCounter = 0; @@ -423,11 +417,10 @@ suite('EditorGroupsService', () => { assert.strictEqual(group.isActive(input), true); assert.strictEqual(group.isActive(inputInactive), false); - assert.strictEqual(group.isOpened(input), true); - assert.strictEqual(group.isOpened(inputInactive), true); + assert.strictEqual(group.contains(input), true); + assert.strictEqual(group.contains(inputInactive), true); assert.strictEqual(group.isEmpty, false); assert.strictEqual(group.count, 2); - assert.strictEqual(editorWillOpenCounter, 2); assert.strictEqual(editorDidOpenCounter, 2); assert.strictEqual(activeEditorChangeCounter, 1); assert.strictEqual(group.getEditorByIndex(0), input); @@ -474,12 +467,11 @@ suite('EditorGroupsService', () => { editorCloseListener.dispose(); editorWillCloseListener.dispose(); - editorWillOpenListener.dispose(); editorGroupChangeListener.dispose(); }); test('openEditors / closeEditors', async () => { - const [part] = createPart(); + const [part] = await createPart(); const group = part.activeGroup; assert.strictEqual(group.isEmpty, true); @@ -504,7 +496,7 @@ suite('EditorGroupsService', () => { }); test('closeEditor - dirty editor handling', async () => { - const [part, instantiationService] = createPart(); + const [part, instantiationService] = await createPart(); const accessor = instantiationService.createInstance(TestServiceAccessor); accessor.fileDialogService.setConfirmResult(ConfirmResult.DONT_SAVE); @@ -528,7 +520,7 @@ suite('EditorGroupsService', () => { }); test('closeEditor (one, opened in multiple groups)', async () => { - const [part] = createPart(); + const [part] = await createPart(); const group = part.activeGroup; assert.strictEqual(group.isEmpty, true); @@ -550,7 +542,7 @@ suite('EditorGroupsService', () => { }); test('closeEditors - dirty editor handling', async () => { - const [part, instantiationService] = createPart(); + const [part, instantiationService] = await createPart(); const accessor = instantiationService.createInstance(TestServiceAccessor); accessor.fileDialogService.setConfirmResult(ConfirmResult.DONT_SAVE); @@ -579,7 +571,7 @@ suite('EditorGroupsService', () => { }); test('closeEditors (except one)', async () => { - const [part] = createPart(); + const [part] = await createPart(); const group = part.activeGroup; assert.strictEqual(group.isEmpty, true); @@ -604,7 +596,7 @@ suite('EditorGroupsService', () => { }); test('closeEditors (except one, sticky editor)', async () => { - const [part] = createPart(); + const [part] = await createPart(); const group = part.activeGroup; assert.strictEqual(group.isEmpty, true); @@ -639,7 +631,7 @@ suite('EditorGroupsService', () => { }); test('closeEditors (saved only)', async () => { - const [part] = createPart(); + const [part] = await createPart(); const group = part.activeGroup; assert.strictEqual(group.isEmpty, true); @@ -663,7 +655,7 @@ suite('EditorGroupsService', () => { }); test('closeEditors (saved only, sticky editor)', async () => { - const [part] = createPart(); + const [part] = await createPart(); const group = part.activeGroup; assert.strictEqual(group.isEmpty, true); @@ -694,7 +686,7 @@ suite('EditorGroupsService', () => { }); test('closeEditors (direction: right)', async () => { - const [part] = createPart(); + const [part] = await createPart(); const group = part.activeGroup; assert.strictEqual(group.isEmpty, true); @@ -720,7 +712,7 @@ suite('EditorGroupsService', () => { }); test('closeEditors (direction: right, sticky editor)', async () => { - const [part] = createPart(); + const [part] = await createPart(); const group = part.activeGroup; assert.strictEqual(group.isEmpty, true); @@ -753,7 +745,7 @@ suite('EditorGroupsService', () => { }); test('closeEditors (direction: left)', async () => { - const [part] = createPart(); + const [part] = await createPart(); const group = part.activeGroup; assert.strictEqual(group.isEmpty, true); @@ -779,7 +771,7 @@ suite('EditorGroupsService', () => { }); test('closeEditors (direction: left, sticky editor)', async () => { - const [part] = createPart(); + const [part] = await createPart(); const group = part.activeGroup; assert.strictEqual(group.isEmpty, true); @@ -813,7 +805,7 @@ suite('EditorGroupsService', () => { }); test('closeAllEditors', async () => { - const [part] = createPart(); + const [part] = await createPart(); const group = part.activeGroup; assert.strictEqual(group.isEmpty, true); @@ -834,7 +826,7 @@ suite('EditorGroupsService', () => { }); test('closeAllEditors - dirty editor handling', async () => { - const [part, instantiationService] = createPart(); + const [part, instantiationService] = await createPart(); const accessor = instantiationService.createInstance(TestServiceAccessor); accessor.fileDialogService.setConfirmResult(ConfirmResult.DONT_SAVE); @@ -863,7 +855,7 @@ suite('EditorGroupsService', () => { }); test('closeAllEditors (sticky editor)', async () => { - const [part] = createPart(); + const [part] = await createPart(); const group = part.activeGroup; assert.strictEqual(group.isEmpty, true); @@ -890,7 +882,7 @@ suite('EditorGroupsService', () => { }); test('moveEditor (same group)', async () => { - const [part] = createPart(); + const [part] = await createPart(); const group = part.activeGroup; assert.strictEqual(group.isEmpty, true); @@ -918,7 +910,7 @@ suite('EditorGroupsService', () => { }); test('moveEditor (across groups)', async () => { - const [part] = createPart(); + const [part] = await createPart(); const group = part.activeGroup; assert.strictEqual(group.isEmpty, true); @@ -939,7 +931,7 @@ suite('EditorGroupsService', () => { }); test('copyEditor (across groups)', async () => { - const [part] = createPart(); + const [part] = await createPart(); const group = part.activeGroup; assert.strictEqual(group.isEmpty, true); @@ -961,7 +953,7 @@ suite('EditorGroupsService', () => { }); test('replaceEditors', async () => { - const [part] = createPart(); + const [part] = await createPart(); const group = part.activeGroup; assert.strictEqual(group.isEmpty, true); @@ -978,7 +970,7 @@ suite('EditorGroupsService', () => { }); test('replaceEditors - dirty editor handling', async () => { - const [part, instantiationService] = createPart(); + const [part, instantiationService] = await createPart(); const accessor = instantiationService.createInstance(TestServiceAccessor); accessor.fileDialogService.setConfirmResult(ConfirmResult.DONT_SAVE); @@ -1007,7 +999,7 @@ suite('EditorGroupsService', () => { }); test('replaceEditors - forceReplaceDirty flag', async () => { - const [part, instantiationService] = createPart(); + const [part, instantiationService] = await createPart(); const accessor = instantiationService.createInstance(TestServiceAccessor); accessor.fileDialogService.setConfirmResult(ConfirmResult.DONT_SAVE); @@ -1034,7 +1026,7 @@ suite('EditorGroupsService', () => { }); test('replaceEditors - proper index handling', async () => { - const [part] = createPart(); + const [part] = await createPart(); const group = part.activeGroup; assert.strictEqual(group.isEmpty, true); @@ -1067,8 +1059,32 @@ suite('EditorGroupsService', () => { assert.strictEqual(group.getEditorByIndex(4), input8); }); - test('find neighbour group (left/right)', function () { - const [part] = createPart(); + test('find editors', async () => { + const [part] = await createPart(); + const group = part.activeGroup; + const group2 = part.addGroup(group, GroupDirection.RIGHT); + assert.strictEqual(group.isEmpty, true); + + const input1 = new TestFileEditorInput(URI.file('foo/bar1'), TEST_EDITOR_INPUT_ID); + const input2 = new TestFileEditorInput(URI.file('foo/bar1'), `${TEST_EDITOR_INPUT_ID}-1`); + const input3 = new TestFileEditorInput(URI.file('foo/bar3'), TEST_EDITOR_INPUT_ID); + const input4 = new TestFileEditorInput(URI.file('foo/bar4'), TEST_EDITOR_INPUT_ID); + const input5 = new TestFileEditorInput(URI.file('foo/bar4'), `${TEST_EDITOR_INPUT_ID}-1`); + + await group.openEditor(input1, { pinned: true }); + await group.openEditor(input2, { pinned: true }); + await group.openEditor(input3, { pinned: true }); + await group.openEditor(input4, { pinned: true }); + await group2.openEditor(input5, { pinned: true }); + + let foundEditors = group.findEditors(URI.file('foo/bar1')); + assert.strictEqual(foundEditors.length, 2); + foundEditors = group2.findEditors(URI.file('foo/bar4')); + assert.strictEqual(foundEditors.length, 1); + }); + + test('find neighbour group (left/right)', async function () { + const [part] = await createPart(); const rootGroup = part.activeGroup; const rightGroup = part.addGroup(rootGroup, GroupDirection.RIGHT); @@ -1076,8 +1092,8 @@ suite('EditorGroupsService', () => { assert.strictEqual(rootGroup, part.findGroup({ direction: GroupDirection.LEFT }, rightGroup)); }); - test('find neighbour group (up/down)', function () { - const [part] = createPart(); + test('find neighbour group (up/down)', async function () { + const [part] = await createPart(); const rootGroup = part.activeGroup; const downGroup = part.addGroup(rootGroup, GroupDirection.DOWN); @@ -1085,8 +1101,8 @@ suite('EditorGroupsService', () => { assert.strictEqual(rootGroup, part.findGroup({ direction: GroupDirection.UP }, downGroup)); }); - test('find group by location (left/right)', function () { - const [part] = createPart(); + test('find group by location (left/right)', async function () { + const [part] = await createPart(); const rootGroup = part.activeGroup; const rightGroup = part.addGroup(rootGroup, GroupDirection.RIGHT); const downGroup = part.addGroup(rightGroup, GroupDirection.DOWN); @@ -1101,16 +1117,16 @@ suite('EditorGroupsService', () => { assert.strictEqual(rightGroup, part.findGroup({ location: GroupLocation.PREVIOUS }, downGroup)); }); - test('applyLayout (2x2)', function () { - const [part] = createPart(); + test('applyLayout (2x2)', async function () { + const [part] = await createPart(); part.applyLayout({ groups: [{ groups: [{}, {}] }, { groups: [{}, {}] }], orientation: GroupOrientation.HORIZONTAL }); assert.strictEqual(part.groups.length, 4); }); - test('centeredLayout', function () { - const [part] = createPart(); + test('centeredLayout', async function () { + const [part] = await createPart(); part.centerLayout(true); @@ -1118,11 +1134,9 @@ suite('EditorGroupsService', () => { }); test('sticky editors', async () => { - const [part] = createPart(); + const [part] = await createPart(); const group = part.activeGroup; - await part.whenRestored; - assert.strictEqual(group.stickyCount, 0); assert.strictEqual(group.getEditors(EditorsOrder.SEQUENTIAL).length, 0); assert.strictEqual(group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE).length, 0); @@ -1220,7 +1234,7 @@ suite('EditorGroupsService', () => { }); test('moveEditor with context (across groups)', async () => { - const [part] = createPart(); + const [part] = await createPart(); const group = part.activeGroup; assert.strictEqual(group.isEmpty, true); @@ -1228,37 +1242,54 @@ suite('EditorGroupsService', () => { const input = new TestFileEditorInput(URI.file('foo/bar'), TEST_EDITOR_INPUT_ID); const inputInactive = new TestFileEditorInput(URI.file('foo/bar/inactive'), TEST_EDITOR_INPUT_ID); - let firstOpenEditorContext: OpenEditorContext | undefined; - Event.once(group.onWillOpenEditor)(e => { - firstOpenEditorContext = e.context; - }); - await group.openEditors([{ editor: input, options: { pinned: true } }, { editor: inputInactive }]); - assert.strictEqual(firstOpenEditorContext, undefined); + const thirdInput = new TestFileEditorInput(URI.file('foo/bar/third'), TEST_EDITOR_INPUT_ID); - const waitForEditorWillOpen = new Promise(resolve => { - Event.once(rightGroup.onWillOpenEditor)(e => resolve(e.context)); + let leftFiredCount = 0; + const leftGroupListener = group.onWillMoveEditor(() => { + leftFiredCount++; }); - group.moveEditor(inputInactive, rightGroup, { index: 0 }); - const context = await waitForEditorWillOpen; - assert.strictEqual(context, OpenEditorContext.MOVE_EDITOR); + let rightFiredCount = 0; + const rightGroupListener = rightGroup.onWillMoveEditor(() => { + rightFiredCount++; + }); + + await group.openEditors([{ editor: input, options: { pinned: true } }, { editor: inputInactive }, { editor: thirdInput }]); + assert.strictEqual(leftFiredCount, 0); + assert.strictEqual(rightFiredCount, 0); + + group.moveEditor(input, rightGroup); + assert.strictEqual(leftFiredCount, 1); + assert.strictEqual(rightFiredCount, 0); + + group.moveEditor(inputInactive, rightGroup); + assert.strictEqual(leftFiredCount, 2); + assert.strictEqual(rightFiredCount, 0); + + rightGroup.moveEditor(inputInactive, group); + assert.strictEqual(leftFiredCount, 2); + assert.strictEqual(rightFiredCount, 1); + + leftGroupListener.dispose(); + rightGroupListener.dispose(); }); test('copyEditor with context (across groups)', async () => { - const [part] = createPart(); + const [part] = await createPart(); const group = part.activeGroup; assert.strictEqual(group.isEmpty, true); + let firedCount = 0; + const moveListener = group.onWillMoveEditor(() => firedCount++); const rightGroup = part.addGroup(group, GroupDirection.RIGHT); const input = new TestFileEditorInput(URI.file('foo/bar'), TEST_EDITOR_INPUT_ID); const inputInactive = new TestFileEditorInput(URI.file('foo/bar/inactive'), TEST_EDITOR_INPUT_ID); await group.openEditors([{ editor: input, options: { pinned: true } }, { editor: inputInactive }]); - const waitForEditorWillOpen = new Promise(resolve => { - Event.once(rightGroup.onWillOpenEditor)(e => resolve(e.context)); - }); + assert.strictEqual(firedCount, 0); group.copyEditor(inputInactive, rightGroup, { index: 0 }); - const context = await waitForEditorWillOpen; - assert.strictEqual(context, OpenEditorContext.COPY_EDITOR); + + assert.strictEqual(firedCount, 0); + moveListener.dispose(); }); }); diff --git a/src/vs/workbench/services/editor/test/browser/editorService.test.ts b/src/vs/workbench/services/editor/test/browser/editorService.test.ts index b2edff9e..4d131178 100644 --- a/src/vs/workbench/services/editor/test/browser/editorService.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorService.test.ts @@ -56,8 +56,8 @@ suite('EditorService', () => { disposables.clear(); }); - function createEditorService(instantiationService: ITestInstantiationService = workbenchInstantiationService()): [EditorPart, EditorService, TestServiceAccessor] { - const part = createEditorPart(instantiationService, disposables); + async function createEditorService(instantiationService: ITestInstantiationService = workbenchInstantiationService()): Promise<[EditorPart, EditorService, TestServiceAccessor]> { + const part = await createEditorPart(instantiationService, disposables); instantiationService.stub(IEditorGroupsService, part); @@ -68,7 +68,7 @@ suite('EditorService', () => { } test('basics', async () => { - const [part, service] = createEditorService(); + const [, service] = await createEditorService(); let input = new TestFileEditorInput(URI.parse('my://resource-basics'), TEST_EDITOR_INPUT_ID); let otherInput = new TestFileEditorInput(URI.parse('my://resource2-basics'), TEST_EDITOR_INPUT_ID); @@ -88,8 +88,6 @@ suite('EditorService', () => { didCloseEditorListenerCounter++; }); - await part.whenRestored; - // Open input let editor = await service.openEditor(input, { pinned: true }); @@ -104,8 +102,9 @@ suite('EditorService', () => { assert.ok(!service.activeTextEditorControl); assert.ok(!service.activeTextEditorMode); assert.strictEqual(service.visibleTextEditorControls.length, 0); - assert.strictEqual(service.isOpen(input), true); - assert.strictEqual(service.isOpen({ resource: input.resource }), true); + assert.strictEqual(service.isOpened(input), true); + assert.strictEqual(service.isOpened({ resource: input.resource, typeId: input.typeId }), true); + assert.strictEqual(service.isOpened({ resource: input.resource, typeId: 'unknownTypeId' }), false); assert.strictEqual(activeEditorChangeEventCounter, 1); assert.strictEqual(visibleEditorChangeEventCounter, 1); @@ -137,10 +136,10 @@ suite('EditorService', () => { assert.strictEqual(input, service.getEditors(EditorsOrder.SEQUENTIAL)[0].editor); assert.strictEqual(otherInput, service.getEditors(EditorsOrder.SEQUENTIAL)[1].editor); assert.strictEqual(service.visibleEditorPanes.length, 1); - assert.strictEqual(service.isOpen(input), true); - assert.strictEqual(service.isOpen({ resource: input.resource }), true); - assert.strictEqual(service.isOpen(otherInput), true); - assert.strictEqual(service.isOpen({ resource: otherInput.resource }), true); + assert.strictEqual(service.isOpened(input), true); + assert.strictEqual(service.isOpened({ resource: input.resource, typeId: input.typeId }), true); + assert.strictEqual(service.isOpened(otherInput), true); + assert.strictEqual(service.isOpened({ resource: otherInput.resource, typeId: otherInput.typeId }), true); assert.strictEqual(activeEditorChangeEventCounter, 4); assert.strictEqual(visibleEditorChangeEventCounter, 4); @@ -172,59 +171,51 @@ suite('EditorService', () => { }); test('isOpen() with side by side editor', async () => { - const [part, service] = createEditorService(); + const [part, service] = await createEditorService(); const input = new TestFileEditorInput(URI.parse('my://resource-openEditors'), TEST_EDITOR_INPUT_ID); const otherInput = new TestFileEditorInput(URI.parse('my://resource2-openEditors'), TEST_EDITOR_INPUT_ID); const sideBySideInput = new SideBySideEditorInput('sideBySide', '', input, otherInput); - await part.whenRestored; - const editor1 = await service.openEditor(sideBySideInput, { pinned: true }); assert.strictEqual(part.activeGroup.count, 1); - assert.strictEqual(service.isOpen(input), false); - assert.strictEqual(service.isOpen(otherInput), false); - assert.strictEqual(service.isOpen(sideBySideInput), true); - assert.strictEqual(service.isOpen({ resource: input.resource }), false); - assert.strictEqual(service.isOpen({ resource: otherInput.resource }), true); + assert.strictEqual(service.isOpened(input), false); + assert.strictEqual(service.isOpened(otherInput), true); + assert.strictEqual(service.isOpened({ resource: input.resource, typeId: input.typeId }), false); + assert.strictEqual(service.isOpened({ resource: otherInput.resource, typeId: otherInput.typeId }), true); const editor2 = await service.openEditor(input, { pinned: true }); assert.strictEqual(part.activeGroup.count, 2); - assert.strictEqual(service.isOpen(input), true); - assert.strictEqual(service.isOpen(otherInput), false); - assert.strictEqual(service.isOpen(sideBySideInput), true); - assert.strictEqual(service.isOpen({ resource: input.resource }), true); - assert.strictEqual(service.isOpen({ resource: otherInput.resource }), true); + assert.strictEqual(service.isOpened(input), true); + assert.strictEqual(service.isOpened(otherInput), true); + assert.strictEqual(service.isOpened({ resource: input.resource, typeId: input.typeId }), true); + assert.strictEqual(service.isOpened({ resource: otherInput.resource, typeId: otherInput.typeId }), true); await editor2?.group?.closeEditor(input); assert.strictEqual(part.activeGroup.count, 1); - assert.strictEqual(service.isOpen(input), false); - assert.strictEqual(service.isOpen(otherInput), false); - assert.strictEqual(service.isOpen(sideBySideInput), true); - assert.strictEqual(service.isOpen({ resource: input.resource }), false); - assert.strictEqual(service.isOpen({ resource: otherInput.resource }), true); + assert.strictEqual(service.isOpened(input), false); + assert.strictEqual(service.isOpened(otherInput), true); + assert.strictEqual(service.isOpened({ resource: input.resource, typeId: input.typeId }), false); + assert.strictEqual(service.isOpened({ resource: otherInput.resource, typeId: otherInput.typeId }), true); await editor1?.group?.closeEditor(sideBySideInput); - assert.strictEqual(service.isOpen(input), false); - assert.strictEqual(service.isOpen(otherInput), false); - assert.strictEqual(service.isOpen(sideBySideInput), false); - assert.strictEqual(service.isOpen({ resource: input.resource }), false); - assert.strictEqual(service.isOpen({ resource: otherInput.resource }), false); + assert.strictEqual(service.isOpened(input), false); + assert.strictEqual(service.isOpened(otherInput), false); + assert.strictEqual(service.isOpened({ resource: input.resource, typeId: input.typeId }), false); + assert.strictEqual(service.isOpened({ resource: otherInput.resource, typeId: otherInput.typeId }), false); }); test('openEditors() / replaceEditors()', async () => { - const [part, service] = createEditorService(); + const [part, service] = await createEditorService(); const input = new TestFileEditorInput(URI.parse('my://resource-openEditors'), TEST_EDITOR_INPUT_ID); const otherInput = new TestFileEditorInput(URI.parse('my://resource2-openEditors'), TEST_EDITOR_INPUT_ID); const replaceInput = new TestFileEditorInput(URI.parse('my://resource3-openEditors'), TEST_EDITOR_INPUT_ID); - await part.whenRestored; - // Open editors await service.openEditors([{ editor: input }, { editor: otherInput }]); assert.strictEqual(part.activeGroup.count, 2); @@ -389,7 +380,7 @@ suite('EditorService', () => { super(id, undefined!, new TestThemeService(), new TestStorageService()); } - getId(): string { + override getId(): string { return 'myEditor'; } @@ -413,15 +404,13 @@ suite('EditorService', () => { }); test('close editor does not dispose when editor opened in other group', async () => { - const [part, service] = createEditorService(); + const [part, service] = await createEditorService(); const input = new TestFileEditorInput(URI.parse('my://resource-close1'), TEST_EDITOR_INPUT_ID); const rootGroup = part.activeGroup; const rightGroup = part.addGroup(rootGroup, GroupDirection.RIGHT); - await part.whenRestored; - // Open input await service.openEditor(input, { pinned: true }); await service.openEditor(input, { pinned: true }, rightGroup); @@ -440,15 +429,13 @@ suite('EditorService', () => { }); test('open to the side', async () => { - const [part, service] = createEditorService(); + const [part, service] = await createEditorService(); const input1 = new TestFileEditorInput(URI.parse('my://resource1-openside'), TEST_EDITOR_INPUT_ID); const input2 = new TestFileEditorInput(URI.parse('my://resource2-openside'), TEST_EDITOR_INPUT_ID); const rootGroup = part.activeGroup; - await part.whenRestored; - await service.openEditor(input1, { pinned: true }, rootGroup); let editor = await service.openEditor(input1, { pinned: true, preserveFocus: true }, SIDE_GROUP); @@ -464,15 +451,13 @@ suite('EditorService', () => { }); test('editor group activation', async () => { - const [part, service] = createEditorService(); + const [part, service] = await createEditorService(); const input1 = new TestFileEditorInput(URI.parse('my://resource1-openside'), TEST_EDITOR_INPUT_ID); const input2 = new TestFileEditorInput(URI.parse('my://resource2-openside'), TEST_EDITOR_INPUT_ID); const rootGroup = part.activeGroup; - await part.whenRestored; - await service.openEditor(input1, { pinned: true }, rootGroup); let editor = await service.openEditor(input2, { pinned: true, preserveFocus: true, activation: EditorActivation.ACTIVATE }, SIDE_GROUP); const sideGroup = editor?.group; @@ -497,15 +482,13 @@ suite('EditorService', () => { }); test('inactive editor group does not activate when closing editor (#117686)', async () => { - const [part, service] = createEditorService(); + const [part, service] = await createEditorService(); const input1 = new TestFileEditorInput(URI.parse('my://resource1-openside'), TEST_EDITOR_INPUT_ID); const input2 = new TestFileEditorInput(URI.parse('my://resource2-openside'), TEST_EDITOR_INPUT_ID); const rootGroup = part.activeGroup; - await part.whenRestored; - await service.openEditor(input1, { pinned: true }, rootGroup); await service.openEditor(input2, { pinned: true }, rootGroup); @@ -523,7 +506,7 @@ suite('EditorService', () => { }); test('active editor change / visible editor change events', async function () { - const [part, service] = createEditorService(); + const [part, service] = await createEditorService(); let input = new TestFileEditorInput(URI.parse('my://resource-active'), TEST_EDITOR_INPUT_ID); let otherInput = new TestFileEditorInput(URI.parse('my://resource2-active'), TEST_EDITOR_INPUT_ID); @@ -553,8 +536,6 @@ suite('EditorService', () => { await timeout(0); // closing an editor will not immediately open the next one, so we need to wait } - await part.whenRestored; - // 1.) open, open same, open other, close let editor = await service.openEditor(input, { pinned: true }); const group = editor?.group!; @@ -741,7 +722,7 @@ suite('EditorService', () => { }); test('two active editor change events when opening editor to the side', async function () { - const [part, service] = createEditorService(); + const [, service] = await createEditorService(); let input = new TestFileEditorInput(URI.parse('my://resource-active'), TEST_EDITOR_INPUT_ID); @@ -755,8 +736,6 @@ suite('EditorService', () => { activeEditorChangeEvents = 0; } - await part.whenRestored; - await service.openEditor(input, { pinned: true }); assertActiveEditorChangedEvent(1); @@ -776,9 +755,7 @@ suite('EditorService', () => { }); test('activeTextEditorControl / activeTextEditorMode', async () => { - const [part, service] = createEditorService(); - - await part.whenRestored; + const [, service] = await createEditorService(); // Open untitled input let editor = await service.openEditor({}); @@ -789,15 +766,13 @@ suite('EditorService', () => { }); test('openEditor returns NULL when opening fails or is inactive', async function () { - const [part, service] = createEditorService(); + const [, service] = await createEditorService(); const input = new TestFileEditorInput(URI.parse('my://resource-active'), TEST_EDITOR_INPUT_ID); const otherInput = new TestFileEditorInput(URI.parse('my://resource2-inactive'), TEST_EDITOR_INPUT_ID); const failingInput = new TestFileEditorInput(URI.parse('my://resource3-failing'), TEST_EDITOR_INPUT_ID); failingInput.setFailToOpen(); - await part.whenRestored; - let editor = await service.openEditor(input, { pinned: true }); assert.ok(editor); @@ -809,7 +784,7 @@ suite('EditorService', () => { }); test('save, saveAll, revertAll', async function () { - const [part, service] = createEditorService(); + const [part, service] = await createEditorService(); const input1 = new TestFileEditorInput(URI.parse('my://resource1'), TEST_EDITOR_INPUT_ID); input1.dirty = true; @@ -820,8 +795,6 @@ suite('EditorService', () => { const rootGroup = part.activeGroup; - await part.whenRestored; - await service.openEditor(input1, { pinned: true }); await service.openEditor(input2, { pinned: true }); await service.openEditor(sameInput1, { pinned: true }, SIDE_GROUP); @@ -888,7 +861,7 @@ suite('EditorService', () => { }); test('saveAll, revertAll (sticky editor)', async function () { - const [part, service] = createEditorService(); + const [, service] = await createEditorService(); const input1 = new TestFileEditorInput(URI.parse('my://resource1'), TEST_EDITOR_INPUT_ID); input1.dirty = true; @@ -897,8 +870,6 @@ suite('EditorService', () => { const sameInput1 = new TestFileEditorInput(URI.parse('my://resource1'), TEST_EDITOR_INPUT_ID); sameInput1.dirty = true; - await part.whenRestored; - await service.openEditor(input1, { pinned: true, sticky: true }); await service.openEditor(input2, { pinned: true }); await service.openEditor(sameInput1, { pinned: true }, SIDE_GROUP); @@ -936,7 +907,7 @@ suite('EditorService', () => { }); async function testFileDeleteEditorClose(dirty: boolean): Promise { - const [part, service, accessor] = createEditorService(); + const [part, service, accessor] = await createEditorService(); const input1 = new TestFileEditorInput(URI.parse('my://resource1'), TEST_EDITOR_INPUT_ID); input1.dirty = dirty; @@ -945,8 +916,6 @@ suite('EditorService', () => { const rootGroup = part.activeGroup; - await part.whenRestored; - await service.openEditor(input1, { pinned: true }); await service.openEditor(input2, { pinned: true }); @@ -966,7 +935,7 @@ suite('EditorService', () => { } test('file move asks input to move', async function () { - const [part, service, accessor] = createEditorService(); + const [part, service, accessor] = await createEditorService(); const input1 = new TestFileEditorInput(URI.parse('my://resource1'), TEST_EDITOR_INPUT_ID); const movedInput = new TestFileEditorInput(URI.parse('my://resource2'), TEST_EDITOR_INPUT_ID); @@ -974,8 +943,6 @@ suite('EditorService', () => { const rootGroup = part.activeGroup; - await part.whenRestored; - await service.openEditor(input1, { pinned: true }); const activeEditorChangePromise = awaitActiveEditorChange(service); @@ -1000,13 +967,11 @@ suite('EditorService', () => { } test('file watcher gets installed for out of workspace files', async function () { - const [part, service, accessor] = createEditorService(); + const [, service, accessor] = await createEditorService(); const input1 = new TestFileEditorInput(URI.parse('file://resource1'), TEST_EDITOR_INPUT_ID); const input2 = new TestFileEditorInput(URI.parse('file://resource2'), TEST_EDITOR_INPUT_ID); - await part.whenRestored; - await service.openEditor(input1, { pinned: true }); assert.strictEqual(accessor.fileService.watches.length, 1); assert.strictEqual(accessor.fileService.watches[0].toString(), input1.resource.toString()); @@ -1021,13 +986,11 @@ suite('EditorService', () => { test('activeEditorPane scopedContextKeyService', async function () { const instantiationService = workbenchInstantiationService({ contextKeyService: instantiationService => instantiationService.createInstance(MockScopableContextKeyService) }); - const [part, service] = createEditorService(instantiationService); + const [part, service] = await createEditorService(instantiationService); const input1 = new TestFileEditorInput(URI.parse('file://resource1'), TEST_EDITOR_INPUT_ID); new TestFileEditorInput(URI.parse('file://resource2'), TEST_EDITOR_INPUT_ID); - await part.whenRestored; - await service.openEditor(input1, { pinned: true }); const editorContextKeyService = service.activeEditorPane?.scopedContextKeyService; @@ -1036,13 +999,11 @@ suite('EditorService', () => { }); test('overrideOpenEditor', async function () { - const [part, service] = createEditorService(); + const [, service] = await createEditorService(); const input1 = new TestFileEditorInput(URI.parse('file://resource1'), TEST_EDITOR_INPUT_ID); const input2 = new TestFileEditorInput(URI.parse('file://resource2'), TEST_EDITOR_INPUT_ID); - await part.whenRestored; - let overrideCalled = false; const handler = service.overrideOpenEditor({ @@ -1065,67 +1026,123 @@ suite('EditorService', () => { handler.dispose(); }); - test('whenClosed', async function () { - const [part, service] = createEditorService(); - - const input1 = new TestFileEditorInput(URI.parse('file://resource1'), TEST_EDITOR_INPUT_ID); - const input2 = new TestFileEditorInput(URI.parse('file://resource2'), TEST_EDITOR_INPUT_ID); - - await part.whenRestored; - - const editor = await service.openEditor(input1, { pinned: true }); - await service.openEditor(input2, { pinned: true }); - - const whenClosed = service.whenClosed([{ resource: input1.resource }, { resource: input2.resource }]); - - editor?.group?.closeAllEditors(); - - await whenClosed; - }); - - test('findEditors', async () => { - const [part, service] = createEditorService(); + test('findEditors (in group)', async () => { + const [part, service] = await createEditorService(); const input = new TestFileEditorInput(URI.parse('my://resource-openEditors'), TEST_EDITOR_INPUT_ID); const otherInput = new TestFileEditorInput(URI.parse('my://resource2-openEditors'), TEST_EDITOR_INPUT_ID); - await part.whenRestored; - // Open editors await service.openEditors([{ editor: input }, { editor: otherInput }]); assert.strictEqual(part.activeGroup.count, 2); // Try using find editors for opened editors { - const found = service.findEditors(input.resource, part.activeGroup); - assert.strictEqual(found.length, 1); - assert.strictEqual(found[0], input); + const found1 = service.findEditors(input.resource, part.activeGroup); + assert.strictEqual(found1.length, 1); + assert.strictEqual(found1[0], input); + + const found2 = service.findEditors(input, part.activeGroup); + assert.strictEqual(found2, input); } { - const found = service.findEditors(otherInput.resource, part.activeGroup); - assert.strictEqual(found.length, 1); - assert.strictEqual(found[0], otherInput); + const found1 = service.findEditors(otherInput.resource, part.activeGroup); + assert.strictEqual(found1.length, 1); + assert.strictEqual(found1[0], otherInput); + + const found2 = service.findEditors(otherInput, part.activeGroup); + assert.strictEqual(found2, otherInput); } // Make sure we don't find non-opened editors { - const found = service.findEditors(URI.parse('my://no-such-resource'), part.activeGroup); - assert.strictEqual(found.length, 0); + const found1 = service.findEditors(URI.parse('my://no-such-resource'), part.activeGroup); + assert.strictEqual(found1.length, 0); + + const found2 = service.findEditors({ resource: URI.parse('my://no-such-resource'), typeId: '' }, part.activeGroup); + assert.strictEqual(found2, undefined); } // Make sure we don't find editors across groups { const newEditor = await service.openEditor(new TestFileEditorInput(URI.parse('my://other-group-resource'), TEST_EDITOR_INPUT_ID), { pinned: true, preserveFocus: true }, SIDE_GROUP); - const found = service.findEditors(input.resource, newEditor!.group!.id); - assert.strictEqual(found.length, 0); + const found1 = service.findEditors(input.resource, newEditor!.group!.id); + assert.strictEqual(found1.length, 0); + + const found2 = service.findEditors(input, newEditor!.group!.id); + assert.strictEqual(found2, undefined); } // Check we don't find editors after closing them await part.activeGroup.closeAllEditors(); { - const found = service.findEditors(input.resource, part.activeGroup); - assert.strictEqual(found.length, 0); + const found1 = service.findEditors(input.resource, part.activeGroup); + assert.strictEqual(found1.length, 0); + + const found2 = service.findEditors(input, part.activeGroup); + assert.strictEqual(found2, undefined); + } + }); + + test('findEditors (across groups)', async () => { + const [part, service] = await createEditorService(); + + const rootGroup = part.activeGroup; + + const input = new TestFileEditorInput(URI.parse('my://resource-openEditors'), TEST_EDITOR_INPUT_ID); + const otherInput = new TestFileEditorInput(URI.parse('my://resource2-openEditors'), TEST_EDITOR_INPUT_ID); + + // Open editors + await service.openEditors([{ editor: input }, { editor: otherInput }]); + const sideEditor = await service.openEditor(input, { pinned: true }, SIDE_GROUP); + + // Try using find editors for opened editors + { + const found1 = service.findEditors(input.resource); + assert.strictEqual(found1.length, 2); + assert.strictEqual(found1[0].editor, input); + assert.strictEqual(found1[0].groupId, sideEditor?.group?.id); + assert.strictEqual(found1[1].editor, input); + assert.strictEqual(found1[1].groupId, rootGroup.id); + + const found2 = service.findEditors(input); + assert.strictEqual(found2.length, 2); + assert.strictEqual(found2[0].editor, input); + assert.strictEqual(found2[0].groupId, sideEditor?.group?.id); + assert.strictEqual(found2[1].editor, input); + assert.strictEqual(found2[1].groupId, rootGroup.id); + } + { + const found1 = service.findEditors(otherInput.resource); + assert.strictEqual(found1.length, 1); + assert.strictEqual(found1[0].editor, otherInput); + assert.strictEqual(found1[0].groupId, rootGroup.id); + + const found2 = service.findEditors(otherInput); + assert.strictEqual(found2.length, 1); + assert.strictEqual(found2[0].editor, otherInput); + assert.strictEqual(found2[0].groupId, rootGroup.id); + } + + // Make sure we don't find non-opened editors + { + const found1 = service.findEditors(URI.parse('my://no-such-resource')); + assert.strictEqual(found1.length, 0); + + const found2 = service.findEditors({ resource: URI.parse('my://no-such-resource'), typeId: '' }); + assert.strictEqual(found2.length, 0); + } + + // Check we don't find editors after closing them + await rootGroup.closeAllEditors(); + await sideEditor?.group?.closeAllEditors(); + { + const found1 = service.findEditors(input.resource); + assert.strictEqual(found1.length, 0); + + const found2 = service.findEditors(input); + assert.strictEqual(found2.length, 0); } }); }); diff --git a/src/vs/workbench/services/editor/test/browser/editorsObserver.test.ts b/src/vs/workbench/services/editor/test/browser/editorsObserver.test.ts index 8516a4d4..8279ac53 100644 --- a/src/vs/workbench/services/editor/test/browser/editorsObserver.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorsObserver.test.ts @@ -4,9 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { EditorOptions, IEditorInputFactoryRegistry, Extensions as EditorExtensions } from 'vs/workbench/common/editor'; +import { EditorOptions, IEditorInputFactoryRegistry, EditorExtensions, SideBySideEditorInput } from 'vs/workbench/common/editor'; import { URI } from 'vs/base/common/uri'; -import { workbenchInstantiationService, TestFileEditorInput, registerTestEditor, TestEditorPart, createEditorPart } from 'vs/workbench/test/browser/workbenchTestServices'; +import { workbenchInstantiationService, TestFileEditorInput, registerTestEditor, TestEditorPart, createEditorPart, registerTestSideBySideEditor } from 'vs/workbench/test/browser/workbenchTestServices'; import { Registry } from 'vs/platform/registry/common/platform'; import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; @@ -28,6 +28,7 @@ suite('EditorsObserver', function () { setup(() => { disposables.add(registerTestEditor(TEST_EDITOR_ID, [new SyncDescriptor(TestFileEditorInput)], TEST_SERIALIZABLE_EDITOR_INPUT_ID)); + disposables.add(registerTestSideBySideEditor()); }); teardown(() => { @@ -38,11 +39,9 @@ suite('EditorsObserver', function () { const instantiationService = workbenchInstantiationService(); instantiationService.invokeFunction(accessor => Registry.as(EditorExtensions.EditorInputFactories).start(accessor)); - const part = createEditorPart(instantiationService, disposables); + const part = await createEditorPart(instantiationService, disposables); disposables.add(toDisposable(() => part.clearState())); - await part.whenRestored; - return part; } @@ -75,11 +74,16 @@ suite('EditorsObserver', function () { assert.strictEqual(currentEditorsMRU[0].groupId, part.activeGroup.id); assert.strictEqual(currentEditorsMRU[0].editor, input1); assert.strictEqual(onDidMostRecentlyActiveEditorsChangeCalled, true); - assert.strictEqual(observer.hasEditor(input1.resource), true); + assert.strictEqual(observer.hasEditor({ resource: input1.resource, typeId: input1.typeId }), true); + assert.strictEqual(observer.hasEditors(input1.resource), true); + assert.strictEqual(observer.hasEditor({ resource: input1.resource, typeId: 'unknownTypeId' }), false); const input2 = new TestFileEditorInput(URI.parse('foo://bar2'), TEST_SERIALIZABLE_EDITOR_INPUT_ID); const input3 = new TestFileEditorInput(URI.parse('foo://bar3'), TEST_SERIALIZABLE_EDITOR_INPUT_ID); + assert.strictEqual(observer.hasEditors(input2.resource), false); + assert.strictEqual(observer.hasEditor({ resource: input2.resource, typeId: input2.typeId }), false); + await part.activeGroup.openEditor(input2, EditorOptions.create({ pinned: true })); await part.activeGroup.openEditor(input3, EditorOptions.create({ pinned: true })); @@ -91,8 +95,8 @@ suite('EditorsObserver', function () { assert.strictEqual(currentEditorsMRU[1].editor, input2); assert.strictEqual(currentEditorsMRU[2].groupId, part.activeGroup.id); assert.strictEqual(currentEditorsMRU[2].editor, input1); - assert.strictEqual(observer.hasEditor(input2.resource), true); - assert.strictEqual(observer.hasEditor(input3.resource), true); + assert.strictEqual(observer.hasEditor({ resource: input2.resource, typeId: input2.typeId }), true); + assert.strictEqual(observer.hasEditor({ resource: input3.resource, typeId: input3.typeId }), true); await part.activeGroup.openEditor(input2, EditorOptions.create({ pinned: true })); @@ -104,9 +108,9 @@ suite('EditorsObserver', function () { assert.strictEqual(currentEditorsMRU[1].editor, input3); assert.strictEqual(currentEditorsMRU[2].groupId, part.activeGroup.id); assert.strictEqual(currentEditorsMRU[2].editor, input1); - assert.strictEqual(observer.hasEditor(input1.resource), true); - assert.strictEqual(observer.hasEditor(input2.resource), true); - assert.strictEqual(observer.hasEditor(input3.resource), true); + assert.strictEqual(observer.hasEditor({ resource: input1.resource, typeId: input1.typeId }), true); + assert.strictEqual(observer.hasEditor({ resource: input2.resource, typeId: input2.typeId }), true); + assert.strictEqual(observer.hasEditor({ resource: input3.resource, typeId: input3.typeId }), true); onDidMostRecentlyActiveEditorsChangeCalled = false; await part.activeGroup.closeEditor(input1); @@ -118,16 +122,16 @@ suite('EditorsObserver', function () { assert.strictEqual(currentEditorsMRU[1].groupId, part.activeGroup.id); assert.strictEqual(currentEditorsMRU[1].editor, input3); assert.strictEqual(onDidMostRecentlyActiveEditorsChangeCalled, true); - assert.strictEqual(observer.hasEditor(input1.resource), false); - assert.strictEqual(observer.hasEditor(input2.resource), true); - assert.strictEqual(observer.hasEditor(input3.resource), true); + assert.strictEqual(observer.hasEditor({ resource: input1.resource, typeId: input1.typeId }), false); + assert.strictEqual(observer.hasEditor({ resource: input2.resource, typeId: input2.typeId }), true); + assert.strictEqual(observer.hasEditor({ resource: input3.resource, typeId: input3.typeId }), true); await part.activeGroup.closeAllEditors(); currentEditorsMRU = observer.editors; assert.strictEqual(currentEditorsMRU.length, 0); - assert.strictEqual(observer.hasEditor(input1.resource), false); - assert.strictEqual(observer.hasEditor(input2.resource), false); - assert.strictEqual(observer.hasEditor(input3.resource), false); + assert.strictEqual(observer.hasEditor({ resource: input1.resource, typeId: input1.typeId }), false); + assert.strictEqual(observer.hasEditor({ resource: input2.resource, typeId: input2.typeId }), false); + assert.strictEqual(observer.hasEditor({ resource: input3.resource, typeId: input3.typeId }), false); listener.dispose(); }); @@ -153,7 +157,8 @@ suite('EditorsObserver', function () { assert.strictEqual(currentEditorsMRU[0].editor, input1); assert.strictEqual(currentEditorsMRU[1].groupId, rootGroup.id); assert.strictEqual(currentEditorsMRU[1].editor, input1); - assert.strictEqual(observer.hasEditor(input1.resource), true); + assert.strictEqual(observer.hasEditors(input1.resource), true); + assert.strictEqual(observer.hasEditor({ resource: input1.resource, typeId: input1.typeId }), true); await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true, activation: EditorActivation.ACTIVATE })); @@ -163,7 +168,8 @@ suite('EditorsObserver', function () { assert.strictEqual(currentEditorsMRU[0].editor, input1); assert.strictEqual(currentEditorsMRU[1].groupId, sideGroup.id); assert.strictEqual(currentEditorsMRU[1].editor, input1); - assert.strictEqual(observer.hasEditor(input1.resource), true); + assert.strictEqual(observer.hasEditors(input1.resource), true); + assert.strictEqual(observer.hasEditor({ resource: input1.resource, typeId: input1.typeId }), true); // Opening an editor inactive should not change // the most recent editor, but rather put it behind @@ -179,8 +185,10 @@ suite('EditorsObserver', function () { assert.strictEqual(currentEditorsMRU[1].editor, input2); assert.strictEqual(currentEditorsMRU[2].groupId, sideGroup.id); assert.strictEqual(currentEditorsMRU[2].editor, input1); - assert.strictEqual(observer.hasEditor(input1.resource), true); - assert.strictEqual(observer.hasEditor(input2.resource), true); + assert.strictEqual(observer.hasEditors(input1.resource), true); + assert.strictEqual(observer.hasEditors(input2.resource), true); + assert.strictEqual(observer.hasEditor({ resource: input1.resource, typeId: input1.typeId }), true); + assert.strictEqual(observer.hasEditor({ resource: input2.resource, typeId: input2.typeId }), true); await rootGroup.closeAllEditors(); @@ -188,15 +196,91 @@ suite('EditorsObserver', function () { assert.strictEqual(currentEditorsMRU.length, 1); assert.strictEqual(currentEditorsMRU[0].groupId, sideGroup.id); assert.strictEqual(currentEditorsMRU[0].editor, input1); - assert.strictEqual(observer.hasEditor(input1.resource), true); - assert.strictEqual(observer.hasEditor(input2.resource), false); + assert.strictEqual(observer.hasEditors(input1.resource), true); + assert.strictEqual(observer.hasEditors(input2.resource), false); + assert.strictEqual(observer.hasEditor({ resource: input1.resource, typeId: input1.typeId }), true); + assert.strictEqual(observer.hasEditor({ resource: input2.resource, typeId: input2.typeId }), false); await sideGroup.closeAllEditors(); currentEditorsMRU = observer.editors; assert.strictEqual(currentEditorsMRU.length, 0); - assert.strictEqual(observer.hasEditor(input1.resource), false); - assert.strictEqual(observer.hasEditor(input2.resource), false); + assert.strictEqual(observer.hasEditors(input1.resource), false); + assert.strictEqual(observer.hasEditors(input2.resource), false); + assert.strictEqual(observer.hasEditor({ resource: input1.resource, typeId: input1.typeId }), false); + assert.strictEqual(observer.hasEditor({ resource: input2.resource, typeId: input2.typeId }), false); + }); + + test('hasEditor/hasEditors - same resource, different type id', async () => { + const [part, observer] = await createEditorObserver(); + + const input1 = new TestFileEditorInput(URI.parse('foo://bar1'), TEST_SERIALIZABLE_EDITOR_INPUT_ID); + const input2 = new TestFileEditorInput(input1.resource, 'otherTypeId'); + + assert.strictEqual(observer.hasEditors(input1.resource), false); + assert.strictEqual(observer.hasEditor({ resource: input1.resource, typeId: input1.typeId }), false); + assert.strictEqual(observer.hasEditor({ resource: input2.resource, typeId: input2.typeId }), false); + + await part.activeGroup.openEditor(input1, EditorOptions.create({ pinned: true })); + + assert.strictEqual(observer.hasEditors(input1.resource), true); + assert.strictEqual(observer.hasEditor({ resource: input1.resource, typeId: input1.typeId }), true); + assert.strictEqual(observer.hasEditor({ resource: input2.resource, typeId: input2.typeId }), false); + + await part.activeGroup.openEditor(input2, EditorOptions.create({ pinned: true })); + + assert.strictEqual(observer.hasEditors(input1.resource), true); + assert.strictEqual(observer.hasEditor({ resource: input1.resource, typeId: input1.typeId }), true); + assert.strictEqual(observer.hasEditor({ resource: input2.resource, typeId: input2.typeId }), true); + + await part.activeGroup.closeEditor(input2); + + assert.strictEqual(observer.hasEditors(input1.resource), true); + assert.strictEqual(observer.hasEditor({ resource: input1.resource, typeId: input1.typeId }), true); + assert.strictEqual(observer.hasEditor({ resource: input2.resource, typeId: input2.typeId }), false); + + await part.activeGroup.closeEditor(input1); + + assert.strictEqual(observer.hasEditors(input1.resource), false); + assert.strictEqual(observer.hasEditor({ resource: input1.resource, typeId: input1.typeId }), false); + assert.strictEqual(observer.hasEditor({ resource: input2.resource, typeId: input2.typeId }), false); + }); + + test('hasEditor/hasEditors - side by side editor support', async () => { + const [part, observer] = await createEditorObserver(); + + const primary = new TestFileEditorInput(URI.parse('foo://bar1'), TEST_SERIALIZABLE_EDITOR_INPUT_ID); + const secondary = new TestFileEditorInput(URI.parse('foo://bar2'), 'otherTypeId'); + + const input = new SideBySideEditorInput('name', undefined, secondary, primary); + + assert.strictEqual(observer.hasEditors(primary.resource), false); + assert.strictEqual(observer.hasEditor({ resource: primary.resource, typeId: primary.typeId }), false); + assert.strictEqual(observer.hasEditor({ resource: secondary.resource, typeId: secondary.typeId }), false); + + await part.activeGroup.openEditor(input, EditorOptions.create({ pinned: true })); + + assert.strictEqual(observer.hasEditors(primary.resource), true); + assert.strictEqual(observer.hasEditor({ resource: primary.resource, typeId: primary.typeId }), true); + assert.strictEqual(observer.hasEditor({ resource: secondary.resource, typeId: secondary.typeId }), false); + + await part.activeGroup.openEditor(primary, EditorOptions.create({ pinned: true })); + + assert.strictEqual(observer.hasEditors(primary.resource), true); + assert.strictEqual(observer.hasEditor({ resource: primary.resource, typeId: primary.typeId }), true); + assert.strictEqual(observer.hasEditor({ resource: secondary.resource, typeId: secondary.typeId }), false); + + await part.activeGroup.closeEditor(input); + + assert.strictEqual(observer.hasEditors(primary.resource), true); + assert.strictEqual(observer.hasEditor({ resource: primary.resource, typeId: primary.typeId }), true); + assert.strictEqual(observer.hasEditor({ resource: secondary.resource, typeId: secondary.typeId }), false); + + await part.activeGroup.closeEditor(primary); + + assert.strictEqual(observer.hasEditors(primary.resource), false); + assert.strictEqual(observer.hasEditor({ resource: primary.resource, typeId: primary.typeId }), false); + assert.strictEqual(observer.hasEditor({ resource: secondary.resource, typeId: secondary.typeId }), false); }); test('copy group', async function () { @@ -220,12 +304,11 @@ suite('EditorsObserver', function () { assert.strictEqual(currentEditorsMRU[1].editor, input2); assert.strictEqual(currentEditorsMRU[2].groupId, rootGroup.id); assert.strictEqual(currentEditorsMRU[2].editor, input1); - assert.strictEqual(observer.hasEditor(input1.resource), true); - assert.strictEqual(observer.hasEditor(input2.resource), true); - assert.strictEqual(observer.hasEditor(input3.resource), true); + assert.strictEqual(observer.hasEditor({ resource: input1.resource, typeId: input1.typeId }), true); + assert.strictEqual(observer.hasEditor({ resource: input2.resource, typeId: input2.typeId }), true); + assert.strictEqual(observer.hasEditor({ resource: input3.resource, typeId: input3.typeId }), true); const copiedGroup = part.copyGroup(rootGroup, rootGroup, GroupDirection.RIGHT); - await copiedGroup.whenRestored; copiedGroup.setActive(true); copiedGroup.focus(); @@ -243,21 +326,21 @@ suite('EditorsObserver', function () { assert.strictEqual(currentEditorsMRU[4].editor, input2); assert.strictEqual(currentEditorsMRU[5].groupId, rootGroup.id); assert.strictEqual(currentEditorsMRU[5].editor, input1); - assert.strictEqual(observer.hasEditor(input1.resource), true); - assert.strictEqual(observer.hasEditor(input2.resource), true); - assert.strictEqual(observer.hasEditor(input3.resource), true); + assert.strictEqual(observer.hasEditor({ resource: input1.resource, typeId: input1.typeId }), true); + assert.strictEqual(observer.hasEditor({ resource: input2.resource, typeId: input2.typeId }), true); + assert.strictEqual(observer.hasEditor({ resource: input3.resource, typeId: input3.typeId }), true); await rootGroup.closeAllEditors(); - assert.strictEqual(observer.hasEditor(input1.resource), true); - assert.strictEqual(observer.hasEditor(input2.resource), true); - assert.strictEqual(observer.hasEditor(input3.resource), true); + assert.strictEqual(observer.hasEditor({ resource: input1.resource, typeId: input1.typeId }), true); + assert.strictEqual(observer.hasEditor({ resource: input2.resource, typeId: input2.typeId }), true); + assert.strictEqual(observer.hasEditor({ resource: input3.resource, typeId: input3.typeId }), true); await copiedGroup.closeAllEditors(); - assert.strictEqual(observer.hasEditor(input1.resource), false); - assert.strictEqual(observer.hasEditor(input2.resource), false); - assert.strictEqual(observer.hasEditor(input3.resource), false); + assert.strictEqual(observer.hasEditor({ resource: input1.resource, typeId: input1.typeId }), false); + assert.strictEqual(observer.hasEditor({ resource: input2.resource, typeId: input2.typeId }), false); + assert.strictEqual(observer.hasEditor({ resource: input3.resource, typeId: input3.typeId }), false); }); test('initial editors are part of observer and state is persisted & restored (single group)', async () => { @@ -275,7 +358,7 @@ suite('EditorsObserver', function () { const storage = new TestStorageService(); const observer = disposables.add(new EditorsObserver(part, storage)); - await part.whenRestored; + await part.whenReady; let currentEditorsMRU = observer.editors; assert.strictEqual(currentEditorsMRU.length, 3); @@ -285,14 +368,14 @@ suite('EditorsObserver', function () { assert.strictEqual(currentEditorsMRU[1].editor, input2); assert.strictEqual(currentEditorsMRU[2].groupId, rootGroup.id); assert.strictEqual(currentEditorsMRU[2].editor, input1); - assert.strictEqual(observer.hasEditor(input1.resource), true); - assert.strictEqual(observer.hasEditor(input2.resource), true); - assert.strictEqual(observer.hasEditor(input3.resource), true); + assert.strictEqual(observer.hasEditor({ resource: input1.resource, typeId: input1.typeId }), true); + assert.strictEqual(observer.hasEditor({ resource: input2.resource, typeId: input2.typeId }), true); + assert.strictEqual(observer.hasEditor({ resource: input3.resource, typeId: input3.typeId }), true); storage.emitWillSaveState(WillSaveStateReason.SHUTDOWN); const restoredObserver = disposables.add(new EditorsObserver(part, storage)); - await part.whenRestored; + await part.whenReady; currentEditorsMRU = restoredObserver.editors; assert.strictEqual(currentEditorsMRU.length, 3); @@ -302,9 +385,9 @@ suite('EditorsObserver', function () { assert.strictEqual(currentEditorsMRU[1].editor, input2); assert.strictEqual(currentEditorsMRU[2].groupId, rootGroup.id); assert.strictEqual(currentEditorsMRU[2].editor, input1); - assert.strictEqual(observer.hasEditor(input1.resource), true); - assert.strictEqual(observer.hasEditor(input2.resource), true); - assert.strictEqual(observer.hasEditor(input3.resource), true); + assert.strictEqual(observer.hasEditor({ resource: input1.resource, typeId: input1.typeId }), true); + assert.strictEqual(observer.hasEditor({ resource: input2.resource, typeId: input2.typeId }), true); + assert.strictEqual(observer.hasEditor({ resource: input3.resource, typeId: input3.typeId }), true); }); test('initial editors are part of observer (multi group)', async () => { @@ -324,7 +407,7 @@ suite('EditorsObserver', function () { const storage = new TestStorageService(); const observer = disposables.add(new EditorsObserver(part, storage)); - await part.whenRestored; + await part.whenReady; let currentEditorsMRU = observer.editors; assert.strictEqual(currentEditorsMRU.length, 3); @@ -334,14 +417,14 @@ suite('EditorsObserver', function () { assert.strictEqual(currentEditorsMRU[1].editor, input2); assert.strictEqual(currentEditorsMRU[2].groupId, rootGroup.id); assert.strictEqual(currentEditorsMRU[2].editor, input1); - assert.strictEqual(observer.hasEditor(input1.resource), true); - assert.strictEqual(observer.hasEditor(input2.resource), true); - assert.strictEqual(observer.hasEditor(input3.resource), true); + assert.strictEqual(observer.hasEditor({ resource: input1.resource, typeId: input1.typeId }), true); + assert.strictEqual(observer.hasEditor({ resource: input2.resource, typeId: input2.typeId }), true); + assert.strictEqual(observer.hasEditor({ resource: input3.resource, typeId: input3.typeId }), true); storage.emitWillSaveState(WillSaveStateReason.SHUTDOWN); const restoredObserver = disposables.add(new EditorsObserver(part, storage)); - await part.whenRestored; + await part.whenReady; currentEditorsMRU = restoredObserver.editors; assert.strictEqual(currentEditorsMRU.length, 3); @@ -351,9 +434,9 @@ suite('EditorsObserver', function () { assert.strictEqual(currentEditorsMRU[1].editor, input2); assert.strictEqual(currentEditorsMRU[2].groupId, rootGroup.id); assert.strictEqual(currentEditorsMRU[2].editor, input1); - assert.strictEqual(restoredObserver.hasEditor(input1.resource), true); - assert.strictEqual(restoredObserver.hasEditor(input2.resource), true); - assert.strictEqual(restoredObserver.hasEditor(input3.resource), true); + assert.strictEqual(restoredObserver.hasEditor({ resource: input1.resource, typeId: input1.typeId }), true); + assert.strictEqual(restoredObserver.hasEditor({ resource: input2.resource, typeId: input2.typeId }), true); + assert.strictEqual(restoredObserver.hasEditor({ resource: input3.resource, typeId: input3.typeId }), true); }); test('observer does not restore editors that cannot be serialized', async () => { @@ -367,22 +450,22 @@ suite('EditorsObserver', function () { const storage = new TestStorageService(); const observer = disposables.add(new EditorsObserver(part, storage)); - await part.whenRestored; + await part.whenReady; let currentEditorsMRU = observer.editors; assert.strictEqual(currentEditorsMRU.length, 1); assert.strictEqual(currentEditorsMRU[0].groupId, rootGroup.id); assert.strictEqual(currentEditorsMRU[0].editor, input1); - assert.strictEqual(observer.hasEditor(input1.resource), true); + assert.strictEqual(observer.hasEditor({ resource: input1.resource, typeId: input1.typeId }), true); storage.emitWillSaveState(WillSaveStateReason.SHUTDOWN); const restoredObserver = disposables.add(new EditorsObserver(part, storage)); - await part.whenRestored; + await part.whenReady; currentEditorsMRU = restoredObserver.editors; assert.strictEqual(currentEditorsMRU.length, 0); - assert.strictEqual(restoredObserver.hasEditor(input1.resource), false); + assert.strictEqual(restoredObserver.hasEditor({ resource: input1.resource, typeId: input1.typeId }), false); }); test('observer closes editors when limit reached (across all groups)', async () => { @@ -406,14 +489,14 @@ suite('EditorsObserver', function () { await rootGroup.openEditor(input4, EditorOptions.create({ pinned: true })); assert.strictEqual(rootGroup.count, 3); - assert.strictEqual(rootGroup.isOpened(input1), false); - assert.strictEqual(rootGroup.isOpened(input2), true); - assert.strictEqual(rootGroup.isOpened(input3), true); - assert.strictEqual(rootGroup.isOpened(input4), true); - assert.strictEqual(observer.hasEditor(input1.resource), false); - assert.strictEqual(observer.hasEditor(input2.resource), true); - assert.strictEqual(observer.hasEditor(input3.resource), true); - assert.strictEqual(observer.hasEditor(input4.resource), true); + assert.strictEqual(rootGroup.contains(input1), false); + assert.strictEqual(rootGroup.contains(input2), true); + assert.strictEqual(rootGroup.contains(input3), true); + assert.strictEqual(rootGroup.contains(input4), true); + assert.strictEqual(observer.hasEditor({ resource: input1.resource, typeId: input1.typeId }), false); + assert.strictEqual(observer.hasEditor({ resource: input2.resource, typeId: input2.typeId }), true); + assert.strictEqual(observer.hasEditor({ resource: input3.resource, typeId: input3.typeId }), true); + assert.strictEqual(observer.hasEditor({ resource: input4.resource, typeId: input4.typeId }), true); input2.setDirty(); part.enforcePartOptions({ limit: { enabled: true, value: 1 } }); @@ -421,29 +504,29 @@ suite('EditorsObserver', function () { await timeout(0); assert.strictEqual(rootGroup.count, 2); - assert.strictEqual(rootGroup.isOpened(input1), false); - assert.strictEqual(rootGroup.isOpened(input2), true); // dirty - assert.strictEqual(rootGroup.isOpened(input3), false); - assert.strictEqual(rootGroup.isOpened(input4), true); - assert.strictEqual(observer.hasEditor(input1.resource), false); - assert.strictEqual(observer.hasEditor(input2.resource), true); - assert.strictEqual(observer.hasEditor(input3.resource), false); - assert.strictEqual(observer.hasEditor(input4.resource), true); + assert.strictEqual(rootGroup.contains(input1), false); + assert.strictEqual(rootGroup.contains(input2), true); // dirty + assert.strictEqual(rootGroup.contains(input3), false); + assert.strictEqual(rootGroup.contains(input4), true); + assert.strictEqual(observer.hasEditor({ resource: input1.resource, typeId: input1.typeId }), false); + assert.strictEqual(observer.hasEditor({ resource: input2.resource, typeId: input2.typeId }), true); + assert.strictEqual(observer.hasEditor({ resource: input3.resource, typeId: input3.typeId }), false); + assert.strictEqual(observer.hasEditor({ resource: input4.resource, typeId: input4.typeId }), true); const input5 = new TestFileEditorInput(URI.parse('foo://bar5'), TEST_EDITOR_INPUT_ID); await sideGroup.openEditor(input5, EditorOptions.create({ pinned: true })); assert.strictEqual(rootGroup.count, 1); - assert.strictEqual(rootGroup.isOpened(input1), false); - assert.strictEqual(rootGroup.isOpened(input2), true); // dirty - assert.strictEqual(rootGroup.isOpened(input3), false); - assert.strictEqual(rootGroup.isOpened(input4), false); - assert.strictEqual(sideGroup.isOpened(input5), true); - assert.strictEqual(observer.hasEditor(input1.resource), false); - assert.strictEqual(observer.hasEditor(input2.resource), true); - assert.strictEqual(observer.hasEditor(input3.resource), false); - assert.strictEqual(observer.hasEditor(input4.resource), false); - assert.strictEqual(observer.hasEditor(input5.resource), true); + assert.strictEqual(rootGroup.contains(input1), false); + assert.strictEqual(rootGroup.contains(input2), true); // dirty + assert.strictEqual(rootGroup.contains(input3), false); + assert.strictEqual(rootGroup.contains(input4), false); + assert.strictEqual(sideGroup.contains(input5), true); + assert.strictEqual(observer.hasEditor({ resource: input1.resource, typeId: input1.typeId }), false); + assert.strictEqual(observer.hasEditor({ resource: input2.resource, typeId: input2.typeId }), true); + assert.strictEqual(observer.hasEditor({ resource: input3.resource, typeId: input3.typeId }), false); + assert.strictEqual(observer.hasEditor({ resource: input4.resource, typeId: input4.typeId }), false); + assert.strictEqual(observer.hasEditor({ resource: input5.resource, typeId: input5.typeId }), true); }); test('observer closes editors when limit reached (in group)', async () => { @@ -467,14 +550,14 @@ suite('EditorsObserver', function () { await rootGroup.openEditor(input4, EditorOptions.create({ pinned: true })); assert.strictEqual(rootGroup.count, 3); // 1 editor got closed due to our limit! - assert.strictEqual(rootGroup.isOpened(input1), false); - assert.strictEqual(rootGroup.isOpened(input2), true); - assert.strictEqual(rootGroup.isOpened(input3), true); - assert.strictEqual(rootGroup.isOpened(input4), true); - assert.strictEqual(observer.hasEditor(input1.resource), false); - assert.strictEqual(observer.hasEditor(input2.resource), true); - assert.strictEqual(observer.hasEditor(input3.resource), true); - assert.strictEqual(observer.hasEditor(input4.resource), true); + assert.strictEqual(rootGroup.contains(input1), false); + assert.strictEqual(rootGroup.contains(input2), true); + assert.strictEqual(rootGroup.contains(input3), true); + assert.strictEqual(rootGroup.contains(input4), true); + assert.strictEqual(observer.hasEditor({ resource: input1.resource, typeId: input1.typeId }), false); + assert.strictEqual(observer.hasEditor({ resource: input2.resource, typeId: input2.typeId }), true); + assert.strictEqual(observer.hasEditor({ resource: input3.resource, typeId: input3.typeId }), true); + assert.strictEqual(observer.hasEditor({ resource: input4.resource, typeId: input4.typeId }), true); await sideGroup.openEditor(input1, EditorOptions.create({ pinned: true })); await sideGroup.openEditor(input2, EditorOptions.create({ pinned: true })); @@ -482,35 +565,35 @@ suite('EditorsObserver', function () { await sideGroup.openEditor(input4, EditorOptions.create({ pinned: true })); assert.strictEqual(sideGroup.count, 3); - assert.strictEqual(sideGroup.isOpened(input1), false); - assert.strictEqual(sideGroup.isOpened(input2), true); - assert.strictEqual(sideGroup.isOpened(input3), true); - assert.strictEqual(sideGroup.isOpened(input4), true); - assert.strictEqual(observer.hasEditor(input1.resource), false); - assert.strictEqual(observer.hasEditor(input2.resource), true); - assert.strictEqual(observer.hasEditor(input3.resource), true); - assert.strictEqual(observer.hasEditor(input4.resource), true); + assert.strictEqual(sideGroup.contains(input1), false); + assert.strictEqual(sideGroup.contains(input2), true); + assert.strictEqual(sideGroup.contains(input3), true); + assert.strictEqual(sideGroup.contains(input4), true); + assert.strictEqual(observer.hasEditor({ resource: input1.resource, typeId: input1.typeId }), false); + assert.strictEqual(observer.hasEditor({ resource: input2.resource, typeId: input2.typeId }), true); + assert.strictEqual(observer.hasEditor({ resource: input3.resource, typeId: input3.typeId }), true); + assert.strictEqual(observer.hasEditor({ resource: input4.resource, typeId: input4.typeId }), true); part.enforcePartOptions({ limit: { enabled: true, value: 1, perEditorGroup: true } }); await timeout(10); assert.strictEqual(rootGroup.count, 1); - assert.strictEqual(rootGroup.isOpened(input1), false); - assert.strictEqual(rootGroup.isOpened(input2), false); - assert.strictEqual(rootGroup.isOpened(input3), false); - assert.strictEqual(rootGroup.isOpened(input4), true); + assert.strictEqual(rootGroup.contains(input1), false); + assert.strictEqual(rootGroup.contains(input2), false); + assert.strictEqual(rootGroup.contains(input3), false); + assert.strictEqual(rootGroup.contains(input4), true); assert.strictEqual(sideGroup.count, 1); - assert.strictEqual(sideGroup.isOpened(input1), false); - assert.strictEqual(sideGroup.isOpened(input2), false); - assert.strictEqual(sideGroup.isOpened(input3), false); - assert.strictEqual(sideGroup.isOpened(input4), true); + assert.strictEqual(sideGroup.contains(input1), false); + assert.strictEqual(sideGroup.contains(input2), false); + assert.strictEqual(sideGroup.contains(input3), false); + assert.strictEqual(sideGroup.contains(input4), true); - assert.strictEqual(observer.hasEditor(input1.resource), false); - assert.strictEqual(observer.hasEditor(input2.resource), false); - assert.strictEqual(observer.hasEditor(input3.resource), false); - assert.strictEqual(observer.hasEditor(input4.resource), true); + assert.strictEqual(observer.hasEditor({ resource: input1.resource, typeId: input1.typeId }), false); + assert.strictEqual(observer.hasEditor({ resource: input2.resource, typeId: input2.typeId }), false); + assert.strictEqual(observer.hasEditor({ resource: input3.resource, typeId: input3.typeId }), false); + assert.strictEqual(observer.hasEditor({ resource: input4.resource, typeId: input4.typeId }), true); }); test('observer does not close sticky', async () => { @@ -533,13 +616,13 @@ suite('EditorsObserver', function () { await rootGroup.openEditor(input4, EditorOptions.create({ pinned: true })); assert.strictEqual(rootGroup.count, 3); - assert.strictEqual(rootGroup.isOpened(input1), true); - assert.strictEqual(rootGroup.isOpened(input2), false); - assert.strictEqual(rootGroup.isOpened(input3), true); - assert.strictEqual(rootGroup.isOpened(input4), true); - assert.strictEqual(observer.hasEditor(input1.resource), true); - assert.strictEqual(observer.hasEditor(input2.resource), false); - assert.strictEqual(observer.hasEditor(input3.resource), true); - assert.strictEqual(observer.hasEditor(input4.resource), true); + assert.strictEqual(rootGroup.contains(input1), true); + assert.strictEqual(rootGroup.contains(input2), false); + assert.strictEqual(rootGroup.contains(input3), true); + assert.strictEqual(rootGroup.contains(input4), true); + assert.strictEqual(observer.hasEditor({ resource: input1.resource, typeId: input1.typeId }), true); + assert.strictEqual(observer.hasEditor({ resource: input2.resource, typeId: input2.typeId }), false); + assert.strictEqual(observer.hasEditor({ resource: input3.resource, typeId: input3.typeId }), true); + assert.strictEqual(observer.hasEditor({ resource: input4.resource, typeId: input4.typeId }), true); }); }); diff --git a/src/vs/workbench/services/environment/browser/environmentService.ts b/src/vs/workbench/services/environment/browser/environmentService.ts index 714a119c..92da6861 100644 --- a/src/vs/workbench/services/environment/browser/environmentService.ts +++ b/src/vs/workbench/services/environment/browser/environmentService.ts @@ -108,9 +108,6 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment @memoize get remoteAuthority(): string | undefined { return this.options.remoteAuthority; } - @memoize - get sessionId(): string { return this.configuration.sessionId; } - @memoize get isBuilt(): boolean { return !!this.productService.commit; } @@ -118,7 +115,7 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment get logsPath(): string { return this.options.logsPath.path; } @memoize - get logLevel(): string | undefined { return this.payload?.get('logLevel') || (this.options.logLevel !== undefined ? LogLevelToString(this.options.logLevel) : undefined); } + get logLevel(): string | undefined { return this.payload?.get('logLevel') || (this.options.developmentOptions?.logLevel !== undefined ? LogLevelToString(this.options.developmentOptions?.logLevel) : undefined); } @memoize get logFile(): URI { return joinPath(this.options.logsPath, 'window.log'); } @@ -236,7 +233,7 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment @memoize get webviewExternalEndpoint(): string { - return (this.webviewEndpoint).replace('{{commit}}', this.productService.commit || '0d728c31ebdf03869d2687d9be0b017667c9ff37'); + return (this.webviewEndpoint).replace('{{commit}}', this.productService.commit || '23a2409675bc1bde94f3532bc7c5826a6e99e4b6'); } @memoize @@ -285,13 +282,26 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment extensionDevelopmentLocationURI: undefined, extensionDevelopmentKind: undefined }; + const developmentOptions = this.options.developmentOptions; + if (developmentOptions) { + if (developmentOptions.extensions?.length) { + extensionHostDebugEnvironment.extensionDevelopmentLocationURI = developmentOptions.extensions.map(e => URI.revive(e.extensionLocation)); + extensionHostDebugEnvironment.isExtensionDevelopment = true; + } + if (developmentOptions) { + extensionHostDebugEnvironment.extensionTestsLocationURI = URI.revive(developmentOptions.extensionTestsPath); + } + } // Fill in selected extra environmental properties if (this.payload) { for (const [key, value] of this.payload) { switch (key) { case 'extensionDevelopmentPath': - extensionHostDebugEnvironment.extensionDevelopmentLocationURI = [URI.parse(value)]; + if (!extensionHostDebugEnvironment.extensionDevelopmentLocationURI) { + extensionHostDebugEnvironment.extensionDevelopmentLocationURI = []; + } + extensionHostDebugEnvironment.extensionDevelopmentLocationURI.push(URI.parse(value)); extensionHostDebugEnvironment.isExtensionDevelopment = true; break; case 'extensionDevelopmentKind': diff --git a/src/vs/workbench/services/environment/common/environmentService.ts b/src/vs/workbench/services/environment/common/environmentService.ts index 094d022f..6604c982 100644 --- a/src/vs/workbench/services/environment/common/environmentService.ts +++ b/src/vs/workbench/services/environment/common/environmentService.ts @@ -29,8 +29,6 @@ export interface IWorkbenchEnvironmentService extends IEnvironmentService { readonly remoteAuthority?: string; - readonly sessionId: string; - readonly logFile: URI; readonly extHostLogsPath: URI; diff --git a/src/vs/workbench/services/environment/electron-sandbox/environmentService.ts b/src/vs/workbench/services/environment/electron-sandbox/environmentService.ts index bbfa2f51..9fb4fb45 100644 --- a/src/vs/workbench/services/environment/electron-sandbox/environmentService.ts +++ b/src/vs/workbench/services/environment/electron-sandbox/environmentService.ts @@ -7,7 +7,7 @@ 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 { AbstractNativeEnvironmentService } 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'; @@ -50,9 +50,6 @@ export class NativeWorkbenchEnvironmentService extends AbstractNativeEnvironment @memoize get machineId() { return this.configuration.machineId; } - @memoize - get sessionId() { return this.configuration.sessionId; } - @memoize get remoteAuthority() { return this.configuration.remoteAuthority; } @@ -60,7 +57,7 @@ export class NativeWorkbenchEnvironmentService extends AbstractNativeEnvironment get execPath() { return this.configuration.execPath; } @memoize - get userRoamingDataHome(): URI { return this.appSettingsHome.with({ scheme: Schemas.userData }); } + override 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`)); } @@ -69,17 +66,22 @@ export class NativeWorkbenchEnvironmentService extends AbstractNativeEnvironment 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}}'; + get webviewExternalEndpoint(): string { return `${Schemas.vscodeWebview}://{{uuid}}`; } - return baseEndpoint.replace('{{commit}}', this.productService.commit || '0d728c31ebdf03869d2687d9be0b017667c9ff37'); + @memoize + get webviewResourceRoot(): string { + // On desktop, this endpoint is only used for the service worker to identify resource loads and + // should never actually be requested. + // + // Required due to https://github.com/electron/electron/issues/28528 + return 'https://{{uuid}}.vscode-webview-test.com/vscode-resource/{{resource}}'; } @memoize - get webviewResourceRoot(): string { return `${Schemas.vscodeWebviewResource}://{{uuid}}/{{resource}}`; } - - @memoize - get webviewCspSource(): string { return `${Schemas.vscodeWebviewResource}:`; } + get webviewCspSource(): string { + const uri = URI.parse(this.webviewResourceRoot.replace('{{uuid}}', '*')); + return `${uri.scheme}://${uri.authority}`; + } @memoize get skipReleaseNotes(): boolean { return !!this.args['skip-release-notes']; } @@ -106,9 +108,8 @@ export class NativeWorkbenchEnvironmentService extends AbstractNativeEnvironment constructor( readonly configuration: INativeWorkbenchConfiguration, - paths: INativeEnvironmentPaths, productService: IProductService ) { - super(configuration, paths, productService); + super(configuration, { homeDir: configuration.homeDir, tmpDir: configuration.tmpDir, userDataDir: configuration.userDataDir }, productService); } } diff --git a/src/vs/workbench/services/environment/electron-sandbox/shellEnvironmentService.ts b/src/vs/workbench/services/environment/electron-sandbox/shellEnvironmentService.ts index fe1416ab..a0c728b7 100644 --- a/src/vs/workbench/services/environment/electron-sandbox/shellEnvironmentService.ts +++ b/src/vs/workbench/services/environment/electron-sandbox/shellEnvironmentService.ts @@ -22,7 +22,7 @@ export class ShellEnvironmentService implements IShellEnvironmentService { declare readonly _serviceBrand: undefined; getShellEnv(): Promise { - return process.getShellEnv(); + return process.shellEnv(); } } diff --git a/src/vs/workbench/services/extensionManagement/browser/builtinExtensionsScannerService.ts b/src/vs/workbench/services/extensionManagement/browser/builtinExtensionsScannerService.ts index 21fcbb0c..7e2a73a6 100644 --- a/src/vs/workbench/services/extensionManagement/browser/builtinExtensionsScannerService.ts +++ b/src/vs/workbench/services/extensionManagement/browser/builtinExtensionsScannerService.ts @@ -57,6 +57,7 @@ export class BuiltinExtensionsScannerService implements IBuiltinExtensionsScanne packageNLS: e.packageNLS, readmeUrl: e.readmePath ? uriIdentityService.extUri.joinPath(builtinExtensionsServiceUrl!, e.readmePath) : undefined, changelogUrl: e.changelogPath ? uriIdentityService.extUri.joinPath(builtinExtensionsServiceUrl!, e.changelogPath) : undefined, + isUnderDevelopment: false })); } } @@ -67,7 +68,7 @@ export class BuiltinExtensionsScannerService implements IBuiltinExtensionsScanne if (environmentService.options && typeof environmentService.options._enableBuiltinExtensions !== 'undefined') { enableBuiltinExtensions = environmentService.options._enableBuiltinExtensions; } else { - enableBuiltinExtensions = environmentService.remoteAuthority ? false : true; + enableBuiltinExtensions = true; } if (enableBuiltinExtensions) { return FileAccess.asBrowserUri('../../../../../../extensions', require); diff --git a/src/vs/workbench/services/extensionManagement/browser/extensionEnablementService.ts b/src/vs/workbench/services/extensionManagement/browser/extensionEnablementService.ts index fc289efd..744731e2 100644 --- a/src/vs/workbench/services/extensionManagement/browser/extensionEnablementService.ts +++ b/src/vs/workbench/services/extensionManagement/browser/extensionEnablementService.ts @@ -14,9 +14,7 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IExtension, isAuthenticaionProviderExtension, isLanguagePackExtension } from 'vs/platform/extensions/common/extensions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ExtensionKindController } from 'vs/workbench/services/extensions/common/extensionsUtil'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IProductService } from 'vs/platform/product/common/productService'; import { StorageManager } from 'vs/platform/extensionManagement/common/extensionEnablementService'; import { webWorkerExtHostConfig } from 'vs/workbench/services/extensions/common/extensions'; import { IUserDataSyncAccountService } from 'vs/platform/userDataSync/common/userDataSyncAccount'; @@ -25,8 +23,10 @@ import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecyc import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IExtensionBisectService } from 'vs/workbench/services/extensionManagement/browser/extensionBisect'; -import { IWorkspaceTrustService, WorkspaceTrustState } from 'vs/platform/workspace/common/workspaceTrust'; +import { IWorkspaceTrustManagementService, IWorkspaceTrustRequestService } from 'vs/platform/workspace/common/workspaceTrust'; import { Promises } from 'vs/base/common/async'; +import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService'; +import { getVirtualWorkspaceScheme } from 'vs/platform/remote/common/remoteHosts'; const SOURCE = 'IWorkbenchExtensionEnablementService'; @@ -38,9 +38,6 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench public readonly onEnablementChanged: Event = this._onEnablementChanged.event; private readonly storageManger: StorageManager; - private extensionsDisabledByTrustRequirement: IExtension[] = []; - - private readonly extensionKindController: ExtensionKindController; constructor( @IStorageService storageService: IStorageService, @@ -50,14 +47,15 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, @IConfigurationService private readonly configurationService: IConfigurationService, @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, - @IProductService productService: IProductService, @IUserDataAutoSyncEnablementService private readonly userDataAutoSyncEnablementService: IUserDataAutoSyncEnablementService, @IUserDataSyncAccountService private readonly userDataSyncAccountService: IUserDataSyncAccountService, @ILifecycleService private readonly lifecycleService: ILifecycleService, @INotificationService private readonly notificationService: INotificationService, @IHostService readonly hostService: IHostService, @IExtensionBisectService private readonly extensionBisectService: IExtensionBisectService, - @IWorkspaceTrustService private readonly workspaceTrustService: IWorkspaceTrustService + @IWorkspaceTrustManagementService private readonly workspaceTrustManagementService: IWorkspaceTrustManagementService, + @IWorkspaceTrustRequestService private readonly workspaceTrustRequestService: IWorkspaceTrustRequestService, + @IExtensionManifestPropertiesService private readonly extensionManifestPropertiesService: IExtensionManifestPropertiesService, ) { super(); this.storageManger = this._register(new StorageManager(storageService)); @@ -65,31 +63,15 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench this._register(extensionManagementService.onDidInstallExtension(this._onDidInstallExtension, this)); this._register(extensionManagementService.onDidUninstallExtension(this._onDidUninstallExtension, this)); - // Trusted extensions notification - // TODO: Confirm that this is the right lifecycle phase - this.lifecycleService.when(LifecyclePhase.Eventually).then(() => { - if (this.extensionsDisabledByTrustRequirement.length > 0) { - this.workspaceTrustService.requireWorkspaceTrust({ modal: false }) - .then(trustState => { - if (trustState === WorkspaceTrustState.Trusted) { - this._onEnablementChanged.fire(this.extensionsDisabledByTrustRequirement); - this.extensionsDisabledByTrustRequirement = []; - } - }); - } - }); - // delay notification for extensions disabled until workbench restored if (this.allUserExtensionsDisabled) { - this.lifecycleService.when(LifecyclePhase.Restored).then(() => { + this.lifecycleService.when(LifecyclePhase.Eventually).then(() => { this.notificationService.prompt(Severity.Info, localize('extensionsDisabled', "All installed extensions are temporarily disabled."), [{ label: localize('Reload', "Reload and Enable Extensions"), run: () => hostService.reload({ disableExtensions: false }) }]); }); } - - this.extensionKindController = new ExtensionKindController(productService, configurationService); } private get hasWorkspace(): boolean { @@ -107,6 +89,9 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench if (this._isDisabledInEnv(extension)) { return EnablementState.DisabledByEnvironment; } + if (this._isDisabledByVirtualWorkspace(extension)) { + return EnablementState.DisabledByVirtualWorkspace; + } if (this._isDisabledByExtensionKind(extension)) { return EnablementState.DisabledByExtensionKind; } @@ -123,7 +108,9 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench return false; } const enablementState = this.getEnablementState(extension); - if (enablementState === EnablementState.DisabledByEnvironment || enablementState === EnablementState.DisabledByExtensionKind) { + if (enablementState === EnablementState.DisabledByEnvironment + || enablementState === EnablementState.DisabledByVirtualWorkspace + || enablementState === EnablementState.DisabledByExtensionKind) { return false; } return true; @@ -174,9 +161,9 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench const result = await Promises.settled(extensions.map(e => { if (this._isDisabledByTrustRequirement(e)) { - return this.workspaceTrustService.requireWorkspaceTrust() + return this.workspaceTrustRequestService.requestWorkspaceTrust({ modal: true }) .then(trustState => { - if (trustState === WorkspaceTrustState.Trusted) { + if (trustState) { return this._setEnablement(e, newState); } else { return Promise.resolve(false); @@ -245,10 +232,17 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench return false; } + private _isDisabledByVirtualWorkspace(extension: IExtension): boolean { + if (getVirtualWorkspaceScheme(this.contextService.getWorkspace()) !== undefined) { + return !this.extensionManifestPropertiesService.canSupportVirtualWorkspace(extension.manifest); + } + return false; + } + private _isDisabledByExtensionKind(extension: IExtension): boolean { if (this.extensionManagementServerService.remoteExtensionManagementServer || this.extensionManagementServerService.webExtensionManagementServer) { const server = this.extensionManagementServerService.getExtensionManagementServer(extension); - for (const extensionKind of this.extensionKindController.getExtensionKind(extension.manifest)) { + for (const extensionKind of this.extensionManifestPropertiesService.getExtensionKind(extension.manifest)) { if (extensionKind === 'ui') { if (this.extensionManagementServerService.localExtensionManagementServer && this.extensionManagementServerService.localExtensionManagementServer === server) { return false; @@ -279,15 +273,11 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench } private _isDisabledByTrustRequirement(extension: IExtension): boolean { - const workspaceTrustState = this.workspaceTrustService.getWorkspaceTrustState(); - - if (extension.manifest.workspaceTrust?.required === 'onStart') { - if (workspaceTrustState !== WorkspaceTrustState.Trusted) { - this._addToWorkspaceDisabledExtensionsByTrustRequirement(extension); - } - return workspaceTrustState !== WorkspaceTrustState.Trusted; + if (this.workspaceTrustManagementService.isWorkpaceTrusted()) { + return false; } - return false; + + return this.extensionManifestPropertiesService.getExtensionUntrustedWorkspaceSupportType(extension.manifest) === false; } private _getEnablementState(identifier: IExtensionIdentifier): EnablementState { @@ -390,26 +380,6 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench return false; } - private _addToWorkspaceDisabledExtensionsByTrustRequirement(extension: IExtension): void { - if (this.extensionsDisabledByTrustRequirement.every(e => !areSameExtensions(extension.identifier, e.identifier))) { - this.extensionsDisabledByTrustRequirement.push(extension); - } - } - - private _removeFromWorkspaceDisabledExtensionsByTrustRequirement(identifier: IExtensionIdentifier): void { - let index = -1; - for (let i = 0; i < this.extensionsDisabledByTrustRequirement.length; i++) { - const disabledExtension = this.extensionsDisabledByTrustRequirement[i]; - if (areSameExtensions(disabledExtension.identifier, identifier)) { - index = i; - break; - } - } - if (index !== -1) { - this.extensionsDisabledByTrustRequirement.splice(index, 1); - } - } - protected _getWorkspaceEnabledExtensions(): IExtensionIdentifier[] { return this._getExtensions(ENABLED_EXTENSIONS_STORAGE_PATH); } @@ -447,7 +417,7 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench private _onDidInstallExtension({ local, error }: DidInstallExtensionEvent): void { if (local && !error && this._isDisabledByTrustRequirement(local)) { - this.workspaceTrustService.requireWorkspaceTrust(); + this._onEnablementChanged.fire([local]); } } @@ -457,10 +427,21 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench } } + private async _getExtensionsByWorkspaceTrustRequirement(): Promise { + const extensions = await this.extensionManagementService.getInstalled(); + return extensions.filter(e => this.extensionManifestPropertiesService.getExtensionUntrustedWorkspaceSupportType(e.manifest) === false); + } + + public async updateEnablementByWorkspaceTrustRequirement(): Promise { + const extensions = await this._getExtensionsByWorkspaceTrustRequirement(); + if (extensions.length) { + this._onEnablementChanged.fire(extensions); + } + } + private _reset(extension: IExtensionIdentifier) { this._removeFromWorkspaceDisabledExtensions(extension); this._removeFromWorkspaceEnabledExtensions(extension); - this._removeFromWorkspaceDisabledExtensionsByTrustRequirement(extension); this.globalExtensionEnablementService.enableExtension(extension); } } diff --git a/src/vs/workbench/services/extensionManagement/common/extensionManagement.ts b/src/vs/workbench/services/extensionManagement/common/extensionManagement.ts index 5cd77eb2..edb80d1a 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionManagement.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionManagement.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Event } from 'vs/base/common/event'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { createDecorator, refineServiceDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IExtension, IScannedExtension, ExtensionType, ITranslatedScannedExtension, IExtensionManifest } from 'vs/platform/extensions/common/extensions'; import { IExtensionManagementService, IGalleryExtension, IExtensionIdentifier, ILocalExtension, InstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement'; import { URI } from 'vs/base/common/uri'; @@ -24,7 +24,7 @@ export interface IExtensionManagementServerService { getExtensionManagementServer(extension: IExtension): IExtensionManagementServer | null; } -export const IWorkbenchExtensionManagementService = createDecorator('extensionManagementService'); +export const IWorkbenchExtensionManagementService = refineServiceDecorator(IExtensionManagementService); export interface IWorkbenchExtensionManagementService extends IExtensionManagementService { readonly _serviceBrand: undefined; installExtensions(extensions: IGalleryExtension[], installOptions?: InstallOptions): Promise; @@ -36,6 +36,7 @@ export const enum EnablementState { DisabledByTrustRequirement, DisabledByExtensionKind, DisabledByEnvironment, + DisabledByVirtualWorkspace, DisabledGlobally, DisabledWorkspace, EnabledGlobally, @@ -90,6 +91,12 @@ export interface IWorkbenchExtensionEnablementService { * Throws error if enablement is requested for workspace and there is no workspace */ setEnablement(extensions: IExtension[], state: EnablementState): Promise; + + /** + * Updates the enablement state of the extensions that require workspace trust when + * workspace trust changes. + */ + updateEnablementByWorkspaceTrustRequirement(): Promise; } export const IWebExtensionsScannerService = createDecorator('IWebExtensionsScannerService'); diff --git a/src/vs/workbench/services/extensionManagement/common/extensionManagementServerService.ts b/src/vs/workbench/services/extensionManagement/common/extensionManagementServerService.ts index bd730af5..d824f860 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionManagementServerService.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionManagementServerService.ts @@ -18,6 +18,7 @@ import { WebRemoteExtensionManagementService } from 'vs/workbench/services/exten import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IProductService } from 'vs/platform/product/common/productService'; +import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService'; export class ExtensionManagementServerService implements IExtensionManagementServerService { @@ -34,10 +35,11 @@ export class ExtensionManagementServerService implements IExtensionManagementSer @IProductService productService: IProductService, @IConfigurationService configurationService: IConfigurationService, @IInstantiationService instantiationService: IInstantiationService, + @IExtensionManifestPropertiesService extensionManifestPropertiesService: IExtensionManifestPropertiesService, ) { const remoteAgentConnection = remoteAgentService.getConnection(); if (remoteAgentConnection) { - const extensionManagementService = new WebRemoteExtensionManagementService(remoteAgentConnection.getChannel('extensions'), galleryService, configurationService, productService); + const extensionManagementService = new WebRemoteExtensionManagementService(remoteAgentConnection.getChannel('extensions'), galleryService, configurationService, productService, extensionManifestPropertiesService); this.remoteExtensionManagementServer = { id: 'remote', extensionManagementService, diff --git a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts index 2daca7d0..c52be5eb 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts @@ -15,7 +15,6 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { CancellationToken } from 'vs/base/common/cancellation'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { localize } from 'vs/nls'; -import { ExtensionKindController } from 'vs/workbench/services/extensions/common/extensionsUtil'; import { IProductService } from 'vs/platform/product/common/productService'; import { Schemas } from 'vs/base/common/network'; import { IDownloadService } from 'vs/platform/download/common/download'; @@ -25,7 +24,8 @@ import Severity from 'vs/base/common/severity'; import { canceled } from 'vs/base/common/errors'; import { IUserDataAutoSyncEnablementService, IUserDataSyncResourceEnablementService, SyncResource } from 'vs/platform/userDataSync/common/userDataSync'; import { Promises } from 'vs/base/common/async'; -import { IWorkspaceTrustService, WorkspaceTrustState } from 'vs/platform/workspace/common/workspaceTrust'; +import { IWorkspaceTrustRequestService } from 'vs/platform/workspace/common/workspaceTrust'; +import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService'; export class ExtensionManagementService extends Disposable implements IWorkbenchExtensionManagementService { @@ -38,8 +38,6 @@ export class ExtensionManagementService extends Disposable implements IWorkbench protected readonly servers: IExtensionManagementServer[] = []; - protected readonly extensionKindController: ExtensionKindController; - constructor( @IExtensionManagementServerService protected readonly extensionManagementServerService: IExtensionManagementServerService, @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, @@ -49,7 +47,8 @@ export class ExtensionManagementService extends Disposable implements IWorkbench @IUserDataAutoSyncEnablementService private readonly userDataAutoSyncEnablementService: IUserDataAutoSyncEnablementService, @IUserDataSyncResourceEnablementService private readonly userDataSyncResourceEnablementService: IUserDataSyncResourceEnablementService, @IDialogService private readonly dialogService: IDialogService, - @IWorkspaceTrustService private readonly workspaceTrustService: IWorkspaceTrustService + @IWorkspaceTrustRequestService private readonly workspaceTrustRequestService: IWorkspaceTrustRequestService, + @IExtensionManifestPropertiesService private readonly extensionManifestPropertiesService: IExtensionManifestPropertiesService, ) { super(); if (this.extensionManagementServerService.localExtensionManagementServer) { @@ -66,8 +65,6 @@ export class ExtensionManagementService extends Disposable implements IWorkbench this.onDidInstallExtension = this._register(this.servers.reduce((emitter: EventMultiplexer, server) => { emitter.add(server.extensionManagementService.onDidInstallExtension); return emitter; }, new EventMultiplexer())).event; this.onUninstallExtension = this._register(this.servers.reduce((emitter: EventMultiplexer, server) => { emitter.add(server.extensionManagementService.onUninstallExtension); return emitter; }, new EventMultiplexer())).event; this.onDidUninstallExtension = this._register(this.servers.reduce((emitter: EventMultiplexer, server) => { emitter.add(server.extensionManagementService.onDidUninstallExtension); return emitter; }, new EventMultiplexer())).event; - - this.extensionKindController = new ExtensionKindController(productService, configurationService); } async getInstalled(type?: ExtensionType): Promise { @@ -111,7 +108,7 @@ export class ExtensionManagementService extends Disposable implements IWorkbench private async uninstallInServer(extension: ILocalExtension, server: IExtensionManagementServer, options?: UninstallOptions): Promise { if (server === this.extensionManagementServerService.localExtensionManagementServer) { const installedExtensions = await this.extensionManagementServerService.remoteExtensionManagementServer!.extensionManagementService.getInstalled(ExtensionType.User); - const dependentNonUIExtensions = installedExtensions.filter(i => !this.extensionKindController.prefersExecuteOnUI(i.manifest) + const dependentNonUIExtensions = installedExtensions.filter(i => !this.extensionManifestPropertiesService.prefersExecuteOnUI(i.manifest) && i.manifest.extensionDependencies && i.manifest.extensionDependencies.some(id => areSameExtensions({ id }, extension.identifier))); if (dependentNonUIExtensions.length) { return Promise.reject(new Error(this.getDependentsErrorMessage(extension, dependentNonUIExtensions))); @@ -182,7 +179,7 @@ export class ExtensionManagementService extends Disposable implements IWorkbench const [local] = await Promises.settled([this.extensionManagementServerService.localExtensionManagementServer, this.extensionManagementServerService.remoteExtensionManagementServer].map(server => this.installVSIX(vsix, server))); return local; } - if (this.extensionKindController.prefersExecuteOnUI(manifest)) { + if (this.extensionManifestPropertiesService.prefersExecuteOnUI(manifest)) { // Install only on local server return this.installVSIX(vsix, this.extensionManagementServerService.localExtensionManagementServer); } @@ -297,7 +294,7 @@ export class ExtensionManagementService extends Disposable implements IWorkbench return this.extensionManagementServerService.localExtensionManagementServer; } - const extensionKind = this.extensionKindController.getExtensionKind(manifest); + const extensionKind = this.extensionManifestPropertiesService.getExtensionKind(manifest); for (const kind of extensionKind) { if (kind === 'ui' && this.extensionManagementServerService.localExtensionManagementServer) { return this.extensionManagementServerService.localExtensionManagementServer; @@ -361,10 +358,22 @@ export class ExtensionManagementService extends Disposable implements IWorkbench } protected async checkForWorkspaceTrust(manifest: IExtensionManifest): Promise { - if (manifest.workspaceTrust?.required === 'onStart') { - const trustState = await this.workspaceTrustService.requireWorkspaceTrust(); - return trustState === WorkspaceTrustState.Trusted ? Promise.resolve() : Promise.reject(canceled()); + if (this.extensionManifestPropertiesService.getExtensionUntrustedWorkspaceSupportType(manifest) === false) { + const trustState = await this.workspaceTrustRequestService.requestWorkspaceTrust({ + modal: true, + message: localize('extensionInstallWorkspaceTrustMessage', "Enabling this extension requires a trusted workspace."), + buttons: [ + { label: localize('extensionInstallWorkspaceTrustButton', "Trust Workspace & Install"), type: 'ContinueWithTrust' }, + { label: localize('extensionInstallWorkspaceTrustContinueButton', "Install"), type: 'ContinueWithoutTrust' }, + { label: localize('extensionInstallWorkspaceTrustManageButton', "Learn More"), type: 'Manage' } + ] + }); + + if (trustState === undefined) { + return Promise.reject(canceled()); + } } + return Promise.resolve(); } } diff --git a/src/vs/workbench/services/extensionManagement/common/remoteExtensionManagementService.ts b/src/vs/workbench/services/extensionManagement/common/remoteExtensionManagementService.ts index a16c16fe..8b982883 100644 --- a/src/vs/workbench/services/extensionManagement/common/remoteExtensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/remoteExtensionManagementService.ts @@ -5,30 +5,27 @@ import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { IExtensionManagementService, IGalleryExtension, IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { ExtensionKindController } from 'vs/workbench/services/extensions/common/extensionsUtil'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IProductService } from 'vs/platform/product/common/productService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ExtensionManagementChannelClient } from 'vs/platform/extensionManagement/common/extensionManagementIpc'; +import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService'; export class WebRemoteExtensionManagementService extends ExtensionManagementChannelClient implements IExtensionManagementService { - protected readonly extensionKindController: ExtensionKindController; - constructor( channel: IChannel, @IExtensionGalleryService protected readonly galleryService: IExtensionGalleryService, @IConfigurationService protected readonly configurationService: IConfigurationService, - @IProductService protected readonly productService: IProductService + @IProductService protected readonly productService: IProductService, + @IExtensionManifestPropertiesService protected readonly extensionManifestPropertiesService: IExtensionManifestPropertiesService, ) { super(channel); - - this.extensionKindController = new ExtensionKindController(productService, configurationService); } - async canInstall(extension: IGalleryExtension): Promise { + override async canInstall(extension: IGalleryExtension): Promise { const manifest = await this.galleryService.getManifest(extension, CancellationToken.None); - return !!manifest && this.extensionKindController.canExecuteOnWorkspace(manifest); + return !!manifest && this.extensionManifestPropertiesService.canExecuteOnWorkspace(manifest); } } diff --git a/src/vs/workbench/services/extensionManagement/common/webExtensionsScannerService.ts b/src/vs/workbench/services/extensionManagement/common/webExtensionsScannerService.ts index e9716851..f8faa18b 100644 --- a/src/vs/workbench/services/extensionManagement/common/webExtensionsScannerService.ts +++ b/src/vs/workbench/services/extensionManagement/common/webExtensionsScannerService.ts @@ -88,7 +88,24 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten const result: IScannedExtension[] = []; for (const e of staticExtensions) { if (Boolean(e.isBuiltin) === builtin) { - const scannedExtension = this.parseStaticExtension(e, builtin); + const scannedExtension = this.parseStaticExtension(e, builtin, false); + if (scannedExtension) { + result.push(scannedExtension); + } + } + } + return result; + } + + /** + * All dev extensions + */ + private getDevExtensions(): IScannedExtension[] { + const devExtensions = this.environmentService.options?.developmentOptions?.extensions; + const result: IScannedExtension[] = []; + if (Array.isArray(devExtensions)) { + for (const e of devExtensions) { + const scannedExtension = this.parseStaticExtension(e, false, true); if (scannedExtension) { result.push(scannedExtension); } @@ -101,24 +118,26 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten const defaultUserWebExtensions = await this.readDefaultUserWebExtensions(); const extensions: IScannedExtension[] = []; for (const e of defaultUserWebExtensions) { - const scannedExtension = this.parseStaticExtension(e, false); + const scannedExtension = this.parseStaticExtension(e, false, false); if (scannedExtension) { extensions.push(scannedExtension); } } - return extensions.concat(this.getStaticExtensions(false)); + return extensions.concat(this.getStaticExtensions(false), this.getDevExtensions()); } - private parseStaticExtension(e: IStaticExtension, builtin: boolean): IScannedExtension | null { + private parseStaticExtension(e: IStaticExtension, builtin: boolean, isUnderDevelopment: boolean): IScannedExtension | null { + const extensionLocation = URI.revive(e.extensionLocation); try { return { identifier: { id: getGalleryExtensionId(e.packageJSON.publisher, e.packageJSON.name) }, - location: e.extensionLocation, + location: extensionLocation, type: builtin ? ExtensionType.System : ExtensionType.User, packageJSON: e.packageJSON, + isUnderDevelopment }; } catch (error) { - this.logService.error(`Error while parsing extension ${e.extensionLocation.toString()}`); + this.logService.error(`Error while parsing extension ${extensionLocation.toString()}`); this.logService.error(error); } return null; @@ -234,7 +253,8 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten type: scannedExtension.type, packageJSON: manifest, readmeUrl: scannedExtension.readmeUrl, - changelogUrl: scannedExtension.changelogUrl + changelogUrl: scannedExtension.changelogUrl, + isUnderDevelopment: scannedExtension.isUnderDevelopment }; } @@ -309,6 +329,7 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten readmeUrl: userExtension.readmeUri, changelogUrl: userExtension.changelogUri, packageNLSUrl: userExtension.packageNLSUri, + isUnderDevelopment: false }; } } diff --git a/src/vs/workbench/services/extensionManagement/electron-sandbox/extensionManagementService.ts b/src/vs/workbench/services/extensionManagement/electron-sandbox/extensionManagementService.ts index 3a052402..1a11ff33 100644 --- a/src/vs/workbench/services/extensionManagement/electron-sandbox/extensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/electron-sandbox/extensionManagementService.ts @@ -17,7 +17,8 @@ import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/enviro import { joinPath } from 'vs/base/common/resources'; import { IUserDataAutoSyncEnablementService, IUserDataSyncResourceEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { IWorkspaceTrustService } from 'vs/platform/workspace/common/workspaceTrust'; +import { IWorkspaceTrustRequestService } from 'vs/platform/workspace/common/workspaceTrust'; +import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService'; export class ExtensionManagementService extends BaseExtensionManagementService { @@ -31,12 +32,13 @@ export class ExtensionManagementService extends BaseExtensionManagementService { @IUserDataAutoSyncEnablementService userDataAutoSyncEnablementService: IUserDataAutoSyncEnablementService, @IUserDataSyncResourceEnablementService userDataSyncResourceEnablementService: IUserDataSyncResourceEnablementService, @IDialogService dialogService: IDialogService, - @IWorkspaceTrustService workspaceTrustService: IWorkspaceTrustService + @IWorkspaceTrustRequestService workspaceTrustRequestService: IWorkspaceTrustRequestService, + @IExtensionManifestPropertiesService extensionManifestPropertiesService: IExtensionManifestPropertiesService, ) { - super(extensionManagementServerService, extensionGalleryService, configurationService, productService, downloadService, userDataAutoSyncEnablementService, userDataSyncResourceEnablementService, dialogService, workspaceTrustService); + super(extensionManagementServerService, extensionGalleryService, configurationService, productService, downloadService, userDataAutoSyncEnablementService, userDataSyncResourceEnablementService, dialogService, workspaceTrustRequestService, extensionManifestPropertiesService); } - protected async installVSIX(vsix: URI, server: IExtensionManagementServer): Promise { + protected override async installVSIX(vsix: URI, server: IExtensionManagementServer): Promise { if (vsix.scheme === Schemas.vscodeRemote && server === this.extensionManagementServerService.localExtensionManagementServer) { const downloadedLocation = joinPath(this.environmentService.tmpDir, generateUuid()); await this.downloadService.download(vsix, downloadedLocation); diff --git a/src/vs/workbench/services/extensionManagement/electron-sandbox/extensionTipsService.ts b/src/vs/workbench/services/extensionManagement/electron-sandbox/extensionTipsService.ts index df25bdb6..35f7415d 100644 --- a/src/vs/workbench/services/extensionManagement/electron-sandbox/extensionTipsService.ts +++ b/src/vs/workbench/services/extensionManagement/electron-sandbox/extensionTipsService.ts @@ -17,7 +17,7 @@ import { Schemas } from 'vs/base/common/network'; class NativeExtensionTipsService extends ExtensionTipsService implements IExtensionTipsService { - _serviceBrand: any; + override _serviceBrand: any; private readonly channel: IChannel; @@ -32,22 +32,22 @@ class NativeExtensionTipsService extends ExtensionTipsService implements IExtens this.channel = sharedProcessService.getChannel('extensionTipsService'); } - getConfigBasedTips(folder: URI): Promise { + override getConfigBasedTips(folder: URI): Promise { if (folder.scheme === Schemas.file) { return this.channel.call('getConfigBasedTips', [folder]); } return super.getConfigBasedTips(folder); } - getImportantExecutableBasedTips(): Promise { + override getImportantExecutableBasedTips(): Promise { return this.channel.call('getImportantExecutableBasedTips'); } - getOtherExecutableBasedTips(): Promise { + override getOtherExecutableBasedTips(): Promise { return this.channel.call('getOtherExecutableBasedTips'); } - getAllWorkspacesTips(): Promise { + override getAllWorkspacesTips(): Promise { return this.channel.call('getAllWorkspacesTips'); } diff --git a/src/vs/workbench/services/extensionManagement/electron-sandbox/remoteExtensionManagementService.ts b/src/vs/workbench/services/extensionManagement/electron-sandbox/remoteExtensionManagementService.ts index 5a467603..3478bc93 100644 --- a/src/vs/workbench/services/extensionManagement/electron-sandbox/remoteExtensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/electron-sandbox/remoteExtensionManagementService.ts @@ -21,6 +21,7 @@ import { WebRemoteExtensionManagementService } from 'vs/workbench/services/exten import { IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; import { Promises } from 'vs/base/common/async'; +import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService'; export class NativeRemoteExtensionManagementService extends WebRemoteExtensionManagementService implements IExtensionManagementService { @@ -33,19 +34,20 @@ export class NativeRemoteExtensionManagementService extends WebRemoteExtensionMa @IExtensionGalleryService galleryService: IExtensionGalleryService, @IConfigurationService configurationService: IConfigurationService, @IProductService productService: IProductService, - @INativeWorkbenchEnvironmentService private readonly environmentService: INativeWorkbenchEnvironmentService + @INativeWorkbenchEnvironmentService private readonly environmentService: INativeWorkbenchEnvironmentService, + @IExtensionManifestPropertiesService extensionManifestPropertiesService: IExtensionManifestPropertiesService, ) { - super(channel, galleryService, configurationService, productService); + super(channel, galleryService, configurationService, productService, extensionManifestPropertiesService); this.localExtensionManagementService = localExtensionManagementServer.extensionManagementService; } - async install(vsix: URI): Promise { + override async install(vsix: URI): Promise { const local = await super.install(vsix); await this.installUIDependenciesAndPackedExtensions(local); return local; } - async installFromGallery(extension: IGalleryExtension, installOptions?: InstallOptions): Promise { + override async installFromGallery(extension: IGalleryExtension, installOptions?: InstallOptions): Promise { const local = await this.doInstallFromGallery(extension, installOptions); await this.installUIDependenciesAndPackedExtensions(local); return local; @@ -125,7 +127,7 @@ export class NativeRemoteExtensionManagementService extends WebRemoteExtensionMa for (let idx = 0; idx < extensions.length; idx++) { const extension = extensions[idx]; const manifest = manifests[idx]; - if (manifest && this.extensionKindController.prefersExecuteOnUI(manifest) === uiExtension) { + if (manifest && this.extensionManifestPropertiesService.prefersExecuteOnUI(manifest) === uiExtension) { result.set(extension.identifier.id.toLowerCase(), extension); extensionsManifests.push(manifest); } diff --git a/src/vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test.ts b/src/vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test.ts index 671e7fe0..49e01269 100644 --- a/src/vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test.ts +++ b/src/vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test.ts @@ -9,29 +9,31 @@ import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManage import { ExtensionEnablementService } from 'vs/workbench/services/extensionManagement/browser/extensionEnablementService'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { Emitter } from 'vs/base/common/event'; -import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; +import { IWorkspace, IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IStorageService, InMemoryStorageService } from 'vs/platform/storage/common/storage'; -import { IExtensionContributions, ExtensionType, IExtension } from 'vs/platform/extensions/common/extensions'; +import { IExtensionContributions, ExtensionType, IExtension, IExtensionManifest } from 'vs/platform/extensions/common/extensions'; import { isUndefinedOrNull } from 'vs/base/common/types'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; -import { productService, TestLifecycleService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestLifecycleService } from 'vs/workbench/test/browser/workbenchTestServices'; import { GlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionEnablementService'; import { IUserDataSyncAccountService, UserDataSyncAccountService } from 'vs/platform/userDataSync/common/userDataSyncAccount'; import { IUserDataAutoSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; -// import { IHostService } from 'vs/workbench/services/host/browser/host'; import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { mock } from 'vs/base/test/common/mock'; import { IExtensionBisectService } from 'vs/workbench/services/extensionManagement/browser/extensionBisect'; -import { IWorkspaceTrustService } from 'vs/platform/workspace/common/workspaceTrust'; -import { TestWorkspaceTrustService } from 'vs/workbench/services/workspaces/test/common/testWorkspaceTrustService'; +import { IWorkspaceTrustManagementService, IWorkspaceTrustRequestService, WorkspaceTrustRequestOptions } from 'vs/platform/workspace/common/workspaceTrust'; +import { TestWorkspaceTrustManagementService } from 'vs/workbench/services/workspaces/test/common/testWorkspaceTrustService'; +import { ExtensionManifestPropertiesService, IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService'; +import { TestContextService, TestProductService } from 'vs/workbench/test/common/workbenchTestServices'; +import { TestWorkspace } from 'vs/platform/workspace/test/common/testWorkspace'; function createStorageService(instantiationService: TestInstantiationService): IStorageService { let service = instantiationService.get(IStorageService); @@ -40,6 +42,7 @@ function createStorageService(instantiationService: TestInstantiationService): I if (!workspaceContextService) { workspaceContextService = instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.FOLDER, + getWorkspace: () => TestWorkspace as IWorkspace }); } service = instantiationService.stub(IStorageService, new InMemoryStorageService()); @@ -55,19 +58,20 @@ export class TestExtensionEnablementService extends ExtensionEnablementService { super( storageService, new GlobalExtensionEnablementService(storageService), - instantiationService.get(IWorkspaceContextService), + instantiationService.get(IWorkspaceContextService) || new TestContextService(), instantiationService.get(IWorkbenchEnvironmentService) || instantiationService.stub(IWorkbenchEnvironmentService, { configuration: Object.create(null) } as IWorkbenchEnvironmentService), extensionManagementService, instantiationService.get(IConfigurationService), extensionManagementServerService, - productService, instantiationService.get(IUserDataAutoSyncEnablementService) || instantiationService.stub(IUserDataAutoSyncEnablementService, >{ isEnabled() { return false; } }), instantiationService.get(IUserDataSyncAccountService) || instantiationService.stub(IUserDataSyncAccountService, UserDataSyncAccountService), instantiationService.get(ILifecycleService) || instantiationService.stub(ILifecycleService, new TestLifecycleService()), instantiationService.get(INotificationService) || instantiationService.stub(INotificationService, new TestNotificationService()), instantiationService.get(IHostService), - new class extends mock() { isDisabledByBisect() { return false; } }, - instantiationService.get(IWorkspaceTrustService) || instantiationService.stub(IWorkspaceTrustService, new TestWorkspaceTrustService()) + new class extends mock() { override isDisabledByBisect() { return false; } }, + instantiationService.get(IWorkspaceTrustManagementService) || instantiationService.stub(IWorkspaceTrustManagementService, new TestWorkspaceTrustManagementService()), + new class extends mock() { override requestWorkspaceTrust(options?: WorkspaceTrustRequestOptions): Promise { return Promise.resolve(true); } }, + instantiationService.get(IExtensionManifestPropertiesService) || instantiationService.stub(IExtensionManifestPropertiesService, new ExtensionManifestPropertiesService(TestProductService, new TestConfigurationService())) ); } @@ -118,7 +122,7 @@ suite('ExtensionEnablementService Test', () => { const extension = aLocalExtension('pub.a'); await testObject.setEnablement([extension], EnablementState.DisabledGlobally); assert.ok(!testObject.isEnabled(extension)); - assert.equal(testObject.getEnablementState(extension), EnablementState.DisabledGlobally); + assert.strictEqual(testObject.getEnablementState(extension), EnablementState.DisabledGlobally); }); test('test disable an extension globally should return truthy promise', () => { @@ -132,7 +136,7 @@ suite('ExtensionEnablementService Test', () => { return testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.DisabledGlobally) .then(() => { assert.ok(target.calledOnce); - assert.deepEqual((target.args[0][0][0]).identifier, { id: 'pub.a' }); + assert.deepStrictEqual((target.args[0][0][0]).identifier, { id: 'pub.a' }); }); }); @@ -144,20 +148,20 @@ suite('ExtensionEnablementService Test', () => { test('test state of globally disabled extension', () => { return testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.DisabledGlobally) - .then(() => assert.equal(testObject.getEnablementState(aLocalExtension('pub.a')), EnablementState.DisabledGlobally)); + .then(() => assert.strictEqual(testObject.getEnablementState(aLocalExtension('pub.a')), EnablementState.DisabledGlobally)); }); test('test state of globally enabled extension', () => { return testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.DisabledGlobally) .then(() => testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.EnabledGlobally)) - .then(() => assert.equal(testObject.getEnablementState(aLocalExtension('pub.a')), EnablementState.EnabledGlobally)); + .then(() => assert.strictEqual(testObject.getEnablementState(aLocalExtension('pub.a')), EnablementState.EnabledGlobally)); }); test('test disable an extension for workspace', async () => { const extension = aLocalExtension('pub.a'); await testObject.setEnablement([extension], EnablementState.DisabledWorkspace); assert.ok(!testObject.isEnabled(extension)); - assert.equal(testObject.getEnablementState(extension), EnablementState.DisabledWorkspace); + assert.strictEqual(testObject.getEnablementState(extension), EnablementState.DisabledWorkspace); }); test('test disable an extension for workspace returns a truthy promise', () => { @@ -173,59 +177,59 @@ suite('ExtensionEnablementService Test', () => { test('test state of workspace disabled extension', () => { return testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.DisabledWorkspace) - .then(() => assert.equal(testObject.getEnablementState(aLocalExtension('pub.a')), EnablementState.DisabledWorkspace)); + .then(() => assert.strictEqual(testObject.getEnablementState(aLocalExtension('pub.a')), EnablementState.DisabledWorkspace)); }); test('test state of workspace and globally disabled extension', () => { return testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.DisabledGlobally) .then(() => testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.DisabledWorkspace)) - .then(() => assert.equal(testObject.getEnablementState(aLocalExtension('pub.a')), EnablementState.DisabledWorkspace)); + .then(() => assert.strictEqual(testObject.getEnablementState(aLocalExtension('pub.a')), EnablementState.DisabledWorkspace)); }); test('test state of workspace enabled extension', () => { return testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.DisabledWorkspace) .then(() => testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.EnabledWorkspace)) - .then(() => assert.equal(testObject.getEnablementState(aLocalExtension('pub.a')), EnablementState.EnabledWorkspace)); + .then(() => assert.strictEqual(testObject.getEnablementState(aLocalExtension('pub.a')), EnablementState.EnabledWorkspace)); }); test('test state of globally disabled and workspace enabled extension', () => { return testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.DisabledGlobally) .then(() => testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.DisabledWorkspace)) .then(() => testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.EnabledWorkspace)) - .then(() => assert.equal(testObject.getEnablementState(aLocalExtension('pub.a')), EnablementState.EnabledWorkspace)); + .then(() => assert.strictEqual(testObject.getEnablementState(aLocalExtension('pub.a')), EnablementState.EnabledWorkspace)); }); test('test state of an extension when disabled for workspace from workspace enabled', () => { return testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.DisabledWorkspace) .then(() => testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.EnabledWorkspace)) .then(() => testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.DisabledWorkspace)) - .then(() => assert.equal(testObject.getEnablementState(aLocalExtension('pub.a')), EnablementState.DisabledWorkspace)); + .then(() => assert.strictEqual(testObject.getEnablementState(aLocalExtension('pub.a')), EnablementState.DisabledWorkspace)); }); test('test state of an extension when disabled globally from workspace enabled', () => { return testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.DisabledWorkspace) .then(() => testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.EnabledWorkspace)) .then(() => testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.DisabledGlobally)) - .then(() => assert.equal(testObject.getEnablementState(aLocalExtension('pub.a')), EnablementState.DisabledGlobally)); + .then(() => assert.strictEqual(testObject.getEnablementState(aLocalExtension('pub.a')), EnablementState.DisabledGlobally)); }); test('test state of an extension when disabled globally from workspace disabled', () => { return testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.DisabledWorkspace) .then(() => testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.DisabledGlobally)) - .then(() => assert.equal(testObject.getEnablementState(aLocalExtension('pub.a')), EnablementState.DisabledGlobally)); + .then(() => assert.strictEqual(testObject.getEnablementState(aLocalExtension('pub.a')), EnablementState.DisabledGlobally)); }); test('test state of an extension when enabled globally from workspace enabled', () => { return testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.DisabledWorkspace) .then(() => testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.EnabledWorkspace)) .then(() => testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.EnabledGlobally)) - .then(() => assert.equal(testObject.getEnablementState(aLocalExtension('pub.a')), EnablementState.EnabledGlobally)); + .then(() => assert.strictEqual(testObject.getEnablementState(aLocalExtension('pub.a')), EnablementState.EnabledGlobally)); }); test('test state of an extension when enabled globally from workspace disabled', () => { return testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.DisabledWorkspace) .then(() => testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.EnabledGlobally)) - .then(() => assert.equal(testObject.getEnablementState(aLocalExtension('pub.a')), EnablementState.EnabledGlobally)); + .then(() => assert.strictEqual(testObject.getEnablementState(aLocalExtension('pub.a')), EnablementState.EnabledGlobally)); }); test('test disable an extension for workspace and then globally', async () => { @@ -233,7 +237,7 @@ suite('ExtensionEnablementService Test', () => { await testObject.setEnablement([extension], EnablementState.DisabledWorkspace); await testObject.setEnablement([extension], EnablementState.DisabledGlobally); assert.ok(!testObject.isEnabled(extension)); - assert.equal(testObject.getEnablementState(extension), EnablementState.DisabledGlobally); + assert.strictEqual(testObject.getEnablementState(extension), EnablementState.DisabledGlobally); }); test('test disable an extension for workspace and then globally return a truthy promise', () => { @@ -249,7 +253,7 @@ suite('ExtensionEnablementService Test', () => { .then(() => testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.DisabledGlobally)) .then(() => { assert.ok(target.calledOnce); - assert.deepEqual((target.args[0][0][0]).identifier, { id: 'pub.a' }); + assert.deepStrictEqual((target.args[0][0][0]).identifier, { id: 'pub.a' }); }); }); @@ -258,7 +262,7 @@ suite('ExtensionEnablementService Test', () => { await testObject.setEnablement([extension], EnablementState.DisabledGlobally); await testObject.setEnablement([extension], EnablementState.DisabledWorkspace); assert.ok(!testObject.isEnabled(extension)); - assert.equal(testObject.getEnablementState(extension), EnablementState.DisabledWorkspace); + assert.strictEqual(testObject.getEnablementState(extension), EnablementState.DisabledWorkspace); }); test('test disable an extension globally and then for workspace return a truthy promise', () => { @@ -274,7 +278,7 @@ suite('ExtensionEnablementService Test', () => { .then(() => testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.DisabledWorkspace)) .then(() => { assert.ok(target.calledOnce); - assert.deepEqual((target.args[0][0][0]).identifier, { id: 'pub.a' }); + assert.deepStrictEqual((target.args[0][0][0]).identifier, { id: 'pub.a' }); }); }); @@ -289,7 +293,7 @@ suite('ExtensionEnablementService Test', () => { await testObject.setEnablement([extension], EnablementState.DisabledGlobally); await testObject.setEnablement([extension], EnablementState.EnabledGlobally); assert.ok(testObject.isEnabled(extension)); - assert.equal(testObject.getEnablementState(extension), EnablementState.EnabledGlobally); + assert.strictEqual(testObject.getEnablementState(extension), EnablementState.EnabledGlobally); }); test('test enable an extension globally return truthy promise', () => { @@ -305,7 +309,7 @@ suite('ExtensionEnablementService Test', () => { .then(() => testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.EnabledGlobally)) .then(() => { assert.ok(target.calledOnce); - assert.deepEqual((target.args[0][0][0]).identifier, { id: 'pub.a' }); + assert.deepStrictEqual((target.args[0][0][0]).identifier, { id: 'pub.a' }); }); }); @@ -319,7 +323,7 @@ suite('ExtensionEnablementService Test', () => { await testObject.setEnablement([extension], EnablementState.DisabledWorkspace); await testObject.setEnablement([extension], EnablementState.EnabledWorkspace); assert.ok(testObject.isEnabled(extension)); - assert.equal(testObject.getEnablementState(extension), EnablementState.EnabledWorkspace); + assert.strictEqual(testObject.getEnablementState(extension), EnablementState.EnabledWorkspace); }); test('test enable an extension for workspace return truthy promise', () => { @@ -335,7 +339,7 @@ suite('ExtensionEnablementService Test', () => { .then(() => testObject.setEnablement([aLocalExtension('pub.b')], EnablementState.EnabledWorkspace)) .then(() => { assert.ok(target.calledOnce); - assert.deepEqual((target.args[0][0][0]).identifier, { id: 'pub.b' }); + assert.deepStrictEqual((target.args[0][0][0]).identifier, { id: 'pub.b' }); }); }); @@ -350,7 +354,7 @@ suite('ExtensionEnablementService Test', () => { await testObject.setEnablement([extension], EnablementState.DisabledGlobally); await testObject.setEnablement([extension], EnablementState.EnabledWorkspace); assert.ok(testObject.isEnabled(extension)); - assert.equal(testObject.getEnablementState(extension), EnablementState.EnabledWorkspace); + assert.strictEqual(testObject.getEnablementState(extension), EnablementState.EnabledWorkspace); }); test('test enable an extension globally when disabled in workspace and gloablly', async () => { @@ -360,7 +364,7 @@ suite('ExtensionEnablementService Test', () => { await testObject.setEnablement([extension], EnablementState.DisabledGlobally); await testObject.setEnablement([extension], EnablementState.EnabledGlobally); assert.ok(testObject.isEnabled(extension)); - assert.equal(testObject.getEnablementState(extension), EnablementState.EnabledGlobally); + assert.strictEqual(testObject.getEnablementState(extension), EnablementState.EnabledGlobally); }); test('test remove an extension from disablement list when uninstalled', async () => { @@ -369,7 +373,7 @@ suite('ExtensionEnablementService Test', () => { await testObject.setEnablement([extension], EnablementState.DisabledGlobally); didUninstallEvent.fire({ identifier: { id: 'pub.a' } }); assert.ok(testObject.isEnabled(extension)); - assert.equal(testObject.getEnablementState(extension), EnablementState.EnabledGlobally); + assert.strictEqual(testObject.getEnablementState(extension), EnablementState.EnabledGlobally); }); test('test isEnabled return false extension is disabled globally', () => { @@ -389,11 +393,11 @@ suite('ExtensionEnablementService Test', () => { }); test('test canChangeEnablement return false for language packs', () => { - assert.equal(testObject.canChangeEnablement(aLocalExtension('pub.a', { localizations: [{ languageId: 'gr', translations: [{ id: 'vscode', path: 'path' }] }] })), false); + assert.strictEqual(testObject.canChangeEnablement(aLocalExtension('pub.a', { localizations: [{ languageId: 'gr', translations: [{ id: 'vscode', path: 'path' }] }] })), false); }); test('test canChangeEnablement return true for auth extension', () => { - assert.equal(testObject.canChangeEnablement(aLocalExtension('pub.a', { authentication: [{ id: 'a', label: 'a' }] })), true); + assert.strictEqual(testObject.canChangeEnablement(aLocalExtension('pub.a', { authentication: [{ id: 'a', label: 'a' }] })), true); }); test('test canChangeEnablement return true for auth extension when user data sync account does not depends on it', () => { @@ -401,7 +405,7 @@ suite('ExtensionEnablementService Test', () => { account: { authenticationProviderId: 'b' } }); testObject = new TestExtensionEnablementService(instantiationService); - assert.equal(testObject.canChangeEnablement(aLocalExtension('pub.a', { authentication: [{ id: 'a', label: 'a' }] })), true); + assert.strictEqual(testObject.canChangeEnablement(aLocalExtension('pub.a', { authentication: [{ id: 'a', label: 'a' }] })), true); }); test('test canChangeEnablement return true for auth extension when user data sync account depends on it but auto sync is off', () => { @@ -409,7 +413,7 @@ suite('ExtensionEnablementService Test', () => { account: { authenticationProviderId: 'a' } }); testObject = new TestExtensionEnablementService(instantiationService); - assert.equal(testObject.canChangeEnablement(aLocalExtension('pub.a', { authentication: [{ id: 'a', label: 'a' }] })), true); + assert.strictEqual(testObject.canChangeEnablement(aLocalExtension('pub.a', { authentication: [{ id: 'a', label: 'a' }] })), true); }); test('test canChangeEnablement return false for auth extension and user data sync account depends on it and auto sync is on', () => { @@ -418,39 +422,39 @@ suite('ExtensionEnablementService Test', () => { account: { authenticationProviderId: 'a' } }); testObject = new TestExtensionEnablementService(instantiationService); - assert.equal(testObject.canChangeEnablement(aLocalExtension('pub.a', { authentication: [{ id: 'a', label: 'a' }] })), false); + assert.strictEqual(testObject.canChangeEnablement(aLocalExtension('pub.a', { authentication: [{ id: 'a', label: 'a' }] })), false); }); test('test canChangeWorkspaceEnablement return true', () => { - assert.equal(testObject.canChangeWorkspaceEnablement(aLocalExtension('pub.a')), true); + assert.strictEqual(testObject.canChangeWorkspaceEnablement(aLocalExtension('pub.a')), true); }); test('test canChangeWorkspaceEnablement return false if there is no workspace', () => { instantiationService.stub(IWorkspaceContextService, 'getWorkbenchState', WorkbenchState.EMPTY); - assert.equal(testObject.canChangeWorkspaceEnablement(aLocalExtension('pub.a')), false); + assert.strictEqual(testObject.canChangeWorkspaceEnablement(aLocalExtension('pub.a')), false); }); test('test canChangeWorkspaceEnablement return false for auth extension', () => { - assert.equal(testObject.canChangeWorkspaceEnablement(aLocalExtension('pub.a', { authentication: [{ id: 'a', label: 'a' }] })), false); + assert.strictEqual(testObject.canChangeWorkspaceEnablement(aLocalExtension('pub.a', { authentication: [{ id: 'a', label: 'a' }] })), false); }); test('test canChangeEnablement return false when extensions are disabled in environment', () => { instantiationService.stub(IWorkbenchEnvironmentService, { disableExtensions: true } as IWorkbenchEnvironmentService); testObject = new TestExtensionEnablementService(instantiationService); - assert.equal(testObject.canChangeEnablement(aLocalExtension('pub.a')), false); + assert.strictEqual(testObject.canChangeEnablement(aLocalExtension('pub.a')), false); }); test('test canChangeEnablement return false when the extension is disabled in environment', () => { instantiationService.stub(IWorkbenchEnvironmentService, { disableExtensions: ['pub.a'] } as IWorkbenchEnvironmentService); testObject = new TestExtensionEnablementService(instantiationService); - assert.equal(testObject.canChangeEnablement(aLocalExtension('pub.a')), false); + assert.strictEqual(testObject.canChangeEnablement(aLocalExtension('pub.a')), false); }); test('test canChangeEnablement return true for system extensions when extensions are disabled in environment', () => { instantiationService.stub(IWorkbenchEnvironmentService, { disableExtensions: true } as IWorkbenchEnvironmentService); testObject = new TestExtensionEnablementService(instantiationService); const extension = aLocalExtension('pub.a', undefined, ExtensionType.System); - assert.equal(testObject.canChangeEnablement(extension), true); + assert.strictEqual(testObject.canChangeEnablement(extension), true); }); test('test canChangeEnablement return false for system extension when extension is disabled in environment', () => { @@ -470,7 +474,46 @@ suite('ExtensionEnablementService Test', () => { }); testObject = new TestExtensionEnablementService(instantiationService); assert.ok(!testObject.isEnabled(extension)); - assert.deepEqual(testObject.getEnablementState(extension), EnablementState.DisabledByEnvironment); + assert.deepStrictEqual(testObject.getEnablementState(extension), EnablementState.DisabledByEnvironment); + }); + + test('test extension does not support vitrual workspace is not enabled in virtual workspace', async () => { + const extension = aLocalExtension2('pub.a', { capabilities: { virtualWorkspaces: false } }); + instantiationService.stub(IWorkspaceContextService, 'getWorkspace', { folders: [{ uri: URI.file('worskapceA').with(({ scheme: 'virtual' })) }] }); + testObject = new TestExtensionEnablementService(instantiationService); + assert.ok(!testObject.isEnabled(extension)); + assert.deepStrictEqual(testObject.getEnablementState(extension), EnablementState.DisabledByVirtualWorkspace); + }); + + test('test canChangeEnablement return false when extension is disabled in virtual workspace', () => { + const extension = aLocalExtension2('pub.a', { capabilities: { virtualWorkspaces: false } }); + instantiationService.stub(IWorkspaceContextService, 'getWorkspace', { folders: [{ uri: URI.file('worskapceA').with(({ scheme: 'virtual' })) }] }); + testObject = new TestExtensionEnablementService(instantiationService); + assert.ok(!testObject.canChangeEnablement(extension)); + }); + + test('test extension does not support vitrual workspace is enabled in virtual workspace', async () => { + const extension = aLocalExtension2('pub.a', { capabilities: { virtualWorkspaces: false } }); + instantiationService.stub(IWorkspaceContextService, 'getWorkspace', { folders: [{ uri: URI.file('worskapceA') }] }); + testObject = new TestExtensionEnablementService(instantiationService); + assert.ok(testObject.isEnabled(extension)); + assert.deepStrictEqual(testObject.getEnablementState(extension), EnablementState.EnabledGlobally); + }); + + test('test extension supports virtual workspace is enabled in virtual workspace', async () => { + const extension = aLocalExtension2('pub.a', { capabilities: { virtualWorkspaces: true } }); + instantiationService.stub(IWorkspaceContextService, 'getWorkspace', { folders: [{ uri: URI.file('worskapceA').with(({ scheme: 'virtual' })) }] }); + testObject = new TestExtensionEnablementService(instantiationService); + assert.ok(testObject.isEnabled(extension)); + assert.deepStrictEqual(testObject.getEnablementState(extension), EnablementState.EnabledGlobally); + }); + + test('test extension without any value for virtual worksapce is enabled in virtual workspace', async () => { + const extension = aLocalExtension2('pub.a'); + instantiationService.stub(IWorkspaceContextService, 'getWorkspace', { folders: [{ uri: URI.file('worskapceA').with(({ scheme: 'virtual' })) }] }); + testObject = new TestExtensionEnablementService(instantiationService); + assert.ok(testObject.isEnabled(extension)); + assert.deepStrictEqual(testObject.getEnablementState(extension), EnablementState.EnabledGlobally); }); test('test local workspace extension is disabled by kind', async () => { @@ -478,7 +521,7 @@ suite('ExtensionEnablementService Test', () => { const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['workspace'] }, { location: URI.file(`pub.a`) }); testObject = new TestExtensionEnablementService(instantiationService); assert.ok(!testObject.isEnabled(localWorkspaceExtension)); - assert.deepEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.DisabledByExtensionKind); + assert.deepStrictEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.DisabledByExtensionKind); }); test('test local workspace + ui extension is enabled by kind', async () => { @@ -486,7 +529,7 @@ suite('ExtensionEnablementService Test', () => { const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['workspace', 'ui'] }, { location: URI.file(`pub.a`) }); testObject = new TestExtensionEnablementService(instantiationService); assert.ok(testObject.isEnabled(localWorkspaceExtension)); - assert.deepEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.EnabledGlobally); + assert.deepStrictEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.EnabledGlobally); }); test('test local ui extension is not disabled by kind', async () => { @@ -494,21 +537,21 @@ suite('ExtensionEnablementService Test', () => { const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`) }); testObject = new TestExtensionEnablementService(instantiationService); assert.ok(testObject.isEnabled(localWorkspaceExtension)); - assert.deepEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.EnabledGlobally); + assert.deepStrictEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.EnabledGlobally); }); test('test canChangeEnablement return false when the local workspace extension is disabled by kind', () => { instantiationService.stub(IExtensionManagementServerService, aMultiExtensionManagementServerService(instantiationService)); const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['workspace'] }, { location: URI.file(`pub.a`) }); testObject = new TestExtensionEnablementService(instantiationService); - assert.equal(testObject.canChangeEnablement(localWorkspaceExtension), false); + assert.strictEqual(testObject.canChangeEnablement(localWorkspaceExtension), false); }); test('test canChangeEnablement return true for local ui extension', () => { instantiationService.stub(IExtensionManagementServerService, aMultiExtensionManagementServerService(instantiationService)); const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`) }); testObject = new TestExtensionEnablementService(instantiationService); - assert.equal(testObject.canChangeEnablement(localWorkspaceExtension), true); + assert.strictEqual(testObject.canChangeEnablement(localWorkspaceExtension), true); }); test('test remote ui extension is disabled by kind', async () => { @@ -516,7 +559,7 @@ suite('ExtensionEnablementService Test', () => { const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); testObject = new TestExtensionEnablementService(instantiationService); assert.ok(!testObject.isEnabled(localWorkspaceExtension)); - assert.deepEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.DisabledByExtensionKind); + assert.deepStrictEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.DisabledByExtensionKind); }); test('test remote ui+workspace extension is disabled by kind', async () => { @@ -524,7 +567,7 @@ suite('ExtensionEnablementService Test', () => { const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['ui', 'workspace'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); testObject = new TestExtensionEnablementService(instantiationService); assert.ok(testObject.isEnabled(localWorkspaceExtension)); - assert.deepEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.EnabledGlobally); + assert.deepStrictEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.EnabledGlobally); }); test('test remote ui extension is disabled by kind when there is no local server', async () => { @@ -532,7 +575,7 @@ suite('ExtensionEnablementService Test', () => { const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); testObject = new TestExtensionEnablementService(instantiationService); assert.ok(!testObject.isEnabled(localWorkspaceExtension)); - assert.deepEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.DisabledByExtensionKind); + assert.deepStrictEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.DisabledByExtensionKind); }); test('test remote workspace extension is not disabled by kind', async () => { @@ -540,21 +583,21 @@ suite('ExtensionEnablementService Test', () => { const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['workspace'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); testObject = new TestExtensionEnablementService(instantiationService); assert.ok(testObject.isEnabled(localWorkspaceExtension)); - assert.deepEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.EnabledGlobally); + assert.deepStrictEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.EnabledGlobally); }); test('test canChangeEnablement return false when the remote ui extension is disabled by kind', () => { instantiationService.stub(IExtensionManagementServerService, aMultiExtensionManagementServerService(instantiationService)); const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); testObject = new TestExtensionEnablementService(instantiationService); - assert.equal(testObject.canChangeEnablement(localWorkspaceExtension), false); + assert.strictEqual(testObject.canChangeEnablement(localWorkspaceExtension), false); }); test('test canChangeEnablement return true for remote workspace extension', () => { instantiationService.stub(IExtensionManagementServerService, aMultiExtensionManagementServerService(instantiationService)); const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['workspace'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); testObject = new TestExtensionEnablementService(instantiationService); - assert.equal(testObject.canChangeEnablement(localWorkspaceExtension), true); + assert.strictEqual(testObject.canChangeEnablement(localWorkspaceExtension), true); }); test('test web extension on local server is disabled by kind when web worker is not enabled', async () => { @@ -562,8 +605,8 @@ suite('ExtensionEnablementService Test', () => { const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['web'] }, { location: URI.file(`pub.a`) }); (instantiationService.get(IConfigurationService)).setUserConfiguration('extensions', { webWorker: false }); testObject = new TestExtensionEnablementService(instantiationService); - assert.equal(testObject.isEnabled(localWorkspaceExtension), false); - assert.deepEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.DisabledByExtensionKind); + assert.strictEqual(testObject.isEnabled(localWorkspaceExtension), false); + assert.deepStrictEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.DisabledByExtensionKind); }); test('test web extension on local server is not disabled by kind when web worker is enabled', async () => { @@ -571,8 +614,8 @@ suite('ExtensionEnablementService Test', () => { const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['web'] }, { location: URI.file(`pub.a`) }); (instantiationService.get(IConfigurationService)).setUserConfiguration('extensions', { webWorker: true }); testObject = new TestExtensionEnablementService(instantiationService); - assert.equal(testObject.isEnabled(localWorkspaceExtension), true); - assert.deepEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.EnabledGlobally); + assert.strictEqual(testObject.isEnabled(localWorkspaceExtension), true); + assert.deepStrictEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.EnabledGlobally); }); test('test web extension on remote server is disabled by kind when web worker is not enabled', async () => { @@ -580,8 +623,8 @@ suite('ExtensionEnablementService Test', () => { const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['web'] }, { location: URI.file(`pub.a`).with({ scheme: 'vscode-remote' }) }); (instantiationService.get(IConfigurationService)).setUserConfiguration('extensions', { webWorker: false }); testObject = new TestExtensionEnablementService(instantiationService); - assert.equal(testObject.isEnabled(localWorkspaceExtension), false); - assert.deepEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.DisabledByExtensionKind); + assert.strictEqual(testObject.isEnabled(localWorkspaceExtension), false); + assert.deepStrictEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.DisabledByExtensionKind); }); test('test web extension on remote server is disabled by kind when web worker is enabled', async () => { @@ -589,16 +632,16 @@ suite('ExtensionEnablementService Test', () => { const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['web'] }, { location: URI.file(`pub.a`).with({ scheme: 'vscode-remote' }) }); (instantiationService.get(IConfigurationService)).setUserConfiguration('extensions', { webWorker: true }); testObject = new TestExtensionEnablementService(instantiationService); - assert.equal(testObject.isEnabled(localWorkspaceExtension), false); - assert.deepEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.DisabledByExtensionKind); + assert.strictEqual(testObject.isEnabled(localWorkspaceExtension), false); + assert.deepStrictEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.DisabledByExtensionKind); }); test('test web extension on web server is not disabled by kind', async () => { instantiationService.stub(IExtensionManagementServerService, anExtensionManagementServerService(anExtensionManagementServer('vscode-local', instantiationService), anExtensionManagementServer('vscode-remote', instantiationService), anExtensionManagementServer('web', instantiationService))); const webExtension = aLocalExtension2('pub.a', { extensionKind: ['web'] }, { location: URI.file(`pub.a`).with({ scheme: 'web' }) }); testObject = new TestExtensionEnablementService(instantiationService); - assert.equal(testObject.isEnabled(webExtension), true); - assert.deepEqual(testObject.getEnablementState(webExtension), EnablementState.EnabledGlobally); + assert.strictEqual(testObject.isEnabled(webExtension), true); + assert.deepStrictEqual(testObject.getEnablementState(webExtension), EnablementState.EnabledGlobally); }); }); @@ -639,7 +682,7 @@ function aLocalExtension(id: string, contributes?: IExtensionContributions, type return aLocalExtension2(id, contributes ? { contributes } : {}, isUndefinedOrNull(type) ? {} : { type }); } -function aLocalExtension2(id: string, manifest: any = {}, properties: any = {}): ILocalExtension { +function aLocalExtension2(id: string, manifest: Partial = {}, properties: any = {}): ILocalExtension { const [publisher, name] = id.split('.'); manifest = { name, publisher, ...manifest }; properties = { diff --git a/src/vs/workbench/services/extensions/browser/extensionService.ts b/src/vs/workbench/services/extensions/browser/extensionService.ts index 996ec543..837f6447 100644 --- a/src/vs/workbench/services/extensions/browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/browser/extensionService.ts @@ -13,7 +13,7 @@ import { IExtensionService, IExtensionHost } from 'vs/workbench/services/extensi import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IFileService } from 'vs/platform/files/common/files'; import { IProductService } from 'vs/platform/product/common/productService'; -import { AbstractExtensionService, ExtensionRunningLocation, ExtensionRunningLocationClassifier, parseScannedExtension } from 'vs/workbench/services/extensions/common/abstractExtensionService'; +import { AbstractExtensionService, ExtensionRunningLocation, ExtensionRunningLocationClassifier, ExtensionRunningPreference, parseScannedExtension } from 'vs/workbench/services/extensions/common/abstractExtensionService'; import { RemoteExtensionHost, IRemoteExtensionHostDataProvider, IRemoteExtensionHostInitData } from 'vs/workbench/services/extensions/common/remoteExtensionHost'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { WebWorkerExtensionHost } from 'vs/workbench/services/extensions/browser/webWorkerExtensionHost'; @@ -26,6 +26,8 @@ import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remot import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService'; +import { IUserDataInitializationService } from 'vs/workbench/services/userData/browser/userDataInit'; export class ExtensionService extends AbstractExtensionService implements IExtensionService { @@ -47,11 +49,13 @@ export class ExtensionService extends AbstractExtensionService implements IExten @IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService, @IWebExtensionsScannerService private readonly _webExtensionsScannerService: IWebExtensionsScannerService, @ILifecycleService private readonly _lifecycleService: ILifecycleService, + @IExtensionManifestPropertiesService extensionManifestPropertiesService: IExtensionManifestPropertiesService, + @IUserDataInitializationService private readonly _userDataInitializationService: IUserDataInitializationService, ) { super( new ExtensionRunningLocationClassifier( (extension) => this._getExtensionKind(extension), - (extensionKinds, isInstalledLocally, isInstalledRemotely) => ExtensionService.pickRunningLocation(extensionKinds, isInstalledLocally, isInstalledRemotely) + (extensionKinds, isInstalledLocally, isInstalledRemotely, preference) => ExtensionService.pickRunningLocation(extensionKinds, isInstalledLocally, isInstalledRemotely, preference) ), instantiationService, notificationService, @@ -63,17 +67,21 @@ export class ExtensionService extends AbstractExtensionService implements IExten extensionManagementService, contextService, configurationService, + extensionManifestPropertiesService ); this._runningLocation = new Map(); - // Initialize only after workbench is ready - this._lifecycleService.when(LifecyclePhase.Ready).then(() => this._initialize()); + // Initialize installed extensions first and do it only after workbench is ready + this._lifecycleService.when(LifecyclePhase.Ready).then(async () => { + await this._userDataInitializationService.initializeInstalledExtensions(this._instantiationService); + this._initialize(); + }); this._initFetchFileSystem(); } - dispose(): void { + override dispose(): void { this._disposables.dispose(); super.dispose(); } @@ -120,23 +128,39 @@ export class ExtensionService extends AbstractExtensionService implements IExten }; } - public static pickRunningLocation(extensionKinds: ExtensionKind[], isInstalledLocally: boolean, isInstalledRemotely: boolean): ExtensionRunningLocation { + public static pickRunningLocation(extensionKinds: ExtensionKind[], isInstalledLocally: boolean, isInstalledRemotely: boolean, preference: ExtensionRunningPreference): ExtensionRunningLocation { + const result: ExtensionRunningLocation[] = []; let canRunRemotely = false; for (const extensionKind of extensionKinds) { if (extensionKind === 'ui' && isInstalledRemotely) { // ui extensions run remotely if possible (but only as a last resort) - canRunRemotely = true; + if (preference === ExtensionRunningPreference.Remote) { + return ExtensionRunningLocation.Remote; + } else { + canRunRemotely = true; + } } if (extensionKind === 'workspace' && isInstalledRemotely) { // workspace extensions run remotely if possible - return ExtensionRunningLocation.Remote; + if (preference === ExtensionRunningPreference.None || preference === ExtensionRunningPreference.Remote) { + return ExtensionRunningLocation.Remote; + } else { + result.push(ExtensionRunningLocation.Remote); + } } if (extensionKind === 'web' && isInstalledLocally) { // web worker extensions run in the local web worker if possible - return ExtensionRunningLocation.LocalWebWorker; + if (preference === ExtensionRunningPreference.None || preference === ExtensionRunningPreference.Local) { + return ExtensionRunningLocation.LocalWebWorker; + } else { + result.push(ExtensionRunningLocation.LocalWebWorker); + } } } - return (canRunRemotely ? ExtensionRunningLocation.Remote : ExtensionRunningLocation.None); + if (canRunRemotely) { + result.push(ExtensionRunningLocation.Remote); + } + return (result.length > 0 ? result[0] : ExtensionRunningLocation.None); } protected _createExtensionHosts(_isInitialStart: boolean): IExtensionHost[] { @@ -194,7 +218,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten public _onExtensionHostExit(code: number): void { // Dispose everything associated with the extension host - this._stopExtensionHosts(); + this.stopExtensionHosts(); // We log the exit code to the console. Do NOT remove this // code as the automated integration tests in browser rely diff --git a/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts b/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts index 681e77fa..fee40e28 100644 --- a/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts +++ b/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts @@ -325,7 +325,7 @@ export class WebWorkerExtensionHost extends Disposable implements IExtensionHost return protocol; } - public dispose(): void { + public override dispose(): void { if (this._isTerminating) { return; } diff --git a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts index 2e7b71e2..8fe00fa6 100644 --- a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts +++ b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts @@ -29,10 +29,10 @@ import { ExtensionActivationReason } from 'vs/workbench/api/common/extHostExtens import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IExtensionActivationHost as IWorkspaceContainsActivationHost, checkGlobFileExists, checkActivateWorkspaceContainsExtension } from 'vs/workbench/api/common/shared/workspaceContains'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { ExtensionKindController } from 'vs/workbench/services/extensions/common/extensionsUtil'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; +import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService'; const hasOwnProperty = Object.hasOwnProperty; const NO_OP_VOID_PROMISE = Promise.resolve(undefined); @@ -42,7 +42,7 @@ export function parseScannedExtension(extension: ITranslatedScannedExtension): I identifier: new ExtensionIdentifier(`${extension.packageJSON.publisher}.${extension.packageJSON.name}`), isBuiltin: extension.type === ExtensionType.System, isUserBuiltin: false, - isUnderDevelopment: false, + isUnderDevelopment: extension.isUnderDevelopment, extensionLocation: extension.location, ...extension.packageJSON, }; @@ -62,6 +62,12 @@ export const enum ExtensionRunningLocation { Remote } +export const enum ExtensionRunningPreference { + None, + Local, + Remote +} + export abstract class AbstractExtensionService extends Disposable implements IExtensionService { public _serviceBrand: undefined; @@ -72,7 +78,7 @@ export abstract class AbstractExtensionService extends Disposable implements IEx protected readonly _onDidChangeExtensionsStatus: Emitter = this._register(new Emitter()); public readonly onDidChangeExtensionsStatus: Event = this._onDidChangeExtensionsStatus.event; - protected readonly _onDidChangeExtensions: Emitter = this._register(new Emitter()); + protected readonly _onDidChangeExtensions: Emitter = this._register(new Emitter({ leakWarningThreshold: 400 })); public readonly onDidChangeExtensions: Event = this._onDidChangeExtensions.event; protected readonly _onWillActivateByEvent = this._register(new Emitter()); @@ -89,8 +95,11 @@ export abstract class AbstractExtensionService extends Disposable implements IEx private readonly _proposedApiController: ProposedApiController; private readonly _isExtensionDevHost: boolean; protected readonly _isExtensionDevTestFromCli: boolean; + private _deltaExtensionsQueue: DeltaExtensionsQueueItem[]; private _inHandleDeltaExtensions: boolean; + private readonly _onDidFinishHandleDeltaExtensions = this._register(new Emitter()); + protected _runningLocation: Map; // --- Members used per extension host process @@ -99,8 +108,6 @@ export abstract class AbstractExtensionService extends Disposable implements IEx private _extensionHostActivationTimes: Map; private _extensionHostExtensionRuntimeErrors: Map; - private readonly _extensionKindController: ExtensionKindController; - constructor( protected readonly _runningLocationClassifier: ExtensionRunningLocationClassifier, @IInstantiationService protected readonly _instantiationService: IInstantiationService, @@ -113,6 +120,7 @@ export abstract class AbstractExtensionService extends Disposable implements IEx @IExtensionManagementService protected readonly _extensionManagementService: IExtensionManagementService, @IWorkspaceContextService private readonly _contextService: IWorkspaceContextService, @IConfigurationService protected readonly _configurationService: IConfigurationService, + @IExtensionManifestPropertiesService protected readonly _extensionManifestPropertiesService: IExtensionManifestPropertiesService, ) { super(); @@ -138,9 +146,8 @@ export abstract class AbstractExtensionService extends Disposable implements IEx this._deltaExtensionsQueue = []; this._inHandleDeltaExtensions = false; - this._runningLocation = new Map(); - this._extensionKindController = new ExtensionKindController(this._productService, this._configurationService); + this._runningLocation = new Map(); this._register(this._extensionEnablementService.onEnablementChanged((extensions) => { let toAdd: IExtension[] = []; @@ -179,7 +186,7 @@ export abstract class AbstractExtensionService extends Disposable implements IEx return this._environmentService.extensionDevelopmentKind; } - return this._extensionKindController.getExtensionKind(extensionDescription); + return this._extensionManifestPropertiesService.getExtensionKind(extensionDescription); } protected _getExtensionHostManager(kind: ExtensionHostKind): ExtensionHostManager | null { @@ -209,6 +216,8 @@ export abstract class AbstractExtensionService extends Disposable implements IEx this._inHandleDeltaExtensions = false; } } + + this._onDidFinishHandleDeltaExtensions.fire(); } private async _deltaExtensions(_toAdd: IExtension[], _toRemove: string[]): Promise { @@ -292,7 +301,7 @@ export abstract class AbstractExtensionService extends Disposable implements IEx for (const extension of toAdd) { const extensionKind = this._getExtensionKind(extension); const isRemote = extension.extensionLocation.scheme === Schemas.vscodeRemote; - const runningLocation = this._runningLocationClassifier.pickRunningLocation(extensionKind, !isRemote, isRemote); + const runningLocation = this._runningLocationClassifier.pickRunningLocation(extensionKind, !isRemote, isRemote, ExtensionRunningPreference.None); this._runningLocation.set(ExtensionIdentifier.toKey(extension.identifier), runningLocation); } groupAdd(ExtensionHostKind.LocalProcess, ExtensionRunningLocation.LocalProcess); @@ -329,7 +338,7 @@ export abstract class AbstractExtensionService extends Disposable implements IEx const extensionKind = this._getExtensionKind(extension); const isRemote = extension.extensionLocation.scheme === Schemas.vscodeRemote; - const runningLocation = this._runningLocationClassifier.pickRunningLocation(extensionKind, !isRemote, isRemote); + const runningLocation = this._runningLocationClassifier.pickRunningLocation(extensionKind, !isRemote, isRemote, ExtensionRunningPreference.None); if (runningLocation === ExtensionRunningLocation.None) { return false; } @@ -425,7 +434,7 @@ export abstract class AbstractExtensionService extends Disposable implements IEx } private async _handleExtensionTests(): Promise { - if (!this._environmentService.extensionDevelopmentLocationURI || !this._environmentService.extensionTestsLocationURI) { + if (!this._environmentService.isExtensionDevelopment || !this._environmentService.extensionTestsLocationURI) { return; } @@ -490,7 +499,9 @@ export abstract class AbstractExtensionService extends Disposable implements IEx this._onDidChangeExtensionsStatus.fire(this._registry.getAllExtensionDescriptions().map(e => e.identifier)); } - protected _stopExtensionHosts(): void { + //#region Stopping / Starting / Restarting + + public stopExtensionHosts(): void { let previouslyActivatedExtensionIds: ExtensionIdentifier[] = []; this._extensionHostActiveExtensions.forEach((value) => { previouslyActivatedExtensionIds.push(value); @@ -510,8 +521,6 @@ export abstract class AbstractExtensionService extends Disposable implements IEx } private _startExtensionHosts(isInitialStart: boolean, initialActivationEvents: string[]): void { - this._stopExtensionHosts(); - const extensionHosts = this._createExtensionHosts(isInitialStart); extensionHosts.forEach((extensionHost) => { const processManager = this._instantiationService.createInstance(ExtensionHostManager, extensionHost, initialActivationEvents); @@ -535,7 +544,7 @@ export abstract class AbstractExtensionService extends Disposable implements IEx protected _onExtensionHostCrashed(extensionHost: ExtensionHostManager, code: number, signal: string | null): void { console.error('Extension host terminated unexpectedly. Code: ', code, ' Signal: ', signal); if (extensionHost.kind === ExtensionHostKind.LocalProcess) { - this._stopExtensionHosts(); + this.stopExtensionHosts(); } else if (extensionHost.kind === ExtensionHostKind.Remote) { for (let i = 0; i < this._extensionHostManagers.length; i++) { if (this._extensionHostManagers[i] === extensionHost) { @@ -547,17 +556,25 @@ export abstract class AbstractExtensionService extends Disposable implements IEx } } + public async startExtensionHosts(): Promise { + this.stopExtensionHosts(); + + if (this._inHandleDeltaExtensions) { + await Event.toPromise(this._onDidFinishHandleDeltaExtensions.event); + } + + this._startExtensionHosts(false, Array.from(this._allRequestedActivateEvents.keys())); + } + + public async restartExtensionHost(): Promise { + this.stopExtensionHosts(); + await this.startExtensionHosts(); + } + + //#endregion + //#region IExtensionService - public restartExtensionHost(): void { - this._stopExtensionHosts(); - this._startExtensionHosts(false, Array.from(this._allRequestedActivateEvents.keys())); - } - - protected startExtensionHost(): void { - this._startExtensionHosts(false, Array.from(this._allRequestedActivateEvents.keys())); - } - public activateByEvent(activationEvent: string, activationKind: ActivationKind = ActivationKind.Normal): Promise { if (this._installedExtensionsReady.isOpen()) { // Extensions have been scanned and interpreted @@ -671,23 +688,8 @@ export abstract class AbstractExtensionService extends Disposable implements IEx return extensions.filter(extension => this._isEnabled(extension)); } - private _isExtensionUnderDevelopment(extension: IExtensionDescription): boolean { - if (this._environmentService.isExtensionDevelopment) { - const extDevLocs = this._environmentService.extensionDevelopmentLocationURI; - if (extDevLocs) { - const extLocation = extension.extensionLocation; - for (let p of extDevLocs) { - if (isEqualOrParent(extLocation, p)) { - return true; - } - } - } - } - return false; - } - protected _isEnabled(extension: IExtensionDescription): boolean { - if (this._isExtensionUnderDevelopment(extension)) { + if (extension.isUnderDevelopment) { // Never disable extensions under development return true; } @@ -803,7 +805,7 @@ export abstract class AbstractExtensionService extends Disposable implements IEx //#region Called by extension host - public _logOrShowMessage(severity: Severity, msg: string): void { + protected _logOrShowMessage(severity: Severity, msg: string): void { if (this._isDev) { this._showMessageToUser(severity, msg); } else { @@ -830,6 +832,21 @@ export abstract class AbstractExtensionService extends Disposable implements IEx this._onDidChangeExtensionsStatus.fire([extensionId]); } + public _onDidActivateExtensionError(extensionId: ExtensionIdentifier, error: Error): void { + type ExtensionActivationErrorClassification = { + extensionId: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth' }; + error: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth' }; + }; + type ExtensionActivationErrorEvent = { + extensionId: string; + error: string; + }; + this._telemetryService.publicLog2('extensionActivationError', { + extensionId: extensionId.value, + error: error.message + }); + } + public _onExtensionRuntimeError(extensionId: ExtensionIdentifier, err: Error): void { const extensionKey = ExtensionIdentifier.toKey(extensionId); if (!this._extensionHostExtensionRuntimeErrors.has(extensionKey)) { @@ -850,7 +867,7 @@ export abstract class AbstractExtensionService extends Disposable implements IEx export class ExtensionRunningLocationClassifier { constructor( public readonly getExtensionKind: (extensionDescription: IExtensionDescription) => ExtensionKind[], - public readonly pickRunningLocation: (extensionKinds: ExtensionKind[], isInstalledLocally: boolean, isInstalledRemotely: boolean) => ExtensionRunningLocation, + public readonly pickRunningLocation: (extensionKinds: ExtensionKind[], isInstalledLocally: boolean, isInstalledRemotely: boolean, preference: ExtensionRunningPreference) => ExtensionRunningLocation, ) { } @@ -862,19 +879,44 @@ export class ExtensionRunningLocationClassifier { const localExtensionsSet = new Set(); localExtensions.forEach(ext => localExtensionsSet.add(ExtensionIdentifier.toKey(ext.identifier))); + const localUnderDevelopmentExtensionsSet = new Set(); + localExtensions.forEach((ext) => { + if (ext.isUnderDevelopment) { + localUnderDevelopmentExtensionsSet.add(ExtensionIdentifier.toKey(ext.identifier)); + } + }); + const remoteExtensionsSet = new Set(); remoteExtensions.forEach(ext => remoteExtensionsSet.add(ExtensionIdentifier.toKey(ext.identifier))); - const pickRunningLocation = (extension: IExtensionDescription): ExtensionRunningLocation => { - const isInstalledLocally = localExtensionsSet.has(ExtensionIdentifier.toKey(extension.identifier)); - const isInstalledRemotely = remoteExtensionsSet.has(ExtensionIdentifier.toKey(extension.identifier)); - const extensionKinds = allExtensionKinds.get(ExtensionIdentifier.toKey(extension.identifier)) || []; - return this.pickRunningLocation(extensionKinds, isInstalledLocally, isInstalledRemotely); + const remoteUnderDevelopmentExtensionsSet = new Set(); + remoteExtensions.forEach((ext) => { + if (ext.isUnderDevelopment) { + remoteUnderDevelopmentExtensionsSet.add(ExtensionIdentifier.toKey(ext.identifier)); + } + }); + + const pickRunningLocation = (extensionIdentifier: ExtensionIdentifier): ExtensionRunningLocation => { + const isInstalledLocally = localExtensionsSet.has(ExtensionIdentifier.toKey(extensionIdentifier)); + const isInstalledRemotely = remoteExtensionsSet.has(ExtensionIdentifier.toKey(extensionIdentifier)); + + const isLocallyUnderDevelopment = localUnderDevelopmentExtensionsSet.has(ExtensionIdentifier.toKey(extensionIdentifier)); + const isRemotelyUnderDevelopment = remoteUnderDevelopmentExtensionsSet.has(ExtensionIdentifier.toKey(extensionIdentifier)); + + let preference = ExtensionRunningPreference.None; + if (isLocallyUnderDevelopment && !isRemotelyUnderDevelopment) { + preference = ExtensionRunningPreference.Local; + } else if (isRemotelyUnderDevelopment && !isLocallyUnderDevelopment) { + preference = ExtensionRunningPreference.Remote; + } + + const extensionKinds = allExtensionKinds.get(ExtensionIdentifier.toKey(extensionIdentifier)) || []; + return this.pickRunningLocation(extensionKinds, isInstalledLocally, isInstalledRemotely, preference); }; const runningLocation = new Map(); - localExtensions.forEach(ext => runningLocation.set(ExtensionIdentifier.toKey(ext.identifier), pickRunningLocation(ext))); - remoteExtensions.forEach(ext => runningLocation.set(ExtensionIdentifier.toKey(ext.identifier), pickRunningLocation(ext))); + localExtensions.forEach(ext => runningLocation.set(ExtensionIdentifier.toKey(ext.identifier), pickRunningLocation(ext.identifier))); + remoteExtensions.forEach(ext => runningLocation.set(ExtensionIdentifier.toKey(ext.identifier), pickRunningLocation(ext.identifier))); return runningLocation; } } @@ -894,7 +936,7 @@ class ProposedApiController { this.enableProposedApiForAll = !_environmentService.isBuilt || // always allow proposed API when running out of sources - (!!_environmentService.extensionDevelopmentLocationURI && productService.quality !== 'stable') || // do not allow proposed API against stable builds when developing an extension + (_environmentService.isExtensionDevelopment && productService.quality !== 'stable') || // do not allow proposed API against stable builds when developing an extension (this.enableProposedApiFor.length === 0 && Array.isArray(_environmentService.extensionEnabledProposedApi)); // always allow proposed API if --enable-proposed-api is provided without extension ID this.productAllowProposedApi = new Set(); diff --git a/src/vs/workbench/services/extensions/common/extensionHostManager.ts b/src/vs/workbench/services/extensions/common/extensionHostManager.ts index 41e5a222..b363ff4c 100644 --- a/src/vs/workbench/services/extensions/common/extensionHostManager.ts +++ b/src/vs/workbench/services/extensions/common/extensionHostManager.ts @@ -71,7 +71,7 @@ export class ExtensionHostManager extends Disposable { return { value: this._createExtensionHostCustomers(protocol) }; }, (err) => { - console.error('Error received from starting extension host'); + console.error(`Error received from starting extension host (kind: ${this.kind})`); console.error(err); return null; } @@ -85,7 +85,7 @@ export class ExtensionHostManager extends Disposable { this._resolveAuthorityAttempt = 0; } - public dispose(): void { + public override dispose(): void { if (this._extensionHost) { this._extensionHost.dispose(); } diff --git a/src/vs/workbench/services/extensions/common/extensionManifestPropertiesService.ts b/src/vs/workbench/services/extensions/common/extensionManifestPropertiesService.ts new file mode 100644 index 00000000..913b0082 --- /dev/null +++ b/src/vs/workbench/services/extensions/common/extensionManifestPropertiesService.ts @@ -0,0 +1,312 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IExtensionManifest, ExtensionKind, ExtensionIdentifier, ExtensionUntrustedWorkpaceSupportType } from 'vs/platform/extensions/common/extensions'; +import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; +import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; +import { isNonEmptyArray } from 'vs/base/common/arrays'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { ExtensionUntrustedWorkspaceSupport } from 'vs/base/common/product'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { isWorkspaceTrustEnabled, WORKSPACE_TRUST_EXTENSION_SUPPORT } from 'vs/workbench/services/workspaces/common/workspaceTrust'; + +export const IExtensionManifestPropertiesService = createDecorator('extensionManifestPropertiesService'); + +export interface IExtensionManifestPropertiesService { + readonly _serviceBrand: undefined; + + prefersExecuteOnUI(manifest: IExtensionManifest): boolean; + prefersExecuteOnWorkspace(manifest: IExtensionManifest): boolean; + prefersExecuteOnWeb(manifest: IExtensionManifest): boolean; + + canExecuteOnUI(manifest: IExtensionManifest): boolean; + canExecuteOnWorkspace(manifest: IExtensionManifest): boolean; + canExecuteOnWeb(manifest: IExtensionManifest): boolean; + + getExtensionKind(manifest: IExtensionManifest): ExtensionKind[]; + getExtensionUntrustedWorkspaceSupportType(manifest: IExtensionManifest): ExtensionUntrustedWorkpaceSupportType; + canSupportVirtualWorkspace(manifest: IExtensionManifest): boolean; +} + +export class ExtensionManifestPropertiesService extends Disposable implements IExtensionManifestPropertiesService { + + readonly _serviceBrand: undefined; + + private _uiExtensionPoints: Set | null = null; + private _productExtensionKindsMap: Map | null = null; + private _configuredExtensionKindsMap: Map | null = null; + + private _productVirtualWorkspaceSupportMap: Map | null = null; + private _configuredVirtualWorkspaceSupportMap: Map | null = null; + + private readonly _configuredExtensionWorkspaceTrustRequestMap: Map; + private readonly _productExtensionWorkspaceTrustRequestMap: Map; + + constructor( + @IProductService private readonly productService: IProductService, + @IConfigurationService private readonly configurationService: IConfigurationService, + ) { + super(); + + // Workspace trust request type (settings.json) + this._configuredExtensionWorkspaceTrustRequestMap = new Map(); + const configuredExtensionWorkspaceTrustRequests = configurationService.inspect<{ [key: string]: { supported: ExtensionUntrustedWorkpaceSupportType, version?: string } }>(WORKSPACE_TRUST_EXTENSION_SUPPORT).userValue || {}; + for (const id of Object.keys(configuredExtensionWorkspaceTrustRequests)) { + this._configuredExtensionWorkspaceTrustRequestMap.set(ExtensionIdentifier.toKey(id), configuredExtensionWorkspaceTrustRequests[id]); + } + + // Workpace trust request type (products.json) + this._productExtensionWorkspaceTrustRequestMap = new Map(); + if (productService.extensionUntrustedWorkspaceSupport) { + for (const id of Object.keys(productService.extensionUntrustedWorkspaceSupport)) { + this._productExtensionWorkspaceTrustRequestMap.set(ExtensionIdentifier.toKey(id), productService.extensionUntrustedWorkspaceSupport[id]); + } + } + } + + prefersExecuteOnUI(manifest: IExtensionManifest): boolean { + const extensionKind = this.getExtensionKind(manifest); + return (extensionKind.length > 0 && extensionKind[0] === 'ui'); + } + + prefersExecuteOnWorkspace(manifest: IExtensionManifest): boolean { + const extensionKind = this.getExtensionKind(manifest); + return (extensionKind.length > 0 && extensionKind[0] === 'workspace'); + } + + prefersExecuteOnWeb(manifest: IExtensionManifest): boolean { + const extensionKind = this.getExtensionKind(manifest); + return (extensionKind.length > 0 && extensionKind[0] === 'web'); + } + + canExecuteOnUI(manifest: IExtensionManifest): boolean { + const extensionKind = this.getExtensionKind(manifest); + return extensionKind.some(kind => kind === 'ui'); + } + + canExecuteOnWorkspace(manifest: IExtensionManifest): boolean { + const extensionKind = this.getExtensionKind(manifest); + return extensionKind.some(kind => kind === 'workspace'); + } + + canExecuteOnWeb(manifest: IExtensionManifest): boolean { + const extensionKind = this.getExtensionKind(manifest); + return extensionKind.some(kind => kind === 'web'); + } + + getExtensionKind(manifest: IExtensionManifest): ExtensionKind[] { + // check in config + let result = this.getConfiguredExtensionKind(manifest); + if (typeof result !== 'undefined') { + return this.toArray(result); + } + + // check product.json + result = this.getProductExtensionKind(manifest); + if (typeof result !== 'undefined') { + return result; + } + + // check the manifest itself + result = manifest.extensionKind; + if (typeof result !== 'undefined') { + return this.toArray(result); + } + + return this.deduceExtensionKind(manifest); + } + + getExtensionUntrustedWorkspaceSupportType(manifest: IExtensionManifest): ExtensionUntrustedWorkpaceSupportType { + // Workspace trust feature is disabled, or extension has no entry point + if (!isWorkspaceTrustEnabled(this.configurationService) || !manifest.main) { + return true; + } + + // Get extension workspace trust requirements from settings.json + const configuredWorkspaceTrustRequest = this.getConfiguredExtensionWorkspaceTrustRequest(manifest); + + // Get extension workspace trust requirements from product.json + const productWorkspaceTrustRequest = this.getProductExtensionWorkspaceTrustRequest(manifest); + + // Use settings.json override value if it exists + if (configuredWorkspaceTrustRequest) { + return configuredWorkspaceTrustRequest; + } + + // Use product.json override value if it exists + if (productWorkspaceTrustRequest?.override) { + return productWorkspaceTrustRequest.override; + } + + // Use extension manifest value if it exists + if (manifest.capabilities?.untrustedWorkspaces?.supported !== undefined) { + return manifest.capabilities.untrustedWorkspaces.supported; + } + + // Use product.json default value if it exists + if (productWorkspaceTrustRequest?.default) { + return productWorkspaceTrustRequest.default; + } + + return false; + } + + canSupportVirtualWorkspace(manifest: IExtensionManifest): boolean { + // check user configured + const userConfiguredVirtualWorkspaceSupport = this.getConfiguredVirtualWorkspaceSupport(manifest); + if (userConfiguredVirtualWorkspaceSupport !== undefined) { + return userConfiguredVirtualWorkspaceSupport; + } + + const productConfiguredWorkspaceSchemes = this.getProductVirtualWorkspaceSupport(manifest); + + // check override from product + if (productConfiguredWorkspaceSchemes?.override !== undefined) { + return productConfiguredWorkspaceSchemes.override; + } + + // check the manifest + if (manifest.capabilities?.virtualWorkspaces !== undefined) { + return manifest.capabilities?.virtualWorkspaces; + } + + // check default from product + if (productConfiguredWorkspaceSchemes?.default !== undefined) { + return productConfiguredWorkspaceSchemes.default; + } + + // Default - supports virtual workspace + return true; + } + + deduceExtensionKind(manifest: IExtensionManifest): ExtensionKind[] { + // Not an UI extension if it has main + if (manifest.main) { + if (manifest.browser) { + return ['workspace', 'web']; + } + return ['workspace']; + } + + if (manifest.browser) { + return ['web']; + } + + // Not an UI nor web extension if it has dependencies or an extension pack + if (isNonEmptyArray(manifest.extensionDependencies) || isNonEmptyArray(manifest.extensionPack)) { + return ['workspace']; + } + + if (manifest.contributes) { + // Not an UI nor web extension if it has no ui contributions + for (const contribution of Object.keys(manifest.contributes)) { + if (!this.isUIExtensionPoint(contribution)) { + return ['workspace']; + } + } + } + + return ['ui', 'workspace', 'web']; + } + + private isUIExtensionPoint(extensionPoint: string): boolean { + if (this._uiExtensionPoints === null) { + const uiExtensionPoints = new Set(); + ExtensionsRegistry.getExtensionPoints().filter(e => e.defaultExtensionKind !== 'workspace').forEach(e => { + uiExtensionPoints.add(e.name); + }); + this._uiExtensionPoints = uiExtensionPoints; + } + return this._uiExtensionPoints.has(extensionPoint); + } + + private getProductExtensionKind(manifest: IExtensionManifest): ExtensionKind[] | undefined { + if (this._productExtensionKindsMap === null) { + const productExtensionKindsMap = new Map(); + if (this.productService.extensionKind) { + for (const id of Object.keys(this.productService.extensionKind)) { + productExtensionKindsMap.set(ExtensionIdentifier.toKey(id), this.productService.extensionKind[id]); + } + } + this._productExtensionKindsMap = productExtensionKindsMap; + } + + const extensionId = getGalleryExtensionId(manifest.publisher, manifest.name); + return this._productExtensionKindsMap.get(ExtensionIdentifier.toKey(extensionId)); + } + + private getConfiguredExtensionKind(manifest: IExtensionManifest): ExtensionKind | ExtensionKind[] | undefined { + if (this._configuredExtensionKindsMap === null) { + const configuredExtensionKindsMap = new Map(); + const configuredExtensionKinds = this.configurationService.getValue<{ [key: string]: ExtensionKind | ExtensionKind[] }>('remote.extensionKind') || {}; + for (const id of Object.keys(configuredExtensionKinds)) { + configuredExtensionKindsMap.set(ExtensionIdentifier.toKey(id), configuredExtensionKinds[id]); + } + this._configuredExtensionKindsMap = configuredExtensionKindsMap; + } + + const extensionId = getGalleryExtensionId(manifest.publisher, manifest.name); + return this._configuredExtensionKindsMap.get(ExtensionIdentifier.toKey(extensionId)); + } + + private getProductVirtualWorkspaceSupport(manifest: IExtensionManifest): { default?: boolean, override?: boolean } | undefined { + if (this._productVirtualWorkspaceSupportMap === null) { + const productWorkspaceSchemesMap = new Map(); + if (this.productService.extensionVirtualWorkspacesSupport) { + for (const id of Object.keys(this.productService.extensionVirtualWorkspacesSupport)) { + productWorkspaceSchemesMap.set(ExtensionIdentifier.toKey(id), this.productService.extensionVirtualWorkspacesSupport[id]); + } + } + this._productVirtualWorkspaceSupportMap = productWorkspaceSchemesMap; + } + + const extensionId = getGalleryExtensionId(manifest.publisher, manifest.name); + return this._productVirtualWorkspaceSupportMap.get(ExtensionIdentifier.toKey(extensionId)); + } + + private getConfiguredVirtualWorkspaceSupport(manifest: IExtensionManifest): boolean | undefined { + if (this._configuredVirtualWorkspaceSupportMap === null) { + const configuredWorkspaceSchemesMap = new Map(); + const configuredWorkspaceSchemes = this.configurationService.getValue<{ [key: string]: boolean }>('extensions.supportVirtualWorkspaces') || {}; + for (const id of Object.keys(configuredWorkspaceSchemes)) { + if (configuredWorkspaceSchemes[id] !== undefined) { + configuredWorkspaceSchemesMap.set(ExtensionIdentifier.toKey(id), configuredWorkspaceSchemes[id]); + } + } + this._configuredVirtualWorkspaceSupportMap = configuredWorkspaceSchemesMap; + } + + const extensionId = getGalleryExtensionId(manifest.publisher, manifest.name); + return this._configuredVirtualWorkspaceSupportMap.get(ExtensionIdentifier.toKey(extensionId)); + } + + private getConfiguredExtensionWorkspaceTrustRequest(manifest: IExtensionManifest): ExtensionUntrustedWorkpaceSupportType | undefined { + const extensionId = getGalleryExtensionId(manifest.publisher, manifest.name); + const extensionWorkspaceTrustRequest = this._configuredExtensionWorkspaceTrustRequestMap.get(ExtensionIdentifier.toKey(extensionId)); + + if (extensionWorkspaceTrustRequest && (extensionWorkspaceTrustRequest.version === undefined || extensionWorkspaceTrustRequest.version === manifest.version)) { + return extensionWorkspaceTrustRequest.supported; + } + + return undefined; + } + + private getProductExtensionWorkspaceTrustRequest(manifest: IExtensionManifest): ExtensionUntrustedWorkspaceSupport | undefined { + const extensionId = getGalleryExtensionId(manifest.publisher, manifest.name); + return this._productExtensionWorkspaceTrustRequestMap.get(ExtensionIdentifier.toKey(extensionId)); + } + + private toArray(extensionKind: ExtensionKind | ExtensionKind[]): ExtensionKind[] { + if (Array.isArray(extensionKind)) { + return extensionKind; + } + return extensionKind === 'ui' ? ['ui', 'workspace'] : [extensionKind]; + } +} + +registerSingleton(IExtensionManifestPropertiesService, ExtensionManifestPropertiesService); diff --git a/src/vs/workbench/services/extensions/common/extensions.ts b/src/vs/workbench/services/extensions/common/extensions.ts index 9689215d..004c6b17 100644 --- a/src/vs/workbench/services/extensions/common/extensions.ts +++ b/src/vs/workbench/services/extensions/common/extensions.ts @@ -41,8 +41,7 @@ export interface IExtensionsStatus { runtimeErrors: Error[]; } -export type ExtensionActivationError = string | MissingDependencyError; -export class MissingDependencyError { +export class MissingExtensionDependency { constructor(readonly dependency: string) { } } @@ -237,10 +236,20 @@ export interface IExtensionService { */ getInspectPort(tryEnableInspector: boolean): Promise; + /** + * Stops the extension hosts. + */ + stopExtensionHosts(): void; + /** * Restarts the extension host. */ - restartExtensionHost(): void; + restartExtensionHost(): Promise; + + /** + * Starts the extension hosts. + */ + startExtensionHosts(): Promise; /** * Modify the environment of the remote extension host @@ -248,10 +257,10 @@ export interface IExtensionService { */ setRemoteEnvironment(env: { [key: string]: string | null }): Promise; - _logOrShowMessage(severity: Severity, msg: string): void; _activateById(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise; _onWillActivateExtension(extensionId: ExtensionIdentifier): void; _onDidActivateExtension(extensionId: ExtensionIdentifier, codeLoadingTime: number, activateCallTime: number, activateResolvedTime: number, activationReason: ExtensionActivationReason): void; + _onDidActivateExtensionError(extensionId: ExtensionIdentifier, error: Error): void; _onExtensionRuntimeError(extensionId: ExtensionIdentifier, err: Error): void; } @@ -269,16 +278,6 @@ export function throwProposedApiError(extension: IExtensionDescription): never { throw new Error(`[${extension.identifier.value}]: Proposed API is only available when running out of dev or with the following command line switch: --enable-proposed-api ${extension.identifier.value}`); } -export function checkRequiresWorkspaceTrust(extension: IExtensionDescription): void { - 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 "workspaceTrust.require" is set to "onStart" or "onDemand" in the extension's package.json.`); -} - export function toExtension(extensionDescription: IExtensionDescription): IExtension { return { type: extensionDescription.isBuiltin ? ExtensionType.System : ExtensionType.User, @@ -316,14 +315,16 @@ export class NullExtensionService implements IExtensionService { readExtensionPointContributions(_extPoint: IExtensionPoint): Promise[]> { return Promise.resolve(Object.create(null)); } getExtensionsStatus(): { [id: string]: IExtensionsStatus; } { return Object.create(null); } getInspectPort(_tryEnableInspector: boolean): Promise { return Promise.resolve(0); } - restartExtensionHost(): void { } + stopExtensionHosts(): void { } + async restartExtensionHost(): Promise { } + async startExtensionHosts(): Promise { } async setRemoteEnvironment(_env: { [key: string]: string | null }): Promise { } canAddExtension(): boolean { return false; } canRemoveExtension(): boolean { return false; } - _logOrShowMessage(_severity: Severity, _msg: string): void { } _activateById(_extensionId: ExtensionIdentifier, _reason: ExtensionActivationReason): Promise { return Promise.resolve(); } _onWillActivateExtension(_extensionId: ExtensionIdentifier): void { } _onDidActivateExtension(_extensionId: ExtensionIdentifier, _codeLoadingTime: number, _activateCallTime: number, _activateResolvedTime: number, _activationReason: ExtensionActivationReason): void { } + _onDidActivateExtensionError(_extensionId: ExtensionIdentifier, _error: Error): void { } _onExtensionRuntimeError(_extensionId: ExtensionIdentifier, _err: Error): void { } _onExtensionHostExit(code: number): void { } } diff --git a/src/vs/workbench/services/extensions/common/extensionsRegistry.ts b/src/vs/workbench/services/extensions/common/extensionsRegistry.ts index 36193d26..dca05b7c 100644 --- a/src/vs/workbench/services/extensions/common/extensionsRegistry.ts +++ b/src/vs/workbench/services/extensions/common/extensionsRegistry.ts @@ -415,6 +415,48 @@ export const schema: IJSONSchema = { } ] }, + capabilities: { + description: nls.localize('vscode.extension.capabilities', "Declare the set of supported capabilities by the extension."), + type: 'object', + properties: { + virtualWorkspaces: { + description: nls.localize('vscode.extension.capabilities.virtualWorkspaces', "Declares whether the extension should be enabled in virtual workspaces. A virtual workspace is a workspace which is not backed by any on-disk resources. When false, this extension will be automatically disabled in virtual workspaces. Default is true."), + type: 'boolean', + default: true + }, + untrustedWorkspaces: { + description: nls.localize('vscode.extension.capabilities.untrustedWorkspaces', 'Declares how the extension should be handled in untrusted workspaces.'), + type: 'object', + required: ['supported'], + defaultSnippets: [ + { body: { supported: '${1:limited}', description: '${2}' } }, + ], + properties: { + supported: { + markdownDescription: nls.localize('vscode.extension.capabilities.untrustedWorkspaces.supported', "Declares the level of support for untrusted workspaces by the extension."), + type: ['string', 'boolean'], + enum: ['limited', true, false], + enumDescriptions: [ + nls.localize('vscode.extension.capabilities.untrustedWorkspaces.supported.limited', "The extension will be enabled in untrusted workspaces with some functionality disabled."), + nls.localize('vscode.extension.capabilities.untrustedWorkspaces.supported.true', "The extension will be enabled in untrusted workspaces with all functionality enabled."), + nls.localize('vscode.extension.capabilities.untrustedWorkspaces.supported.false', "The extension will not be enabled in untrusted workspaces."), + ] + }, + restrictedConfigurations: { + description: nls.localize('vscode.extension.capabilities.untrustedWorkspaces.restrictedConfigurations', "A list of configuration keys contributed by the extension that should not use workspace values in untrusted workspaces."), + type: 'array', + items: { + type: 'string' + } + }, + description: { + type: 'string', + markdownDescription: nls.localize('vscode.extension.capabilities.untrustedWorkspaces.description', "A description of how workspace trust affects the extensions behavior and why it is needed. This only applies when `supported` is not `true`."), + } + } + } + } + }, scripts: { type: 'object', properties: { diff --git a/src/vs/workbench/services/extensions/common/extensionsUtil.ts b/src/vs/workbench/services/extensions/common/extensionsUtil.ts index ce539f48..93a11415 100644 --- a/src/vs/workbench/services/extensions/common/extensionsUtil.ts +++ b/src/vs/workbench/services/extensions/common/extensionsUtil.ts @@ -10,8 +10,7 @@ import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/ex import { isNonEmptyArray } from 'vs/base/common/arrays'; import { IProductService } from 'vs/platform/product/common/productService'; - -export class ExtensionKindController { +export class ExtensionKindController2 { constructor( @IProductService private readonly productService: IProductService, @IConfigurationService private readonly configurationService: IConfigurationService, diff --git a/src/vs/workbench/services/extensions/common/remoteExtensionHost.ts b/src/vs/workbench/services/extensions/common/remoteExtensionHost.ts index 7e8a66b2..4b07928a 100644 --- a/src/vs/workbench/services/extensions/common/remoteExtensionHost.ts +++ b/src/vs/workbench/services/extensions/common/remoteExtensionHost.ts @@ -81,7 +81,7 @@ export class RemoteExtensionHost extends Disposable implements IExtensionHost { this._hasLostConnection = false; this._terminating = false; - this._register(this._lifecycleService.onShutdown(reason => this.dispose())); + this._register(this._lifecycleService.onDidShutdown(() => this.dispose())); const devOpts = parseExtensionDevOptions(this._environmentService); this._isExtensionDevHost = devOpts.isExtensionDevHost; @@ -269,7 +269,7 @@ export class RemoteExtensionHost extends Disposable implements IExtensionHost { return Promise.resolve(false); } - dispose(): void { + override dispose(): void { super.dispose(); this._terminating = true; diff --git a/src/vs/workbench/services/extensions/common/rpcProtocol.ts b/src/vs/workbench/services/extensions/common/rpcProtocol.ts index a9fef74e..096c87fd 100644 --- a/src/vs/workbench/services/extensions/common/rpcProtocol.ts +++ b/src/vs/workbench/services/extensions/common/rpcProtocol.ts @@ -110,7 +110,7 @@ export class RPCProtocol extends Disposable implements IRPCProtocol { this._protocol.onMessage((msg) => this._receiveOneMessage(msg)); } - public dispose(): void { + public override dispose(): void { this._isDisposed = true; // Release all outstanding promises with a canceled error diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts index 9b60b035..b3a09f41 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts @@ -6,7 +6,7 @@ import { LocalProcessExtensionHost } from 'vs/workbench/services/extensions/electron-browser/localProcessExtensionHost'; import { CachedExtensionScanner } from 'vs/workbench/services/extensions/electron-browser/cachedExtensionScanner'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { AbstractExtensionService, ExtensionRunningLocation, ExtensionRunningLocationClassifier, parseScannedExtension } from 'vs/workbench/services/extensions/common/abstractExtensionService'; +import { AbstractExtensionService, ExtensionRunningLocation, ExtensionRunningLocationClassifier, ExtensionRunningPreference, parseScannedExtension } from 'vs/workbench/services/extensions/common/abstractExtensionService'; import * as nls from 'vs/nls'; import { runWhenIdle } from 'vs/base/common/async'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -15,7 +15,7 @@ import { IWorkbenchExtensionEnablementService, EnablementState, IWebExtensionsSc import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IRemoteExtensionHostDataProvider, RemoteExtensionHost, IRemoteExtensionHostInitData } from 'vs/workbench/services/extensions/common/remoteExtensionHost'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; -import { IRemoteAuthorityResolverService, RemoteAuthorityResolverError, ResolverResult } from 'vs/platform/remote/common/remoteAuthorityResolver'; +import { IRemoteAuthorityResolverService, RemoteAuthorityResolverError, RemoteTrustOption, ResolverResult } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; @@ -40,6 +40,14 @@ import { ILogService } from 'vs/platform/log/common/log'; import { CATEGORIES } from 'vs/workbench/common/actions'; import { Schemas } from 'vs/base/common/network'; import { ExtensionHostExitCode } from 'vs/workbench/services/extensions/common/extensionHostProtocol'; +import { updateProxyConfigurationsScope } from 'vs/platform/request/common/request'; +import { ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; +import { Codicon } from 'vs/base/common/codicons'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust'; +import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService'; + +const MACHINE_PROMPT = false; export class ExtensionService extends AbstractExtensionService implements IExtensionService { @@ -50,7 +58,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten constructor( @IInstantiationService instantiationService: IInstantiationService, @INotificationService notificationService: INotificationService, - @IWorkbenchEnvironmentService protected readonly _environmentService: IWorkbenchEnvironmentService, + @IWorkbenchEnvironmentService _environmentService: IWorkbenchEnvironmentService, @ITelemetryService telemetryService: ITelemetryService, @IWorkbenchExtensionEnablementService extensionEnablementService: IWorkbenchExtensionEnablementService, @IFileService fileService: IFileService, @@ -67,11 +75,14 @@ export class ExtensionService extends AbstractExtensionService implements IExten @IRemoteExplorerService private readonly _remoteExplorerService: IRemoteExplorerService, @IExtensionGalleryService private readonly _extensionGalleryService: IExtensionGalleryService, @ILogService private readonly _logService: ILogService, + @IDialogService private readonly _dialogService: IDialogService, + @IWorkspaceTrustManagementService private readonly _workspaceTrustManagementService: IWorkspaceTrustManagementService, + @IExtensionManifestPropertiesService extensionManifestPropertiesService: IExtensionManifestPropertiesService, ) { super( new ExtensionRunningLocationClassifier( (extension) => this._getExtensionKind(extension), - (extensionKinds, isInstalledLocally, isInstalledRemotely) => this._pickRunningLocation(extensionKinds, isInstalledLocally, isInstalledRemotely) + (extensionKinds, isInstalledLocally, isInstalledRemotely, preference) => this._pickRunningLocation(extensionKinds, isInstalledLocally, isInstalledRemotely, preference) ), instantiationService, notificationService, @@ -83,6 +94,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten extensionManagementService, contextService, configurationService, + extensionManifestPropertiesService ); this._enableLocalWebWorker = this._isLocalWebWorkerEnabled(); @@ -162,26 +174,47 @@ export class ExtensionService extends AbstractExtensionService implements IExten }; } - private _pickRunningLocation(extensionKinds: ExtensionKind[], isInstalledLocally: boolean, isInstalledRemotely: boolean): ExtensionRunningLocation { + private _pickRunningLocation(extensionKinds: ExtensionKind[], isInstalledLocally: boolean, isInstalledRemotely: boolean, preference: ExtensionRunningPreference): ExtensionRunningLocation { + return ExtensionService.pickRunningLocation(extensionKinds, isInstalledLocally, isInstalledRemotely, preference, Boolean(this._environmentService.remoteAuthority), this._enableLocalWebWorker); + } + + public static pickRunningLocation(extensionKinds: ExtensionKind[], isInstalledLocally: boolean, isInstalledRemotely: boolean, preference: ExtensionRunningPreference, hasRemoteExtHost: boolean, hasWebWorkerExtHost: boolean): ExtensionRunningLocation { + const result: ExtensionRunningLocation[] = []; for (const extensionKind of extensionKinds) { if (extensionKind === 'ui' && isInstalledLocally) { // ui extensions run locally if possible - return ExtensionRunningLocation.LocalProcess; + if (preference === ExtensionRunningPreference.None || preference === ExtensionRunningPreference.Local) { + return ExtensionRunningLocation.LocalProcess; + } else { + result.push(ExtensionRunningLocation.LocalProcess); + } } if (extensionKind === 'workspace' && isInstalledRemotely) { // workspace extensions run remotely if possible - return ExtensionRunningLocation.Remote; + if (preference === ExtensionRunningPreference.None || preference === ExtensionRunningPreference.Remote) { + return ExtensionRunningLocation.Remote; + } else { + result.push(ExtensionRunningLocation.Remote); + } } - if (extensionKind === 'workspace' && !this._environmentService.remoteAuthority) { + if (extensionKind === 'workspace' && !hasRemoteExtHost) { // workspace extensions also run locally if there is no remote - return ExtensionRunningLocation.LocalProcess; + if (preference === ExtensionRunningPreference.None || preference === ExtensionRunningPreference.Local) { + return ExtensionRunningLocation.LocalProcess; + } else { + result.push(ExtensionRunningLocation.LocalProcess); + } } - if (extensionKind === 'web' && isInstalledLocally && this._enableLocalWebWorker) { + if (extensionKind === 'web' && isInstalledLocally && hasWebWorkerExtHost) { // web worker extensions run in the local web worker if possible - return ExtensionRunningLocation.LocalWebWorker; + if (preference === ExtensionRunningPreference.None || preference === ExtensionRunningPreference.Local) { + return ExtensionRunningLocation.LocalWebWorker; + } else { + result.push(ExtensionRunningLocation.LocalWebWorker); + } } } - return ExtensionRunningLocation.None; + return (result.length > 0 ? result[0] : ExtensionRunningLocation.None); } protected _createExtensionHosts(isInitialStart: boolean): IExtensionHost[] { @@ -204,7 +237,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten return result; } - protected _onExtensionHostCrashed(extensionHost: ExtensionHostManager, code: number, signal: string | null): void { + protected override _onExtensionHostCrashed(extensionHost: ExtensionHostManager, code: number, signal: string | null): void { const activatedExtensions = Array.from(this._extensionHostActiveExtensions.values()); super._onExtensionHostCrashed(extensionHost, code, signal); @@ -236,7 +269,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten }, { label: nls.localize('restart', "Restart Extension Host"), - run: () => this.startExtensionHost() + run: () => this.startExtensionHosts() }] ); @@ -335,6 +368,38 @@ export class ExtensionService extends AbstractExtensionService implements IExten return; } + let promptForMachineTrust = MACHINE_PROMPT; + + if (resolverResult.options?.trust === RemoteTrustOption.DisableTrust) { + promptForMachineTrust = false; + this._workspaceTrustManagementService.setWorkspaceTrust(true); + } else if (resolverResult.options?.trust === RemoteTrustOption.MachineTrusted) { + promptForMachineTrust = false; + } + + if (promptForMachineTrust) { + const dialogResult = await this._dialogService.show( + Severity.Info, + nls.localize('machineTrustQuestion', "Do you trust the machine you're connecting to?"), + [nls.localize('yes', "Yes, connect."), nls.localize('no', "No, do not connect.")], + { + cancelId: 1, + custom: { + icon: Codicon.remoteExplorer + }, + // checkbox: { label: nls.localize('remember', "Remember my choice"), checked: true } + } + ); + + if (dialogResult.choice !== 0) { + // Did not confirm trust + this._notificationService.notify({ severity: Severity.Warning, message: nls.localize('trustFailure', "Refused to connect to untrusted machine.") }); + // Proceed with the local extension host + await this._startLocalExtensionHost(localExtensions); + return; + } + } + // set the resolved authority this._remoteAuthorityResolverService._setResolvedAuthority(resolverResult.authority, resolverResult.options); this._remoteExplorerService.setTunnelInformation(resolverResult.tunnelInformation); @@ -363,6 +428,8 @@ export class ExtensionService extends AbstractExtensionService implements IExten await this._startLocalExtensionHost(localExtensions); return; } + + updateProxyConfigurationsScope(remoteEnv.useHostProxy ? ConfigurationScope.APPLICATION : ConfigurationScope.MACHINE); } await this._startLocalExtensionHost(localExtensions, remoteAuthority, remoteEnv, remoteExtensions); @@ -408,7 +475,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten } } - public async getInspectPort(tryEnableInspector: boolean): Promise { + public override async getInspectPort(tryEnableInspector: boolean): Promise { const localProcessExtensionHost = this._getExtensionHostManager(ExtensionHostKind.LocalProcess); if (localProcessExtensionHost) { return localProcessExtensionHost.getInspectPort(tryEnableInspector); @@ -418,7 +485,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten public _onExtensionHostExit(code: number): void { // Dispose everything associated with the extension host - this._stopExtensionHosts(); + this.stopExtensionHosts(); if (this._isExtensionDevTestFromCli) { // When CLI testing make sure to exit with proper exit code diff --git a/src/vs/workbench/services/extensions/electron-browser/localProcessExtensionHost.ts b/src/vs/workbench/services/extensions/electron-browser/localProcessExtensionHost.ts index 78ed41b2..72535eae 100644 --- a/src/vs/workbench/services/extensions/electron-browser/localProcessExtensionHost.ts +++ b/src/vs/workbench/services/extensions/electron-browser/localProcessExtensionHost.ts @@ -127,7 +127,7 @@ export class LocalProcessExtensionHost implements IExtensionHost { this._toDispose.add(this._onExit); this._toDispose.add(this._lifecycleService.onWillShutdown(e => this._onWillShutdown(e))); - this._toDispose.add(this._lifecycleService.onShutdown(reason => this.terminate())); + this._toDispose.add(this._lifecycleService.onDidShutdown(reason => this.terminate())); this._toDispose.add(this._extensionHostDebugService.onClose(event => { if (this._isExtensionDevHost && this._environmentService.debugExtensionHost.debugId === event.sessionId) { this._nativeHostService.closeWindow(); diff --git a/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts b/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts index fe69f179..96ab14ef 100644 --- a/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts +++ b/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts @@ -105,7 +105,7 @@ function _createExtHostProtocol(): Promise { let protocol: PersistentProtocol | null = null; let timer = setTimeout(() => { - reject(new Error('VSCODE_EXTHOST_IPC_SOCKET timeout')); + onTerminate('VSCODE_EXTHOST_IPC_SOCKET timeout'); }, 60000); const reconnectionGraceTime = ProtocolConstants.ReconnectionGraceTime; diff --git a/src/vs/workbench/services/extensions/node/proxyResolver.ts b/src/vs/workbench/services/extensions/node/proxyResolver.ts index 1d37bfff..23ece190 100644 --- a/src/vs/workbench/services/extensions/node/proxyResolver.ts +++ b/src/vs/workbench/services/extensions/node/proxyResolver.ts @@ -14,7 +14,7 @@ import { ExtHostExtensionService } from 'vs/workbench/api/node/extHostExtensionS import { URI } from 'vs/base/common/uri'; import { ILogService } from 'vs/platform/log/common/log'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; -import { LogLevel, createHttpPatch, ProxyResolveEvent, createProxyResolver, createTlsPatch } from 'vscode-proxy-agent'; +import { LogLevel, createHttpPatch, ProxyResolveEvent, createProxyResolver, createTlsPatch, ProxySupportSetting } from 'vscode-proxy-agent'; export function connectProxyResolver( extHostWorkspace: IExtHostWorkspaceProvider, @@ -72,11 +72,11 @@ export function connectProxyResolver( function createPatchedModules(configProvider: ExtHostConfigProvider, resolveProxy: ReturnType) { const proxySetting = { config: configProvider.getConfiguration('http') - .get<'override' | 'on' | 'off'>('proxySupport') || 'off' + .get('proxySupport') || 'off' }; configProvider.onDidChangeConfiguration(e => { proxySetting.config = configProvider.getConfiguration('http') - .get<'override' | 'on' | 'off'>('proxySupport') || 'off'; + .get('proxySupport') || 'off'; }); const certSetting = { config: !!configProvider.getConfiguration('http') diff --git a/src/vs/workbench/services/extensions/test/browser/extensionService.test.ts b/src/vs/workbench/services/extensions/test/browser/extensionService.test.ts index 6d8c4943..9287c2c8 100644 --- a/src/vs/workbench/services/extensions/test/browser/extensionService.test.ts +++ b/src/vs/workbench/services/extensions/test/browser/extensionService.test.ts @@ -5,84 +5,84 @@ import * as assert from 'assert'; import { ExtensionService as BrowserExtensionService } from 'vs/workbench/services/extensions/browser/extensionService'; -import { ExtensionRunningLocation } from 'vs/workbench/services/extensions/common/abstractExtensionService'; +import { ExtensionRunningLocation, ExtensionRunningPreference } from 'vs/workbench/services/extensions/common/abstractExtensionService'; suite('BrowserExtensionService', () => { test('pickRunningLocation', () => { - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation([], false, false), ExtensionRunningLocation.None); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation([], false, true), ExtensionRunningLocation.None); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation([], true, false), ExtensionRunningLocation.None); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation([], true, true), ExtensionRunningLocation.None); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation([], false, false, ExtensionRunningPreference.None), ExtensionRunningLocation.None); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation([], false, true, ExtensionRunningPreference.None), ExtensionRunningLocation.None); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation([], true, false, ExtensionRunningPreference.None), ExtensionRunningLocation.None); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation([], true, true, ExtensionRunningPreference.None), ExtensionRunningLocation.None); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui'], false, false), ExtensionRunningLocation.None); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui'], false, true), ExtensionRunningLocation.Remote); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui'], true, false), ExtensionRunningLocation.None); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui'], true, true), ExtensionRunningLocation.Remote); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui'], false, false, ExtensionRunningPreference.None), ExtensionRunningLocation.None); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui'], false, true, ExtensionRunningPreference.None), ExtensionRunningLocation.Remote); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui'], true, false, ExtensionRunningPreference.None), ExtensionRunningLocation.None); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui'], true, true, ExtensionRunningPreference.None), ExtensionRunningLocation.Remote); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace'], false, false), ExtensionRunningLocation.None); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace'], false, true), ExtensionRunningLocation.Remote); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace'], true, false), ExtensionRunningLocation.None); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace'], true, true), ExtensionRunningLocation.Remote); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace'], false, false, ExtensionRunningPreference.None), ExtensionRunningLocation.None); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace'], false, true, ExtensionRunningPreference.None), ExtensionRunningLocation.Remote); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace'], true, false, ExtensionRunningPreference.None), ExtensionRunningLocation.None); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace'], true, true, ExtensionRunningPreference.None), ExtensionRunningLocation.Remote); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web'], false, false), ExtensionRunningLocation.None); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web'], false, true), ExtensionRunningLocation.None); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web'], true, false), ExtensionRunningLocation.LocalWebWorker); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web'], true, true), ExtensionRunningLocation.LocalWebWorker); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web'], false, false, ExtensionRunningPreference.None), ExtensionRunningLocation.None); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web'], false, true, ExtensionRunningPreference.None), ExtensionRunningLocation.None); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web'], true, false, ExtensionRunningPreference.None), ExtensionRunningLocation.LocalWebWorker); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web'], true, true, ExtensionRunningPreference.None), ExtensionRunningLocation.LocalWebWorker); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'workspace'], false, false), ExtensionRunningLocation.None); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'workspace'], false, true), ExtensionRunningLocation.Remote); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'workspace'], true, false), ExtensionRunningLocation.None); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'workspace'], true, true), ExtensionRunningLocation.Remote); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'ui'], false, false), ExtensionRunningLocation.None); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'ui'], false, true), ExtensionRunningLocation.Remote); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'ui'], true, false), ExtensionRunningLocation.None); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'ui'], true, true), ExtensionRunningLocation.Remote); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'workspace'], false, false, ExtensionRunningPreference.None), ExtensionRunningLocation.None); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'workspace'], false, true, ExtensionRunningPreference.None), ExtensionRunningLocation.Remote); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'workspace'], true, false, ExtensionRunningPreference.None), ExtensionRunningLocation.None); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'workspace'], true, true, ExtensionRunningPreference.None), ExtensionRunningLocation.Remote); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'ui'], false, false, ExtensionRunningPreference.None), ExtensionRunningLocation.None); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'ui'], false, true, ExtensionRunningPreference.None), ExtensionRunningLocation.Remote); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'ui'], true, false, ExtensionRunningPreference.None), ExtensionRunningLocation.None); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'ui'], true, true, ExtensionRunningPreference.None), ExtensionRunningLocation.Remote); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'workspace'], false, false), ExtensionRunningLocation.None); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'workspace'], false, true), ExtensionRunningLocation.Remote); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'workspace'], true, false), ExtensionRunningLocation.LocalWebWorker); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'workspace'], true, true), ExtensionRunningLocation.LocalWebWorker); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'web'], false, false), ExtensionRunningLocation.None); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'web'], false, true), ExtensionRunningLocation.Remote); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'web'], true, false), ExtensionRunningLocation.LocalWebWorker); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'web'], true, true), ExtensionRunningLocation.Remote); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'workspace'], false, false, ExtensionRunningPreference.None), ExtensionRunningLocation.None); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'workspace'], false, true, ExtensionRunningPreference.None), ExtensionRunningLocation.Remote); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'workspace'], true, false, ExtensionRunningPreference.None), ExtensionRunningLocation.LocalWebWorker); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'workspace'], true, true, ExtensionRunningPreference.None), ExtensionRunningLocation.LocalWebWorker); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'web'], false, false, ExtensionRunningPreference.None), ExtensionRunningLocation.None); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'web'], false, true, ExtensionRunningPreference.None), ExtensionRunningLocation.Remote); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'web'], true, false, ExtensionRunningPreference.None), ExtensionRunningLocation.LocalWebWorker); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'web'], true, true, ExtensionRunningPreference.None), ExtensionRunningLocation.Remote); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'web'], false, false), ExtensionRunningLocation.None); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'web'], false, true), ExtensionRunningLocation.Remote); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'web'], true, false), ExtensionRunningLocation.LocalWebWorker); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'web'], true, true), ExtensionRunningLocation.LocalWebWorker); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'ui'], false, false), ExtensionRunningLocation.None); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'ui'], false, true), ExtensionRunningLocation.Remote); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'ui'], true, false), ExtensionRunningLocation.LocalWebWorker); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'ui'], true, true), ExtensionRunningLocation.LocalWebWorker); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'web'], false, false, ExtensionRunningPreference.None), ExtensionRunningLocation.None); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'web'], false, true, ExtensionRunningPreference.None), ExtensionRunningLocation.Remote); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'web'], true, false, ExtensionRunningPreference.None), ExtensionRunningLocation.LocalWebWorker); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'web'], true, true, ExtensionRunningPreference.None), ExtensionRunningLocation.LocalWebWorker); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'ui'], false, false, ExtensionRunningPreference.None), ExtensionRunningLocation.None); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'ui'], false, true, ExtensionRunningPreference.None), ExtensionRunningLocation.Remote); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'ui'], true, false, ExtensionRunningPreference.None), ExtensionRunningLocation.LocalWebWorker); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'ui'], true, true, ExtensionRunningPreference.None), ExtensionRunningLocation.LocalWebWorker); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'web', 'workspace'], false, false), ExtensionRunningLocation.None); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'web', 'workspace'], false, true), ExtensionRunningLocation.Remote); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'web', 'workspace'], true, false), ExtensionRunningLocation.LocalWebWorker); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'web', 'workspace'], true, true), ExtensionRunningLocation.LocalWebWorker); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'workspace', 'web'], false, false), ExtensionRunningLocation.None); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'workspace', 'web'], false, true), ExtensionRunningLocation.Remote); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'workspace', 'web'], true, false), ExtensionRunningLocation.LocalWebWorker); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'workspace', 'web'], true, true), ExtensionRunningLocation.Remote); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'web', 'workspace'], false, false, ExtensionRunningPreference.None), ExtensionRunningLocation.None); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'web', 'workspace'], false, true, ExtensionRunningPreference.None), ExtensionRunningLocation.Remote); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'web', 'workspace'], true, false, ExtensionRunningPreference.None), ExtensionRunningLocation.LocalWebWorker); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'web', 'workspace'], true, true, ExtensionRunningPreference.None), ExtensionRunningLocation.LocalWebWorker); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'workspace', 'web'], false, false, ExtensionRunningPreference.None), ExtensionRunningLocation.None); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'workspace', 'web'], false, true, ExtensionRunningPreference.None), ExtensionRunningLocation.Remote); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'workspace', 'web'], true, false, ExtensionRunningPreference.None), ExtensionRunningLocation.LocalWebWorker); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'workspace', 'web'], true, true, ExtensionRunningPreference.None), ExtensionRunningLocation.Remote); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'ui', 'workspace'], false, false), ExtensionRunningLocation.None); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'ui', 'workspace'], false, true), ExtensionRunningLocation.Remote); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'ui', 'workspace'], true, false), ExtensionRunningLocation.LocalWebWorker); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'ui', 'workspace'], true, true), ExtensionRunningLocation.LocalWebWorker); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'workspace', 'ui'], false, false), ExtensionRunningLocation.None); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'workspace', 'ui'], false, true), ExtensionRunningLocation.Remote); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'workspace', 'ui'], true, false), ExtensionRunningLocation.LocalWebWorker); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'workspace', 'ui'], true, true), ExtensionRunningLocation.LocalWebWorker); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'ui', 'workspace'], false, false, ExtensionRunningPreference.None), ExtensionRunningLocation.None); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'ui', 'workspace'], false, true, ExtensionRunningPreference.None), ExtensionRunningLocation.Remote); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'ui', 'workspace'], true, false, ExtensionRunningPreference.None), ExtensionRunningLocation.LocalWebWorker); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'ui', 'workspace'], true, true, ExtensionRunningPreference.None), ExtensionRunningLocation.LocalWebWorker); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'workspace', 'ui'], false, false, ExtensionRunningPreference.None), ExtensionRunningLocation.None); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'workspace', 'ui'], false, true, ExtensionRunningPreference.None), ExtensionRunningLocation.Remote); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'workspace', 'ui'], true, false, ExtensionRunningPreference.None), ExtensionRunningLocation.LocalWebWorker); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'workspace', 'ui'], true, true, ExtensionRunningPreference.None), ExtensionRunningLocation.LocalWebWorker); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'ui', 'web'], false, false), ExtensionRunningLocation.None); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'ui', 'web'], false, true), ExtensionRunningLocation.Remote); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'ui', 'web'], true, false), ExtensionRunningLocation.LocalWebWorker); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'ui', 'web'], true, true), ExtensionRunningLocation.Remote); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'web', 'ui'], false, false), ExtensionRunningLocation.None); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'web', 'ui'], false, true), ExtensionRunningLocation.Remote); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'web', 'ui'], true, false), ExtensionRunningLocation.LocalWebWorker); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'web', 'ui'], true, true), ExtensionRunningLocation.Remote); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'ui', 'web'], false, false, ExtensionRunningPreference.None), ExtensionRunningLocation.None); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'ui', 'web'], false, true, ExtensionRunningPreference.None), ExtensionRunningLocation.Remote); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'ui', 'web'], true, false, ExtensionRunningPreference.None), ExtensionRunningLocation.LocalWebWorker); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'ui', 'web'], true, true, ExtensionRunningPreference.None), ExtensionRunningLocation.Remote); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'web', 'ui'], false, false, ExtensionRunningPreference.None), ExtensionRunningLocation.None); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'web', 'ui'], false, true, ExtensionRunningPreference.None), ExtensionRunningLocation.Remote); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'web', 'ui'], true, false, ExtensionRunningPreference.None), ExtensionRunningLocation.LocalWebWorker); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'web', 'ui'], true, true, ExtensionRunningPreference.None), ExtensionRunningLocation.Remote); }); }); diff --git a/src/vs/workbench/services/extensions/test/common/extensionManifestPropertiesService.test.ts b/src/vs/workbench/services/extensions/test/common/extensionManifestPropertiesService.test.ts new file mode 100644 index 00000000..71ce4b82 --- /dev/null +++ b/src/vs/workbench/services/extensions/test/common/extensionManifestPropertiesService.test.ts @@ -0,0 +1,155 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { IExtensionManifest, ExtensionKind, ExtensionUntrustedWorkpaceSupportType } from 'vs/platform/extensions/common/extensions'; +import { ExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService'; +import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; +import { TestProductService } from 'vs/workbench/test/common/workbenchTestServices'; +import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { isWeb } from 'vs/base/common/platform'; + +suite('ExtensionManifestPropertiesService - ExtensionKind', () => { + + function check(manifest: Partial, expected: ExtensionKind[]): void { + const extensionManifestPropertiesService = new ExtensionManifestPropertiesService(TestProductService, new TestConfigurationService()); + assert.deepStrictEqual(extensionManifestPropertiesService.deduceExtensionKind(manifest), expected); + } + + test('declarative with extension dependencies => workspace', () => { + check({ extensionDependencies: ['ext1'] }, ['workspace']); + }); + + test('declarative extension pack => workspace', () => { + check({ extensionPack: ['ext1', 'ext2'] }, ['workspace']); + }); + + test('declarative with unknown contribution point => workspace', () => { + check({ contributes: { 'unknownPoint': { something: true } } }, ['workspace']); + }); + + test('simple declarative => ui, workspace, web', () => { + check({}, ['ui', 'workspace', 'web']); + }); + + test('only browser => web', () => { + check({ browser: 'main.browser.js' }, ['web']); + }); + + test('only main => workspace', () => { + check({ main: 'main.js' }, ['workspace']); + }); + + test('main and browser => workspace, web', () => { + check({ main: 'main.js', browser: 'main.browser.js' }, ['workspace', 'web']); + }); +}); + + +// Workspace Trust is disabled in web at the moment +if (!isWeb) { + suite('ExtensionManifestPropertiesService - ExtensionUntrustedWorkpaceSupportType', () => { + let testObject: ExtensionManifestPropertiesService; + let instantiationService: TestInstantiationService; + let testConfigurationService: TestConfigurationService; + + setup(async () => { + instantiationService = new TestInstantiationService(); + + testConfigurationService = new TestConfigurationService(); + instantiationService.stub(IConfigurationService, testConfigurationService); + await testConfigurationService.setUserConfiguration('security', { workspace: { trust: { enabled: true } } }); + }); + + teardown(() => testObject.dispose()); + + function assertUntrustedWorkspaceSupport(extensionMaifest: IExtensionManifest, expected: ExtensionUntrustedWorkpaceSupportType): void { + testObject = instantiationService.createInstance(ExtensionManifestPropertiesService); + const untrustedWorkspaceSupport = testObject.getExtensionUntrustedWorkspaceSupportType(extensionMaifest); + + assert.strictEqual(untrustedWorkspaceSupport, expected); + } + + function getExtensionManifest(properties: any = {}): IExtensionManifest { + return Object.create({ name: 'a', publisher: 'pub', version: '1.0.0', ...properties }) as IExtensionManifest; + } + + test('test extension workspace trust request when main entry point is missing', () => { + instantiationService.stub(IProductService, >{}); + + const extensionMaifest = getExtensionManifest(); + assertUntrustedWorkspaceSupport(extensionMaifest, true); + }); + + test('test extension workspace trust request when workspace trust is disabled', async () => { + instantiationService.stub(IProductService, >{}); + await testConfigurationService.setUserConfiguration('security', { workspace: { trust: { enabled: false } } }); + + const extensionMaifest = getExtensionManifest({ main: './out/extension.js' }); + assertUntrustedWorkspaceSupport(extensionMaifest, true); + }); + + test('test extension workspace trust request when override exists in settings.json', async () => { + instantiationService.stub(IProductService, >{}); + + await testConfigurationService.setUserConfiguration('security', { workspace: { trust: { extensionUntrustedSupport: { 'pub.a': { supported: true } } } } }); + const extensionMaifest = getExtensionManifest({ main: './out/extension.js', capabilities: { untrustedWorkspaces: { supported: 'limited' } } }); + assertUntrustedWorkspaceSupport(extensionMaifest, true); + }); + + test('test extension workspace trust request when override for the version exists in settings.json', async () => { + instantiationService.stub(IProductService, >{}); + + await testConfigurationService.setUserConfiguration('security', { workspace: { trust: { extensionUntrustedSupport: { 'pub.a': { supported: true, version: '1.0.0' } } } } }); + const extensionMaifest = getExtensionManifest({ main: './out/extension.js', capabilities: { untrustedWorkspaces: { supported: 'limited' } } }); + assertUntrustedWorkspaceSupport(extensionMaifest, true); + }); + + test('test extension workspace trust request when override for a different version exists in settings.json', async () => { + instantiationService.stub(IProductService, >{}); + + await testConfigurationService.setUserConfiguration('security', { + workspace: { + trust: { + enabled: true, + extensionUntrustedSupport: { 'pub.a': { supported: true, version: '2.0.0' } } + } + } + }); + const extensionMaifest = getExtensionManifest({ main: './out/extension.js', capabilities: { untrustedWorkspaces: { supported: 'limited' } } }); + assertUntrustedWorkspaceSupport(extensionMaifest, 'limited'); + }); + + test('test extension workspace trust request when default exists in product.json', () => { + instantiationService.stub(IProductService, >{ extensionUntrustedWorkspaceSupport: { 'pub.a': { default: true } } }); + + const extensionMaifest = getExtensionManifest({ main: './out/extension.js' }); + assertUntrustedWorkspaceSupport(extensionMaifest, true); + }); + + test('test extension workspace trust request when override exists in product.json', () => { + instantiationService.stub(IProductService, >{ extensionUntrustedWorkspaceSupport: { 'pub.a': { override: 'limited' } } }); + + const extensionMaifest = getExtensionManifest({ main: './out/extension.js', capabilities: { untrustedWorkspaces: { supported: true } } }); + assertUntrustedWorkspaceSupport(extensionMaifest, 'limited'); + }); + + test('test extension workspace trust request when value exists in package.json', () => { + instantiationService.stub(IProductService, >{}); + + const extensionMaifest = getExtensionManifest({ main: './out/extension.js', capabilities: { untrustedWorkspaces: { supported: 'limited' } } }); + assertUntrustedWorkspaceSupport(extensionMaifest, 'limited'); + }); + + test('test extension workspace trust request when no value exists in package.json', () => { + instantiationService.stub(IProductService, >{}); + + const extensionMaifest = getExtensionManifest({ main: './out/extension.js' }); + assertUntrustedWorkspaceSupport(extensionMaifest, false); + }); + }); +} diff --git a/src/vs/workbench/services/extensions/test/common/extensionsUtil.test.ts b/src/vs/workbench/services/extensions/test/common/extensionsUtil.test.ts deleted file mode 100644 index 4ca1afc4..00000000 --- a/src/vs/workbench/services/extensions/test/common/extensionsUtil.test.ts +++ /dev/null @@ -1,43 +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 { deduceExtensionKind } from 'vs/workbench/services/extensions/common/extensionsUtil'; -import { IExtensionManifest, ExtensionKind } from 'vs/platform/extensions/common/extensions'; - -suite('ExtensionKind', () => { - - function check(manifest: Partial, expected: ExtensionKind[]): void { - assert.deepStrictEqual(deduceExtensionKind(manifest), expected); - } - - test('declarative with extension dependencies => workspace', () => { - check({ extensionDependencies: ['ext1'] }, ['workspace']); - }); - - test('declarative extension pack => workspace', () => { - check({ extensionPack: ['ext1', 'ext2'] }, ['workspace']); - }); - - test('declarative with unknown contribution point => workspace', () => { - check({ contributes: { 'unknownPoint': { something: true } } }, ['workspace']); - }); - - test('simple declarative => ui, workspace, web', () => { - check({}, ['ui', 'workspace', 'web']); - }); - - test('only browser => web', () => { - check({ browser: 'main.browser.js' }, ['web']); - }); - - test('only main => workspace', () => { - check({ main: 'main.js' }, ['workspace']); - }); - - test('main and browser => workspace, web', () => { - check({ main: 'main.js', browser: 'main.browser.js' }, ['workspace', 'web']); - }); -}); diff --git a/src/vs/workbench/services/files/browser/elevatedFileService.ts b/src/vs/workbench/services/files/browser/elevatedFileService.ts new file mode 100644 index 00000000..2aa9dce6 --- /dev/null +++ b/src/vs/workbench/services/files/browser/elevatedFileService.ts @@ -0,0 +1,28 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { VSBuffer, VSBufferReadable, VSBufferReadableStream } from 'vs/base/common/buffer'; +import { URI } from 'vs/base/common/uri'; +import { IFileStatWithMetadata, IWriteFileOptions } from 'vs/platform/files/common/files'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IElevatedFileService } from 'vs/workbench/services/files/common/elevatedFileService'; + +export class BrowserElevatedFileService implements IElevatedFileService { + + readonly _serviceBrand: undefined; + + isSupported(resource: URI): boolean { + // Saving elevated is currently not supported in web for as + // long as we have no generic support from the file service + // (https://github.com/microsoft/vscode/issues/48659) + return false; + } + + async writeFileElevated(resource: URI, value: VSBuffer | VSBufferReadable | VSBufferReadableStream, options?: IWriteFileOptions): Promise { + throw new Error('Unsupported'); + } +} + +registerSingleton(IElevatedFileService, BrowserElevatedFileService); diff --git a/src/vs/workbench/services/files/common/elevatedFileService.ts b/src/vs/workbench/services/files/common/elevatedFileService.ts new file mode 100644 index 00000000..c607b9be --- /dev/null +++ b/src/vs/workbench/services/files/common/elevatedFileService.ts @@ -0,0 +1,27 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { URI } from 'vs/base/common/uri'; +import { VSBuffer, VSBufferReadable, VSBufferReadableStream } from 'vs/base/common/buffer'; +import { IFileStatWithMetadata, IWriteFileOptions } from 'vs/platform/files/common/files'; + +export const IElevatedFileService = createDecorator('elevatedFileService'); + +export interface IElevatedFileService { + + readonly _serviceBrand: undefined; + + /** + * Whether saving elevated is supported for the provided resource. + */ + isSupported(resource: URI): boolean; + + /** + * Attempts to write to the target resource elevated. This may bring + * up a dialog to ask for admin username / password. + */ + writeFileElevated(resource: URI, value: VSBuffer | VSBufferReadable | VSBufferReadableStream, options?: IWriteFileOptions): Promise; +} diff --git a/src/vs/workbench/services/files/electron-sandbox/elevatedFileService.ts b/src/vs/workbench/services/files/electron-sandbox/elevatedFileService.ts new file mode 100644 index 00000000..e47aa8ea --- /dev/null +++ b/src/vs/workbench/services/files/electron-sandbox/elevatedFileService.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 { VSBuffer, VSBufferReadable, VSBufferReadableStream } from 'vs/base/common/buffer'; +import { Schemas } from 'vs/base/common/network'; +import { join } from 'vs/base/common/path'; +import { URI } from 'vs/base/common/uri'; +import { IFileService, IFileStatWithMetadata, IWriteFileOptions } from 'vs/platform/files/common/files'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; +import { IElevatedFileService } from 'vs/workbench/services/files/common/elevatedFileService'; + +export class NativeElevatedFileService implements IElevatedFileService { + + readonly _serviceBrand: undefined; + + constructor( + @INativeHostService private readonly nativeHostService: INativeHostService, + @IFileService private readonly fileService: IFileService, + @INativeWorkbenchEnvironmentService private readonly environmentService: INativeWorkbenchEnvironmentService + ) { } + + isSupported(resource: URI): boolean { + // Saving elevated is currently only supported for local + // files for as long as we have no generic support from + // the file service + // (https://github.com/microsoft/vscode/issues/48659) + return resource.scheme === Schemas.file; + } + + async writeFileElevated(resource: URI, value: VSBuffer | VSBufferReadable | VSBufferReadableStream, options?: IWriteFileOptions): Promise { + const source = URI.file(join(this.environmentService.userDataPath, `code-elevated-${Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 6)}`)); + try { + // write into a tmp file first + await this.fileService.writeFile(source, value, options); + + // then sudo prompt copy + await this.nativeHostService.writeElevated(source, resource, options); + } finally { + + // clean up + await this.fileService.del(source); + } + + return this.fileService.resolve(resource, { resolveMetadata: true }); + } +} + +registerSingleton(IElevatedFileService, NativeElevatedFileService); diff --git a/src/vs/workbench/services/history/browser/history.ts b/src/vs/workbench/services/history/browser/history.ts index f43ce56c..4ce401e8 100644 --- a/src/vs/workbench/services/history/browser/history.ts +++ b/src/vs/workbench/services/history/browser/history.ts @@ -7,7 +7,7 @@ import { localize } from 'vs/nls'; import { URI, UriComponents } from 'vs/base/common/uri'; import { IEditor } from 'vs/editor/common/editorCommon'; import { ITextEditorOptions, IResourceEditorInput, TextEditorSelectionRevealType, IEditorOptions } from 'vs/platform/editor/common/editor'; -import { IEditorInput, IEditorPane, Extensions as EditorExtensions, EditorInput, IEditorCloseEvent, IEditorInputFactoryRegistry, EditorResourceAccessor, IEditorIdentifier, GroupIdentifier, EditorsOrder, SideBySideEditor } from 'vs/workbench/common/editor'; +import { IEditorInput, IEditorPane, EditorExtensions, EditorInput, IEditorCloseEvent, IEditorInputFactoryRegistry, EditorResourceAccessor, IEditorIdentifier, GroupIdentifier, EditorsOrder, SideBySideEditor } from 'vs/workbench/common/editor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { FileChangesEvent, IFileService, FileChangeType, FILES_EXCLUDE_CONFIG, FileOperationEvent, FileOperation } from 'vs/platform/files/common/files'; @@ -37,6 +37,7 @@ import { IdleValue } from 'vs/base/common/async'; import { ResourceGlobMatcher } from 'vs/workbench/common/resources'; import { IPathService } from 'vs/workbench/services/path/common/pathService'; import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; +import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; /** * Stores the selection & view state of an editor and allows to compare it to other selection states. @@ -121,7 +122,8 @@ export class HistoryService extends Disposable implements IHistoryService { @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @IPathService private readonly pathService: IPathService, - @IUriIdentityService private readonly uriIdentityService: IUriIdentityService + @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, + @ILifecycleService private readonly lifecycleService: ILifecycleService ) { super(); @@ -262,7 +264,7 @@ export class HistoryService extends Disposable implements IHistoryService { } private onEditorDispose(editor: EditorInput, listener: Function, mapEditorToDispose: Map): void { - const toDispose = Event.once(editor.onDispose)(() => listener()); + const toDispose = Event.once(editor.onWillDispose)(() => listener()); let disposables = mapEditorToDispose.get(editor); if (!disposables) { @@ -664,7 +666,7 @@ export class HistoryService extends Disposable implements IHistoryService { return false; } - if (this.layoutService.isRestored() && !this.fileService.canHandleResource(inputResource)) { + if (this.lifecycleService.phase >= LifecyclePhase.Restored && !this.fileService.canHandleResource(inputResource)) { return false; // make sure to only check this when workbench has restored (for https://github.com/microsoft/vscode/issues/48275) } @@ -695,12 +697,12 @@ export class HistoryService extends Disposable implements IHistoryService { return; // ignore if editor was replaced } - const factory = this.editorInputFactory.getEditorInputFactory(editor.getTypeId()); - if (!factory || !factory.canSerialize(editor)) { - return; // we need a factory from this point that can serialize this editor + const editorSerializer = this.editorInputFactory.getEditorInputSerializer(editor); + if (!editorSerializer || !editorSerializer.canSerialize(editor)) { + return; // we need a serializer from this point that can serialize this editor } - const serialized = factory.serialize(editor); + const serialized = editorSerializer.serialize(editor); if (typeof serialized !== 'string') { return; // we need something to deserialize from } @@ -720,7 +722,7 @@ export class HistoryService extends Disposable implements IHistoryService { this.recentlyClosedEditors.push({ resource: EditorResourceAccessor.getOriginalUri(editor), associatedResources, - serialized: { typeId: editor.getTypeId(), value: serialized }, + serialized: { typeId: editor.typeId, value: serialized }, index: event.index, sticky: event.sticky }); @@ -765,9 +767,9 @@ export class HistoryService extends Disposable implements IHistoryService { } // Deserialize and open editor unless already opened - const restoredEditor = this.editorInputFactory.getEditorInputFactory(lastClosedEditor.serialized.typeId)?.deserialize(this.instantiationService, lastClosedEditor.serialized.value); + const restoredEditor = this.editorInputFactory.getEditorInputSerializer(lastClosedEditor.serialized.typeId)?.deserialize(this.instantiationService, lastClosedEditor.serialized.value); let editorPane: IEditorPane | undefined = undefined; - if (restoredEditor && !this.editorGroupService.activeGroup.isOpened(restoredEditor)) { + if (restoredEditor && !this.editorGroupService.activeGroup.contains(restoredEditor)) { // Fix for https://github.com/microsoft/vscode/issues/107850 // If opening an editor fails, it is possible that we get // another editor-close event as a result. But we really do @@ -962,7 +964,7 @@ export class HistoryService extends Disposable implements IHistoryService { this.editorHistoryListeners.clear(); } - getHistory(): ReadonlyArray { + getHistory(): readonly (IEditorInput | IResourceEditorInput)[] { this.ensureHistoryLoaded(this.history); return this.history.slice(0); @@ -1003,12 +1005,12 @@ export class HistoryService extends Disposable implements IHistoryService { return { resource: URI.revive(serializedEditorHistoryEntry.resourceJSON) }; } - // Editor input: via factory + // Editor input: via serializer const { editorInputJSON } = serializedEditorHistoryEntry; if (editorInputJSON?.deserialized) { - const factory = this.editorInputFactory.getEditorInputFactory(editorInputJSON.typeId); - if (factory) { - const input = factory.deserialize(this.instantiationService, editorInputJSON.deserialized); + const editorSerializer = this.editorInputFactory.getEditorInputSerializer(editorInputJSON.typeId); + if (editorSerializer) { + const input = editorSerializer.deserialize(this.instantiationService, editorInputJSON.deserialized); if (input) { this.onEditorDispose(input, () => this.removeFromHistory(input), this.editorHistoryListeners); } @@ -1027,13 +1029,13 @@ export class HistoryService extends Disposable implements IHistoryService { const entries: ISerializedEditorHistoryEntry[] = coalesce(this.history.map((input): ISerializedEditorHistoryEntry | undefined => { - // Editor input: try via factory + // Editor input: try via serializer if (input instanceof EditorInput) { - const factory = this.editorInputFactory.getEditorInputFactory(input.getTypeId()); - if (factory) { - const deserialized = factory.serialize(input); + const editorSerializer = this.editorInputFactory.getEditorInputSerializer(input); + if (editorSerializer) { + const deserialized = editorSerializer.serialize(input); if (deserialized) { - return { editorInputJSON: { typeId: input.getTypeId(), deserialized } }; + return { editorInputJSON: { typeId: input.typeId, deserialized } }; } } } @@ -1120,10 +1122,10 @@ export class HistoryService extends Disposable implements IHistoryService { //#region Editor Most Recently Used History - private recentlyUsedEditorsStack: ReadonlyArray | undefined = undefined; + private recentlyUsedEditorsStack: readonly IEditorIdentifier[] | undefined = undefined; private recentlyUsedEditorsStackIndex = 0; - private recentlyUsedEditorsInGroupStack: ReadonlyArray | undefined = undefined; + private recentlyUsedEditorsInGroupStack: readonly IEditorIdentifier[] | undefined = undefined; private recentlyUsedEditorsInGroupStackIndex = 0; private navigatingInRecentlyUsedEditorsStack = false; @@ -1161,8 +1163,8 @@ export class HistoryService extends Disposable implements IHistoryService { } } - private ensureRecentlyUsedStack(indexModifier: (index: number) => number, groupId?: GroupIdentifier): [ReadonlyArray, number] { - let editors: ReadonlyArray; + private ensureRecentlyUsedStack(indexModifier: (index: number) => number, groupId?: GroupIdentifier): [readonly IEditorIdentifier[], number] { + let editors: readonly IEditorIdentifier[]; let index: number; const group = typeof groupId === 'number' ? this.editorGroupService.getGroup(groupId) : undefined; diff --git a/src/vs/workbench/services/history/common/history.ts b/src/vs/workbench/services/history/common/history.ts index e8b39f1b..254c40fb 100644 --- a/src/vs/workbench/services/history/common/history.ts +++ b/src/vs/workbench/services/history/common/history.ts @@ -52,7 +52,7 @@ export interface IHistoryService { /** * Get the entire history of editors that were opened. */ - getHistory(): ReadonlyArray; + getHistory(): readonly (IEditorInput | IResourceEditorInput)[]; /** * Removes an entry from history. diff --git a/src/vs/workbench/services/history/test/browser/history.test.ts b/src/vs/workbench/services/history/test/browser/history.test.ts index 3af981db..da0879a5 100644 --- a/src/vs/workbench/services/history/test/browser/history.test.ts +++ b/src/vs/workbench/services/history/test/browser/history.test.ts @@ -16,6 +16,7 @@ import { EditorService } from 'vs/workbench/services/editor/browser/editorServic import { DisposableStore } from 'vs/base/common/lifecycle'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { timeout } from 'vs/base/common/async'; +import { Event } from 'vs/base/common/event'; suite('HistoryService', function () { @@ -25,9 +26,7 @@ suite('HistoryService', function () { async function createServices(): Promise<[EditorPart, HistoryService, EditorService]> { const instantiationService = workbenchInstantiationService(); - const part = createEditorPart(instantiationService, disposables); - - await part.whenRestored; + const part = await createEditorPart(instantiationService, disposables); instantiationService.stub(IEditorGroupsService, part); @@ -51,7 +50,7 @@ suite('HistoryService', function () { }); test('back / forward', async () => { - const [part, historyService] = await createServices(); + const [part, historyService, editorService] = await createServices(); const input1 = new TestFileEditorInput(URI.parse('foo://bar1'), TEST_EDITOR_INPUT_ID); await part.activeGroup.openEditor(input1, EditorOptions.create({ pinned: true })); @@ -61,10 +60,14 @@ suite('HistoryService', function () { await part.activeGroup.openEditor(input2, EditorOptions.create({ pinned: true })); assert.strictEqual(part.activeGroup.activeEditor, input2); + let editorChangePromise = Event.toPromise(editorService.onDidActiveEditorChange); historyService.back(); + await editorChangePromise; assert.strictEqual(part.activeGroup.activeEditor, input1); + editorChangePromise = Event.toPromise(editorService.onDidActiveEditorChange); historyService.forward(); + await editorChangePromise; assert.strictEqual(part.activeGroup.activeEditor, input2); }); @@ -101,7 +104,7 @@ suite('HistoryService', function () { }); test('open next/previous recently used editor (single group)', async () => { - const [part, historyService] = await createServices(); + const [part, historyService, editorService] = await createServices(); const input1 = new TestFileEditorInput(URI.parse('foo://bar1'), TEST_EDITOR_INPUT_ID); const input2 = new TestFileEditorInput(URI.parse('foo://bar2'), TEST_EDITOR_INPUT_ID); @@ -112,21 +115,29 @@ suite('HistoryService', function () { await part.activeGroup.openEditor(input2, EditorOptions.create({ pinned: true })); assert.strictEqual(part.activeGroup.activeEditor, input2); + let editorChangePromise = Event.toPromise(editorService.onDidActiveEditorChange); historyService.openPreviouslyUsedEditor(); + await editorChangePromise; assert.strictEqual(part.activeGroup.activeEditor, input1); + editorChangePromise = Event.toPromise(editorService.onDidActiveEditorChange); historyService.openNextRecentlyUsedEditor(); + await editorChangePromise; assert.strictEqual(part.activeGroup.activeEditor, input2); + editorChangePromise = Event.toPromise(editorService.onDidActiveEditorChange); historyService.openPreviouslyUsedEditor(part.activeGroup.id); + await editorChangePromise; assert.strictEqual(part.activeGroup.activeEditor, input1); + editorChangePromise = Event.toPromise(editorService.onDidActiveEditorChange); historyService.openNextRecentlyUsedEditor(part.activeGroup.id); + await editorChangePromise; assert.strictEqual(part.activeGroup.activeEditor, input2); }); test('open next/previous recently used editor (multi group)', async () => { - const [part, historyService] = await createServices(); + const [part, historyService, editorService] = await createServices(); const rootGroup = part.activeGroup; const input1 = new TestFileEditorInput(URI.parse('foo://bar1'), TEST_EDITOR_INPUT_ID); @@ -137,17 +148,21 @@ suite('HistoryService', function () { await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true })); await sideGroup.openEditor(input2, EditorOptions.create({ pinned: true })); + let editorChangePromise = Event.toPromise(editorService.onDidActiveEditorChange); historyService.openPreviouslyUsedEditor(); + await editorChangePromise; assert.strictEqual(part.activeGroup, rootGroup); assert.strictEqual(rootGroup.activeEditor, input1); + editorChangePromise = Event.toPromise(editorService.onDidActiveEditorChange); historyService.openNextRecentlyUsedEditor(); + await editorChangePromise; assert.strictEqual(part.activeGroup, sideGroup); assert.strictEqual(sideGroup.activeEditor, input2); }); test('open next/previous recently is reset when other input opens', async () => { - const [part, historyService] = await createServices(); + const [part, historyService, editorService] = await createServices(); const input1 = new TestFileEditorInput(URI.parse('foo://bar1'), TEST_EDITOR_INPUT_ID); const input2 = new TestFileEditorInput(URI.parse('foo://bar2'), TEST_EDITOR_INPUT_ID); @@ -158,16 +173,22 @@ suite('HistoryService', function () { await part.activeGroup.openEditor(input2, EditorOptions.create({ pinned: true })); await part.activeGroup.openEditor(input3, EditorOptions.create({ pinned: true })); + let editorChangePromise = Event.toPromise(editorService.onDidActiveEditorChange); historyService.openPreviouslyUsedEditor(); + await editorChangePromise; assert.strictEqual(part.activeGroup.activeEditor, input2); await timeout(0); await part.activeGroup.openEditor(input4, EditorOptions.create({ pinned: true })); + editorChangePromise = Event.toPromise(editorService.onDidActiveEditorChange); historyService.openPreviouslyUsedEditor(); + await editorChangePromise; assert.strictEqual(part.activeGroup.activeEditor, input2); + editorChangePromise = Event.toPromise(editorService.onDidActiveEditorChange); historyService.openNextRecentlyUsedEditor(); + await editorChangePromise; assert.strictEqual(part.activeGroup.activeEditor, input4); }); }); diff --git a/src/vs/workbench/services/host/browser/browserHostService.ts b/src/vs/workbench/services/host/browser/browserHostService.ts index b3ca09ae..8506a81b 100644 --- a/src/vs/workbench/services/host/browser/browserHostService.ts +++ b/src/vs/workbench/services/host/browser/browserHostService.ts @@ -11,6 +11,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IWindowSettings, IWindowOpenable, IOpenWindowOptions, isFolderToOpen, isWorkspaceToOpen, isFileToOpen, IOpenEmptyWindowOptions, IPathData, IFileToOpen } from 'vs/platform/windows/common/windows'; import { pathsToEditors } from 'vs/workbench/common/editor'; +import { whenEditorClosed } from 'vs/workbench/browser/editor'; import { IFileService } from 'vs/platform/files/common/files'; import { ILabelService } from 'vs/platform/label/common/label'; import { IModifierKeyStatus, ModifierKeyEmitter, trackFocus } from 'vs/base/browser/dom'; @@ -26,6 +27,9 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { BeforeShutdownEvent, ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { ILogService } from 'vs/platform/log/common/log'; import { getWorkspaceIdentifier } from 'vs/workbench/services/workspaces/browser/workspaces'; +import { localize } from 'vs/nls'; +import Severity from 'vs/base/common/severity'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; /** * A workspace to open in the workbench can either be: @@ -61,8 +65,10 @@ export interface IWorkspaceProvider { * - `payload`: arbitrary payload that should be made available * to the opening window via the `IWorkspaceProvider.payload` property. * @param payload optional payload to send to the workspace to open. + * + * @returns true if successfully opened, false otherwise. */ - open(workspace: IWorkspace, options?: { reuse?: boolean, payload?: object }): Promise; + open(workspace: IWorkspace, options?: { reuse?: boolean, payload?: object }): Promise; } enum HostShutdownReason { @@ -99,7 +105,8 @@ export class BrowserHostService extends Disposable implements IHostService { @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @IInstantiationService private readonly instantiationService: IInstantiationService, @ILifecycleService private readonly lifecycleService: ILifecycleService, - @ILogService private readonly logService: ILogService + @ILogService private readonly logService: ILogService, + @IDialogService private readonly dialogService: IDialogService ) { super(); @@ -109,7 +116,7 @@ export class BrowserHostService extends Disposable implements IHostService { this.workspaceProvider = new class implements IWorkspaceProvider { readonly workspace = undefined; readonly trusted = undefined; - async open() { } + async open() { return true; } }; } @@ -307,8 +314,8 @@ export class BrowserHostService extends Disposable implements IHostService { if (waitMarkerFileURI) { (async () => { - // Wait for the resources to be closed in the editor... - await editorService.whenClosed(fileOpenables.map(openable => ({ resource: openable.fileUri })), { waitForSaved: true }); + // Wait for the resources to be closed in the text editor... + await this.instantiationService.invokeFunction(accessor => whenEditorClosed(accessor, fileOpenables.map(fileOpenable => fileOpenable.fileUri))); // ...before deleting the wait marker file await this.fileService.del(waitMarkerFileURI); @@ -371,7 +378,7 @@ export class BrowserHostService extends Disposable implements IHostService { return this.doOpen(undefined, { reuse: options?.forceReuseWindow }); } - private doOpen(workspace: IWorkspace, options?: { reuse?: boolean, payload?: object }): Promise { + private async doOpen(workspace: IWorkspace, options?: { reuse?: boolean, payload?: object }): Promise { // We know that `workspaceProvider.open` will trigger a shutdown // with `options.reuse` so we update `shutdownReason` to reflect that @@ -379,7 +386,13 @@ export class BrowserHostService extends Disposable implements IHostService { this.shutdownReason = HostShutdownReason.Api; } - return this.workspaceProvider.open(workspace, options); + const opened = await this.workspaceProvider.open(workspace, options); + if (!opened) { + const showResult = await this.dialogService.show(Severity.Warning, localize('unableToOpenExternal', "The browser interrupted the opening of a new tab or window. Press 'Open' to open it anyway."), [localize('open', "Open"), localize('cancel', "Cancel")], { cancelId: 2 }); + if (showResult.choice === 0) { + await this.workspaceProvider.open(workspace, options); + } + } } async toggleFullScreen(): Promise { diff --git a/src/vs/workbench/services/hover/browser/hover.ts b/src/vs/workbench/services/hover/browser/hover.ts index 2b36a026..4b537f17 100644 --- a/src/vs/workbench/services/hover/browser/hover.ts +++ b/src/vs/workbench/services/hover/browser/hover.ts @@ -6,7 +6,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IDisposable } from 'vs/base/common/lifecycle'; import { IMarkdownString } from 'vs/base/common/htmlContent'; -import { AnchorPosition } from 'vs/base/browser/ui/contextview/contextview'; +import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget'; export const IHoverService = createDecorator('hoverService'); @@ -82,10 +82,20 @@ export interface IHoverOptions { hideOnHover?: boolean; /** - * Whether to anchor the hover above (default) or below the target. This option will be ignored - * if there is not enough room to layout the hover in the specified anchor position. + * Position of the hover. Default is to show above the target. + * It will be ignored if there is not enough room to layout the hover in the specified position. */ - anchorPosition?: AnchorPosition; + hoverPosition?: HoverPosition; + + /** + * Whether to show the hover pointer + */ + showPointer?: boolean; + + /** + * Whether to show a compact hover + */ + compact?: boolean; } export interface IHoverAction { @@ -128,4 +138,10 @@ export interface IHoverTarget extends IDisposable { * hover using `MouseEvent.pageX`. */ x?: number; + + /** + * An optional absolute y coordinate to position the hover with, for example to position the + * hover using `MouseEvent.pageY`. + */ + y?: number; } diff --git a/src/vs/workbench/services/hover/browser/hoverService.ts b/src/vs/workbench/services/hover/browser/hoverService.ts index 9a2f6085..0e1b8093 100644 --- a/src/vs/workbench/services/hover/browser/hoverService.ts +++ b/src/vs/workbench/services/hover/browser/hoverService.ts @@ -101,6 +101,7 @@ registerThemingParticipant((theme, collector) => { const hoverBackground = theme.getColor(editorHoverBackground); if (hoverBackground) { collector.addRule(`.monaco-workbench .workbench-hover { background-color: ${hoverBackground}; }`); + collector.addRule(`.monaco-workbench .workbench-hover-pointer:after { background-color: ${hoverBackground}; }`); } const hoverBorder = theme.getColor(editorHoverBorder); if (hoverBorder) { @@ -108,6 +109,9 @@ registerThemingParticipant((theme, collector) => { collector.addRule(`.monaco-workbench .workbench-hover .hover-row:not(:first-child):not(:empty) { border-top: 1px solid ${hoverBorder.transparent(0.5)}; }`); collector.addRule(`.monaco-workbench .workbench-hover hr { border-top: 1px solid ${hoverBorder.transparent(0.5)}; }`); collector.addRule(`.monaco-workbench .workbench-hover hr { border-bottom: 0px solid ${hoverBorder.transparent(0.5)}; }`); + + collector.addRule(`.monaco-workbench .workbench-hover-pointer:after { border-right: 1px solid ${hoverBorder}; }`); + collector.addRule(`.monaco-workbench .workbench-hover-pointer:after { border-bottom: 1px solid ${hoverBorder}; }`); } const link = theme.getColor(textLinkForeground); if (link) { diff --git a/src/vs/workbench/services/hover/browser/hoverWidget.ts b/src/vs/workbench/services/hover/browser/hoverWidget.ts index fe21b0cc..e1312fe8 100644 --- a/src/vs/workbench/services/hover/browser/hoverWidget.ts +++ b/src/vs/workbench/services/hover/browser/hoverWidget.ts @@ -11,7 +11,7 @@ import { IHoverTarget, IHoverOptions } from 'vs/workbench/services/hover/browser import { KeyCode } from 'vs/base/common/keyCodes'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { EDITOR_FONT_DEFAULTS, IEditorOptions } from 'vs/editor/common/config/editorOptions'; -import { HoverWidget as BaseHoverWidget, renderHoverAction } from 'vs/base/browser/ui/hover/hoverWidget'; +import { HoverPosition, HoverWidget as BaseHoverWidget, renderHoverAction } from 'vs/base/browser/ui/hover/hoverWidget'; import { Widget } from 'vs/base/browser/ui/widget'; import { AnchorPosition } from 'vs/base/browser/ui/contextview/contextview'; import { IOpenerService } from 'vs/platform/opener/common/opener'; @@ -20,17 +20,27 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { MarkdownRenderer } from 'vs/editor/browser/core/markdownRenderer'; const $ = dom.$; +type TargetRect = { + left: number, + right: number, + top: number, + bottom: number, + width: number, + height: number, + center: { x: number, y: number }, +}; export class HoverWidget extends Widget { private readonly _messageListeners = new DisposableStore(); private readonly _mouseTracker: CompositeMouseTracker; private readonly _hover: BaseHoverWidget; + private readonly _hoverPointer: HTMLElement | undefined; private readonly _target: IHoverTarget; private readonly _linkHandler: (url: string) => any; private _isDisposed: boolean = false; - private _anchor: AnchorPosition; + private _hoverPosition: HoverPosition; private _x: number = 0; private _y: number = 0; @@ -42,7 +52,7 @@ export class HoverWidget extends Widget { private readonly _onRequestLayout = this._register(new Emitter()); get onRequestLayout(): Event { return this._onRequestLayout.event; } - get anchor(): AnchorPosition { return this._anchor; } + get anchor(): AnchorPosition { return this._hoverPosition === HoverPosition.BELOW ? AnchorPosition.BELOW : AnchorPosition.ABOVE; } get x(): number { return this._x; } get y(): number { return this._y; } @@ -60,13 +70,17 @@ export class HoverWidget extends Widget { this._target = 'targetElements' in options.target ? options.target : new ElementHoverTarget(options.target); + this._hoverPointer = options.showPointer ? $('div.workbench-hover-pointer') : undefined; this._hover = this._register(new BaseHoverWidget()); this._hover.containerDomNode.classList.add('workbench-hover', 'fadeIn'); + if (options.compact) { + this._hover.containerDomNode.classList.add('workbench-hover', 'compact'); + } if (options.additionalClasses) { this._hover.containerDomNode.classList.add(...options.additionalClasses); } - this._anchor = options.anchorPosition ?? AnchorPosition.ABOVE; + this._hoverPosition = options.hoverPosition ?? HoverPosition.ABOVE; // Don't allow mousedown out of the widget, otherwise preventDefault will call and text will // not be selected. @@ -129,17 +143,17 @@ export class HoverWidget extends Widget { const mouseTrackerTargets = [...this._target.targetElements]; let hideOnHover: boolean; - if (options.hideOnHover === undefined) { - if (options.actions && options.actions.length > 0) { - // If there are actions, require hover so they can be accessed - hideOnHover = false; - } else { + if (options.actions && options.actions.length > 0) { + // If there are actions, require hover so they can be accessed + hideOnHover = false; + } else { + if (options.hideOnHover === undefined) { // Defaults to true when string, false when markdown as it may contain links hideOnHover = typeof options.text === 'string'; + } else { + // It's set explicitly + hideOnHover = options.hideOnHover; } - } else { - // It's set explicitly - hideOnHover = options.hideOnHover; } if (!hideOnHover) { mouseTrackerTargets.push(this._hover.containerDomNode); @@ -150,6 +164,9 @@ export class HoverWidget extends Widget { } public render(container?: HTMLElement): void { + if (this._hoverPointer) { + container?.appendChild(this._hoverPointer); + } if (this._hover.containerDomNode.parentElement !== container) { container?.appendChild(this._hover.containerDomNode); } @@ -162,40 +179,185 @@ export class HoverWidget extends Widget { this._hover.contentsDomNode.style.maxHeight = ''; const targetBounds = this._target.targetElements.map(e => e.getBoundingClientRect()); + const top = Math.min(...targetBounds.map(e => e.top)); + const right = Math.max(...targetBounds.map(e => e.right)); + const bottom = Math.max(...targetBounds.map(e => e.bottom)); + const left = Math.min(...targetBounds.map(e => e.left)); + const width = right - left; + const height = bottom - top; - // Get horizontal alignment and position - let targetLeft = this._target.x !== undefined ? this._target.x : Math.min(...targetBounds.map(e => e.left)); - if (targetLeft + this._hover.containerDomNode.clientWidth >= document.documentElement.clientWidth) { - this._x = document.documentElement.clientWidth - this._workbenchLayoutService.getWindowBorderWidth() - 1; - this._hover.containerDomNode.classList.add('right-aligned'); - } else { - this._x = targetLeft; - } + const targetRect: TargetRect = { + top, right, bottom, left, width, height, + center: { + x: left + (width / 2), + y: top + (height / 2) + } + }; - // Get vertical alignment and position - if (this._anchor === AnchorPosition.ABOVE) { - const targetTop = Math.min(...targetBounds.map(e => e.top)); - if (targetTop - this._hover.containerDomNode.clientHeight < 0) { - const targetBottom = Math.max(...targetBounds.map(e => e.bottom)); - this._anchor = AnchorPosition.BELOW; - this._y = targetBottom - 2; - } else { - this._y = targetTop; - } - } else { - const targetBottom = Math.max(...targetBounds.map(e => e.bottom)); - if (targetBottom + this._hover.containerDomNode.clientHeight > window.innerHeight) { - const targetTop = Math.min(...targetBounds.map(e => e.top)); - this._anchor = AnchorPosition.ABOVE; - this._y = targetTop; - } else { - this._y = targetBottom - 2; - } + this.adjustHorizontalHoverPosition(targetRect); + this.adjustVerticalHoverPosition(targetRect); + this.computeXCordinate(targetRect); + this.computeYCordinate(targetRect); + + if (this._hoverPointer) { + // reset + this._hoverPointer.classList.remove('top'); + this._hoverPointer.classList.remove('left'); + this._hoverPointer.classList.remove('right'); + this._hoverPointer.classList.remove('bottom'); + + this.setHoverPointerPosition(targetRect); } this._hover.onContentsChanged(); } + private computeXCordinate(target: TargetRect): void { + if (this._target.x !== undefined) { + this._x = this._target.x; + } + + else if (this._hoverPosition === HoverPosition.RIGHT) { + this._x = target.right; + } + + else if (this._hoverPosition === HoverPosition.LEFT) { + this._x = target.left; + } + + else { + if (this._hoverPointer) { + this._x = target.center.x - (this._hover.containerDomNode.clientWidth / 2); + } else { + if (target.left + this._hover.containerDomNode.clientWidth >= document.documentElement.clientWidth) { + this._hover.containerDomNode.classList.add('right-aligned'); + this._x = document.documentElement.clientWidth - this._workbenchLayoutService.getWindowBorderWidth() - 1; + } else { + this._x = target.left; + } + } + } + + // Hover on left is going beyond window + if (this._x < document.documentElement.clientLeft) { + this._x = target.left; + } + + } + + private computeYCordinate(target: TargetRect): void { + if (this._target.y !== undefined) { + this._y = this._target.y; + } + + else if (this._hoverPosition === HoverPosition.ABOVE) { + this._y = target.top; + } + + else if (this._hoverPosition === HoverPosition.BELOW) { + this._y = target.bottom - 2; + } + + else { + if (this._hoverPointer) { + this._y = target.center.y + (this._hover.containerDomNode.clientHeight / 2); + } else { + this._y = target.bottom; + } + } + + // Hover on bottom is going beyond window + if (this._y > window.innerHeight) { + this._y = target.bottom; + } + } + + private adjustHorizontalHoverPosition(target: TargetRect): void { + // Do not adjust horizontal hover position if x cordiante is provided + if (this._target.x !== undefined) { + return; + } + + // Position hover on right to target + if (this._hoverPosition === HoverPosition.RIGHT) { + // Hover on the right is going beyond window. + if (target.right + this._hover.containerDomNode.clientWidth >= document.documentElement.clientWidth) { + this._hoverPosition = HoverPosition.LEFT; + } + } + + // Position hover on left to target + if (this._hoverPosition === HoverPosition.LEFT) { + // Hover on the left is going beyond window. + if (target.left - this._hover.containerDomNode.clientWidth <= document.documentElement.clientLeft) { + this._hoverPosition = HoverPosition.RIGHT; + } + } + } + + private adjustVerticalHoverPosition(target: TargetRect): void { + // Do not adjust vertical hover position if y cordiante is provided + if (this._target.y !== undefined) { + return; + } + + // Position hover on top of the target + if (this._hoverPosition === HoverPosition.ABOVE) { + // Hover on top is going beyond window + if (target.top - this._hover.containerDomNode.clientHeight < 0) { + this._hoverPosition = HoverPosition.BELOW; + } + } + + // Position hover below the target + else if (this._hoverPosition === HoverPosition.BELOW) { + // Hover on bottom is going beyond window + if (target.bottom + this._hover.containerDomNode.clientHeight > window.innerHeight) { + this._hoverPosition = HoverPosition.ABOVE; + } + } + } + + private setHoverPointerPosition(target: TargetRect): void { + if (!this._hoverPointer) { + return; + } + + switch (this._hoverPosition) { + case HoverPosition.LEFT: + case HoverPosition.RIGHT: + this._hoverPointer.classList.add(this._hoverPosition === HoverPosition.LEFT ? 'right' : 'left'); + const hoverHeight = this._hover.containerDomNode.clientHeight; + + // If hover is taller than target and aligned with target's bottom, then show the pointer at the center of target + if (hoverHeight > target.height && this._y === target.bottom) { + this._hoverPointer.style.top = `${target.center.y - target.top - 3}px`; + } + + // Otherwise show the pointer at the center of hover + else { + this._hoverPointer.style.top = `${Math.round((hoverHeight / 2)) - 3}px`; + } + + break; + case HoverPosition.ABOVE: + case HoverPosition.BELOW: + this._hoverPointer.classList.add(this._hoverPosition === HoverPosition.ABOVE ? 'bottom' : 'top'); + const hoverWidth = this._hover.containerDomNode.clientWidth; + + // If hover is wider than target and aligned with target's left, then show the pointer at the center of target + if (hoverWidth > target.width && this._x === target.left) { + this._hoverPointer.style.left = `${target.center.x - target.left - 3}px`; + } + + // Otherwise show the pointer at the center of hover + else { + this._hoverPointer.style.left = `${Math.round((hoverWidth / 2)) - 3}px`; + } + break; + } + } + public focus() { this._hover.containerDomNode.focus(); } @@ -204,10 +366,13 @@ export class HoverWidget extends Widget { this.dispose(); } - public dispose(): void { + public override dispose(): void { if (!this._isDisposed) { this._onDispose.fire(); - this._hover.containerDomNode.parentElement?.removeChild(this.domNode); + if (this._hoverPointer) { + this._hoverPointer.parentElement?.removeChild(this._hoverPointer); + } + this._hover.containerDomNode.parentElement?.removeChild(this._hover.containerDomNode); this._messageListeners.dispose(); this._target.dispose(); super.dispose(); diff --git a/src/vs/workbench/services/hover/browser/media/hover.css b/src/vs/workbench/services/hover/browser/media/hover.css index febd78d6..e0ab2b10 100644 --- a/src/vs/workbench/services/hover/browser/media/hover.css +++ b/src/vs/workbench/services/hover/browser/media/hover.css @@ -14,6 +14,63 @@ max-width: 700px; } +.monaco-workbench .workbench-hover.compact { + font-size: 12px; +} + +.monaco-workbench .workbench-hover.compact .hover-contents { + padding: 2px 8px; +} + +.monaco-workbench .workbench-hover-pointer { + position: absolute; + /* Must be higher than workbench hover z-index */ + z-index: 41; +} + +.monaco-workbench .workbench-hover-pointer:after { + content: ''; + position: absolute; + width: 5px; + height: 5px; +} + +.monaco-workbench .workbench-hover-pointer.left { + left: -3px; +} + +.monaco-workbench .workbench-hover-pointer.left:after { + -moz-transform: rotate(135deg); + -webkit-transform: rotate(135deg); +} + +.monaco-workbench .workbench-hover-pointer.right { + right: 3px; +} + +.monaco-workbench .workbench-hover-pointer.right:after { + -moz-transform: rotate(315deg); + -webkit-transform: rotate(315deg); +} + +.monaco-workbench .workbench-hover-pointer.top { + top: -3px; +} + +.monaco-workbench .workbench-hover-pointer.top:after { + -moz-transform: rotate(225deg); + -webkit-transform: rotate(225deg); +} + +.monaco-workbench .workbench-hover-pointer.bottom { + bottom: 3px; +} + +.monaco-workbench .workbench-hover-pointer.bottom:after { + -moz-transform: rotate(45deg); + -webkit-transform: rotate(45deg); +} + .monaco-workbench .workbench-hover a { color: #3794ff; } diff --git a/src/vs/workbench/services/keybinding/browser/keybindingService.ts b/src/vs/workbench/services/keybinding/browser/keybindingService.ts index 728be660..eea2f001 100644 --- a/src/vs/workbench/services/keybinding/browser/keybindingService.ts +++ b/src/vs/workbench/services/keybinding/browser/keybindingService.ts @@ -14,7 +14,7 @@ import { KeybindingParser } from 'vs/base/common/keybindingParser'; import { OS, OperatingSystem, isMacintosh } from 'vs/base/common/platform'; import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { Extensions as ConfigExtensions, IConfigurationNode, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; +import { ConfigurationScope, Extensions as ConfigExtensions, IConfigurationNode, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { ContextKeyExpr, IContextKeyService, ContextKeyExpression, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { Extensions, IJSONContributionRegistry } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; @@ -353,7 +353,7 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { return JSON.stringify(info, null, '\t'); } - public customKeybindingsCount(): number { + public override customKeybindingsCount(): number { return this.userKeybindings.keybindings.length; } @@ -578,7 +578,7 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { return desc; } - public getDefaultKeybindingsContent(): string { + public override getDefaultKeybindingsContent(): string { const resolver = this._getResolver(); const defaultKeybindings = resolver.getDefaultKeybindings(); const boundCommands = resolver.getDefaultBoundCommands(); @@ -612,7 +612,7 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { return '// ' + nls.localize('unboundCommands', "Here are other available commands: ") + '\n// - ' + pretty; } - mightProducePrintableCharacter(event: IKeyboardEvent): boolean { + override mightProducePrintableCharacter(event: IKeyboardEvent): boolean { if (event.ctrlKey || event.metaKey || event.altKey) { // ignore ctrl/cmd/alt-combination but not shift-combinatios return false; @@ -843,11 +843,12 @@ const keyboardConfiguration: IConfigurationNode = { 'title': nls.localize('keyboardConfigurationTitle', "Keyboard"), 'properties': { 'keyboard.dispatch': { - 'type': 'string', - 'enum': ['code', 'keyCode'], - 'default': 'code', - 'markdownDescription': nls.localize('dispatch', "Controls the dispatching logic for key presses to use either `code` (recommended) or `keyCode`."), - 'included': OS === OperatingSystem.Macintosh || OS === OperatingSystem.Linux + scope: ConfigurationScope.APPLICATION, + type: 'string', + enum: ['code', 'keyCode'], + default: 'code', + markdownDescription: nls.localize('dispatch', "Controls the dispatching logic for key presses to use either `code` (recommended) or `keyCode`."), + included: OS === OperatingSystem.Macintosh || OS === OperatingSystem.Linux } } }; diff --git a/src/vs/workbench/services/keybinding/browser/keyboardLayoutService.ts b/src/vs/workbench/services/keybinding/browser/keyboardLayoutService.ts index 20165cf4..6d71a818 100644 --- a/src/vs/workbench/services/keybinding/browser/keyboardLayoutService.ts +++ b/src/vs/workbench/services/keybinding/browser/keyboardLayoutService.ts @@ -190,7 +190,7 @@ export class BrowserKeyboardMapperFactoryBase { // return; // } - // // the keyboard layout doesn't actually match the key event or the keymap from chromium + // the keyboard layout doesn't actually match the key event or the keymap from chromium // this._notificationService.prompt( // Severity.Info, // nls.localize('missing.keyboardlayout', 'Fail to find matching keyboard layout'), diff --git a/src/vs/workbench/services/keybinding/common/macLinuxFallbackKeyboardMapper.ts b/src/vs/workbench/services/keybinding/common/macLinuxFallbackKeyboardMapper.ts index eff466f8..b76f6925 100644 --- a/src/vs/workbench/services/keybinding/common/macLinuxFallbackKeyboardMapper.ts +++ b/src/vs/workbench/services/keybinding/common/macLinuxFallbackKeyboardMapper.ts @@ -46,7 +46,7 @@ export class MacLinuxFallbackKeyboardMapper implements IKeyboardMapper { private _scanCodeToKeyCode(scanCode: ScanCode): KeyCode { const immutableKeyCode = IMMUTABLE_CODE_TO_KEY_CODE[scanCode]; - if (immutableKeyCode !== -1) { + if (immutableKeyCode !== KeyCode.DependsOnKbLayout) { return immutableKeyCode; } diff --git a/src/vs/workbench/services/keybinding/common/macLinuxKeyboardMapper.ts b/src/vs/workbench/services/keybinding/common/macLinuxKeyboardMapper.ts index 79e6c4cf..b4062798 100644 --- a/src/vs/workbench/services/keybinding/common/macLinuxKeyboardMapper.ts +++ b/src/vs/workbench/services/keybinding/common/macLinuxKeyboardMapper.ts @@ -49,7 +49,7 @@ export class NativeResolvedKeybinding extends BaseResolvedKeybinding= 0) { const immutableScanCode = IMMUTABLE_KEY_CODE_TO_CODE[keyCode]; - if (immutableScanCode !== -1) { + if (immutableScanCode !== ScanCode.DependsOnKbLayout) { code = immutableScanCode; } } diff --git a/src/vs/workbench/services/keybinding/common/windowsKeyboardMapper.ts b/src/vs/workbench/services/keybinding/common/windowsKeyboardMapper.ts index ccca77c4..cc5d83b7 100644 --- a/src/vs/workbench/services/keybinding/common/windowsKeyboardMapper.ts +++ b/src/vs/workbench/services/keybinding/common/windowsKeyboardMapper.ts @@ -180,7 +180,7 @@ export class WindowsKeyboardMapper implements IKeyboardMapper { for (let scanCode = ScanCode.None; scanCode < ScanCode.MAX_VALUE; scanCode++) { const immutableKeyCode = IMMUTABLE_CODE_TO_KEY_CODE[scanCode]; - if (immutableKeyCode !== -1) { + if (immutableKeyCode !== KeyCode.DependsOnKbLayout) { this._scanCodeToKeyCode[scanCode] = immutableKeyCode; this._keyCodeToLabel[immutableKeyCode] = KeyCodeUtils.toString(immutableKeyCode); this._keyCodeExists[immutableKeyCode] = true; @@ -201,7 +201,7 @@ export class WindowsKeyboardMapper implements IKeyboardMapper { const rawMapping = rawMappings[strCode]; const immutableKeyCode = IMMUTABLE_CODE_TO_KEY_CODE[scanCode]; - if (immutableKeyCode !== -1) { + if (immutableKeyCode !== KeyCode.DependsOnKbLayout) { const keyCode = NATIVE_KEY_CODE_TO_KEY_CODE[rawMapping.vkey] || KeyCode.Unknown; if (keyCode === KeyCode.Unknown || immutableKeyCode === keyCode) { continue; @@ -337,7 +337,7 @@ export class WindowsKeyboardMapper implements IKeyboardMapper { let cnt = 0; result.push(`-----------------------------------------------------------------------------------------------------------------------------------------`); for (let scanCode = ScanCode.None; scanCode < ScanCode.MAX_VALUE; scanCode++) { - if (IMMUTABLE_CODE_TO_KEY_CODE[scanCode] !== -1) { + if (IMMUTABLE_CODE_TO_KEY_CODE[scanCode] !== KeyCode.DependsOnKbLayout) { if (immutableSamples.indexOf(scanCode) === -1) { continue; } diff --git a/src/vs/workbench/services/keybinding/test/browser/keybindingEditing.test.ts b/src/vs/workbench/services/keybinding/test/browser/keybindingEditing.test.ts index e93988e6..679e0b10 100644 --- a/src/vs/workbench/services/keybinding/test/browser/keybindingEditing.test.ts +++ b/src/vs/workbench/services/keybinding/test/browser/keybindingEditing.test.ts @@ -27,26 +27,26 @@ import { ILogService, NullLogService } from 'vs/platform/log/common/log'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; +import { IWorkingCopyBackupService } from 'vs/workbench/services/workingCopy/common/workingCopyBackup'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { KeybindingsEditingService } from 'vs/workbench/services/keybinding/common/keybindingEditing'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { TextModelResolverService } from 'vs/workbench/services/textmodelResolver/common/textModelResolverService'; -import { TestBackupFileService, TestEditorGroupsService, TestEditorService, TestEnvironmentService, TestLifecycleService, TestPathService, TestTextFileService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestWorkingCopyBackupService, TestEditorGroupsService, TestEditorService, TestEnvironmentService, TestLifecycleService, TestPathService, TestTextFileService } from 'vs/workbench/test/browser/workbenchTestServices'; import { FileService } from 'vs/platform/files/common/fileService'; import { Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; import { FileUserDataProvider } from 'vs/workbench/services/userData/common/fileUserDataProvider'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; -import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { IWorkingCopyService, WorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { ILabelService } from 'vs/platform/label/common/label'; import { LabelService } from 'vs/workbench/services/label/common/labelService'; import { IFilesConfigurationService, FilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { WorkingCopyFileService, IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService'; -import { TestTextResourcePropertiesService, TestContextService, TestWorkingCopyService } from 'vs/workbench/test/common/workbenchTestServices'; +import { TestTextResourcePropertiesService, TestContextService } from 'vs/workbench/test/common/workbenchTestServices'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; import { IPathService } from 'vs/workbench/services/path/common/pathService'; @@ -98,7 +98,7 @@ suite('KeybindingsEditing', () => { instantiationService.stub(IContextKeyService, instantiationService.createInstance(MockContextKeyService)); instantiationService.stub(IEditorGroupsService, new TestEditorGroupsService()); instantiationService.stub(IEditorService, new TestEditorService()); - instantiationService.stub(IWorkingCopyService, new TestWorkingCopyService()); + instantiationService.stub(IWorkingCopyService, disposables.add(new WorkingCopyService())); instantiationService.stub(ITelemetryService, NullTelemetryService); instantiationService.stub(IModeService, ModeServiceImpl); instantiationService.stub(ILogService, new NullLogService()); @@ -111,11 +111,10 @@ suite('KeybindingsEditing', () => { fileService.registerProvider(Schemas.userData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.userData, new NullLogService()))); instantiationService.stub(IFileService, fileService); instantiationService.stub(IUriIdentityService, new UriIdentityService(fileService)); - instantiationService.stub(IWorkingCopyService, disposables.add(new TestWorkingCopyService())); instantiationService.stub(IWorkingCopyFileService, disposables.add(instantiationService.createInstance(WorkingCopyFileService))); instantiationService.stub(ITextFileService, disposables.add(instantiationService.createInstance(TestTextFileService))); instantiationService.stub(ITextModelService, disposables.add(instantiationService.createInstance(TextModelResolverService))); - instantiationService.stub(IBackupFileService, new TestBackupFileService()); + instantiationService.stub(IWorkingCopyBackupService, new TestWorkingCopyBackupService()); testObject = disposables.add(instantiationService.createInstance(KeybindingsEditingService)); @@ -129,7 +128,7 @@ suite('KeybindingsEditing', () => { await testObject.editKeybinding(aResolvedKeybindingItem({ firstPart: { keyCode: KeyCode.Escape } }), 'alt+c', undefined); assert.fail('Should fail with parse errors'); } catch (error) { - assert.equal(error.message, 'Unable to write to the keybindings configuration file. Please open it to correct errors/warnings in the file and try again.'); + assert.strictEqual(error.message, 'Unable to write to the keybindings configuration file. Please open it to correct errors/warnings in the file and try again.'); } }); @@ -139,7 +138,7 @@ suite('KeybindingsEditing', () => { await testObject.editKeybinding(aResolvedKeybindingItem({ firstPart: { keyCode: KeyCode.Escape } }), 'alt+c', undefined); assert.fail('Should fail with parse errors'); } catch (error) { - assert.equal(error.message, 'Unable to write to the keybindings configuration file. Please open it to correct errors/warnings in the file and try again.'); + assert.strictEqual(error.message, 'Unable to write to the keybindings configuration file. Please open it to correct errors/warnings in the file and try again.'); } }); @@ -147,7 +146,7 @@ suite('KeybindingsEditing', () => { instantiationService.stub(ITextFileService, 'isDirty', true); return testObject.editKeybinding(aResolvedKeybindingItem({ firstPart: { keyCode: KeyCode.Escape } }), 'alt+c', undefined) .then(() => assert.fail('Should fail with dirty error'), - error => assert.equal(error.message, 'Unable to write because the keybindings configuration file is dirty. Please save it first and then try again.')); + error => assert.strictEqual(error.message, 'Unable to write because the keybindings configuration file is dirty. Please save it first and then try again.')); }); test('errors cases - did not find an array', async () => { @@ -156,7 +155,7 @@ suite('KeybindingsEditing', () => { await testObject.editKeybinding(aResolvedKeybindingItem({ firstPart: { keyCode: KeyCode.Escape } }), 'alt+c', undefined); assert.fail('Should fail'); } catch (error) { - assert.equal(error.message, 'Unable to write to the keybindings configuration file. It has an object which is not of type Array. Please open the file to clean up and try again.'); + assert.strictEqual(error.message, 'Unable to write to the keybindings configuration file. It has an object which is not of type Array. Please open the file to clean up and try again.'); } }); @@ -164,59 +163,59 @@ suite('KeybindingsEditing', () => { await fileService.writeFile(environmentService.keybindingsResource, VSBuffer.fromString('')); const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: 'a' }, { key: 'escape', command: '-a' }]; await testObject.editKeybinding(aResolvedKeybindingItem({ firstPart: { keyCode: KeyCode.Escape }, command: 'a' }), 'alt+c', undefined); - assert.deepEqual(await getUserKeybindings(), expected); + assert.deepStrictEqual(await getUserKeybindings(), expected); }); test('edit a default keybinding to an empty array', async () => { await writeToKeybindingsFile(); const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: 'a' }, { key: 'escape', command: '-a' }]; await testObject.editKeybinding(aResolvedKeybindingItem({ firstPart: { keyCode: KeyCode.Escape }, command: 'a' }), 'alt+c', undefined); - return assert.deepEqual(await getUserKeybindings(), expected); + return assert.deepStrictEqual(await getUserKeybindings(), expected); }); test('edit a default keybinding in an existing array', async () => { await writeToKeybindingsFile({ command: 'b', key: 'shift+c' }); const expected: IUserFriendlyKeybinding[] = [{ key: 'shift+c', command: 'b' }, { key: 'alt+c', command: 'a' }, { key: 'escape', command: '-a' }]; await testObject.editKeybinding(aResolvedKeybindingItem({ firstPart: { keyCode: KeyCode.Escape }, command: 'a' }), 'alt+c', undefined); - return assert.deepEqual(await getUserKeybindings(), expected); + return assert.deepStrictEqual(await getUserKeybindings(), expected); }); test('add another keybinding', async () => { const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: 'a' }]; await testObject.addKeybinding(aResolvedKeybindingItem({ firstPart: { keyCode: KeyCode.Escape }, command: 'a' }), 'alt+c', undefined); - return assert.deepEqual(await getUserKeybindings(), expected); + return assert.deepStrictEqual(await getUserKeybindings(), expected); }); test('add a new default keybinding', async () => { const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: 'a' }]; await testObject.addKeybinding(aResolvedKeybindingItem({ command: 'a' }), 'alt+c', undefined); - return assert.deepEqual(await getUserKeybindings(), expected); + return assert.deepStrictEqual(await getUserKeybindings(), expected); }); test('add a new default keybinding using edit', async () => { const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: 'a' }]; await testObject.editKeybinding(aResolvedKeybindingItem({ command: 'a' }), 'alt+c', undefined); - assert.deepEqual(await getUserKeybindings(), expected); + assert.deepStrictEqual(await getUserKeybindings(), expected); }); test('edit an user keybinding', async () => { await writeToKeybindingsFile({ key: 'escape', command: 'b' }); const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: 'b' }]; await testObject.editKeybinding(aResolvedKeybindingItem({ firstPart: { keyCode: KeyCode.Escape }, command: 'b', isDefault: false }), 'alt+c', undefined); - assert.deepEqual(await getUserKeybindings(), expected); + assert.deepStrictEqual(await getUserKeybindings(), expected); }); test('edit an user keybinding with more than one element', async () => { await writeToKeybindingsFile({ key: 'escape', command: 'b' }, { key: 'alt+shift+g', command: 'c' }); const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: 'b' }, { key: 'alt+shift+g', command: 'c' }]; await testObject.editKeybinding(aResolvedKeybindingItem({ firstPart: { keyCode: KeyCode.Escape }, command: 'b', isDefault: false }), 'alt+c', undefined); - assert.deepEqual(await getUserKeybindings(), expected); + assert.deepStrictEqual(await getUserKeybindings(), expected); }); test('remove a default keybinding', async () => { const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: '-a' }]; await testObject.removeKeybinding(aResolvedKeybindingItem({ command: 'a', firstPart: { keyCode: KeyCode.KEY_C, modifiers: { altKey: true } } })); - assert.deepEqual(await getUserKeybindings(), expected); + assert.deepStrictEqual(await getUserKeybindings(), expected); }); test('remove a default keybinding should not ad duplicate entries', async () => { @@ -226,25 +225,25 @@ suite('KeybindingsEditing', () => { await testObject.removeKeybinding(aResolvedKeybindingItem({ command: 'a', firstPart: { keyCode: KeyCode.KEY_C, modifiers: { altKey: true } } })); await testObject.removeKeybinding(aResolvedKeybindingItem({ command: 'a', firstPart: { keyCode: KeyCode.KEY_C, modifiers: { altKey: true } } })); await testObject.removeKeybinding(aResolvedKeybindingItem({ command: 'a', firstPart: { keyCode: KeyCode.KEY_C, modifiers: { altKey: true } } })); - assert.deepEqual(await getUserKeybindings(), expected); + assert.deepStrictEqual(await getUserKeybindings(), expected); }); test('remove a user keybinding', async () => { await writeToKeybindingsFile({ key: 'alt+c', command: 'b' }); await testObject.removeKeybinding(aResolvedKeybindingItem({ command: 'b', firstPart: { keyCode: KeyCode.KEY_C, modifiers: { altKey: true } }, isDefault: false })); - assert.deepEqual(await getUserKeybindings(), []); + assert.deepStrictEqual(await getUserKeybindings(), []); }); test('reset an edited keybinding', async () => { await writeToKeybindingsFile({ key: 'alt+c', command: 'b' }); await testObject.resetKeybinding(aResolvedKeybindingItem({ command: 'b', firstPart: { keyCode: KeyCode.KEY_C, modifiers: { altKey: true } }, isDefault: false })); - assert.deepEqual(await getUserKeybindings(), []); + assert.deepStrictEqual(await getUserKeybindings(), []); }); test('reset a removed keybinding', async () => { await writeToKeybindingsFile({ key: 'alt+c', command: '-b' }); await testObject.resetKeybinding(aResolvedKeybindingItem({ command: 'b', isDefault: false })); - assert.deepEqual(await getUserKeybindings(), []); + assert.deepStrictEqual(await getUserKeybindings(), []); }); test('reset multiple removed keybindings', async () => { @@ -252,42 +251,42 @@ suite('KeybindingsEditing', () => { await writeToKeybindingsFile({ key: 'alt+shift+c', command: '-b' }); await writeToKeybindingsFile({ key: 'escape', command: '-b' }); await testObject.resetKeybinding(aResolvedKeybindingItem({ command: 'b', isDefault: false })); - assert.deepEqual(await getUserKeybindings(), []); + assert.deepStrictEqual(await getUserKeybindings(), []); }); test('add a new keybinding to unassigned keybinding', async () => { await writeToKeybindingsFile({ key: 'alt+c', command: '-a' }); const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: '-a' }, { key: 'shift+alt+c', command: 'a' }]; await testObject.editKeybinding(aResolvedKeybindingItem({ command: 'a', isDefault: false }), 'shift+alt+c', undefined); - assert.deepEqual(await getUserKeybindings(), expected); + assert.deepStrictEqual(await getUserKeybindings(), expected); }); test('add when expression', async () => { await writeToKeybindingsFile({ key: 'alt+c', command: '-a' }); const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: '-a' }, { key: 'shift+alt+c', command: 'a', when: 'editorTextFocus' }]; await testObject.editKeybinding(aResolvedKeybindingItem({ command: 'a', isDefault: false }), 'shift+alt+c', 'editorTextFocus'); - assert.deepEqual(await getUserKeybindings(), expected); + assert.deepStrictEqual(await getUserKeybindings(), expected); }); test('update command and when expression', async () => { await writeToKeybindingsFile({ key: 'alt+c', command: '-a', when: 'editorTextFocus && !editorReadonly' }); const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: '-a', when: 'editorTextFocus && !editorReadonly' }, { key: 'shift+alt+c', command: 'a', when: 'editorTextFocus' }]; await testObject.editKeybinding(aResolvedKeybindingItem({ command: 'a', isDefault: false }), 'shift+alt+c', 'editorTextFocus'); - assert.deepEqual(await getUserKeybindings(), expected); + assert.deepStrictEqual(await getUserKeybindings(), expected); }); test('update when expression', async () => { await writeToKeybindingsFile({ key: 'alt+c', command: '-a', when: 'editorTextFocus && !editorReadonly' }, { key: 'shift+alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }); const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: '-a', when: 'editorTextFocus && !editorReadonly' }, { key: 'shift+alt+c', command: 'a', when: 'editorTextFocus' }]; await testObject.editKeybinding(aResolvedKeybindingItem({ command: 'a', isDefault: false, when: 'editorTextFocus && !editorReadonly' }), 'shift+alt+c', 'editorTextFocus'); - assert.deepEqual(await getUserKeybindings(), expected); + assert.deepStrictEqual(await getUserKeybindings(), expected); }); test('remove when expression', async () => { await writeToKeybindingsFile({ key: 'alt+c', command: '-a', when: 'editorTextFocus && !editorReadonly' }); const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: '-a', when: 'editorTextFocus && !editorReadonly' }, { key: 'shift+alt+c', command: 'a' }]; await testObject.editKeybinding(aResolvedKeybindingItem({ command: 'a', isDefault: false }), 'shift+alt+c', undefined); - assert.deepEqual(await getUserKeybindings(), expected); + assert.deepStrictEqual(await getUserKeybindings(), expected); }); async function writeToKeybindingsFile(...keybindings: IUserFriendlyKeybinding[]): Promise { diff --git a/src/vs/workbench/services/label/common/labelService.ts b/src/vs/workbench/services/label/common/labelService.ts index 550b36e9..d8f65990 100644 --- a/src/vs/workbench/services/label/common/labelService.ts +++ b/src/vs/workbench/services/label/common/labelService.ts @@ -99,7 +99,7 @@ export class LabelService extends Disposable implements ILabelService { private formatters: ResourceLabelFormatter[] = []; - private readonly _onDidChangeFormatters = this._register(new Emitter()); + private readonly _onDidChangeFormatters = this._register(new Emitter({ leakWarningThreshold: 400 })); readonly onDidChangeFormatters = this._onDidChangeFormatters.event; constructor( diff --git a/src/vs/workbench/services/label/test/browser/label.test.ts b/src/vs/workbench/services/label/test/browser/label.test.ts index 7a032c47..fb8db654 100644 --- a/src/vs/workbench/services/label/test/browser/label.test.ts +++ b/src/vs/workbench/services/label/test/browser/label.test.ts @@ -31,8 +31,8 @@ suite('URI Label', () => { }); const uri1 = URI.parse('vscode://microsoft.com/1/2/3/4/5'); - assert.equal(labelService.getUriLabel(uri1, { relative: false }), 'LABEL//1/2/3/4/5/microsoft.com/END'); - assert.equal(labelService.getUriBasenameLabel(uri1), 'END'); + assert.strictEqual(labelService.getUriLabel(uri1, { relative: false }), 'LABEL//1/2/3/4/5/microsoft.com/END'); + assert.strictEqual(labelService.getUriBasenameLabel(uri1), 'END'); }); test('separator', function () { @@ -47,8 +47,8 @@ suite('URI Label', () => { }); const uri1 = URI.parse('vscode://microsoft.com/1/2/3/4/5'); - assert.equal(labelService.getUriLabel(uri1, { relative: false }), 'LABEL\\\\1\\2\\3\\4\\5\\microsoft.com\\END'); - assert.equal(labelService.getUriBasenameLabel(uri1), 'END'); + assert.strictEqual(labelService.getUriLabel(uri1, { relative: false }), 'LABEL\\\\1\\2\\3\\4\\5\\microsoft.com\\END'); + assert.strictEqual(labelService.getUriBasenameLabel(uri1), 'END'); }); test('custom authority', function () { @@ -62,8 +62,8 @@ suite('URI Label', () => { }); const uri1 = URI.parse('vscode://microsoft.com/1/2/3/4/5'); - assert.equal(labelService.getUriLabel(uri1, { relative: false }), 'LABEL//1/2/3/4/5/microsoft.com/END'); - assert.equal(labelService.getUriBasenameLabel(uri1), 'END'); + assert.strictEqual(labelService.getUriLabel(uri1, { relative: false }), 'LABEL//1/2/3/4/5/microsoft.com/END'); + assert.strictEqual(labelService.getUriBasenameLabel(uri1), 'END'); }); test('mulitple authority', function () { @@ -94,8 +94,8 @@ suite('URI Label', () => { // Make sure the most specific authority is picked const uri1 = URI.parse('vscode://microsoft.com/1/2/3/4/5'); - assert.equal(labelService.getUriLabel(uri1, { relative: false }), 'second'); - assert.equal(labelService.getUriBasenameLabel(uri1), 'second'); + assert.strictEqual(labelService.getUriLabel(uri1, { relative: false }), 'second'); + assert.strictEqual(labelService.getUriBasenameLabel(uri1), 'second'); }); test('custom query', function () { @@ -110,7 +110,7 @@ suite('URI Label', () => { }); const uri1 = URI.parse(`vscode://microsoft.com/1/2/3/4/5?${encodeURIComponent(JSON.stringify({ prefix: 'prefix', path: 'path' }))}`); - assert.equal(labelService.getUriLabel(uri1, { relative: false }), 'LABELprefix: path/END'); + assert.strictEqual(labelService.getUriLabel(uri1, { relative: false }), 'LABELprefix: path/END'); }); test('custom query without value', function () { @@ -125,7 +125,7 @@ suite('URI Label', () => { }); const uri1 = URI.parse(`vscode://microsoft.com/1/2/3/4/5?${encodeURIComponent(JSON.stringify({ path: 'path' }))}`); - assert.equal(labelService.getUriLabel(uri1, { relative: false }), 'LABEL: path/END'); + assert.strictEqual(labelService.getUriLabel(uri1, { relative: false }), 'LABEL: path/END'); }); test('custom query without query json', function () { @@ -140,7 +140,7 @@ suite('URI Label', () => { }); const uri1 = URI.parse('vscode://microsoft.com/1/2/3/4/5?path=foo'); - assert.equal(labelService.getUriLabel(uri1, { relative: false }), 'LABEL: /END'); + assert.strictEqual(labelService.getUriLabel(uri1, { relative: false }), 'LABEL: /END'); }); test('custom query without query', function () { @@ -155,7 +155,7 @@ suite('URI Label', () => { }); const uri1 = URI.parse('vscode://microsoft.com/1/2/3/4/5'); - assert.equal(labelService.getUriLabel(uri1, { relative: false }), 'LABEL: /END'); + assert.strictEqual(labelService.getUriLabel(uri1, { relative: false }), 'LABEL: /END'); }); }); @@ -202,7 +202,7 @@ suite('multi-root workspace', () => { Object.entries(tests).forEach(([path, label]) => { const generated = labelService.getUriLabel(URI.file(path), { relative: true }); - assert.equal(generated, label); + assert.strictEqual(generated, label); }); }); @@ -225,7 +225,7 @@ suite('multi-root workspace', () => { Object.entries(tests).forEach(([path, label]) => { const generated = labelService.getUriLabel(URI.file(path), { relative: true }); - assert.equal(generated, label, path); + assert.strictEqual(generated, label, path); }); }); @@ -246,7 +246,7 @@ suite('multi-root workspace', () => { Object.entries(tests).forEach(([path, label]) => { const generated = labelService.getUriLabel(URI.file(path), { relative: true }); - assert.equal(generated, label, path); + assert.strictEqual(generated, label, path); }); }); }); @@ -287,7 +287,7 @@ suite('workspace at FSP root', () => { Object.entries(tests).forEach(([uriString, label]) => { const generated = labelService.getUriLabel(URI.parse(uriString), { relative: false }); - assert.equal(generated, label); + assert.strictEqual(generated, label); }); }); @@ -300,7 +300,7 @@ suite('workspace at FSP root', () => { Object.entries(tests).forEach(([uriString, label]) => { const generated = labelService.getUriLabel(URI.parse(uriString), { relative: true }); - assert.equal(generated, label); + assert.strictEqual(generated, label); }); }); }); diff --git a/src/vs/workbench/services/layout/browser/layoutService.ts b/src/vs/workbench/services/layout/browser/layoutService.ts index 551797a8..1c385ecb 100644 --- a/src/vs/workbench/services/layout/browser/layoutService.ts +++ b/src/vs/workbench/services/layout/browser/layoutService.ts @@ -123,10 +123,15 @@ export interface IWorkbenchLayoutService extends ILayoutService { /** * Asks the part service if all parts have been fully restored. For editor part - * this means that the contents of editors have loaded. + * this means that the contents of visible editors have loaded. */ isRestored(): boolean; + /** + * A promise for to await the `isRestored()` condition to be `true`. + */ + readonly whenRestored: Promise; + /** * Returns whether the given part has the keyboard focus or not. */ diff --git a/src/vs/workbench/services/lifecycle/browser/lifecycleService.ts b/src/vs/workbench/services/lifecycle/browser/lifecycleService.ts index fe323702..83924726 100644 --- a/src/vs/workbench/services/lifecycle/browser/lifecycleService.ts +++ b/src/vs/workbench/services/lifecycle/browser/lifecycleService.ts @@ -13,13 +13,11 @@ import { addDisposableListener } from 'vs/base/browser/dom'; export class BrowserLifecycleService extends AbstractLifecycleService { - declare readonly _serviceBrand: undefined; - private beforeUnloadDisposable: IDisposable | undefined = undefined; private expectedUnload = false; constructor( - @ILogService readonly logService: ILogService + @ILogService logService: ILogService ) { super(logService); @@ -102,7 +100,7 @@ export class BrowserLifecycleService extends AbstractLifecycleService { return; } - // No Veto: continue with Will Shutdown + // No Veto: continue with willShutdown this._onWillShutdown.fire({ join(promise, id) { logService.error(`[lifecycle] Long running operations during shutdown are unsupported in the web (id: ${id})`); @@ -110,8 +108,8 @@ export class BrowserLifecycleService extends AbstractLifecycleService { reason: ShutdownReason.QUIT }); - // Finally end with Shutdown event - this._onShutdown.fire(); + // Finally end with didShutdown + this._onDidShutdown.fire(); } } diff --git a/src/vs/workbench/services/lifecycle/common/lifecycle.ts b/src/vs/workbench/services/lifecycle/common/lifecycle.ts index 355cd492..093f22ac 100644 --- a/src/vs/workbench/services/lifecycle/common/lifecycle.ts +++ b/src/vs/workbench/services/lifecycle/common/lifecycle.ts @@ -92,23 +92,29 @@ export const enum LifecyclePhase { /** * The first phase signals that we are about to startup getting ready. + * + * Note: doing work in this phase blocks an editor from showing to + * the user, so please rather consider to use `Restored` phase. */ Starting = 1, /** - * Services are ready and the view is about to restore its state. + * Services are ready and the window is about to restore its UI state. + * + * Note: doing work in this phase blocks an editor from showing to + * the user, so please rather consider to use `Restored` phase. */ Ready = 2, /** - * Views, panels and editors have restored. For editors this means, that - * they show their contents fully. + * Views, panels and editors have restored. Editors are given a bit of + * time to restore their contents. */ Restored = 3, /** * The last phase after views, panels and editors have restored and - * some time has passed (few seconds). + * some time has passed (2-5 seconds). */ Eventually = 4 } @@ -149,9 +155,10 @@ export interface ILifecycleService { readonly onBeforeShutdown: Event; /** - * Fired when no client is preventing the shutdown from happening (from onBeforeShutdown). - * Can be used to save UI state even if that is long running through the WillShutdownEvent#join() - * method. + * Fired when no client is preventing the shutdown from happening (from `onBeforeShutdown`). + * + * This event can be joined with a long running operation via `WillShutdownEvent#join()` to + * handle long running shutdown operations. * * The event carries a shutdown reason that indicates how the shutdown was triggered. */ @@ -159,9 +166,11 @@ export interface ILifecycleService { /** * Fired when the shutdown is about to happen after long running shutdown operations - * have finished (from onWillShutdown). This is the right place to dispose resources. + * have finished (from `onWillShutdown`). + * + * This event should be used to dispose resources. */ - readonly onShutdown: Event; + readonly onDidShutdown: Event; /** * Returns a promise that resolves when a certain lifecycle phase @@ -185,7 +194,7 @@ export const NullLifecycleService: ILifecycleService = { onBeforeShutdown: Event.None, onWillShutdown: Event.None, - onShutdown: Event.None, + onDidShutdown: Event.None, phase: LifecyclePhase.Restored, startupKind: StartupKind.NewWindow, diff --git a/src/vs/workbench/services/lifecycle/common/lifecycleService.ts b/src/vs/workbench/services/lifecycle/common/lifecycleService.ts index a7116f0d..30ddd666 100644 --- a/src/vs/workbench/services/lifecycle/common/lifecycleService.ts +++ b/src/vs/workbench/services/lifecycle/common/lifecycleService.ts @@ -20,8 +20,8 @@ export abstract class AbstractLifecycleService extends Disposable implements ILi protected readonly _onWillShutdown = this._register(new Emitter()); readonly onWillShutdown = this._onWillShutdown.event; - protected readonly _onShutdown = this._register(new Emitter()); - readonly onShutdown = this._onShutdown.event; + protected readonly _onDidShutdown = this._register(new Emitter()); + readonly onDidShutdown = this._onDidShutdown.event; protected _startupKind: StartupKind = StartupKind.NewWindow; get startupKind(): StartupKind { return this._startupKind; } diff --git a/src/vs/workbench/services/lifecycle/electron-sandbox/lifecycleService.ts b/src/vs/workbench/services/lifecycle/electron-sandbox/lifecycleService.ts index 1741a9f8..47dc6d84 100644 --- a/src/vs/workbench/services/lifecycle/electron-sandbox/lifecycleService.ts +++ b/src/vs/workbench/services/lifecycle/electron-sandbox/lifecycleService.ts @@ -25,15 +25,13 @@ export class NativeLifecycleService extends AbstractLifecycleService { private static readonly BEFORE_SHUTDOWN_WARNING_DELAY = 5000; private static readonly WILL_SHUTDOWN_WARNING_DELAY = 5000; - declare readonly _serviceBrand: undefined; - private shutdownReason: ShutdownReason | undefined; constructor( @INotificationService private readonly notificationService: INotificationService, @INativeHostService private readonly nativeHostService: INativeHostService, @IStorageService readonly storageService: IStorageService, - @ILogService readonly logService: ILogService + @ILogService logService: ILogService ) { super(logService); @@ -89,8 +87,8 @@ export class NativeLifecycleService extends AbstractLifecycleService { // trigger onWillShutdown events and joining await this.handleWillShutdown(reply.reason); - // trigger onShutdown event now that we know we will quit - this._onShutdown.fire(); + // trigger onDidShutdown event now that we know we will quit + this._onDidShutdown.fire(); // acknowledge to main side ipcRenderer.send(reply.replyChannel, windowId); diff --git a/src/vs/workbench/services/log/electron-sandbox/logService.ts b/src/vs/workbench/services/log/electron-sandbox/logService.ts index 4ace2bc3..ac35a244 100644 --- a/src/vs/workbench/services/log/electron-sandbox/logService.ts +++ b/src/vs/workbench/services/log/electron-sandbox/logService.ts @@ -3,18 +3,16 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { LogService, ConsoleLogger, MultiplexLogService, ILogger } from 'vs/platform/log/common/log'; +import { LogService, ConsoleLogger, MultiplexLogService, ILogger, LogLevel } from 'vs/platform/log/common/log'; import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; import { LogLevelChannelClient, FollowerLogService, LoggerChannelClient } from 'vs/platform/log/common/logIpc'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/services'; export class NativeLogService extends LogService { - constructor(name: string, loggerService: LoggerChannelClient, mainProcessService: IMainProcessService, environmentService: INativeWorkbenchEnvironmentService) { + constructor(name: string, logLevel: LogLevel, loggerService: LoggerChannelClient, loggerClient: LogLevelChannelClient, environmentService: INativeWorkbenchEnvironmentService) { const disposables = new DisposableStore(); - const loggerClient = new LogLevelChannelClient(mainProcessService.getChannel('logLevel')); // Extension development test CLI: forward everything to main side const loggers: ILogger[] = []; @@ -25,7 +23,7 @@ export class NativeLogService extends LogService { // Normal logger: spdylog and console else { loggers.push( - disposables.add(new ConsoleLogger(environmentService.configuration.logLevel)), + disposables.add(new ConsoleLogger(logLevel)), disposables.add(loggerService.createLogger(environmentService.logFile, { name })) ); } diff --git a/src/vs/workbench/services/mode/common/workbenchModeService.ts b/src/vs/workbench/services/mode/common/workbenchModeService.ts index c6033d4e..7294d461 100644 --- a/src/vs/workbench/services/mode/common/workbenchModeService.ts +++ b/src/vs/workbench/services/mode/common/workbenchModeService.ts @@ -156,7 +156,7 @@ export class WorkbenchModeServiceImpl extends ModeServiceImpl { }); } - protected _onReady(): Promise { + protected override _onReady(): Promise { if (!this._onReadyPromise) { this._onReadyPromise = Promise.resolve( this._extensionService.whenInstalledExtensionsRegistered().then(() => true) diff --git a/src/vs/workbench/services/path/browser/pathService.ts b/src/vs/workbench/services/path/browser/pathService.ts index dd43ac3f..b1d5cac9 100644 --- a/src/vs/workbench/services/path/browser/pathService.ts +++ b/src/vs/workbench/services/path/browser/pathService.ts @@ -39,7 +39,7 @@ function defaultUriScheme(environmentService: IWorkbenchEnvironmentService, cont return configuration.scheme; } - throw new Error('Empty workspace is not supported in browser when there is no remote connection.'); + return Schemas.file; } registerSingleton(IPathService, BrowserPathService, true); diff --git a/src/vs/workbench/services/preferences/browser/keybindingsEditorModel.ts b/src/vs/workbench/services/preferences/browser/keybindingsEditorModel.ts index fd75f337..ce411da0 100644 --- a/src/vs/workbench/services/preferences/browser/keybindingsEditorModel.ts +++ b/src/vs/workbench/services/preferences/browser/keybindingsEditorModel.ts @@ -140,7 +140,7 @@ export class KeybindingsEditorModel extends EditorModel { return result; } - resolve(actionLabels: Map): Promise { + override async resolve(actionLabels = new Map()): Promise { const workbenchActionsRegistry = Registry.as(ActionExtensions.WorkbenchActions); this._keybindingItemsSortedByPrecedence = []; @@ -158,7 +158,6 @@ export class KeybindingsEditorModel extends EditorModel { this._keybindingItemsSortedByPrecedence.push(KeybindingsEditorModel.toKeybindingEntry(command, keybindingItem, workbenchActionsRegistry, actionLabels)); } this._keybindingItems = this._keybindingItemsSortedByPrecedence.slice(0).sort((a, b) => KeybindingsEditorModel.compareKeybindingData(a, b)); - return Promise.resolve(this); } private static getId(keybindingItem: IKeybindingItem): string { diff --git a/src/vs/workbench/services/preferences/browser/preferencesEditorInput.ts b/src/vs/workbench/services/preferences/browser/preferencesEditorInput.ts index f679a2c6..ccd36b41 100644 --- a/src/vs/workbench/services/preferences/browser/preferencesEditorInput.ts +++ b/src/vs/workbench/services/preferences/browser/preferencesEditorInput.ts @@ -22,19 +22,19 @@ import { IFilesConfigurationService } from 'vs/workbench/services/filesConfigura import { Schemas } from 'vs/base/common/network'; export class PreferencesEditorInput extends SideBySideEditorInput { - static readonly ID: string = 'workbench.editorinputs.preferencesEditorInput'; + static override readonly ID: string = 'workbench.editorinputs.preferencesEditorInput'; - getTypeId(): string { + override get typeId(): string { return PreferencesEditorInput.ID; } - getTitle(verbosity: Verbosity): string { + override getTitle(verbosity: Verbosity): string { return this.primary.getTitle(verbosity); } } export class DefaultPreferencesEditorInput extends ResourceEditorInput { - static readonly ID = 'workbench.editorinputs.defaultpreferences'; + static override readonly ID = 'workbench.editorinputs.defaultpreferences'; constructor( defaultSettingsResource: URI, @ITextModelService textModelResolverService: ITextModelService, @@ -48,11 +48,11 @@ export class DefaultPreferencesEditorInput extends ResourceEditorInput { super(defaultSettingsResource, nls.localize('settingsEditorName', "Default Settings"), '', undefined, textModelResolverService, textFileService, editorService, editorGroupService, fileService, labelService, filesConfigurationService); } - getTypeId(): string { + override get typeId(): string { return DefaultPreferencesEditorInput.ID; } - matches(other: unknown): boolean { + override matches(other: unknown): boolean { if (other instanceof DefaultPreferencesEditorInput) { return true; } @@ -84,23 +84,23 @@ export class KeybindingsEditorInput extends EditorInput { this.keybindingsModel = instantiationService.createInstance(KeybindingsEditorModel, OS); } - getTypeId(): string { + override get typeId(): string { return KeybindingsEditorInput.ID; } - getName(): string { + override getName(): string { return nls.localize('keybindingsInputName', "Keyboard Shortcuts"); } - async resolve(): Promise { + override async resolve(): Promise { return this.keybindingsModel; } - matches(otherInput: unknown): boolean { + override matches(otherInput: unknown): boolean { return otherInput instanceof KeybindingsEditorInput; } - dispose(): void { + override dispose(): void { this.keybindingsModel.dispose(); super.dispose(); @@ -125,23 +125,23 @@ export class SettingsEditor2Input extends EditorInput { this._settingsModel = _preferencesService.createSettings2EditorModel(); } - matches(otherInput: unknown): boolean { + override matches(otherInput: unknown): boolean { return otherInput instanceof SettingsEditor2Input; } - getTypeId(): string { + override get typeId(): string { return SettingsEditor2Input.ID; } - getName(): string { + override getName(): string { return nls.localize('settingsEditor2InputName', "Settings"); } - async resolve(): Promise { + override async resolve(): Promise { return this._settingsModel; } - dispose(): void { + override dispose(): void { this._settingsModel.dispose(); super.dispose(); diff --git a/src/vs/workbench/services/preferences/browser/preferencesService.ts b/src/vs/workbench/services/preferences/browser/preferencesService.ts index b0daf446..62c6cda5 100644 --- a/src/vs/workbench/services/preferences/browser/preferencesService.ts +++ b/src/vs/workbench/services/preferences/browser/preferencesService.ts @@ -532,7 +532,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic return null; } - private createSettingsIfNotExists(target: ConfigurationTarget, resource: URI): Promise { + private async createSettingsIfNotExists(target: ConfigurationTarget, resource: URI): Promise { if (this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE && target === ConfigurationTarget.WORKSPACE) { const workspaceConfig = this.contextService.getWorkspace().configuration; if (!workspaceConfig) { @@ -547,19 +547,25 @@ export class PreferencesService extends Disposable implements IPreferencesServic return undefined; }); } - return this.createIfNotExists(resource, emptyEditableSettingsContent).then(() => { }); + await this.createIfNotExists(resource, emptyEditableSettingsContent).then(() => { }); } - private createIfNotExists(resource: URI, contents: string): Promise { - return this.textFileService.read(resource, { acceptTextOnly: true }).then(undefined, error => { + private async createIfNotExists(resource: URI, contents: string): Promise { + try { + await this.textFileService.read(resource, { acceptTextOnly: true }); + } catch (error) { if ((error).fileOperationResult === FileOperationResult.FILE_NOT_FOUND) { - return this.textFileService.write(resource, contents).then(undefined, error => { - return Promise.reject(new Error(nls.localize('fail.createSettings', "Unable to create '{0}' ({1}).", this.labelService.getUriLabel(resource, { relative: true }), getErrorMessage(error)))); - }); + try { + await this.textFileService.write(resource, contents); + return; + } catch (error2) { + throw new Error(nls.localize('fail.createSettings', "Unable to create '{0}' ({1}).", this.labelService.getUriLabel(resource, { relative: true }), getErrorMessage(error2))); + } + } else { + throw error; } - return Promise.reject(error); - }); + } } private getMostCommonlyUsedSettings(): string[] { @@ -644,7 +650,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic return position; } - public dispose(): void { + public override dispose(): void { this._onDispose.fire(); super.dispose(); } diff --git a/src/vs/workbench/services/preferences/common/preferences.ts b/src/vs/workbench/services/preferences/common/preferences.ts index a3386ef5..41205cd7 100644 --- a/src/vs/workbench/services/preferences/common/preferences.ts +++ b/src/vs/workbench/services/preferences/common/preferences.ts @@ -77,6 +77,7 @@ export interface ISetting { enumDescriptionsAreMarkdown?: boolean; tags?: string[]; disallowSyncIgnore?: boolean; + restricted?: boolean; extensionInfo?: IConfigurationExtensionInfo; validator?: (value: any) => string | null; enumItemLabels?: string[]; @@ -183,7 +184,7 @@ export class SettingsEditorOptions extends EditorOptions implements ISettingsEdi }; focusSearch?: boolean; - static create(options: ISettingsEditorOptions): SettingsEditorOptions { + static override create(options: ISettingsEditorOptions): SettingsEditorOptions { const newOptions = new SettingsEditorOptions(); options = { ...{ diff --git a/src/vs/workbench/services/preferences/common/preferencesModels.ts b/src/vs/workbench/services/preferences/common/preferencesModels.ts index 50611fa1..1195390b 100644 --- a/src/vs/workbench/services/preferences/common/preferencesModels.ts +++ b/src/vs/workbench/services/preferences/common/preferencesModels.ts @@ -130,7 +130,7 @@ export class SettingsEditorModel extends AbstractSettingsModel implements ISetti constructor(reference: IReference, private _configurationTarget: ConfigurationTarget) { super(); this.settingsModel = reference.object.textEditorModel!; - this._register(this.onDispose(() => reference.dispose())); + this._register(this.onWillDispose(() => reference.dispose())); this._register(this.settingsModel.onDidChangeContent(() => { this._settingsGroups = undefined; this._onDidChangeGroups.fire(); @@ -235,7 +235,7 @@ export class Settings2EditorModel extends AbstractSettingsModel implements ISett })); } - protected get filterGroups(): ISettingsGroup[] { + protected override get filterGroups(): ISettingsGroup[] { // Don't filter "commonly used" return this.settingsGroups.slice(1); } @@ -434,12 +434,12 @@ export class WorkspaceConfigurationEditorModel extends SettingsEditorModel { return this._configurationGroups; } - protected parse(): void { + protected override parse(): void { super.parse(); this._configurationGroups = parse(this.settingsModel, (property: string, previousParents: string[]): boolean => previousParents.length === 0); } - protected isSettingsProperty(property: string, previousParents: string[]): boolean { + protected override isSettingsProperty(property: string, previousParents: string[]): boolean { return property === 'settings' && previousParents.length === 1; } @@ -643,6 +643,7 @@ export class DefaultSettings extends Disposable { enumDescriptionsAreMarkdown: !prop.enumDescriptions, tags: prop.tags, disallowSyncIgnore: prop.disallowSyncIgnore, + restricted: prop.restricted, extensionInfo: extensionInfo, deprecationMessage: prop.markdownDeprecationMessage || prop.deprecationMessage, deprecationMessageIsMarkdown: !!prop.markdownDeprecationMessage, @@ -725,7 +726,7 @@ export class DefaultSettingsEditorModel extends AbstractSettingsModel implements this._register(defaultSettings.onDidChange(() => this._onDidChangeGroups.fire())); this._model = reference.object.textEditorModel!; - this._register(this.onDispose(() => reference.dispose())); + this._register(this.onWillDispose(() => reference.dispose())); } get uri(): URI { @@ -740,7 +741,7 @@ export class DefaultSettingsEditorModel extends AbstractSettingsModel implements return this.defaultSettings.getSettingsGroups(); } - protected get filterGroups(): ISettingsGroup[] { + protected override get filterGroups(): ISettingsGroup[] { // Don't look at "commonly used" for filter return this.settingsGroups.slice(1); } @@ -868,7 +869,7 @@ export class DefaultSettingsEditorModel extends AbstractSettingsModel implements return []; } - getPreference(key: string): ISetting | undefined { + override getPreference(key: string): ISetting | undefined { for (const group of this.settingsGroups) { for (const section of group.sections) { for (const setting of section.settings) { @@ -1043,7 +1044,7 @@ class RawSettingsContentBuilder extends SettingsContentBuilder { super(0); } - pushGroup(settingsGroups: ISettingsGroup): void { + override pushGroup(settingsGroups: ISettingsGroup): void { this._pushGroup(settingsGroups, this.indent); } diff --git a/src/vs/workbench/services/preferences/test/browser/keybindingsEditorModel.test.ts b/src/vs/workbench/services/preferences/test/browser/keybindingsEditorModel.test.ts index 73ff4383..498689eb 100644 --- a/src/vs/workbench/services/preferences/test/browser/keybindingsEditorModel.test.ts +++ b/src/vs/workbench/services/preferences/test/browser/keybindingsEditorModel.test.ts @@ -155,11 +155,11 @@ suite('KeybindingsEditorModel', () => { await testObject.resolve(new Map()); const actual = testObject.fetch('')[0]; - assert.equal(actual.keybindingItem.command, expected.command); - assert.equal(actual.keybindingItem.commandLabel, ''); - assert.equal(actual.keybindingItem.commandDefaultLabel, null); - assert.equal(actual.keybindingItem.keybinding.getAriaLabel(), expected.resolvedKeybinding!.getAriaLabel()); - assert.equal(actual.keybindingItem.when, expected.when!.serialize()); + assert.strictEqual(actual.keybindingItem.command, expected.command); + assert.strictEqual(actual.keybindingItem.commandLabel, ''); + assert.strictEqual(actual.keybindingItem.commandDefaultLabel, null); + assert.strictEqual(actual.keybindingItem.keybinding.getAriaLabel(), expected.resolvedKeybinding!.getAriaLabel()); + assert.strictEqual(actual.keybindingItem.when, expected.when!.serialize()); }); test('convert keybinding with title to entry', async () => { @@ -169,11 +169,11 @@ suite('KeybindingsEditorModel', () => { await testObject.resolve(new Map()); const actual = testObject.fetch('')[0]; - assert.equal(actual.keybindingItem.command, expected.command); - assert.equal(actual.keybindingItem.commandLabel, 'Some Title'); - assert.equal(actual.keybindingItem.commandDefaultLabel, null); - assert.equal(actual.keybindingItem.keybinding.getAriaLabel(), expected.resolvedKeybinding!.getAriaLabel()); - assert.equal(actual.keybindingItem.when, expected.when!.serialize()); + assert.strictEqual(actual.keybindingItem.command, expected.command); + assert.strictEqual(actual.keybindingItem.commandLabel, 'Some Title'); + assert.strictEqual(actual.keybindingItem.commandDefaultLabel, null); + assert.strictEqual(actual.keybindingItem.keybinding.getAriaLabel(), expected.resolvedKeybinding!.getAriaLabel()); + assert.strictEqual(actual.keybindingItem.when, expected.when!.serialize()); }); test('convert without title and binding to entry', async () => { @@ -182,11 +182,11 @@ suite('KeybindingsEditorModel', () => { await testObject.resolve(new Map()); const actual = testObject.fetch('').filter(element => element.keybindingItem.command === 'command_without_keybinding')[0]; - assert.equal(actual.keybindingItem.command, 'command_without_keybinding'); - assert.equal(actual.keybindingItem.commandLabel, ''); - assert.equal(actual.keybindingItem.commandDefaultLabel, null); - assert.equal(actual.keybindingItem.keybinding, null); - assert.equal(actual.keybindingItem.when, ''); + assert.strictEqual(actual.keybindingItem.command, 'command_without_keybinding'); + assert.strictEqual(actual.keybindingItem.commandLabel, ''); + assert.strictEqual(actual.keybindingItem.commandDefaultLabel, null); + assert.strictEqual(actual.keybindingItem.keybinding, undefined); + assert.strictEqual(actual.keybindingItem.when, ''); }); test('convert with title and without binding to entry', async () => { @@ -196,11 +196,11 @@ suite('KeybindingsEditorModel', () => { await testObject.resolve(new Map()); const actual = testObject.fetch('').filter(element => element.keybindingItem.command === id)[0]; - assert.equal(actual.keybindingItem.command, id); - assert.equal(actual.keybindingItem.commandLabel, 'some title'); - assert.equal(actual.keybindingItem.commandDefaultLabel, null); - assert.equal(actual.keybindingItem.keybinding, null); - assert.equal(actual.keybindingItem.when, ''); + assert.strictEqual(actual.keybindingItem.command, id); + assert.strictEqual(actual.keybindingItem.commandLabel, 'some title'); + assert.strictEqual(actual.keybindingItem.commandDefaultLabel, null); + assert.strictEqual(actual.keybindingItem.keybinding, undefined); + assert.strictEqual(actual.keybindingItem.when, ''); }); test('filter by command id', async () => { @@ -270,8 +270,8 @@ suite('KeybindingsEditorModel', () => { await testObject.resolve(new Map()); const actual = testObject.fetch(`@command:${command}`); - assert.equal(actual.length, 1); - assert.deepEqual(actual[0].keybindingItem.command, command); + assert.strictEqual(actual.length, 1); + assert.deepStrictEqual(actual[0].keybindingItem.command, command); }); test('filter by command prefix with same commands', async () => { @@ -281,9 +281,9 @@ suite('KeybindingsEditorModel', () => { await testObject.resolve(new Map()); const actual = testObject.fetch(`@command:${command}`); - assert.equal(actual.length, 2); - assert.deepEqual(actual[0].keybindingItem.command, command); - assert.deepEqual(actual[1].keybindingItem.command, command); + assert.strictEqual(actual.length, 2); + assert.deepStrictEqual(actual[0].keybindingItem.command, command); + assert.deepStrictEqual(actual[1].keybindingItem.command, command); }); test('filter by when context', async () => { @@ -305,9 +305,9 @@ suite('KeybindingsEditorModel', () => { await testObject.resolve(new Map()); const actual = testObject.fetch('cmd').filter(element => element.keybindingItem.command === command); - assert.equal(1, actual.length); - assert.deepEqual(actual[0].keybindingMatches!.firstPart, { metaKey: true }); - assert.deepEqual(actual[0].keybindingMatches!.chordPart, {}); + assert.strictEqual(1, actual.length); + assert.deepStrictEqual(actual[0].keybindingMatches!.firstPart, { metaKey: true }); + assert.deepStrictEqual(actual[0].keybindingMatches!.chordPart, {}); }); test('filter by meta key', async () => { @@ -319,9 +319,9 @@ suite('KeybindingsEditorModel', () => { await testObject.resolve(new Map()); const actual = testObject.fetch('meta').filter(element => element.keybindingItem.command === command); - assert.equal(1, actual.length); - assert.deepEqual(actual[0].keybindingMatches!.firstPart, { metaKey: true }); - assert.deepEqual(actual[0].keybindingMatches!.chordPart, {}); + assert.strictEqual(1, actual.length); + assert.deepStrictEqual(actual[0].keybindingMatches!.firstPart, { metaKey: true }); + assert.deepStrictEqual(actual[0].keybindingMatches!.chordPart, {}); }); test('filter by command key', async () => { @@ -333,9 +333,9 @@ suite('KeybindingsEditorModel', () => { await testObject.resolve(new Map()); const actual = testObject.fetch('command').filter(element => element.keybindingItem.command === command); - assert.equal(1, actual.length); - assert.deepEqual(actual[0].keybindingMatches!.firstPart, { metaKey: true }); - assert.deepEqual(actual[0].keybindingMatches!.chordPart, {}); + assert.strictEqual(1, actual.length); + assert.deepStrictEqual(actual[0].keybindingMatches!.firstPart, { metaKey: true }); + assert.deepStrictEqual(actual[0].keybindingMatches!.chordPart, {}); }); test('filter by windows key', async () => { @@ -347,9 +347,9 @@ suite('KeybindingsEditorModel', () => { await testObject.resolve(new Map()); const actual = testObject.fetch('windows').filter(element => element.keybindingItem.command === command); - assert.equal(1, actual.length); - assert.deepEqual(actual[0].keybindingMatches!.firstPart, { metaKey: true }); - assert.deepEqual(actual[0].keybindingMatches!.chordPart, {}); + assert.strictEqual(1, actual.length); + assert.deepStrictEqual(actual[0].keybindingMatches!.firstPart, { metaKey: true }); + assert.deepStrictEqual(actual[0].keybindingMatches!.chordPart, {}); }); test('filter by alt key', async () => { @@ -359,9 +359,9 @@ suite('KeybindingsEditorModel', () => { await testObject.resolve(new Map()); const actual = testObject.fetch('alt').filter(element => element.keybindingItem.command === command); - assert.equal(1, actual.length); - assert.deepEqual(actual[0].keybindingMatches!.firstPart, { altKey: true }); - assert.deepEqual(actual[0].keybindingMatches!.chordPart, {}); + assert.strictEqual(1, actual.length); + assert.deepStrictEqual(actual[0].keybindingMatches!.firstPart, { altKey: true }); + assert.deepStrictEqual(actual[0].keybindingMatches!.chordPart, {}); }); test('filter by option key', async () => { @@ -371,9 +371,9 @@ suite('KeybindingsEditorModel', () => { await testObject.resolve(new Map()); const actual = testObject.fetch('option').filter(element => element.keybindingItem.command === command); - assert.equal(1, actual.length); - assert.deepEqual(actual[0].keybindingMatches!.firstPart, { altKey: true }); - assert.deepEqual(actual[0].keybindingMatches!.chordPart, {}); + assert.strictEqual(1, actual.length); + assert.deepStrictEqual(actual[0].keybindingMatches!.firstPart, { altKey: true }); + assert.deepStrictEqual(actual[0].keybindingMatches!.chordPart, {}); }); test('filter by ctrl key', async () => { @@ -383,9 +383,9 @@ suite('KeybindingsEditorModel', () => { await testObject.resolve(new Map()); const actual = testObject.fetch('ctrl').filter(element => element.keybindingItem.command === command); - assert.equal(1, actual.length); - assert.deepEqual(actual[0].keybindingMatches!.firstPart, { ctrlKey: true }); - assert.deepEqual(actual[0].keybindingMatches!.chordPart, {}); + assert.strictEqual(1, actual.length); + assert.deepStrictEqual(actual[0].keybindingMatches!.firstPart, { ctrlKey: true }); + assert.deepStrictEqual(actual[0].keybindingMatches!.chordPart, {}); }); test('filter by control key', async () => { @@ -395,9 +395,9 @@ suite('KeybindingsEditorModel', () => { await testObject.resolve(new Map()); const actual = testObject.fetch('control').filter(element => element.keybindingItem.command === command); - assert.equal(1, actual.length); - assert.deepEqual(actual[0].keybindingMatches!.firstPart, { ctrlKey: true }); - assert.deepEqual(actual[0].keybindingMatches!.chordPart, {}); + assert.strictEqual(1, actual.length); + assert.deepStrictEqual(actual[0].keybindingMatches!.firstPart, { ctrlKey: true }); + assert.deepStrictEqual(actual[0].keybindingMatches!.chordPart, {}); }); test('filter by shift key', async () => { @@ -407,9 +407,9 @@ suite('KeybindingsEditorModel', () => { await testObject.resolve(new Map()); const actual = testObject.fetch('shift').filter(element => element.keybindingItem.command === command); - assert.equal(1, actual.length); - assert.deepEqual(actual[0].keybindingMatches!.firstPart, { shiftKey: true }); - assert.deepEqual(actual[0].keybindingMatches!.chordPart, {}); + assert.strictEqual(1, actual.length); + assert.deepStrictEqual(actual[0].keybindingMatches!.firstPart, { shiftKey: true }); + assert.deepStrictEqual(actual[0].keybindingMatches!.chordPart, {}); }); test('filter by arrow', async () => { @@ -419,9 +419,9 @@ suite('KeybindingsEditorModel', () => { await testObject.resolve(new Map()); const actual = testObject.fetch('arrow').filter(element => element.keybindingItem.command === command); - assert.equal(1, actual.length); - assert.deepEqual(actual[0].keybindingMatches!.firstPart, { keyCode: true }); - assert.deepEqual(actual[0].keybindingMatches!.chordPart, {}); + assert.strictEqual(1, actual.length); + assert.deepStrictEqual(actual[0].keybindingMatches!.firstPart, { keyCode: true }); + assert.deepStrictEqual(actual[0].keybindingMatches!.chordPart, {}); }); test('filter by modifier and key', async () => { @@ -431,9 +431,9 @@ suite('KeybindingsEditorModel', () => { await testObject.resolve(new Map()); const actual = testObject.fetch('alt right').filter(element => element.keybindingItem.command === command); - assert.equal(1, actual.length); - assert.deepEqual(actual[0].keybindingMatches!.firstPart, { altKey: true, keyCode: true }); - assert.deepEqual(actual[0].keybindingMatches!.chordPart, {}); + assert.strictEqual(1, actual.length); + assert.deepStrictEqual(actual[0].keybindingMatches!.firstPart, { altKey: true, keyCode: true }); + assert.deepStrictEqual(actual[0].keybindingMatches!.chordPart, {}); }); test('filter by key and modifier', async () => { @@ -443,7 +443,7 @@ suite('KeybindingsEditorModel', () => { await testObject.resolve(new Map()); const actual = testObject.fetch('right alt').filter(element => element.keybindingItem.command === command); - assert.equal(0, actual.length); + assert.strictEqual(0, actual.length); }); test('filter by modifiers and key', async () => { @@ -454,9 +454,9 @@ suite('KeybindingsEditorModel', () => { await testObject.resolve(new Map()); const actual = testObject.fetch('alt cmd esc').filter(element => element.keybindingItem.command === command); - assert.equal(1, actual.length); - assert.deepEqual(actual[0].keybindingMatches!.firstPart, { altKey: true, metaKey: true, keyCode: true }); - assert.deepEqual(actual[0].keybindingMatches!.chordPart, {}); + assert.strictEqual(1, actual.length); + assert.deepStrictEqual(actual[0].keybindingMatches!.firstPart, { altKey: true, metaKey: true, keyCode: true }); + assert.deepStrictEqual(actual[0].keybindingMatches!.chordPart, {}); }); test('filter by modifiers in random order and key', async () => { @@ -467,9 +467,9 @@ suite('KeybindingsEditorModel', () => { await testObject.resolve(new Map()); const actual = testObject.fetch('cmd shift esc').filter(element => element.keybindingItem.command === command); - assert.equal(1, actual.length); - assert.deepEqual(actual[0].keybindingMatches!.firstPart, { metaKey: true, shiftKey: true, keyCode: true }); - assert.deepEqual(actual[0].keybindingMatches!.chordPart, {}); + assert.strictEqual(1, actual.length); + assert.deepStrictEqual(actual[0].keybindingMatches!.firstPart, { metaKey: true, shiftKey: true, keyCode: true }); + assert.deepStrictEqual(actual[0].keybindingMatches!.chordPart, {}); }); test('filter by first part', async () => { @@ -480,9 +480,9 @@ suite('KeybindingsEditorModel', () => { await testObject.resolve(new Map()); const actual = testObject.fetch('cmd shift esc').filter(element => element.keybindingItem.command === command); - assert.equal(1, actual.length); - assert.deepEqual(actual[0].keybindingMatches!.firstPart, { metaKey: true, shiftKey: true, keyCode: true }); - assert.deepEqual(actual[0].keybindingMatches!.chordPart, {}); + assert.strictEqual(1, actual.length); + assert.deepStrictEqual(actual[0].keybindingMatches!.firstPart, { metaKey: true, shiftKey: true, keyCode: true }); + assert.deepStrictEqual(actual[0].keybindingMatches!.chordPart, {}); }); test('filter matches in chord part', async () => { @@ -493,9 +493,9 @@ suite('KeybindingsEditorModel', () => { await testObject.resolve(new Map()); const actual = testObject.fetch('cmd del').filter(element => element.keybindingItem.command === command); - assert.equal(1, actual.length); - assert.deepEqual(actual[0].keybindingMatches!.firstPart, { metaKey: true }); - assert.deepEqual(actual[0].keybindingMatches!.chordPart, { keyCode: true }); + assert.strictEqual(1, actual.length); + assert.deepStrictEqual(actual[0].keybindingMatches!.firstPart, { metaKey: true }); + assert.deepStrictEqual(actual[0].keybindingMatches!.chordPart, { keyCode: true }); }); test('filter matches first part and in chord part', async () => { @@ -506,9 +506,9 @@ suite('KeybindingsEditorModel', () => { await testObject.resolve(new Map()); const actual = testObject.fetch('cmd shift esc del').filter(element => element.keybindingItem.command === command); - assert.equal(1, actual.length); - assert.deepEqual(actual[0].keybindingMatches!.firstPart, { shiftKey: true, metaKey: true, keyCode: true }); - assert.deepEqual(actual[0].keybindingMatches!.chordPart, { keyCode: true }); + assert.strictEqual(1, actual.length); + assert.deepStrictEqual(actual[0].keybindingMatches!.firstPart, { shiftKey: true, metaKey: true, keyCode: true }); + assert.deepStrictEqual(actual[0].keybindingMatches!.chordPart, { keyCode: true }); }); test('filter exact matches', async () => { @@ -518,9 +518,9 @@ suite('KeybindingsEditorModel', () => { await testObject.resolve(new Map()); const actual = testObject.fetch('"ctrl c"').filter(element => element.keybindingItem.command === command); - assert.equal(1, actual.length); - assert.deepEqual(actual[0].keybindingMatches!.firstPart, { ctrlKey: true, keyCode: true }); - assert.deepEqual(actual[0].keybindingMatches!.chordPart, {}); + assert.strictEqual(1, actual.length); + assert.deepStrictEqual(actual[0].keybindingMatches!.firstPart, { ctrlKey: true, keyCode: true }); + assert.deepStrictEqual(actual[0].keybindingMatches!.chordPart, {}); }); test('filter exact matches with first and chord part', async () => { @@ -530,9 +530,9 @@ suite('KeybindingsEditorModel', () => { await testObject.resolve(new Map()); const actual = testObject.fetch('"shift meta escape ctrl c"').filter(element => element.keybindingItem.command === command); - assert.equal(1, actual.length); - assert.deepEqual(actual[0].keybindingMatches!.firstPart, { shiftKey: true, metaKey: true, keyCode: true }); - assert.deepEqual(actual[0].keybindingMatches!.chordPart, { ctrlKey: true, keyCode: true }); + assert.strictEqual(1, actual.length); + assert.deepStrictEqual(actual[0].keybindingMatches!.firstPart, { shiftKey: true, metaKey: true, keyCode: true }); + assert.deepStrictEqual(actual[0].keybindingMatches!.chordPart, { ctrlKey: true, keyCode: true }); }); test('filter exact matches with first and chord part no results', async () => { @@ -543,7 +543,7 @@ suite('KeybindingsEditorModel', () => { await testObject.resolve(new Map()); const actual = testObject.fetch('"cmd shift esc del"').filter(element => element.keybindingItem.command === command); - assert.equal(0, actual.length); + assert.strictEqual(0, actual.length); }); test('filter matches with + separator', async () => { @@ -553,9 +553,9 @@ suite('KeybindingsEditorModel', () => { await testObject.resolve(new Map()); const actual = testObject.fetch('"control+c"').filter(element => element.keybindingItem.command === command); - assert.equal(1, actual.length); - assert.deepEqual(actual[0].keybindingMatches!.firstPart, { ctrlKey: true, keyCode: true }); - assert.deepEqual(actual[0].keybindingMatches!.chordPart, {}); + assert.strictEqual(1, actual.length); + assert.deepStrictEqual(actual[0].keybindingMatches!.firstPart, { ctrlKey: true, keyCode: true }); + assert.deepStrictEqual(actual[0].keybindingMatches!.chordPart, {}); }); test('filter by keybinding prefix', async () => { @@ -565,9 +565,9 @@ suite('KeybindingsEditorModel', () => { await testObject.resolve(new Map()); const actual = testObject.fetch('@keybinding:control+c').filter(element => element.keybindingItem.command === command); - assert.equal(1, actual.length); - assert.deepEqual(actual[0].keybindingMatches!.firstPart, { ctrlKey: true, keyCode: true }); - assert.deepEqual(actual[0].keybindingMatches!.chordPart, {}); + assert.strictEqual(1, actual.length); + assert.deepStrictEqual(actual[0].keybindingMatches!.firstPart, { ctrlKey: true, keyCode: true }); + assert.deepStrictEqual(actual[0].keybindingMatches!.chordPart, {}); }); test('filter matches with + separator in first and chord parts', async () => { @@ -577,9 +577,9 @@ suite('KeybindingsEditorModel', () => { await testObject.resolve(new Map()); const actual = testObject.fetch('"shift+meta+escape ctrl+c"').filter(element => element.keybindingItem.command === command); - assert.equal(1, actual.length); - assert.deepEqual(actual[0].keybindingMatches!.firstPart, { shiftKey: true, metaKey: true, keyCode: true }); - assert.deepEqual(actual[0].keybindingMatches!.chordPart, { keyCode: true, ctrlKey: true }); + assert.strictEqual(1, actual.length); + assert.deepStrictEqual(actual[0].keybindingMatches!.firstPart, { shiftKey: true, metaKey: true, keyCode: true }); + assert.deepStrictEqual(actual[0].keybindingMatches!.chordPart, { keyCode: true, ctrlKey: true }); }); test('filter by keybinding prefix with chord', async () => { @@ -589,9 +589,9 @@ suite('KeybindingsEditorModel', () => { await testObject.resolve(new Map()); const actual = testObject.fetch('@keybinding:"shift+meta+escape ctrl+c"').filter(element => element.keybindingItem.command === command); - assert.equal(1, actual.length); - assert.deepEqual(actual[0].keybindingMatches!.firstPart, { shiftKey: true, metaKey: true, keyCode: true }); - assert.deepEqual(actual[0].keybindingMatches!.chordPart, { keyCode: true, ctrlKey: true }); + assert.strictEqual(1, actual.length); + assert.deepStrictEqual(actual[0].keybindingMatches!.firstPart, { shiftKey: true, metaKey: true, keyCode: true }); + assert.deepStrictEqual(actual[0].keybindingMatches!.chordPart, { keyCode: true, ctrlKey: true }); }); test('filter exact matches with space #32993', async () => { @@ -601,7 +601,7 @@ suite('KeybindingsEditorModel', () => { await testObject.resolve(new Map()); const actual = testObject.fetch('"ctrl+space"').filter(element => element.keybindingItem.command === command); - assert.equal(1, actual.length); + assert.strictEqual(1, actual.length); }); test('filter exact matches with user settings label', async () => { @@ -612,8 +612,8 @@ suite('KeybindingsEditorModel', () => { await testObject.resolve(new Map()); const actual = testObject.fetch('"down"').filter(element => element.keybindingItem.command === command); - assert.equal(1, actual.length); - assert.deepEqual(actual[0].keybindingMatches!.firstPart, { keyCode: true }); + assert.strictEqual(1, actual.length); + assert.deepStrictEqual(actual[0].keybindingMatches!.firstPart, { keyCode: true }); }); test('filter modifiers are not matched when not completely matched (prefix)', async () => { @@ -625,9 +625,9 @@ suite('KeybindingsEditorModel', () => { await testObject.resolve(new Map()); const actual = testObject.fetch(term); - assert.equal(1, actual.length); - assert.equal(command, actual[0].keybindingItem.command); - assert.equal(1, actual[0].commandIdMatches?.length); + assert.strictEqual(1, actual.length); + assert.strictEqual(command, actual[0].keybindingItem.command); + assert.strictEqual(1, actual[0].commandIdMatches?.length); }); test('filter modifiers are not matched when not completely matched (includes)', async () => { @@ -639,9 +639,9 @@ suite('KeybindingsEditorModel', () => { await testObject.resolve(new Map()); const actual = testObject.fetch(term); - assert.equal(1, actual.length); - assert.equal(command, actual[0].keybindingItem.command); - assert.equal(1, actual[0].commandIdMatches?.length); + assert.strictEqual(1, actual.length); + assert.strictEqual(command, actual[0].keybindingItem.command); + assert.strictEqual(1, actual[0].commandIdMatches?.length); }); test('filter modifiers are matched with complete term', async () => { @@ -652,8 +652,8 @@ suite('KeybindingsEditorModel', () => { await testObject.resolve(new Map()); const actual = testObject.fetch('alt').filter(element => element.keybindingItem.command === command); - assert.equal(1, actual.length); - assert.deepEqual(actual[0].keybindingMatches!.firstPart, { altKey: true }); + assert.strictEqual(1, actual.length); + assert.deepStrictEqual(actual[0].keybindingMatches!.firstPart, { altKey: true }); }); function prepareKeybindingService(...keybindingItems: ResolvedKeybindingItem[]): ResolvedKeybindingItem[] { @@ -669,25 +669,25 @@ suite('KeybindingsEditorModel', () => { } function assertKeybindingItems(actual: ResolvedKeybindingItem[], expected: ResolvedKeybindingItem[]) { - assert.equal(actual.length, expected.length); + assert.strictEqual(actual.length, expected.length); for (let i = 0; i < actual.length; i++) { assertKeybindingItem(actual[i], expected[i]); } } function assertKeybindingItem(actual: ResolvedKeybindingItem, expected: ResolvedKeybindingItem): void { - assert.equal(actual.command, expected.command); + assert.strictEqual(actual.command, expected.command); if (actual.when) { assert.ok(!!expected.when); - assert.equal(actual.when.serialize(), expected.when!.serialize()); + assert.strictEqual(actual.when.serialize(), expected.when!.serialize()); } else { assert.ok(!expected.when); } - assert.equal(actual.isDefault, expected.isDefault); + assert.strictEqual(actual.isDefault, expected.isDefault); if (actual.resolvedKeybinding) { assert.ok(!!expected.resolvedKeybinding); - assert.equal(actual.resolvedKeybinding.getLabel(), expected.resolvedKeybinding!.getLabel()); + assert.strictEqual(actual.resolvedKeybinding.getLabel(), expected.resolvedKeybinding!.getLabel()); } else { assert.ok(!expected.resolvedKeybinding); } diff --git a/src/vs/workbench/services/preferences/test/browser/preferencesService.test.ts b/src/vs/workbench/services/preferences/test/browser/preferencesService.test.ts index 79c20bb4..25c4bba9 100644 --- a/src/vs/workbench/services/preferences/test/browser/preferencesService.test.ts +++ b/src/vs/workbench/services/preferences/test/browser/preferencesService.test.ts @@ -58,7 +58,7 @@ suite('PreferencesService', () => { class TestEditorService2 extends TestEditorService { lastOpenEditorOptions: any; - async openEditor(editor: any, optionsOrGroup?: any): Promise { + override async openEditor(editor: any, optionsOrGroup?: any): Promise { this.lastOpenEditorOptions = optionsOrGroup; return undefined; } diff --git a/src/vs/workbench/services/preferences/test/common/preferencesValidation.test.ts b/src/vs/workbench/services/preferences/test/common/preferencesValidation.test.ts index 7a8e0c48..e9f69bb9 100644 --- a/src/vs/workbench/services/preferences/test/common/preferencesValidation.test.ts +++ b/src/vs/workbench/services/preferences/test/common/preferencesValidation.test.ts @@ -17,11 +17,11 @@ suite('Preferences Validation', () => { } public accepts(input: string) { - assert.equal(this.validator(input), '', `Expected ${JSON.stringify(this.settings)} to accept \`${input}\`. Got ${this.validator(input)}.`); + assert.strictEqual(this.validator(input), '', `Expected ${JSON.stringify(this.settings)} to accept \`${input}\`. Got ${this.validator(input)}.`); } public rejects(input: string) { - assert.notEqual(this.validator(input), '', `Expected ${JSON.stringify(this.settings)} to reject \`${input}\`.`); + assert.notStrictEqual(this.validator(input), '', `Expected ${JSON.stringify(this.settings)} to reject \`${input}\`.`); return { withMessage: (message: string) => { @@ -259,11 +259,11 @@ suite('Preferences Validation', () => { } public accepts(input: string[]) { - assert.equal(this.validator(input), '', `Expected ${JSON.stringify(this.settings)} to accept \`${JSON.stringify(input)}\`. Got ${this.validator(input)}.`); + assert.strictEqual(this.validator(input), '', `Expected ${JSON.stringify(this.settings)} to accept \`${JSON.stringify(input)}\`. Got ${this.validator(input)}.`); } public rejects(input: any[]) { - assert.notEqual(this.validator(input), '', `Expected ${JSON.stringify(this.settings)} to reject \`${JSON.stringify(input)}\`.`); + assert.notStrictEqual(this.validator(input), '', `Expected ${JSON.stringify(this.settings)} to reject \`${JSON.stringify(input)}\`.`); return { withMessage: (message: string) => { diff --git a/src/vs/workbench/services/progress/browser/progressIndicator.ts b/src/vs/workbench/services/progress/browser/progressIndicator.ts index 79ad961b..5b3ee4f0 100644 --- a/src/vs/workbench/services/progress/browser/progressIndicator.ts +++ b/src/vs/workbench/services/progress/browser/progressIndicator.ts @@ -77,9 +77,9 @@ export class EditorProgressIndicator extends ProgressBarIndicator { })); } - show(infinite: true, delay?: number): IProgressRunner; - show(total: number, delay?: number): IProgressRunner; - show(infiniteOrTotal: true | number, delay?: number): IProgressRunner { + override show(infinite: true, delay?: number): IProgressRunner; + override show(total: number, delay?: number): IProgressRunner; + override show(infiniteOrTotal: true | number, delay?: number): IProgressRunner { // No editor open: ignore any progress reporting if (this.group.isEmpty) { @@ -93,7 +93,7 @@ export class EditorProgressIndicator extends ProgressBarIndicator { return super.show(infiniteOrTotal, delay); } - async showWhile(promise: Promise, delay?: number): Promise { + override async showWhile(promise: Promise, delay?: number): Promise { // No editor open: ignore any progress reporting if (this.group.isEmpty) { diff --git a/src/vs/workbench/services/progress/browser/progressService.ts b/src/vs/workbench/services/progress/browser/progressService.ts index ae4f2303..2a21a892 100644 --- a/src/vs/workbench/services/progress/browser/progressService.ts +++ b/src/vs/workbench/services/progress/browser/progressService.ts @@ -180,8 +180,8 @@ export class ProgressService extends Disposable implements IProgressService { private readonly _onDidReport = this._register(new Emitter()); readonly onDidReport = this._onDidReport.event; - private readonly _onDispose = this._register(new Emitter()); - readonly onDispose = this._onDispose.event; + private readonly _onWillDispose = this._register(new Emitter()); + readonly onWillDispose = this._onWillDispose.event; private _step: IProgressStep | undefined = undefined; get step() { return this._step; } @@ -213,9 +213,9 @@ export class ProgressService extends Disposable implements IProgressService { this.dispose(); } - dispose(): void { + override dispose(): void { this._done = true; - this._onDispose.fire(); + this._onWillDispose.fire(); super.dispose(); } @@ -252,7 +252,7 @@ export class ProgressService extends Disposable implements IProgressService { promise.finally(() => onDidReportListener.dispose()); // When the progress model gets disposed, we are done as well - Event.once(progressStateModel.onDispose)(() => promiseResolve()); + Event.once(progressStateModel.onWillDispose)(() => promiseResolve()); return promise; }); @@ -274,7 +274,7 @@ export class ProgressService extends Disposable implements IProgressService { super(`progress.button.${button}`, button, undefined, true); } - async run(): Promise { + override async run(): Promise { progressStateModel.cancel(index); } }; @@ -290,7 +290,7 @@ export class ProgressService extends Disposable implements IProgressService { super('progress.cancel', localize('cancel', "Cancel"), undefined, true); } - async run(): Promise { + override async run(): Promise { progressStateModel.cancel(); } }; @@ -381,7 +381,7 @@ export class ProgressService extends Disposable implements IProgressService { // Show initially updateNotification(progressStateModel.step); const listener = progressStateModel.onDidReport(step => updateNotification(step)); - Event.once(progressStateModel.onDispose)(() => listener.dispose()); + Event.once(progressStateModel.onWillDispose)(() => listener.dispose()); // Clean up eventually (async () => { diff --git a/src/vs/workbench/services/progress/test/browser/progressIndicator.test.ts b/src/vs/workbench/services/progress/test/browser/progressIndicator.test.ts index e90d4794..7da4e78d 100644 --- a/src/vs/workbench/services/progress/test/browser/progressIndicator.test.ts +++ b/src/vs/workbench/services/progress/test/browser/progressIndicator.test.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { IAction } from 'vs/base/common/actions'; import { IEditorControl } from 'vs/workbench/common/editor'; import { CompositeScope, CompositeProgressIndicator } from 'vs/workbench/services/progress/browser/progressIndicator'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; @@ -24,9 +23,6 @@ class TestViewlet implements IViewlet { hasFocus() { return false; } getId(): string { return this.id; } getTitle(): string { return this.id; } - getActions(): IAction[] { return []; } - getSecondaryActions(): IAction[] { return []; } - getContextMenuActions(): IAction[] { return []; } getControl(): IEditorControl { return null!; } focus(): void { } getOptimalWidth(): number { return 10; } diff --git a/src/vs/workbench/services/quickinput/browser/quickInputService.ts b/src/vs/workbench/services/quickinput/browser/quickInputService.ts index fcdad256..70a77d28 100644 --- a/src/vs/workbench/services/quickinput/browser/quickInputService.ts +++ b/src/vs/workbench/services/quickinput/browser/quickInputService.ts @@ -27,7 +27,7 @@ export class QuickInputService extends BaseQuickInputService { @IContextKeyService contextKeyService: IContextKeyService, @IThemeService themeService: IThemeService, @IAccessibilityService accessibilityService: IAccessibilityService, - @ILayoutService protected readonly layoutService: ILayoutService, + @ILayoutService layoutService: ILayoutService, ) { super(instantiationService, contextKeyService, themeService, accessibilityService, layoutService); @@ -39,7 +39,7 @@ export class QuickInputService extends BaseQuickInputService { this._register(this.onHide(() => this.inQuickInputContext.set(false))); } - protected createController(): QuickInputController { + protected override createController(): QuickInputController { return super.createController(this.layoutService, { ignoreFocusOut: () => !this.configurationService.getValue('workbench.quickOpen.closeOnFocusLost'), backKeybindingLabel: () => this.keybindingService.lookupKeybinding('workbench.action.quickInputBack')?.getLabel() || undefined, diff --git a/src/vs/workbench/services/remote/browser/tunnelServiceImpl.ts b/src/vs/workbench/services/remote/browser/tunnelServiceImpl.ts index 2f88ae5e..029c2e53 100644 --- a/src/vs/workbench/services/remote/browser/tunnelServiceImpl.ts +++ b/src/vs/workbench/services/remote/browser/tunnelServiceImpl.ts @@ -31,7 +31,7 @@ export class TunnelService extends AbstractTunnelService { return undefined; } - canTunnel(uri: URI): boolean { + override canTunnel(uri: URI): boolean { return super.canTunnel(uri) && !!this.environmentService.remoteAuthority; } } diff --git a/src/vs/workbench/services/remote/common/remoteAgentEnvironmentChannel.ts b/src/vs/workbench/services/remote/common/remoteAgentEnvironmentChannel.ts index 24000d04..2caad0f4 100644 --- a/src/vs/workbench/services/remote/common/remoteAgentEnvironmentChannel.ts +++ b/src/vs/workbench/services/remote/common/remoteAgentEnvironmentChannel.ts @@ -42,7 +42,8 @@ export interface IRemoteAgentEnvironmentDTO { workspaceStorageHome: UriComponents; userHome: UriComponents; os: platform.OperatingSystem; - marks: performance.PerformanceMark[] + marks: performance.PerformanceMark[]; + useHostProxy: boolean; } export class RemoteExtensionEnvironmentChannelClient { @@ -66,7 +67,8 @@ export class RemoteExtensionEnvironmentChannelClient { workspaceStorageHome: URI.revive(data.workspaceStorageHome), userHome: URI.revive(data.userHome), os: data.os, - marks: data.marks + marks: data.marks, + useHostProxy: data.useHostProxy }; } diff --git a/src/vs/workbench/services/remote/common/remoteExplorerService.ts b/src/vs/workbench/services/remote/common/remoteExplorerService.ts index d1dfc7a3..efae8651 100644 --- a/src/vs/workbench/services/remote/common/remoteExplorerService.ts +++ b/src/vs/workbench/services/remote/common/remoteExplorerService.ts @@ -247,7 +247,12 @@ export class PortsAttributes extends Disposable { const match = (attributesKey).match(PortsAttributes.RANGE); key = { start: Number(match![1]), end: Number(match![2]) }; } else { - const regTest: RegExp = RegExp(attributesKey); + let regTest: RegExp | undefined = undefined; + try { + regTest = RegExp(attributesKey); + } catch (e) { + // The user entered an invalid regular expression. + } if (regTest) { key = regTest; } @@ -774,6 +779,7 @@ class RemoteExplorerService implements IRemoteExplorerService { } setEditable(tunnelItem: ITunnelItem | undefined, editId: TunnelEditId, data: IEditableData | null): void { + console.log('setting edit ' + data); if (!data) { this._editable = undefined; } else { diff --git a/src/vs/workbench/services/remote/electron-browser/tunnelServiceImpl.ts b/src/vs/workbench/services/remote/electron-browser/tunnelServiceImpl.ts index e3c74cd9..41921078 100644 --- a/src/vs/workbench/services/remote/electron-browser/tunnelServiceImpl.ts +++ b/src/vs/workbench/services/remote/electron-browser/tunnelServiceImpl.ts @@ -25,7 +25,7 @@ export class TunnelService extends BaseTunnelService { super(nodeSocketFactory, logService, signService, productService); } - canTunnel(uri: URI): boolean { + override canTunnel(uri: URI): boolean { return super.canTunnel(uri) && !!this.environmentService.remoteAuthority; } } diff --git a/src/vs/workbench/services/request/browser/requestService.ts b/src/vs/workbench/services/request/browser/requestService.ts index 16aa984a..383b1db2 100644 --- a/src/vs/workbench/services/request/browser/requestService.ts +++ b/src/vs/workbench/services/request/browser/requestService.ts @@ -21,7 +21,7 @@ export class BrowserRequestService extends RequestService { super(configurationService, logService); } - async request(options: IRequestOptions, token: CancellationToken): Promise { + override async request(options: IRequestOptions, token: CancellationToken): Promise { try { const context = await super.request(options, token); const connection = this.remoteAgentService.getConnection(); diff --git a/src/vs/workbench/services/request/electron-sandbox/requestService.ts b/src/vs/workbench/services/request/electron-sandbox/requestService.ts index 86d4700e..4fb44ec4 100644 --- a/src/vs/workbench/services/request/electron-sandbox/requestService.ts +++ b/src/vs/workbench/services/request/electron-sandbox/requestService.ts @@ -20,7 +20,7 @@ export class NativeRequestService extends RequestService { super(configurationService, logService); } - async resolveProxy(url: string): Promise { + override async resolveProxy(url: string): Promise { return this.nativeHostService.resolveProxy(url); } } diff --git a/src/vs/workbench/services/search/common/fileSearchManager.ts b/src/vs/workbench/services/search/common/fileSearchManager.ts index 01ea1a1b..24dde6a0 100644 --- a/src/vs/workbench/services/search/common/fileSearchManager.ts +++ b/src/vs/workbench/services/search/common/fileSearchManager.ts @@ -12,7 +12,6 @@ import { StopWatch } from 'vs/base/common/stopwatch'; import { URI } from 'vs/base/common/uri'; import { IFileMatch, IFileSearchProviderStats, IFolderQuery, ISearchCompleteStats, IFileQuery, QueryGlobTester, resolvePatternsForProvider } from 'vs/workbench/services/search/common/search'; import { FileSearchProvider, FileSearchOptions } from 'vs/workbench/services/search/common/searchExtTypes'; -import { nextTick } from 'vs/base/common/process'; export interface IInternalFileMatch { base: URI; @@ -105,72 +104,62 @@ class FileSearchEngine { }); } - private searchInFolder(fq: IFolderQuery, onResult: (match: IInternalFileMatch) => void): Promise { + private async searchInFolder(fq: IFolderQuery, onResult: (match: IInternalFileMatch) => void): Promise { const cancellation = new CancellationTokenSource(); - return new Promise((resolve, reject) => { - const options = this.getSearchOptionsForFolder(fq); - const tree = this.initDirectoryTree(); + const options = this.getSearchOptionsForFolder(fq); + const tree = this.initDirectoryTree(); - const queryTester = new QueryGlobTester(this.config, fq); - const noSiblingsClauses = !queryTester.hasSiblingExcludeClauses(); + const queryTester = new QueryGlobTester(this.config, fq); + const noSiblingsClauses = !queryTester.hasSiblingExcludeClauses(); - let providerSW: StopWatch; - new Promise(_resolve => nextTick(_resolve)) - .then(() => { - this.activeCancellationTokens.add(cancellation); + let providerSW: StopWatch; - providerSW = StopWatch.create(); - return this.provider.provideFileSearchResults( - { - pattern: this.config.filePattern || '' - }, - options, - cancellation.token); - }) - .then(results => { - const providerTime = providerSW.elapsed(); - const postProcessSW = StopWatch.create(); + try { + this.activeCancellationTokens.add(cancellation); - if (this.isCanceled && !this.isLimitHit) { - return null; + providerSW = StopWatch.create(); + const results = await this.provider.provideFileSearchResults( + { + pattern: this.config.filePattern || '' + }, + options, + cancellation.token); + const providerTime = providerSW.elapsed(); + const postProcessSW = StopWatch.create(); + + if (this.isCanceled && !this.isLimitHit) { + return null; + } + + if (results) { + results.forEach(result => { + const relativePath = path.posix.relative(fq.folder.path, result.path); + + if (noSiblingsClauses) { + const basename = path.basename(result.path); + this.matchFile(onResult, { base: fq.folder, relativePath, basename }); + + return; } - if (results) { - results.forEach(result => { - const relativePath = path.posix.relative(fq.folder.path, result.path); + // TODO: Optimize siblings clauses with ripgrep here. + this.addDirectoryEntries(tree, fq.folder, relativePath, onResult); + }); + } - if (noSiblingsClauses) { - const basename = path.basename(result.path); - this.matchFile(onResult, { base: fq.folder, relativePath, basename }); + if (this.isCanceled && !this.isLimitHit) { + return null; + } - return; - } - - // TODO: Optimize siblings clauses with ripgrep here. - this.addDirectoryEntries(tree, fq.folder, relativePath, onResult); - }); - } - - this.activeCancellationTokens.delete(cancellation); - if (this.isCanceled && !this.isLimitHit) { - return null; - } - - this.matchDirectoryTree(tree, queryTester, onResult); - return { - providerTime, - postProcessTime: postProcessSW.elapsed() - }; - }).then( - stats => { - cancellation.dispose(); - resolve(stats); - }, - err => { - cancellation.dispose(); - reject(err); - }); - }); + this.matchDirectoryTree(tree, queryTester, onResult); + return { + providerTime, + postProcessTime: postProcessSW.elapsed() + }; + } finally { + cancellation.dispose(); + this.activeCancellationTokens.delete(cancellation); + } } private getSearchOptionsForFolder(fq: IFolderQuery): FileSearchOptions { diff --git a/src/vs/workbench/services/search/common/search.ts b/src/vs/workbench/services/search/common/search.ts index 7174115b..01c51f67 100644 --- a/src/vs/workbench/services/search/common/search.ts +++ b/src/vs/workbench/services/search/common/search.ts @@ -17,6 +17,9 @@ import { ITelemetryData } from 'vs/platform/telemetry/common/telemetry'; import { Event } from 'vs/base/common/event'; import * as paths from 'vs/base/common/path'; import { isPromiseCanceledError } from 'vs/base/common/errors'; +import { TextSearchCompleteMessageType } from 'vs/workbench/services/search/common/searchExtTypes'; + +export { TextSearchCompleteMessageType }; export const VIEWLET_ID = 'workbench.view.search'; export const PANEL_ID = 'workbench.panel.search'; @@ -205,6 +208,7 @@ export function isProgressMessage(p: ISearchProgressItem | ISerializedSearchProg export interface ISearchCompleteStats { limitHit?: boolean; + messages: { text: string, type: TextSearchCompleteMessageType }[]; stats?: IFileSearchStats | ITextSearchStats; } @@ -504,11 +508,13 @@ export interface ISearchEngine { export interface ISerializedSearchSuccess { type: 'success'; limitHit: boolean; + messages: { text: string, type: TextSearchCompleteMessageType }[]; stats?: IFileSearchStats | ITextSearchStats; } export interface ISearchEngineSuccess { limitHit: boolean; + messages: { text: string, type: TextSearchCompleteMessageType }[]; stats: ISearchEngineStats; } diff --git a/src/vs/workbench/services/search/common/searchExtTypes.ts b/src/vs/workbench/services/search/common/searchExtTypes.ts index 2d31c88e..4c378ec4 100644 --- a/src/vs/workbench/services/search/common/searchExtTypes.ts +++ b/src/vs/workbench/services/search/common/searchExtTypes.ts @@ -216,6 +216,14 @@ export interface TextSearchOptions extends SearchOptions { afterContext?: number; } +/** + * Represents the severiry of a TextSearchComplete message. + */ +export enum TextSearchCompleteMessageType { + Information = 1, + Warning = 2, +} + /** * Information collected when text search is complete. */ @@ -228,6 +236,15 @@ export interface TextSearchComplete { * - If search hits an internal limit which is less than `maxResults`, this should be true. */ limitHit?: boolean; + + /** + * Additional information regarding the state of the completed search. + * + * Supports links in markdown syntax: + * - Click to [run a command](command:workbench.action.OpenQuickPick) + * - Click to [open a website](https://aka.ms) + */ + message?: { text: string, type: TextSearchCompleteMessageType } | { text: string, type: TextSearchCompleteMessageType }[]; } /** diff --git a/src/vs/workbench/services/search/common/searchService.ts b/src/vs/workbench/services/search/common/searchService.ts index eb7bab99..d459d8e2 100644 --- a/src/vs/workbench/services/search/common/searchService.ts +++ b/src/vs/workbench/services/search/common/searchService.ts @@ -145,13 +145,15 @@ export class SearchService extends Disposable implements ISearchService { if (!completes.length) { return { limitHit: false, - results: [] + results: [], + messages: [], }; } - return { + return { limitHit: completes[0] && completes[0].limitHit, stats: completes[0].stats, + messages: arrays.coalesce(arrays.flatten(completes.map(i => i.messages))).filter(arrays.uniqueFilter(message => message.type + message.text)), results: arrays.flatten(completes.map((c: ISearchComplete) => c.results)) }; })(); diff --git a/src/vs/workbench/services/search/common/textSearchManager.ts b/src/vs/workbench/services/search/common/textSearchManager.ts index 03daa687..30df394a 100644 --- a/src/vs/workbench/services/search/common/textSearchManager.ts +++ b/src/vs/workbench/services/search/common/textSearchManager.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as path from 'vs/base/common/path'; -import { mapArrayOrNot } from 'vs/base/common/arrays'; +import { flatten, mapArrayOrNot } from 'vs/base/common/arrays'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import * as resources from 'vs/base/common/resources'; @@ -12,8 +12,8 @@ import * as glob from 'vs/base/common/glob'; import { URI } from 'vs/base/common/uri'; import { IExtendedExtensionSearchOptions, IFileMatch, IFolderQuery, IPatternInfo, ISearchCompleteStats, ITextQuery, ITextSearchContext, ITextSearchMatch, ITextSearchResult, QueryGlobTester, resolvePatternsForProvider } from 'vs/workbench/services/search/common/search'; import { TextSearchProvider, TextSearchResult, TextSearchMatch, TextSearchComplete, Range, TextSearchOptions, TextSearchQuery } from 'vs/workbench/services/search/common/searchExtTypes'; -import { nextTick } from 'vs/base/common/process'; import { Schemas } from 'vs/base/common/network'; +import { isArray } from 'vs/base/common/types'; export interface IFileUtils { readdir: (resource: URI) => Promise; @@ -71,6 +71,11 @@ export class TextSearchManager { const someFolderHitLImit = results.some(result => !!result && !!result.limitHit); resolve({ limitHit: this.isLimitHit || someFolderHitLImit, + messages: flatten(results.map(result => { + if (!result?.message) { return []; } + if (isArray(result.message)) { return result.message; } + else { return [result.message]; } + })), stats: { type: 'textSearchProvider' } @@ -109,7 +114,7 @@ export class TextSearchManager { }; } - private searchInFolder(folderQuery: IFolderQuery, onResult: (result: TextSearchResult) => void, token: CancellationToken): Promise { + private async searchInFolder(folderQuery: IFolderQuery, onResult: (result: TextSearchResult) => void, token: CancellationToken): Promise { const queryTester = new QueryGlobTester(this.query, folderQuery); const testingPs: Promise[] = []; const progress = { @@ -138,12 +143,9 @@ export class TextSearchManager { }; const searchOptions = this.getSearchOptionsForFolder(folderQuery); - return new Promise(resolve => nextTick(resolve)) - .then(() => this.provider.provideTextSearchResults(patternInfoToQuery(this.query.contentPattern), searchOptions, progress, token)) - .then(result => { - return Promise.all(testingPs) - .then(() => result); - }); + const result = await this.provider.provideTextSearchResults(patternInfoToQuery(this.query.contentPattern), searchOptions, progress, token); + await Promise.all(testingPs); + return result; } private validateProviderResult(result: TextSearchResult): boolean { diff --git a/src/vs/workbench/services/search/electron-browser/searchService.ts b/src/vs/workbench/services/search/electron-browser/searchService.ts index 7eb33c67..3a667048 100644 --- a/src/vs/workbench/services/search/electron-browser/searchService.ts +++ b/src/vs/workbench/services/search/electron-browser/searchService.ts @@ -138,7 +138,8 @@ export class DiskSearch implements ISearchResultProvider { c({ limitHit: ev.limitHit, results: result, - stats: ev.stats + stats: ev.stats, + messages: ev.messages, }); } else { e(ev.error); diff --git a/src/vs/workbench/services/search/node/fileSearch.ts b/src/vs/workbench/services/search/node/fileSearch.ts index 71b7f1b2..ab3e5647 100644 --- a/src/vs/workbench/services/search/node/fileSearch.ts +++ b/src/vs/workbench/services/search/node/fileSearch.ts @@ -295,7 +295,7 @@ export class FileWalker { /** * Public for testing. */ - readStdout(cmd: childProcess.ChildProcess, encoding: string, cb: (err: Error | null, stdout?: string) => void): void { + readStdout(cmd: childProcess.ChildProcess, encoding: BufferEncoding, cb: (err: Error | null, stdout?: string) => void): void { let all = ''; this.collectStdout(cmd, encoding, () => { }, (err: Error | null, stdout?: string, last?: boolean) => { if (err) { @@ -310,7 +310,7 @@ export class FileWalker { }); } - private collectStdout(cmd: childProcess.ChildProcess, encoding: string, onMessage: (message: IProgressMessage) => void, cb: (err: Error | null, stdout?: string, last?: boolean) => void): void { + private collectStdout(cmd: childProcess.ChildProcess, encoding: BufferEncoding, onMessage: (message: IProgressMessage) => void, cb: (err: Error | null, stdout?: string, last?: boolean) => void): void { let onData = (err: Error | null, stdout?: string, last?: boolean) => { if (err || last) { onData = () => { }; @@ -357,7 +357,7 @@ export class FileWalker { }); } - private forwardData(stream: Readable, encoding: string, cb: (err: Error | null, stdout?: string) => void): StringDecoder { + private forwardData(stream: Readable, encoding: BufferEncoding, cb: (err: Error | null, stdout?: string) => void): StringDecoder { const decoder = new StringDecoder(encoding); stream.on('data', (data: Buffer) => { cb(null, decoder.write(data)); @@ -373,7 +373,7 @@ export class FileWalker { return buffers; } - private decodeData(buffers: Buffer[], encoding: string): string { + private decodeData(buffers: Buffer[], encoding: BufferEncoding): string { const decoder = new StringDecoder(encoding); return buffers.map(buffer => decoder.write(buffer)).join(''); } @@ -640,7 +640,8 @@ export class Engine implements ISearchEngine { this.walker.walk(this.folderQueries, this.extraFiles, onResult, onProgress, (err: Error | null, isLimitHit: boolean) => { done(err, { limitHit: isLimitHit, - stats: this.walker.getStats() + stats: this.walker.getStats(), + messages: [], }); }); } diff --git a/src/vs/workbench/services/search/node/rawSearchService.ts b/src/vs/workbench/services/search/node/rawSearchService.ts index 38ac328f..f5e19bdd 100644 --- a/src/vs/workbench/services/search/node/rawSearchService.ts +++ b/src/vs/workbench/services/search/node/rawSearchService.ts @@ -195,6 +195,7 @@ export class SearchService implements IRawSearchService { workspaceFolderCount: config.folderQueries.length, resultCount: sortedResults.length }, + messages: result.messages, limitHit: result.limitHit || typeof config.maxResults === 'number' && results.length > config.maxResults } as ISerializedSearchSuccess, sortedResults]; }); diff --git a/src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts b/src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts index da0483de..d871f4ab 100644 --- a/src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts +++ b/src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts @@ -199,9 +199,9 @@ export class RipgrepParser extends EventEmitter { } - on(event: 'result', listener: (result: TextSearchResult) => void): this; - on(event: 'hitLimit', listener: () => void): this; - on(event: string, listener: (...args: any[]) => void): this { + override on(event: 'result', listener: (result: TextSearchResult) => void): this; + override on(event: 'hitLimit', listener: () => void): this; + override on(event: string, listener: (...args: any[]) => void): this { super.on(event, listener); return this; } diff --git a/src/vs/workbench/services/search/test/common/replace.test.ts b/src/vs/workbench/services/search/test/common/replace.test.ts index 3c18ccb9..84bf17a4 100644 --- a/src/vs/workbench/services/search/test/common/replace.test.ts +++ b/src/vs/workbench/services/search/test/common/replace.test.ts @@ -10,12 +10,12 @@ suite('Replace Pattern test', () => { test('parse replace string', () => { const testParse = (input: string, expected: string, expectedHasParameters: boolean) => { let actual = new ReplacePattern(input, { pattern: 'somepattern', isRegExp: true }); - assert.equal(expected, actual.pattern); - assert.equal(expectedHasParameters, actual.hasParameters); + assert.strictEqual(expected, actual.pattern); + assert.strictEqual(expectedHasParameters, actual.hasParameters); actual = new ReplacePattern('hello' + input + 'hi', { pattern: 'sonepattern', isRegExp: true }); - assert.equal('hello' + expected + 'hi', actual.pattern); - assert.equal(expectedHasParameters, actual.hasParameters); + assert.strictEqual('hello' + expected + 'hi', actual.pattern); + assert.strictEqual(expectedHasParameters, actual.hasParameters); }; // no backslash => no treatment @@ -79,162 +79,162 @@ suite('Replace Pattern test', () => { test('create pattern by passing regExp', () => { let expected = /abc/; let actual = new ReplacePattern('hello', false, expected).regExp; - assert.deepEqual(expected, actual); + assert.deepStrictEqual(expected, actual); expected = /abc/; actual = new ReplacePattern('hello', false, /abc/g).regExp; - assert.deepEqual(expected, actual); + assert.deepStrictEqual(expected, actual); let testObject = new ReplacePattern('hello$0', false, /abc/g); - assert.equal(false, testObject.hasParameters); + assert.strictEqual(false, testObject.hasParameters); testObject = new ReplacePattern('hello$0', true, /abc/g); - assert.equal(true, testObject.hasParameters); + assert.strictEqual(true, testObject.hasParameters); }); test('get replace string if given text is a complete match', () => { let testObject = new ReplacePattern('hello', { pattern: 'bla', isRegExp: true }); let actual = testObject.getReplaceString('bla'); - assert.equal('hello', actual); + assert.strictEqual('hello', actual); testObject = new ReplacePattern('hello', { pattern: 'bla', isRegExp: false }); actual = testObject.getReplaceString('bla'); - assert.equal('hello', actual); + assert.strictEqual('hello', actual); testObject = new ReplacePattern('hello', { pattern: '(bla)', isRegExp: true }); actual = testObject.getReplaceString('bla'); - assert.equal('hello', actual); + assert.strictEqual('hello', actual); testObject = new ReplacePattern('hello$0', { pattern: '(bla)', isRegExp: true }); actual = testObject.getReplaceString('bla'); - assert.equal('hellobla', actual); + assert.strictEqual('hellobla', actual); testObject = new ReplacePattern('import * as $1 from \'$2\';', { pattern: 'let\\s+(\\w+)\\s*=\\s*require\\s*\\(\\s*[\'\"]([\\w.\\-/]+)\\s*[\'\"]\\s*\\)\\s*', isRegExp: true }); actual = testObject.getReplaceString('let fs = require(\'fs\')'); - assert.equal('import * as fs from \'fs\';', actual); + assert.strictEqual('import * as fs from \'fs\';', actual); actual = testObject.getReplaceString('let something = require(\'fs\')'); - assert.equal('import * as something from \'fs\';', actual); + assert.strictEqual('import * as something from \'fs\';', actual); actual = testObject.getReplaceString('let require(\'fs\')'); - assert.equal(null, actual); + assert.strictEqual(null, actual); testObject = new ReplacePattern('import * as $1 from \'$1\';', { pattern: 'let\\s+(\\w+)\\s*=\\s*require\\s*\\(\\s*[\'\"]([\\w.\\-/]+)\\s*[\'\"]\\s*\\)\\s*', isRegExp: true }); actual = testObject.getReplaceString('let something = require(\'fs\')'); - assert.equal('import * as something from \'something\';', actual); + assert.strictEqual('import * as something from \'something\';', actual); testObject = new ReplacePattern('import * as $2 from \'$1\';', { pattern: 'let\\s+(\\w+)\\s*=\\s*require\\s*\\(\\s*[\'\"]([\\w.\\-/]+)\\s*[\'\"]\\s*\\)\\s*', isRegExp: true }); actual = testObject.getReplaceString('let something = require(\'fs\')'); - assert.equal('import * as fs from \'something\';', actual); + assert.strictEqual('import * as fs from \'something\';', actual); testObject = new ReplacePattern('import * as $0 from \'$0\';', { pattern: 'let\\s+(\\w+)\\s*=\\s*require\\s*\\(\\s*[\'\"]([\\w.\\-/]+)\\s*[\'\"]\\s*\\)\\s*', isRegExp: true }); actual = testObject.getReplaceString('let something = require(\'fs\');'); - assert.equal('import * as let something = require(\'fs\') from \'let something = require(\'fs\')\';', actual); + assert.strictEqual('import * as let something = require(\'fs\') from \'let something = require(\'fs\')\';', actual); testObject = new ReplacePattern('import * as $1 from \'$2\';', { pattern: 'let\\s+(\\w+)\\s*=\\s*require\\s*\\(\\s*[\'\"]([\\w.\\-/]+)\\s*[\'\"]\\s*\\)\\s*', isRegExp: false }); actual = testObject.getReplaceString('let fs = require(\'fs\');'); - assert.equal(null, actual); + assert.strictEqual(null, actual); testObject = new ReplacePattern('cat$1', { pattern: 'for(.*)', isRegExp: true }); actual = testObject.getReplaceString('for ()'); - assert.equal('cat ()', actual); + assert.strictEqual('cat ()', actual); }); test('case operations', () => { let testObject = new ReplacePattern('a\\u$1l\\u\\l\\U$2M$3n', { pattern: 'a(l)l(good)m(e)n', isRegExp: true }); let actual = testObject.getReplaceString('allgoodmen'); - assert.equal('aLlGoODMen', actual); + assert.strictEqual('aLlGoODMen', actual); }); test('get replace string for no matches', () => { let testObject = new ReplacePattern('hello', { pattern: 'bla', isRegExp: true }); let actual = testObject.getReplaceString('foo'); - assert.equal(null, actual); + assert.strictEqual(null, actual); testObject = new ReplacePattern('hello', { pattern: 'bla', isRegExp: false }); actual = testObject.getReplaceString('foo'); - assert.equal(null, actual); + assert.strictEqual(null, actual); }); test('get replace string if match is sub-string of the text', () => { let testObject = new ReplacePattern('hello', { pattern: 'bla', isRegExp: true }); let actual = testObject.getReplaceString('this is a bla text'); - assert.equal('hello', actual); + assert.strictEqual('hello', actual); testObject = new ReplacePattern('hello', { pattern: 'bla', isRegExp: false }); actual = testObject.getReplaceString('this is a bla text'); - assert.equal('hello', actual); + assert.strictEqual('hello', actual); testObject = new ReplacePattern('that', { pattern: 'this(?=.*bla)', isRegExp: true }); actual = testObject.getReplaceString('this is a bla text'); - assert.equal('that', actual); + assert.strictEqual('that', actual); testObject = new ReplacePattern('$1at', { pattern: '(th)is(?=.*bla)', isRegExp: true }); actual = testObject.getReplaceString('this is a bla text'); - assert.equal('that', actual); + assert.strictEqual('that', actual); testObject = new ReplacePattern('$1e', { pattern: '(th)is(?=.*bla)', isRegExp: true }); actual = testObject.getReplaceString('this is a bla text'); - assert.equal('the', actual); + assert.strictEqual('the', actual); testObject = new ReplacePattern('$1ere', { pattern: '(th)is(?=.*bla)', isRegExp: true }); actual = testObject.getReplaceString('this is a bla text'); - assert.equal('there', actual); + assert.strictEqual('there', actual); testObject = new ReplacePattern('$1', { pattern: '(th)is(?=.*bla)', isRegExp: true }); actual = testObject.getReplaceString('this is a bla text'); - assert.equal('th', actual); + assert.strictEqual('th', actual); testObject = new ReplacePattern('ma$1', { pattern: '(th)is(?=.*bla)', isRegExp: true }); actual = testObject.getReplaceString('this is a bla text'); - assert.equal('math', actual); + assert.strictEqual('math', actual); testObject = new ReplacePattern('ma$1s', { pattern: '(th)is(?=.*bla)', isRegExp: true }); actual = testObject.getReplaceString('this is a bla text'); - assert.equal('maths', actual); + assert.strictEqual('maths', actual); testObject = new ReplacePattern('ma$1s', { pattern: '(th)is(?=.*bla)', isRegExp: true }); actual = testObject.getReplaceString('this is a bla text'); - assert.equal('maths', actual); + assert.strictEqual('maths', actual); testObject = new ReplacePattern('$0', { pattern: '(th)is(?=.*bla)', isRegExp: true }); actual = testObject.getReplaceString('this is a bla text'); - assert.equal('this', actual); + assert.strictEqual('this', actual); testObject = new ReplacePattern('$0$1', { pattern: '(th)is(?=.*bla)', isRegExp: true }); actual = testObject.getReplaceString('this is a bla text'); - assert.equal('thisth', actual); + assert.strictEqual('thisth', actual); testObject = new ReplacePattern('foo', { pattern: 'bla(?=\\stext$)', isRegExp: true }); actual = testObject.getReplaceString('this is a bla text'); - assert.equal('foo', actual); + assert.strictEqual('foo', actual); testObject = new ReplacePattern('f$1', { pattern: 'b(la)(?=\\stext$)', isRegExp: true }); actual = testObject.getReplaceString('this is a bla text'); - assert.equal('fla', actual); + assert.strictEqual('fla', actual); testObject = new ReplacePattern('f$0', { pattern: 'b(la)(?=\\stext$)', isRegExp: true }); actual = testObject.getReplaceString('this is a bla text'); - assert.equal('fbla', actual); + assert.strictEqual('fbla', actual); testObject = new ReplacePattern('$0ah', { pattern: 'b(la)(?=\\stext$)', isRegExp: true }); actual = testObject.getReplaceString('this is a bla text'); - assert.equal('blaah', actual); + assert.strictEqual('blaah', actual); testObject = new ReplacePattern('newrege$1', true, /Testrege(\w*)/); actual = testObject.getReplaceString('Testregex', true); - assert.equal('Newregex', actual); + assert.strictEqual('Newregex', actual); testObject = new ReplacePattern('newrege$1', true, /TESTREGE(\w*)/); actual = testObject.getReplaceString('TESTREGEX', true); - assert.equal('NEWREGEX', actual); + assert.strictEqual('NEWREGEX', actual); testObject = new ReplacePattern('new_rege$1', true, /Test_Rege(\w*)/); actual = testObject.getReplaceString('Test_Regex', true); - assert.equal('New_Regex', actual); + assert.strictEqual('New_Regex', actual); testObject = new ReplacePattern('new-rege$1', true, /Test-Rege(\w*)/); actual = testObject.getReplaceString('Test-Regex', true); - assert.equal('New-Regex', actual); + assert.strictEqual('New-Regex', actual); }); }); diff --git a/src/vs/workbench/services/search/test/common/search.test.ts b/src/vs/workbench/services/search/test/common/search.test.ts index 6ec3c814..4faa4bb4 100644 --- a/src/vs/workbench/services/search/test/common/search.test.ts +++ b/src/vs/workbench/services/search/test/common/search.test.ts @@ -13,7 +13,7 @@ suite('TextSearchResult', () => { }; function assertOneLinePreviewRangeText(text: string, result: TextSearchMatch): void { - assert.equal( + assert.strictEqual( result.preview.text.substring((result.preview.matches).startColumn, (result.preview.matches).endColumn), text); } @@ -21,49 +21,49 @@ suite('TextSearchResult', () => { test('empty without preview options', () => { const range = new OneLineRange(5, 0, 0); const result = new TextSearchMatch('', range); - assert.deepEqual(result.ranges, range); + assert.deepStrictEqual(result.ranges, range); assertOneLinePreviewRangeText('', result); }); test('empty with preview options', () => { const range = new OneLineRange(5, 0, 0); const result = new TextSearchMatch('', range, previewOptions1); - assert.deepEqual(result.ranges, range); + assert.deepStrictEqual(result.ranges, range); assertOneLinePreviewRangeText('', result); }); test('short without preview options', () => { const range = new OneLineRange(5, 4, 7); const result = new TextSearchMatch('foo bar', range); - assert.deepEqual(result.ranges, range); + assert.deepStrictEqual(result.ranges, range); assertOneLinePreviewRangeText('bar', result); }); test('short with preview options', () => { const range = new OneLineRange(5, 4, 7); const result = new TextSearchMatch('foo bar', range, previewOptions1); - assert.deepEqual(result.ranges, range); + assert.deepStrictEqual(result.ranges, range); assertOneLinePreviewRangeText('bar', result); }); test('leading', () => { const range = new OneLineRange(5, 25, 28); const result = new TextSearchMatch('long text very long text foo', range, previewOptions1); - assert.deepEqual(result.ranges, range); + assert.deepStrictEqual(result.ranges, range); assertOneLinePreviewRangeText('foo', result); }); test('trailing', () => { const range = new OneLineRange(5, 0, 3); const result = new TextSearchMatch('foo long text very long text long text very long text long text very long text long text very long text long text very long text', range, previewOptions1); - assert.deepEqual(result.ranges, range); + assert.deepStrictEqual(result.ranges, range); assertOneLinePreviewRangeText('foo', result); }); test('middle', () => { const range = new OneLineRange(5, 30, 33); const result = new TextSearchMatch('long text very long text long foo text very long text long text very long text long text very long text long text very long text', range, previewOptions1); - assert.deepEqual(result.ranges, range); + assert.deepStrictEqual(result.ranges, range); assertOneLinePreviewRangeText('foo', result); }); @@ -75,7 +75,7 @@ suite('TextSearchResult', () => { const range = new OneLineRange(0, 4, 7); const result = new TextSearchMatch('foo bar', range, previewOptions); - assert.deepEqual(result.ranges, range); + assert.deepStrictEqual(result.ranges, range); assertOneLinePreviewRangeText('b', result); }); @@ -87,12 +87,12 @@ suite('TextSearchResult', () => { const range = new SearchRange(5, 4, 6, 3); const result = new TextSearchMatch('foo bar\nfoo bar', range, previewOptions); - assert.deepEqual(result.ranges, range); - assert.equal(result.preview.text, 'foo bar\nfoo bar'); - assert.equal((result.preview.matches).startLineNumber, 0); - assert.equal((result.preview.matches).startColumn, 4); - assert.equal((result.preview.matches).endLineNumber, 1); - assert.equal((result.preview.matches).endColumn, 3); + assert.deepStrictEqual(result.ranges, range); + assert.strictEqual(result.preview.text, 'foo bar\nfoo bar'); + assert.strictEqual((result.preview.matches).startLineNumber, 0); + assert.strictEqual((result.preview.matches).startColumn, 4); + assert.strictEqual((result.preview.matches).endLineNumber, 1); + assert.strictEqual((result.preview.matches).endColumn, 3); }); test('compacts multiple ranges on long lines', () => { @@ -105,8 +105,8 @@ suite('TextSearchResult', () => { const range2 = new SearchRange(5, 133, 5, 136); const range3 = new SearchRange(5, 141, 5, 144); const result = new TextSearchMatch('foo bar 123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 foo bar baz bar', [range1, range2, range3], previewOptions); - assert.deepEqual(result.preview.matches, [new SearchRange(0, 4, 0, 7), new SearchRange(0, 42, 0, 45), new SearchRange(0, 50, 0, 53)]); - assert.equal(result.preview.text, 'foo bar 123456⟪ 117 characters skipped ⟫o bar baz bar'); + assert.deepStrictEqual(result.preview.matches, [new OneLineRange(0, 4, 7), new OneLineRange(0, 42, 45), new OneLineRange(0, 50, 53)]); + assert.strictEqual(result.preview.text, 'foo bar 123456⟪ 117 characters skipped ⟫o bar baz bar'); }); test('trims lines endings', () => { @@ -116,8 +116,8 @@ suite('TextSearchResult', () => { charsPerLine: 10000 }; - assert.equal(new TextSearchMatch('foo bar\n', range, previewOptions).preview.text, 'foo bar'); - assert.equal(new TextSearchMatch('foo bar\r\n', range, previewOptions).preview.text, 'foo bar'); + assert.strictEqual(new TextSearchMatch('foo bar\n', range, previewOptions).preview.text, 'foo bar'); + assert.strictEqual(new TextSearchMatch('foo bar\r\n', range, previewOptions).preview.text, 'foo bar'); }); // test('all lines of multiline match', () => { @@ -128,7 +128,7 @@ suite('TextSearchResult', () => { // const range = new SearchRange(5, 4, 6, 3); // const result = new TextSearchResult('foo bar\nfoo bar', range, previewOptions); - // assert.deepEqual(result.range, range); + // assert.deepStrictEqual(result.range, range); // assertPreviewRangeText('bar\nfoo', result); // }); }); diff --git a/src/vs/workbench/services/search/test/common/searchHelpers.test.ts b/src/vs/workbench/services/search/test/common/searchHelpers.test.ts index a39fead7..96f243e7 100644 --- a/src/vs/workbench/services/search/test/common/searchHelpers.test.ts +++ b/src/vs/workbench/services/search/test/common/searchHelpers.test.ts @@ -19,8 +19,8 @@ suite('SearchHelpers', () => { test('simple', () => { const results = editorMatchesToTextSearchResults([new FindMatch(new Range(6, 1, 6, 2), null)], mockTextModel); - assert.equal(results.length, 1); - assert.equal(results[0].preview.text, '6\n'); + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].preview.text, '6\n'); assert.deepEqual(results[0].preview.matches, [new Range(0, 0, 0, 1)]); assert.deepEqual(results[0].ranges, [new Range(5, 0, 5, 1)]); }); @@ -33,7 +33,7 @@ suite('SearchHelpers', () => { new FindMatch(new Range(9, 1, 10, 3), null), ], mockTextModel); - assert.equal(results.length, 2); + assert.strictEqual(results.length, 2); assert.deepEqual(results[0].preview.matches, [ new Range(0, 0, 0, 1), new Range(0, 3, 2, 1), @@ -42,7 +42,7 @@ suite('SearchHelpers', () => { new Range(5, 0, 5, 1), new Range(5, 3, 7, 1), ]); - assert.equal(results[0].preview.text, '6\n7\n8\n'); + assert.strictEqual(results[0].preview.text, '6\n7\n8\n'); assert.deepEqual(results[1].preview.matches, [ new Range(0, 0, 1, 2), @@ -50,7 +50,7 @@ suite('SearchHelpers', () => { assert.deepEqual(results[1].ranges, [ new Range(8, 0, 9, 2), ]); - assert.equal(results[1].preview.text, '9\n10\n'); + assert.strictEqual(results[1].preview.text, '9\n10\n'); }); }); @@ -90,7 +90,7 @@ suite('SearchHelpers', () => { ranges: new Range(0, 0, 0, 10) }]; - assert.deepEqual(addContextToEditorMatches(matches, mockTextModel, getQuery()), matches); + assert.deepStrictEqual(addContextToEditorMatches(matches, mockTextModel, getQuery()), matches); }); test('simple', () => { @@ -102,7 +102,7 @@ suite('SearchHelpers', () => { ranges: new Range(1, 0, 1, 10) }]; - assert.deepEqual(addContextToEditorMatches(matches, mockTextModel, getQuery(1, 2)), [ + assert.deepStrictEqual(addContextToEditorMatches(matches, mockTextModel, getQuery(1, 2)), [ { text: '1', lineNumber: 0 @@ -136,7 +136,7 @@ suite('SearchHelpers', () => { ranges: new Range(2, 0, 2, 10) }]; - assert.deepEqual(addContextToEditorMatches(matches, mockTextModel, getQuery(1, 2)), [ + assert.deepStrictEqual(addContextToEditorMatches(matches, mockTextModel, getQuery(1, 2)), [ { text: '1', lineNumber: 0 @@ -170,7 +170,7 @@ suite('SearchHelpers', () => { ranges: new Range(MOCK_LINE_COUNT - 1, 0, MOCK_LINE_COUNT - 1, 10) }]; - assert.deepEqual(addContextToEditorMatches(matches, mockTextModel, getQuery(1, 2)), [ + assert.deepStrictEqual(addContextToEditorMatches(matches, mockTextModel, getQuery(1, 2)), [ matches[0], { text: '2', @@ -188,4 +188,4 @@ suite('SearchHelpers', () => { ]); }); }); -}); \ No newline at end of file +}); 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 f5f4190f..433043e3 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 @@ -47,7 +47,8 @@ class TestSearchEngine implements ISearchEngine { if (self.isCanceled) { done(null!, { limitHit: false, - stats: stats + stats: stats, + messages: [], }); return; } @@ -55,7 +56,8 @@ class TestSearchEngine implements ISearchEngine { if (!result) { done(null!, { limitHit: false, - stats: stats + stats: stats, + messages: [], }); } else { onResult(result); 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 87da9737..5d2ec866 100644 --- a/src/vs/workbench/services/search/test/node/fileSearch.integrationTest.ts +++ b/src/vs/workbench/services/search/test/node/fileSearch.integrationTest.ts @@ -38,7 +38,7 @@ async function doSearchTest(query: IFileQuery, expectedResultCount: number | Fun } }); - assert.equal(results.length, expectedResultCount, `rg ${results.length} !== ${expectedResultCount}`); + assert.strictEqual(results.length, expectedResultCount, `rg ${results.length} !== ${expectedResultCount}`); } flakySuite('FileSearch-integration', function () { diff --git a/src/vs/workbench/services/search/test/node/ripgrepFileSearch.test.ts b/src/vs/workbench/services/search/test/node/ripgrepFileSearch.test.ts index 9b3d7cfa..5e949c91 100644 --- a/src/vs/workbench/services/search/test/node/ripgrepFileSearch.test.ts +++ b/src/vs/workbench/services/search/test/node/ripgrepFileSearch.test.ts @@ -10,7 +10,7 @@ import { fixDriveC, getAbsoluteGlob } from 'vs/workbench/services/search/node/ri suite('RipgrepFileSearch - etc', () => { function testGetAbsGlob(params: string[]): void { const [folder, glob, expectedResult] = params; - assert.equal(fixDriveC(getAbsoluteGlob(folder, glob)), expectedResult, JSON.stringify(params)); + assert.strictEqual(fixDriveC(getAbsoluteGlob(folder, glob)), expectedResult, JSON.stringify(params)); } (!platform.isWindows ? test.skip : test)('getAbsoluteGlob_win', () => { diff --git a/src/vs/workbench/services/search/test/node/ripgrepTextSearchEngine.test.ts b/src/vs/workbench/services/search/test/node/ripgrepTextSearchEngine.test.ts index 3815dee8..da0c3dd5 100644 --- a/src/vs/workbench/services/search/test/node/ripgrepTextSearchEngine.test.ts +++ b/src/vs/workbench/services/search/test/node/ripgrepTextSearchEngine.test.ts @@ -11,21 +11,21 @@ import { Range, TextSearchResult } from 'vs/workbench/services/search/common/sea suite('RipgrepTextSearchEngine', () => { test('unicodeEscapesToPCRE2', async () => { - assert.equal(unicodeEscapesToPCRE2('\\u1234'), '\\x{1234}'); - assert.equal(unicodeEscapesToPCRE2('\\u1234\\u0001'), '\\x{1234}\\x{0001}'); - assert.equal(unicodeEscapesToPCRE2('foo\\u1234bar'), 'foo\\x{1234}bar'); - assert.equal(unicodeEscapesToPCRE2('\\\\\\u1234'), '\\\\\\x{1234}'); - assert.equal(unicodeEscapesToPCRE2('foo\\\\\\u1234'), 'foo\\\\\\x{1234}'); + assert.strictEqual(unicodeEscapesToPCRE2('\\u1234'), '\\x{1234}'); + assert.strictEqual(unicodeEscapesToPCRE2('\\u1234\\u0001'), '\\x{1234}\\x{0001}'); + assert.strictEqual(unicodeEscapesToPCRE2('foo\\u1234bar'), 'foo\\x{1234}bar'); + assert.strictEqual(unicodeEscapesToPCRE2('\\\\\\u1234'), '\\\\\\x{1234}'); + assert.strictEqual(unicodeEscapesToPCRE2('foo\\\\\\u1234'), 'foo\\\\\\x{1234}'); - assert.equal(unicodeEscapesToPCRE2('\\u{1234}'), '\\x{1234}'); - assert.equal(unicodeEscapesToPCRE2('\\u{1234}\\u{0001}'), '\\x{1234}\\x{0001}'); - assert.equal(unicodeEscapesToPCRE2('foo\\u{1234}bar'), 'foo\\x{1234}bar'); - assert.equal(unicodeEscapesToPCRE2('[\\u00A0-\\u00FF]'), '[\\x{00A0}-\\x{00FF}]'); + assert.strictEqual(unicodeEscapesToPCRE2('\\u{1234}'), '\\x{1234}'); + assert.strictEqual(unicodeEscapesToPCRE2('\\u{1234}\\u{0001}'), '\\x{1234}\\x{0001}'); + assert.strictEqual(unicodeEscapesToPCRE2('foo\\u{1234}bar'), 'foo\\x{1234}bar'); + assert.strictEqual(unicodeEscapesToPCRE2('[\\u00A0-\\u00FF]'), '[\\x{00A0}-\\x{00FF}]'); - assert.equal(unicodeEscapesToPCRE2('foo\\u{123456}7bar'), 'foo\\u{123456}7bar'); - assert.equal(unicodeEscapesToPCRE2('\\u123'), '\\u123'); - assert.equal(unicodeEscapesToPCRE2('foo'), 'foo'); - assert.equal(unicodeEscapesToPCRE2(''), ''); + assert.strictEqual(unicodeEscapesToPCRE2('foo\\u{123456}7bar'), 'foo\\u{123456}7bar'); + assert.strictEqual(unicodeEscapesToPCRE2('\\u123'), '\\u123'); + assert.strictEqual(unicodeEscapesToPCRE2('foo'), 'foo'); + assert.strictEqual(unicodeEscapesToPCRE2(''), ''); }); test('fixRegexNewline - src', () => { @@ -41,7 +41,7 @@ suite('RipgrepTextSearchEngine', () => { ]; for (const [input, expected] of ttable) { - assert.equal(fixRegexNewline(input), expected, `${input} -> ${expected}`); + assert.strictEqual(fixRegexNewline(input), expected, `${input} -> ${expected}`); } }); @@ -49,7 +49,7 @@ suite('RipgrepTextSearchEngine', () => { function testFixRegexNewline([inputReg, testStr, shouldMatch]: readonly [string, string, boolean]): void { const fixed = fixRegexNewline(inputReg); const reg = new RegExp(fixed); - assert.equal(reg.test(testStr), shouldMatch, `${inputReg} => ${reg}, ${testStr}, ${shouldMatch}`); + assert.strictEqual(reg.test(testStr), shouldMatch, `${inputReg} => ${reg}, ${testStr}, ${shouldMatch}`); } ([ @@ -74,7 +74,7 @@ suite('RipgrepTextSearchEngine', () => { function testFixNewline([inputReg, testStr, shouldMatch = true]: readonly [string, string, boolean?]): void { const fixed = fixNewline(inputReg); const reg = new RegExp(fixed); - assert.equal(reg.test(testStr), shouldMatch, `${inputReg} => ${reg}, ${testStr}, ${shouldMatch}`); + assert.strictEqual(reg.test(testStr), shouldMatch, `${inputReg} => ${reg}, ${testStr}, ${shouldMatch}`); } ([ @@ -105,7 +105,7 @@ suite('RipgrepTextSearchEngine', () => { inputData.forEach(d => testParser.handleData(d)); testParser.flush(); - assert.deepEqual(actualResults, expectedResults); + assert.deepStrictEqual(actualResults, expectedResults); } function makeRgMatch(relativePath: string, text: string, lineNumber: number, matchRanges: { start: number, end: number }[]): string { 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 2f95736a..0fad3392 100644 --- a/src/vs/workbench/services/search/test/node/search.test.ts +++ b/src/vs/workbench/services/search/test/node/search.test.ts @@ -45,7 +45,7 @@ flakySuite('FileSearchEngine', () => { } }, () => { }, (error) => { assert.ok(!error); - assert.equal(count, 4); + assert.strictEqual(count, 4); done(); }); }); @@ -64,7 +64,7 @@ flakySuite('FileSearchEngine', () => { } }, () => { }, (error) => { assert.ok(!error); - assert.equal(count, 1); + assert.strictEqual(count, 1); done(); }); }); @@ -83,7 +83,7 @@ flakySuite('FileSearchEngine', () => { } }, () => { }, (error) => { assert.ok(!error); - assert.equal(count, 1); + assert.strictEqual(count, 1); done(); }); }); @@ -103,7 +103,7 @@ flakySuite('FileSearchEngine', () => { } }, () => { }, (error, complete) => { assert.ok(!error); - assert.equal(count, 0); + assert.strictEqual(count, 0); assert.ok(complete.limitHit); done(); }); @@ -124,7 +124,7 @@ flakySuite('FileSearchEngine', () => { } }, () => { }, (error, complete) => { assert.ok(!error); - assert.equal(count, 0); + assert.strictEqual(count, 0); assert.ok(!complete.limitHit); done(); }); @@ -145,7 +145,7 @@ flakySuite('FileSearchEngine', () => { } }, () => { }, (error, complete) => { assert.ok(!error); - assert.equal(count, 0); + assert.strictEqual(count, 0); assert.ok(complete.limitHit); done(); }); @@ -166,7 +166,7 @@ flakySuite('FileSearchEngine', () => { } }, () => { }, (error, complete) => { assert.ok(!error); - assert.equal(count, 0); + assert.strictEqual(count, 0); assert.ok(!complete.limitHit); done(); }); @@ -186,7 +186,7 @@ flakySuite('FileSearchEngine', () => { } }, () => { }, (error) => { assert.ok(!error); - assert.equal(count, 1); + assert.strictEqual(count, 1); done(); }); }); @@ -205,7 +205,7 @@ flakySuite('FileSearchEngine', () => { } }, () => { }, (error) => { assert.ok(!error); - assert.equal(count, 7); + assert.strictEqual(count, 7); done(); }); }); @@ -224,7 +224,7 @@ flakySuite('FileSearchEngine', () => { } }, () => { }, (error) => { assert.ok(!error); - assert.equal(count, 3); + assert.strictEqual(count, 3); done(); }); }); @@ -247,7 +247,7 @@ flakySuite('FileSearchEngine', () => { } }, () => { }, (error, complete) => { assert.ok(!error); - assert.equal(count, 1); + assert.strictEqual(count, 1); done(); }); }); @@ -270,7 +270,7 @@ flakySuite('FileSearchEngine', () => { } }, () => { }, (error, complete) => { assert.ok(!error); - assert.equal(count, 0); + assert.strictEqual(count, 0); assert.ok(complete.limitHit); done(); }); @@ -290,7 +290,7 @@ flakySuite('FileSearchEngine', () => { } }, () => { }, (error) => { assert.ok(!error); - assert.equal(count, 1); + assert.strictEqual(count, 1); done(); }); }); @@ -309,7 +309,7 @@ flakySuite('FileSearchEngine', () => { } }, () => { }, (error) => { assert.ok(!error); - assert.equal(count, 14); + assert.strictEqual(count, 14); done(); }); }); @@ -328,7 +328,7 @@ flakySuite('FileSearchEngine', () => { } }, () => { }, (error) => { assert.ok(!error); - assert.equal(count, 0); + assert.strictEqual(count, 0); done(); }); }); @@ -350,7 +350,7 @@ flakySuite('FileSearchEngine', () => { res = result; }, () => { }, (error) => { assert.ok(!error); - assert.equal(count, 1); + assert.strictEqual(count, 1); assert.strictEqual(path.basename(res.relativePath), 'site.less'); done(); }); @@ -371,7 +371,7 @@ flakySuite('FileSearchEngine', () => { } }, () => { }, (error) => { assert.ok(!error); - assert.equal(count, 8); + assert.strictEqual(count, 8); done(); }); }); @@ -390,7 +390,7 @@ flakySuite('FileSearchEngine', () => { } }, () => { }, (error) => { assert.ok(!error); - assert.equal(count, 1); + assert.strictEqual(count, 1); done(); }); }); @@ -409,7 +409,7 @@ flakySuite('FileSearchEngine', () => { } }, () => { }, (error) => { assert.ok(!error); - assert.equal(count, 1); + assert.strictEqual(count, 1); done(); }); }); @@ -429,7 +429,7 @@ flakySuite('FileSearchEngine', () => { } }, () => { }, (error) => { assert.ok(!error); - assert.equal(count, 8); + assert.strictEqual(count, 8); done(); }); }); @@ -449,7 +449,7 @@ flakySuite('FileSearchEngine', () => { } }, () => { }, (error) => { assert.ok(!error); - assert.equal(count, 8); + assert.strictEqual(count, 8); done(); }); }); @@ -469,7 +469,7 @@ flakySuite('FileSearchEngine', () => { } }, () => { }, (error) => { assert.ok(!error); - assert.equal(count, 13); + assert.strictEqual(count, 13); done(); }); }); @@ -489,7 +489,7 @@ flakySuite('FileSearchEngine', () => { } }, () => { }, (error) => { assert.ok(!error); - assert.equal(count, 1); + assert.strictEqual(count, 1); done(); }); }); @@ -523,7 +523,7 @@ flakySuite('FileSearchEngine', () => { } }, () => { }, (error) => { assert.ok(!error); - assert.equal(count, 5); + assert.strictEqual(count, 5); done(); }); }); @@ -544,8 +544,8 @@ flakySuite('FileSearchEngine', () => { res = result; }, () => { }, (error) => { assert.ok(!error); - assert.equal(count, 1); - assert.equal(path.basename(res.relativePath), '汉语.txt'); + assert.strictEqual(count, 1); + assert.strictEqual(path.basename(res.relativePath), '汉语.txt'); done(); }); }); @@ -564,7 +564,7 @@ flakySuite('FileSearchEngine', () => { } }, () => { }, (error) => { assert.ok(!error); - assert.equal(count, 0); + assert.strictEqual(count, 0); done(); }); }); @@ -585,8 +585,8 @@ flakySuite('FileSearchEngine', () => { res = result; }, () => { }, (error) => { assert.ok(!error); - assert.equal(count, 1); - assert.equal(path.basename(res.relativePath), 'company.js'); + assert.strictEqual(count, 1); + assert.strictEqual(path.basename(res.relativePath), 'company.js'); done(); }); }); @@ -636,8 +636,8 @@ flakySuite('FileSearchEngine', () => { res = result; }, () => { }, (error) => { assert.ok(!error); - assert.equal(count, 1); - assert.equal(path.basename(res.relativePath), 'company.js'); + assert.strictEqual(count, 1); + assert.strictEqual(path.basename(res.relativePath), 'company.js'); done(); }); }); @@ -664,8 +664,8 @@ flakySuite('FileSearchEngine', () => { res = result; }, () => { }, (error) => { assert.ok(!error); - assert.equal(count, 1); - assert.equal(path.basename(res.relativePath), 'site.css'); + assert.strictEqual(count, 1); + assert.strictEqual(path.basename(res.relativePath), 'site.css'); done(); }); }); @@ -690,7 +690,7 @@ flakySuite('FileSearchEngine', () => { } }, () => { }, (error) => { assert.ok(!error); - assert.equal(count, 2); + assert.strictEqual(count, 2); done(); }); }); @@ -712,7 +712,7 @@ flakySuite('FileSearchEngine', () => { } }, () => { }, (error) => { assert.ok(!error); - assert.equal(count, 1); + assert.strictEqual(count, 1); done(); }); }); @@ -731,7 +731,7 @@ flakySuite('FileWalker', () => { }); const cmd1 = walker.spawnFindCmd(TEST_ROOT_FOLDER); walker.readStdout(cmd1, 'utf8', (err1, stdout1) => { - assert.equal(err1, null); + assert.strictEqual(err1, null); assert.notStrictEqual(stdout1!.split('\n').indexOf(file0), -1, stdout1); assert.notStrictEqual(stdout1!.split('\n').indexOf(file1), -1, stdout1); @@ -742,7 +742,7 @@ flakySuite('FileWalker', () => { }); const cmd2 = walker.spawnFindCmd(TEST_ROOT_FOLDER); walker.readStdout(cmd2, 'utf8', (err2, stdout2) => { - assert.equal(err2, null); + assert.strictEqual(err2, null); assert.notStrictEqual(stdout1!.split('\n').indexOf(file0), -1, stdout1); assert.strictEqual(stdout2!.split('\n').indexOf(file1), -1, stdout2); done(); @@ -764,7 +764,7 @@ flakySuite('FileWalker', () => { const walker = new FileWalker({ type: QueryType.File, folderQueries }); const cmd1 = walker.spawnFindCmd(folderQueries[0]); walker.readStdout(cmd1, 'utf8', (err1, stdout1) => { - assert.equal(err1, null); + assert.strictEqual(err1, null); assert(outputContains(stdout1!, file0), stdout1); assert(!outputContains(stdout1!, file1), stdout1); done(); @@ -779,7 +779,7 @@ flakySuite('FileWalker', () => { const walker = new FileWalker({ type: QueryType.File, folderQueries: ROOT_FOLDER_QUERY, excludePattern: { '**/something': true } }); const cmd1 = walker.spawnFindCmd(TEST_ROOT_FOLDER); walker.readStdout(cmd1, 'utf8', (err1, stdout1) => { - assert.equal(err1, null); + assert.strictEqual(err1, null); assert.notStrictEqual(stdout1!.split('\n').indexOf(file0), -1, stdout1); assert.notStrictEqual(stdout1!.split('\n').indexOf(file1), -1, stdout1); assert.notStrictEqual(stdout1!.split('\n').indexOf(file2), -1, stdout1); @@ -787,7 +787,7 @@ flakySuite('FileWalker', () => { const walker = new FileWalker({ type: QueryType.File, folderQueries: ROOT_FOLDER_QUERY, excludePattern: { '{**/examples,**/more}': true } }); const cmd2 = walker.spawnFindCmd(TEST_ROOT_FOLDER); walker.readStdout(cmd2, 'utf8', (err2, stdout2) => { - assert.equal(err2, null); + assert.strictEqual(err2, null); assert.notStrictEqual(stdout1!.split('\n').indexOf(file0), -1, stdout1); assert.strictEqual(stdout2!.split('\n').indexOf(file1), -1, stdout2); assert.strictEqual(stdout2!.split('\n').indexOf(file2), -1, stdout2); @@ -803,14 +803,14 @@ flakySuite('FileWalker', () => { const walker = new FileWalker({ type: QueryType.File, folderQueries: ROOT_FOLDER_QUERY, excludePattern: { '**/examples/something': true } }); const cmd1 = walker.spawnFindCmd(TEST_ROOT_FOLDER); walker.readStdout(cmd1, 'utf8', (err1, stdout1) => { - assert.equal(err1, null); + assert.strictEqual(err1, null); assert.notStrictEqual(stdout1!.split('\n').indexOf(file0), -1, stdout1); assert.notStrictEqual(stdout1!.split('\n').indexOf(file1), -1, stdout1); const walker = new FileWalker({ type: QueryType.File, folderQueries: ROOT_FOLDER_QUERY, excludePattern: { '**/examples/subfolder': true } }); const cmd2 = walker.spawnFindCmd(TEST_ROOT_FOLDER); walker.readStdout(cmd2, 'utf8', (err2, stdout2) => { - assert.equal(err2, null); + assert.strictEqual(err2, null); assert.notStrictEqual(stdout1!.split('\n').indexOf(file0), -1, stdout1); assert.strictEqual(stdout2!.split('\n').indexOf(file1), -1, stdout2); done(); @@ -825,14 +825,14 @@ flakySuite('FileWalker', () => { const walker = new FileWalker({ type: QueryType.File, folderQueries: ROOT_FOLDER_QUERY, excludePattern: { '**/subfolder/something': true } }); const cmd1 = walker.spawnFindCmd(TEST_ROOT_FOLDER); walker.readStdout(cmd1, 'utf8', (err1, stdout1) => { - assert.equal(err1, null); + assert.strictEqual(err1, null); assert.notStrictEqual(stdout1!.split('\n').indexOf(file0), -1, stdout1); assert.notStrictEqual(stdout1!.split('\n').indexOf(file1), -1, stdout1); const walker = new FileWalker({ type: QueryType.File, folderQueries: ROOT_FOLDER_QUERY, excludePattern: { '**/subfolder/anotherfolder': true } }); const cmd2 = walker.spawnFindCmd(TEST_ROOT_FOLDER); walker.readStdout(cmd2, 'utf8', (err2, stdout2) => { - assert.equal(err2, null); + assert.strictEqual(err2, null); assert.notStrictEqual(stdout1!.split('\n').indexOf(file0), -1, stdout1); assert.strictEqual(stdout2!.split('\n').indexOf(file1), -1, stdout2); done(); @@ -847,14 +847,14 @@ flakySuite('FileWalker', () => { const walker = new FileWalker({ type: QueryType.File, folderQueries: ROOT_FOLDER_QUERY, excludePattern: { 'examples/something': true } }); const cmd1 = walker.spawnFindCmd(TEST_ROOT_FOLDER); walker.readStdout(cmd1, 'utf8', (err1, stdout1) => { - assert.equal(err1, null); + assert.strictEqual(err1, null); assert.notStrictEqual(stdout1!.split('\n').indexOf(file0), -1, stdout1); assert.notStrictEqual(stdout1!.split('\n').indexOf(file1), -1, stdout1); const walker = new FileWalker({ type: QueryType.File, folderQueries: ROOT_FOLDER_QUERY, excludePattern: { 'examples/subfolder': true } }); const cmd2 = walker.spawnFindCmd(TEST_ROOT_FOLDER); walker.readStdout(cmd2, 'utf8', (err2, stdout2) => { - assert.equal(err2, null); + assert.strictEqual(err2, null); assert.notStrictEqual(stdout1!.split('\n').indexOf(file0), -1, stdout1); assert.strictEqual(stdout2!.split('\n').indexOf(file1), -1, stdout2); done(); @@ -885,7 +885,7 @@ flakySuite('FileWalker', () => { }); const cmd1 = walker.spawnFindCmd(TEST_ROOT_FOLDER); walker.readStdout(cmd1, 'utf8', (err1, stdout1) => { - assert.equal(err1, null); + assert.strictEqual(err1, null); for (const fileIn of filesIn) { assert.notStrictEqual(stdout1!.split('\n').indexOf(fileIn), -1, stdout1); } 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 92f078f7..84ec7215 100644 --- a/src/vs/workbench/services/search/test/node/textSearch.integrationTest.ts +++ b/src/vs/workbench/services/search/test/node/textSearch.integrationTest.ts @@ -39,7 +39,7 @@ function doSearchTest(query: ITextQuery, expectedResultCount: number | Function) if (typeof expectedResultCount === 'function') { assert(expectedResultCount(c)); } else { - assert.equal(c, expectedResultCount, `rg ${c} !== ${expectedResultCount}`); + assert.strictEqual(c, expectedResultCount, `rg ${c} !== ${expectedResultCount}`); } return results; @@ -308,7 +308,7 @@ flakySuite('TextSearch-integration', function () { return doSearchTest(config, 1).then(results => { const matchRange = (results[0].results![0]).ranges; - assert.deepEqual(matchRange, [{ + assert.deepStrictEqual(matchRange, [{ startLineNumber: 0, startColumn: 1, endLineNumber: 0, @@ -325,10 +325,10 @@ flakySuite('TextSearch-integration', function () { }; return doSearchTest(config, 15).then(results => { - assert.equal(results.length, 3); - assert.equal(results[0].results!.length, 1); + assert.strictEqual(results.length, 3); + assert.strictEqual(results[0].results!.length, 1); const match = results[0].results![0]; - assert.equal((match.ranges).length, 5); + assert.strictEqual((match.ranges).length, 5); }); }); @@ -342,14 +342,14 @@ flakySuite('TextSearch-integration', function () { }; return doSearchTest(config, 4).then(results => { - assert.equal(results.length, 4); - assert.equal((results[0].results![0]).lineNumber, 25); - assert.equal((results[0].results![0]).text, ' compiler.addUnit(prog,"input.ts");'); - // assert.equal((results[1].results[0]).preview.text, ' compiler.typeCheck();\n'); // See https://github.com/BurntSushi/ripgrep/issues/1095 - assert.equal((results[2].results![0]).lineNumber, 27); - assert.equal((results[2].results![0]).text, ' compiler.emit();'); - assert.equal((results[3].results![0]).lineNumber, 28); - assert.equal((results[3].results![0]).text, ''); + assert.strictEqual(results.length, 4); + assert.strictEqual((results[0].results![0]).lineNumber, 25); + assert.strictEqual((results[0].results![0]).text, ' compiler.addUnit(prog,"input.ts");'); + // assert.strictEqual((results[1].results[0]).preview.text, ' compiler.typeCheck();\n'); // See https://github.com/BurntSushi/ripgrep/issues/1095 + assert.strictEqual((results[2].results![0]).lineNumber, 27); + assert.strictEqual((results[2].results![0]).text, ' compiler.emit();'); + assert.strictEqual((results[3].results![0]).lineNumber, 28); + assert.strictEqual((results[3].results![0]).text, ''); }); }); @@ -370,8 +370,8 @@ flakySuite('TextSearch-integration', function () { throw new Error('expected fail'); }, err => { const searchError = deserializeSearchError(err); - assert.equal(searchError.message, 'Unknown encoding: invalidEncoding'); - assert.equal(searchError.code, SearchErrorCode.unknownEncoding); + assert.strictEqual(searchError.message, 'Unknown encoding: invalidEncoding'); + assert.strictEqual(searchError.code, SearchErrorCode.unknownEncoding); }); }); @@ -387,8 +387,8 @@ flakySuite('TextSearch-integration', function () { }, err => { const searchError = deserializeSearchError(err); const regexParseErrorForUnclosedParenthesis = 'Regex parse error: unmatched closing parenthesis'; - assert.equal(searchError.message, regexParseErrorForUnclosedParenthesis); - assert.equal(searchError.code, SearchErrorCode.regexParseError); + assert.strictEqual(searchError.message, regexParseErrorForUnclosedParenthesis); + assert.strictEqual(searchError.code, SearchErrorCode.regexParseError); }); }); @@ -404,8 +404,8 @@ flakySuite('TextSearch-integration', function () { }, err => { const searchError = deserializeSearchError(err); const regexParseErrorForLookAround = 'Regex parse error: lookbehind assertion is not fixed length'; - assert.equal(searchError.message, regexParseErrorForLookAround); - assert.equal(searchError.code, SearchErrorCode.regexParseError); + assert.strictEqual(searchError.message, regexParseErrorForLookAround); + assert.strictEqual(searchError.code, SearchErrorCode.regexParseError); }); }); @@ -424,8 +424,8 @@ flakySuite('TextSearch-integration', function () { throw new Error('expected fail'); }, err => { const searchError = deserializeSearchError(err); - assert.equal(searchError.message, 'Error parsing glob \'/{{}\': nested alternate groups are not allowed'); - assert.equal(searchError.code, SearchErrorCode.globParseError); + assert.strictEqual(searchError.message, 'Error parsing glob \'/{{}\': nested alternate groups are not allowed'); + assert.strictEqual(searchError.code, SearchErrorCode.globParseError); }); }); }); diff --git a/src/vs/workbench/services/statusbar/common/statusbar.ts b/src/vs/workbench/services/statusbar/common/statusbar.ts index 3d65b4fb..ee19a350 100644 --- a/src/vs/workbench/services/statusbar/common/statusbar.ts +++ b/src/vs/workbench/services/statusbar/common/statusbar.ts @@ -115,6 +115,11 @@ export interface IStatusbarService { * Focuses the previous status bar entry. If none focused, focuses the last. */ focusPreviousEntry(): void; + + /** + * Returns true if a status bar entry is focused. + */ + isEntryFocused(): boolean; } export interface IStatusbarEntryAccessor extends IDisposable { diff --git a/src/vs/workbench/services/telemetry/electron-sandbox/telemetryService.ts b/src/vs/workbench/services/telemetry/electron-sandbox/telemetryService.ts index 736e0b9d..15e0573c 100644 --- a/src/vs/workbench/services/telemetry/electron-sandbox/telemetryService.ts +++ b/src/vs/workbench/services/telemetry/electron-sandbox/telemetryService.ts @@ -39,7 +39,7 @@ export class TelemetryService extends Disposable implements ITelemetryService { const channel = sharedProcessService.getChannel('telemetryAppender'); const config: ITelemetryServiceConfig = { appender: new TelemetryAppenderClient(channel), - commonProperties: resolveWorkbenchCommonProperties(storageService, fileService, environmentService.os.release, productService.commit, productService.version, environmentService.machineId, productService.msftInternalDomains, environmentService.installSourcePath, environmentService.remoteAuthority), + commonProperties: resolveWorkbenchCommonProperties(storageService, fileService, environmentService.os.release, environmentService.os.hostname, productService.commit, productService.version, environmentService.machineId, productService.msftInternalDomains, environmentService.installSourcePath, environmentService.remoteAuthority), piiPaths: [environmentService.appRoot, environmentService.extensionsPath], sendErrorTelemetry: true }; diff --git a/src/vs/workbench/services/telemetry/electron-sandbox/workbenchCommonProperties.ts b/src/vs/workbench/services/telemetry/electron-sandbox/workbenchCommonProperties.ts index 59e9af6b..d81230c3 100644 --- a/src/vs/workbench/services/telemetry/electron-sandbox/workbenchCommonProperties.ts +++ b/src/vs/workbench/services/telemetry/electron-sandbox/workbenchCommonProperties.ts @@ -14,6 +14,7 @@ export async function resolveWorkbenchCommonProperties( storageService: IStorageService, fileService: IFileService, release: string, + hostname: string, commit: string | undefined, version: string | undefined, machineId: string, @@ -21,7 +22,7 @@ export async function resolveWorkbenchCommonProperties( installSourcePath: string, remoteAuthority?: string ): Promise<{ [name: string]: string | boolean | undefined }> { - const result = await resolveCommonProperties(fileService, release, process.arch, commit, version, machineId, msftInternalDomains, installSourcePath); + const result = await resolveCommonProperties(fileService, release, hostname, process.arch, commit, version, machineId, msftInternalDomains, installSourcePath); const instanceId = storageService.get(instanceStorageKey, StorageScope.GLOBAL)!; const firstSessionDate = storageService.get(firstSessionDateStorageKey, StorageScope.GLOBAL)!; const lastSessionDate = storageService.get(lastSessionDateStorageKey, StorageScope.GLOBAL)!; diff --git a/src/vs/workbench/services/telemetry/test/browser/commonProperties.test.ts b/src/vs/workbench/services/telemetry/test/browser/commonProperties.test.ts index eb9f1cf0..4fa28897 100644 --- a/src/vs/workbench/services/telemetry/test/browser/commonProperties.test.ts +++ b/src/vs/workbench/services/telemetry/test/browser/commonProperties.test.ts @@ -37,7 +37,7 @@ suite('Browser Telemetry - common properties', function () { assert.ok('common.isNewSession' in props, 'isNewSession'); assert.ok('common.machineId' in props, 'machineId'); - assert.equal(props['userId'], '1'); + assert.strictEqual(props['userId'], '1'); }); test('mixes in additional dyanmic properties', async function () { @@ -54,9 +54,9 @@ suite('Browser Telemetry - common properties', function () { }; const props = await resolveWorkbenchCommonProperties(testStorageService, commit, version, undefined, resolveCommonTelemetryProperties); - assert.equal(props['userId'], '1'); + assert.strictEqual(props['userId'], 1); const props2 = await resolveWorkbenchCommonProperties(testStorageService, commit, version, undefined, resolveCommonTelemetryProperties); - assert.equal(props2['userId'], '2'); + assert.strictEqual(props2['userId'], 2); }); }); diff --git a/src/vs/workbench/services/telemetry/test/electron-browser/commonProperties.test.ts b/src/vs/workbench/services/telemetry/test/electron-browser/commonProperties.test.ts index 258b5005..b311ad72 100644 --- a/src/vs/workbench/services/telemetry/test/electron-browser/commonProperties.test.ts +++ b/src/vs/workbench/services/telemetry/test/electron-browser/commonProperties.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import * as fs from 'fs'; import { join } from 'vs/base/common/path'; -import { release, tmpdir } from 'os'; +import { release, tmpdir, hostname } from 'os'; import { resolveWorkbenchCommonProperties } from 'vs/workbench/services/telemetry/electron-sandbox/workbenchCommonProperties'; import { getRandomTestPath } from 'vs/base/test/node/testUtils'; import { IStorageService, StorageScope, InMemoryStorageService, StorageTarget } from 'vs/platform/storage/common/storage'; @@ -46,7 +46,7 @@ suite('Telemetry - common properties', function () { test('default', async function () { await fs.promises.mkdir(parentDir, { recursive: true }); fs.writeFileSync(installSource, 'my.install.source'); - const props = await resolveWorkbenchCommonProperties(testStorageService, testFileService, release(), commit, version, 'someMachineId', undefined, installSource); + const props = await resolveWorkbenchCommonProperties(testStorageService, testFileService, release(), hostname(), commit, version, 'someMachineId', undefined, installSource); assert.ok('commitHash' in props); assert.ok('sessionID' in props); assert.ok('timestamp' in props); @@ -59,7 +59,7 @@ suite('Telemetry - common properties', function () { // assert.ok('common.version.renderer' in first.data); assert.ok('common.platformVersion' in props, 'platformVersion'); assert.ok('version' in props); - assert.equal(props['common.source'], 'my.install.source'); + assert.strictEqual(props['common.source'], 'my.install.source'); assert.ok('common.firstSessionDate' in props, 'firstSessionDate'); assert.ok('common.lastSessionDate' in props, 'lastSessionDate'); // conditional, see below, 'lastSessionDate'ow assert.ok('common.isNewSession' in props, 'isNewSession'); @@ -67,7 +67,7 @@ suite('Telemetry - common properties', function () { assert.ok('common.instanceId' in props, 'instanceId'); assert.ok('common.machineId' in props, 'machineId'); fs.unlinkSync(installSource); - const props_1 = await resolveWorkbenchCommonProperties(testStorageService, testFileService, release(), commit, version, 'someMachineId', undefined, installSource); + const props_1 = await resolveWorkbenchCommonProperties(testStorageService, testFileService, release(), hostname(), commit, version, 'someMachineId', undefined, installSource); assert.ok(!('common.source' in props_1)); }); @@ -75,14 +75,14 @@ suite('Telemetry - common properties', function () { testStorageService.store('telemetry.lastSessionDate', new Date().toUTCString(), StorageScope.GLOBAL, StorageTarget.MACHINE); - const props = await resolveWorkbenchCommonProperties(testStorageService, testFileService, release(), commit, version, 'someMachineId', undefined, installSource); + const props = await resolveWorkbenchCommonProperties(testStorageService, testFileService, release(), hostname(), commit, version, 'someMachineId', undefined, installSource); assert.ok('common.lastSessionDate' in props); // conditional, see below assert.ok('common.isNewSession' in props); - assert.equal(props['common.isNewSession'], 0); + assert.strictEqual(props['common.isNewSession'], '0'); }); test('values chance on ask', async function () { - const props = await resolveWorkbenchCommonProperties(testStorageService, testFileService, release(), commit, version, 'someMachineId', undefined, installSource); + const props = await resolveWorkbenchCommonProperties(testStorageService, testFileService, release(), hostname(), commit, version, 'someMachineId', undefined, installSource); let value1 = props['common.sequence']; let value2 = props['common.sequence']; assert.ok(value1 !== value2, 'seq'); diff --git a/src/vs/workbench/services/textMate/electron-sandbox/textMateService.ts b/src/vs/workbench/services/textMate/electron-sandbox/textMateService.ts index 209e19fb..59ba4816 100644 --- a/src/vs/workbench/services/textMate/electron-sandbox/textMateService.ts +++ b/src/vs/workbench/services/textMate/electron-sandbox/textMateService.ts @@ -88,7 +88,7 @@ class ModelWorkerTextMateTokenizer extends Disposable { this._worker.acceptRemovedModel(this._model.uri.toString()); } - public dispose() { + public override dispose() { super.dispose(); this._endSync(); } @@ -189,7 +189,7 @@ export class TextMateService extends AbstractTextMateService { return response; } - protected _onDidCreateGrammarFactory(grammarDefinitions: IValidGrammarDefinition[]): void { + protected override _onDidCreateGrammarFactory(grammarDefinitions: IValidGrammarDefinition[]): void { this._killWorker(); if (RUN_TEXTMATE_IN_WORKER) { @@ -218,14 +218,14 @@ export class TextMateService extends AbstractTextMateService { } } - protected _doUpdateTheme(grammarFactory: TMGrammarFactory, theme: IRawTheme, colorMap: string[]): void { + protected override _doUpdateTheme(grammarFactory: TMGrammarFactory, theme: IRawTheme, colorMap: string[]): void { super._doUpdateTheme(grammarFactory, theme, colorMap); if (this._currentTheme && this._currentTokenColorMap && this._workerProxy) { this._workerProxy.acceptTheme(this._currentTheme, this._currentTokenColorMap); } } - protected _onDidDisposeGrammarFactory(): void { + protected override _onDidDisposeGrammarFactory(): void { this._killWorker(); } diff --git a/src/vs/workbench/services/textMate/electron-sandbox/textMateWorker.ts b/src/vs/workbench/services/textMate/electron-sandbox/textMateWorker.ts index 5b811274..c4af0042 100644 --- a/src/vs/workbench/services/textMate/electron-sandbox/textMateWorker.ts +++ b/src/vs/workbench/services/textMate/electron-sandbox/textMateWorker.ts @@ -55,7 +55,7 @@ class TextMateWorkerModel extends MirrorTextModel { this._resetTokenization(); } - public dispose(): void { + public override dispose(): void { this._isDisposed = true; super.dispose(); } @@ -65,7 +65,7 @@ class TextMateWorkerModel extends MirrorTextModel { this._resetTokenization(); } - onEvents(e: IModelChangedEvent): void { + override onEvents(e: IModelChangedEvent): void { super.onEvents(e); for (let i = 0; i < e.changes.length; i++) { const change = e.changes[i]; diff --git a/src/vs/workbench/services/textfile/browser/browserTextFileService.ts b/src/vs/workbench/services/textfile/browser/browserTextFileService.ts index ad654cd4..ade0b177 100644 --- a/src/vs/workbench/services/textfile/browser/browserTextFileService.ts +++ b/src/vs/workbench/services/textfile/browser/browserTextFileService.ts @@ -9,7 +9,7 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; export class BrowserTextFileService extends AbstractTextFileService { - protected registerListeners(): void { + protected override registerListeners(): void { super.registerListeners(); // Lifecycle diff --git a/src/vs/workbench/services/textfile/browser/textFileService.ts b/src/vs/workbench/services/textfile/browser/textFileService.ts index 610c623c..dd30a774 100644 --- a/src/vs/workbench/services/textfile/browser/textFileService.ts +++ b/src/vs/workbench/services/textfile/browser/textFileService.ts @@ -5,8 +5,8 @@ import { localize } from 'vs/nls'; import { URI } from 'vs/base/common/uri'; -import { ITextFileService, ITextFileStreamContent, ITextFileContent, IResourceEncodings, IReadTextFileOptions, IWriteTextFileOptions, toBufferOrReadable, TextFileOperationError, TextFileOperationResult, ITextFileSaveOptions, ITextFileEditorModelManager, IResourceEncoding, stringToSnapshot, ITextFileSaveAsOptions } from 'vs/workbench/services/textfile/common/textfiles'; -import { IRevertOptions, IEncodingSupport } from 'vs/workbench/common/editor'; +import { IEncodingSupport, ITextFileService, ITextFileStreamContent, ITextFileContent, IResourceEncodings, IReadTextFileOptions, IWriteTextFileOptions, toBufferOrReadable, TextFileOperationError, TextFileOperationResult, ITextFileSaveOptions, ITextFileEditorModelManager, IResourceEncoding, stringToSnapshot, ITextFileSaveAsOptions, IReadTextFileEncodingOptions } from 'vs/workbench/services/textfile/common/textfiles'; +import { IRevertOptions } from 'vs/workbench/common/editor'; import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IFileService, FileOperationError, FileOperationResult, IFileStatWithMetadata, ICreateFileOptions, IFileStreamContent } from 'vs/platform/files/common/files'; import { Disposable } from 'vs/base/common/lifecycle'; @@ -20,7 +20,7 @@ import { createTextBufferFactoryFromSnapshot, createTextBufferFactoryFromStream import { IModelService } from 'vs/editor/common/services/modelService'; import { joinPath, dirname, basename, toLocalResource, extname, isEqual } from 'vs/base/common/resources'; import { IDialogService, IFileDialogService, IConfirmation } from 'vs/platform/dialogs/common/dialogs'; -import { VSBuffer, VSBufferReadable, bufferToStream } from 'vs/base/common/buffer'; +import { VSBuffer, VSBufferReadable, bufferToStream, VSBufferReadableStream } from 'vs/base/common/buffer'; import { ITextSnapshot, ITextModel } from 'vs/editor/common/model'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService'; import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; @@ -35,10 +35,11 @@ import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/ur import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { WORKSPACE_EXTENSION } from 'vs/platform/workspaces/common/workspaces'; import { UTF8, UTF8_with_bom, UTF16be, UTF16le, encodingExists, toEncodeReadable, toDecodeStream, IDecodeStreamResult } from 'vs/workbench/services/textfile/common/encoding'; -import { consumeStream } from 'vs/base/common/stream'; +import { consumeStream, ReadableStream } from 'vs/base/common/stream'; import { IModeService } from 'vs/editor/common/services/modeService'; import { ILogService } from 'vs/platform/log/common/log'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { IElevatedFileService } from 'vs/workbench/services/files/common/elevatedFileService'; /** * The workbench file service implementation implements the raw file service spec and adds additional methods on top. @@ -68,7 +69,8 @@ export abstract class AbstractTextFileService extends Disposable implements ITex @IWorkingCopyFileService private readonly workingCopyFileService: IWorkingCopyFileService, @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, @IModeService private readonly modeService: IModeService, - @ILogService protected readonly logService: ILogService + @ILogService protected readonly logService: ILogService, + @IElevatedFileService private readonly elevatedFileService: IElevatedFileService ) { super(); @@ -78,7 +80,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex protected registerListeners(): void { // Lifecycle - this.lifecycleService.onShutdown(() => this.dispose()); + this.lifecycleService.onDidShutdown(() => this.dispose()); } //#region text file read / write / create @@ -136,10 +138,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex } // read through encoding library - const decoder = await toDecodeStream(bufferStream.value, { - guessEncoding: options?.autoGuessEncoding || this.textResourceConfigurationService.getValue(resource, 'files.autoGuessEncoding'), - overwriteEncoding: detectedEncoding => this.encoding.getReadEncoding(resource, options, detectedEncoding) - }); + const decoder = await this.doGetDecodedStream(resource, bufferStream.value, options); // validate binary if (options?.acceptTextOnly && decoder.detected.seemsBinary) { @@ -149,25 +148,33 @@ export abstract class AbstractTextFileService extends Disposable implements ITex return [bufferStream, decoder]; } - async create(operations: { resource: URI, value?: string | ITextSnapshot, options?: ICreateFileOptions }[], undoInfo?: IFileOperationUndoRedoInfo, token?: CancellationToken): Promise { - const operationsWithContents: ICreateFileOperation[] = await Promise.all(operations.map(async o => { - const contents = await this.getEncodedReadable(o.resource, o.value); + async create(operations: { resource: URI, value?: string | ITextSnapshot, options?: ICreateFileOptions }[], undoInfo?: IFileOperationUndoRedoInfo): Promise { + const operationsWithContents: ICreateFileOperation[] = await Promise.all(operations.map(async operation => { + const contents = await this.getEncodedReadable(operation.resource, operation.value); return { - resource: o.resource, + resource: operation.resource, contents, - overwrite: o.options?.overwrite + overwrite: operation.options?.overwrite }; })); - return this.workingCopyFileService.create(operationsWithContents, undoInfo, token); + return this.workingCopyFileService.create(operationsWithContents, CancellationToken.None, undoInfo); } async write(resource: URI, value: string | ITextSnapshot, options?: IWriteTextFileOptions): Promise { const readable = await this.getEncodedReadable(resource, value, options); + if (options?.writeElevated && this.elevatedFileService.isSupported(resource)) { + return this.elevatedFileService.writeFileElevated(resource, readable, options); + } + return this.fileService.writeFile(resource, readable, options); } + async getEncodedReadable(resource: URI, value: ITextSnapshot): Promise; + async getEncodedReadable(resource: URI, value: string): Promise; + async getEncodedReadable(resource: URI, value?: ITextSnapshot): Promise; + async getEncodedReadable(resource: URI, value?: string): Promise; async getEncodedReadable(resource: URI, value?: string | ITextSnapshot): Promise; async getEncodedReadable(resource: URI, value: string | ITextSnapshot, options?: IWriteTextFileOptions): Promise; async getEncodedReadable(resource: URI, value?: string | ITextSnapshot, options?: IWriteTextFileOptions): Promise { @@ -188,6 +195,19 @@ export abstract class AbstractTextFileService extends Disposable implements ITex return toEncodeReadable(snapshot, encoding, { addBOM }); } + async getDecodedStream(resource: URI, value: VSBufferReadableStream, options?: IReadTextFileEncodingOptions): Promise> { + return (await this.doGetDecodedStream(resource, value, options)).stream; + } + + private doGetDecodedStream(resource: URI, stream: VSBufferReadableStream, options?: IReadTextFileEncodingOptions): Promise { + + // read through encoding library + return toDecodeStream(stream, { + guessEncoding: options?.autoGuessEncoding || this.textResourceConfigurationService.getValue(resource, 'files.autoGuessEncoding'), + overwriteEncoding: detectedEncoding => this.encoding.getReadEncoding(resource, options, detectedEncoding) + }); + } + //#endregion @@ -251,7 +271,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex // However, this will only work if the source exists // and is not orphaned, so we need to check that too. if (this.fileService.canHandleResource(source) && this.uriIdentityService.extUri.isEqual(source, target) && (await this.fileService.exists(source))) { - await this.workingCopyFileService.move([{ file: { source, target } }]); + await this.workingCopyFileService.move([{ file: { source, target } }], CancellationToken.None); // At this point we don't know whether we have a // model for the source or the target URI so we @@ -326,7 +346,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex sourceModelEncoding = sourceModelWithEncodingSupport.getEncoding(); } - // Prefer an existing model if it is already loaded for the given target resource + // Prefer an existing model if it is already resolved for the given target resource let targetExists: boolean = false; let targetModel = this.files.get(target); if (targetModel?.isResolved()) { @@ -346,7 +366,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex targetModel = await this.files.resolve(target, { encoding: sourceModelEncoding }); } catch (error) { // if the target already exists and was not created by us, it is possible - // that we cannot load the target as text model if it is binary or too + // that we cannot resolve the target as text model if it is binary or too // large. in that case we have to delete the target file first and then // re-run the operation. if (targetExists) { @@ -600,7 +620,7 @@ export class EncodingOracle extends Disposable implements IResourceEncodings { }; } - getReadEncoding(resource: URI, options: IReadTextFileOptions | undefined, detectedEncoding: string | null): Promise { + getReadEncoding(resource: URI, options: IReadTextFileEncodingOptions | undefined, detectedEncoding: string | null): Promise { let preferredEncoding: string | undefined; // Encoding passed in as option diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index 6c00a4b4..52f5f864 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -3,29 +3,29 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from 'vs/nls'; import { Emitter } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; import { assertIsDefined, withNullAsUndefined } from 'vs/base/common/types'; -import { ITextFileService, TextFileEditorModelState, ITextFileEditorModel, ITextFileStreamContent, ITextFileLoadOptions, IResolvedTextFileEditorModel, ITextFileSaveOptions, TextFileLoadReason } from 'vs/workbench/services/textfile/common/textfiles'; -import { EncodingMode, IRevertOptions, SaveReason } from 'vs/workbench/common/editor'; +import { EncodingMode, ITextFileService, TextFileEditorModelState, ITextFileEditorModel, ITextFileStreamContent, ITextFileResolveOptions, IResolvedTextFileEditorModel, ITextFileSaveOptions, TextFileResolveReason } from 'vs/workbench/services/textfile/common/textfiles'; +import { IRevertOptions, SaveReason } from 'vs/workbench/common/editor'; import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel'; -import { IBackupFileService, IResolvedBackup } from 'vs/workbench/services/backup/common/backup'; +import { IWorkingCopyBackupService, IResolvedWorkingCopyBackup } from 'vs/workbench/services/workingCopy/common/workingCopyBackup'; import { IFileService, FileOperationError, FileOperationResult, FileChangesEvent, FileChangeType, IFileStatWithMetadata, ETAG_DISABLED, FileSystemProviderCapabilities } from 'vs/platform/files/common/files'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IModelService } from 'vs/editor/common/services/modelService'; import { timeout, TaskSequentializer } from 'vs/base/common/async'; import { ITextBufferFactory, ITextModel } from 'vs/editor/common/model'; -import { INotificationService } from 'vs/platform/notification/common/notification'; import { ILogService } from 'vs/platform/log/common/log'; import { basename } from 'vs/base/common/path'; -import { IWorkingCopyService, IWorkingCopyBackup, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { IWorkingCopyBackup, WorkingCopyCapabilities, NO_TYPE_ID, IWorkingCopyBackupMeta } from 'vs/workbench/services/workingCopy/common/workingCopy'; import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { ILabelService } from 'vs/platform/label/common/label'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { UTF8 } from 'vs/workbench/services/textfile/common/encoding'; +import { createTextBufferFactoryFromStream } from 'vs/editor/common/model/textModel'; -interface IBackupMetaData { +interface IBackupMetaData extends IWorkingCopyBackupMeta { mtime: number; ctime: number; size: number; @@ -43,8 +43,8 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil private readonly _onDidChangeContent = this._register(new Emitter()); readonly onDidChangeContent = this._onDidChangeContent.event; - private readonly _onDidLoad = this._register(new Emitter()); - readonly onDidLoad = this._onDidLoad.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; @@ -66,6 +66,8 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil //#endregion + readonly typeId = NO_TYPE_ID; // IMPORTANT: never change this to not break existing assumptions (e.g. backups) + readonly capabilities = WorkingCopyCapabilities.None; readonly name = basename(this.labelService.getUriLabel(this.resource)); @@ -92,12 +94,11 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil public readonly resource: URI, private preferredEncoding: string | undefined, // encoding as chosen by the user private preferredMode: string | undefined, // mode as chosen by the user - @INotificationService private readonly notificationService: INotificationService, @IModeService modeService: IModeService, @IModelService modelService: IModelService, @IFileService private readonly fileService: IFileService, @ITextFileService private readonly textFileService: ITextFileService, - @IBackupFileService private readonly backupFileService: IBackupFileService, + @IWorkingCopyBackupService private readonly workingCopyBackupService: IWorkingCopyBackupService, @ILogService private readonly logService: ILogService, @IWorkingCopyService private readonly workingCopyService: IWorkingCopyService, @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService, @@ -179,7 +180,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil this.modelService.setMode(this.textEditorModel, languageSelection); } - setMode(mode: string): void { + override setMode(mode: string): void { super.setMode(mode); this.preferredMode = mode; @@ -201,7 +202,12 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil }; } - return { meta, content: withNullAsUndefined(this.createSnapshot()) }; + // Fill in content the same way we would do when + // saving the file via the text file service + // encoding support (hardcode UTF-8) + const content = await this.textFileService.getEncodedReadable(this.resource, withNullAsUndefined(this.createSnapshot()), { encoding: UTF8 }); + + return { meta, content }; } //#endregion @@ -221,7 +227,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil const softUndo = options?.soft; if (!softUndo) { try { - await this.load({ forceReadFromFile: true }); + await this.resolve({ forceReadFromFile: true }); } catch (error) { // FileNotFound means the file got deleted meanwhile, so ignore it @@ -246,52 +252,52 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil //#endregion - //#region Load + //#region Resolve - async load(options?: ITextFileLoadOptions): Promise { - this.logService.trace('[text file model] load() - enter', this.resource.toString(true)); + override async resolve(options?: ITextFileResolveOptions): Promise { + this.logService.trace('[text file model] resolve() - enter', this.resource.toString(true)); // Return early if we are disposed if (this.isDisposed()) { - this.logService.trace('[text file model] load() - exit - without loading because model is disposed', this.resource.toString(true)); + this.logService.trace('[text file model] resolve() - exit - without resolving because model is disposed', this.resource.toString(true)); - return this; + return; } // Unless there are explicit contents provided, it is important that we do not - // load a model that is dirty or is in the process of saving to prevent data + // resolve a model 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('[text file model] load() - exit - without loading because model is dirty or being saved', this.resource.toString(true)); + this.logService.trace('[text file model] resolve() - exit - without resolving because model is dirty or being saved', this.resource.toString(true)); - return this; + return; } - return this.doLoad(options); + return this.doResolve(options); } - private async doLoad(options?: ITextFileLoadOptions): Promise { + private async doResolve(options?: ITextFileResolveOptions): Promise { // First check if we have contents to use for the model if (options?.contents) { - return this.loadFromBuffer(options.contents, options); + return this.resolveFromBuffer(options.contents, options); } - // Second, check if we have a backup to load from (only for new models) + // Second, check if we have a backup to resolve from (only for new models) const isNewModel = !this.isResolved(); if (isNewModel) { - const loadedFromBackup = await this.loadFromBackup(options); - if (loadedFromBackup) { - return loadedFromBackup; + const resolvedFromBackup = await this.resolveFromBackup(options); + if (resolvedFromBackup) { + return; } } - // Finally, load from file resource - return this.loadFromFile(options); + // Finally, resolve from file resource + return this.resolveFromFile(options); } - private async loadFromBuffer(buffer: ITextBufferFactory, options?: ITextFileLoadOptions): Promise { - this.logService.trace('[text file model] loadFromBuffer()', this.resource.toString(true)); + private async resolveFromBuffer(buffer: ITextBufferFactory, options?: ITextFileResolveOptions): Promise { + this.logService.trace('[text file model] resolveFromBuffer()', this.resource.toString(true)); // Try to resolve metdata from disk let mtime: number; @@ -321,8 +327,8 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil const preferredEncoding = await this.textFileService.encoding.getPreferredWriteEncoding(this.resource, this.preferredEncoding); - // Load with buffer - this.loadFromContent({ + // Resolve with buffer + this.resolveFromContent({ resource: this.resource, name: this.name, mtime, @@ -331,15 +337,13 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil etag, value: buffer, encoding: preferredEncoding.encoding - }, true /* dirty (loaded from buffer) */, options); - - return this; + }, true /* dirty (resolved from buffer) */, options); } - private async loadFromBackup(options?: ITextFileLoadOptions): Promise { + private async resolveFromBackup(options?: ITextFileResolveOptions): Promise { // Resolve backup if any - const backup = await this.backupFileService.resolve(this.resource); + const backup = await this.workingCopyBackupService.resolve(this); // Resolve preferred encoding if we need it let encoding = UTF8; @@ -350,45 +354,45 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // Abort if someone else managed to resolve the model by now let isNewModel = !this.isResolved(); if (!isNewModel) { - this.logService.trace('[text file model] loadFromBackup() - exit - without loading because previously new model got created meanwhile', this.resource.toString(true)); + this.logService.trace('[text file model] resolveFromBackup() - exit - without resolving because previously new model got created meanwhile', this.resource.toString(true)); - return this; // imply that loading has happened in another operation + return true; // imply that resolving has happened in another operation } - // Try to load from backup if we have any + // Try to resolve from backup if we have any if (backup) { - return this.doLoadFromBackup(backup, encoding, options); + await this.doResolveFromBackup(backup, encoding, options); + + return true; } - // Otherwise signal back that loading did not happen - return undefined; + // Otherwise signal back that resolving did not happen + return false; } - private doLoadFromBackup(backup: IResolvedBackup, encoding: string, options?: ITextFileLoadOptions): TextFileEditorModel { - this.logService.trace('[text file model] doLoadFromBackup()', this.resource.toString(true)); + private async doResolveFromBackup(backup: IResolvedWorkingCopyBackup, encoding: string, options?: ITextFileResolveOptions): Promise { + this.logService.trace('[text file model] doResolveFromBackup()', this.resource.toString(true)); - // Load with backup - this.loadFromContent({ + // Resolve with backup + 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: backup.value, + value: await createTextBufferFactoryFromStream(await this.textFileService.getDecodedStream(this.resource, backup.value, { encoding: UTF8 })), encoding - }, true /* dirty (loaded from backup) */, options); + }, true /* dirty (resolved from backup) */, options); // Restore orphaned flag based on state - if (backup.meta && backup.meta.orphaned) { + if (backup.meta?.orphaned) { this.setOrphaned(true); } - - return this; } - private async loadFromFile(options?: ITextFileLoadOptions): Promise { - this.logService.trace('[text file model] loadFromFile()', this.resource.toString(true)); + private async resolveFromFile(options?: ITextFileResolveOptions): Promise { + this.logService.trace('[text file model] resolveFromFile()', this.resource.toString(true)); const forceReadFromFile = options?.forceReadFromFile; const allowBinary = this.isResolved() /* always allow if we resolved previously */ || options?.allowBinary; @@ -409,18 +413,18 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil try { const content = await this.textFileService.readStream(this.resource, { acceptTextOnly: !allowBinary, etag, encoding: this.preferredEncoding }); - // Clear orphaned state when loading was successful + // Clear orphaned state when resolving was successful this.setOrphaned(false); // Return early if the model content has changed // meanwhile to prevent loosing any changes if (currentVersionId !== this.versionId) { - this.logService.trace('[text file model] loadFromFile() - exit - without loading because model content changed', this.resource.toString(true)); + this.logService.trace('[text file model] resolveFromFile() - exit - without resolving because model content changed', this.resource.toString(true)); - return this; + return; } - return this.loadFromContent(content, false /* not dirty (loaded from file) */, options); + return this.resolveFromContent(content, false /* not dirty (resolved from file) */, options); } catch (error) { const result = error.fileOperationResult; @@ -430,15 +434,15 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // NotModified status is expected and can be handled gracefully // if we are resolved if (this.isResolved() && result === FileOperationResult.FILE_NOT_MODIFIED_SINCE) { - return this; + return; } // Unless we are forced to read from the file, Ignore when a model has been resolved once - // and the file was deleted meanwhile. Since we already have the model loaded, we can return + // and the file was deleted meanwhile. Since we already have the model resolved, we can return // to this state and update the orphaned flag to indicate that this model has no version on // disk anymore. if (this.isResolved() && result === FileOperationResult.FILE_NOT_FOUND && !forceReadFromFile) { - return this; + return; } // Otherwise bubble up the error @@ -446,14 +450,14 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } } - private loadFromContent(content: ITextFileStreamContent, dirty: boolean, options?: ITextFileLoadOptions): TextFileEditorModel { - this.logService.trace('[text file model] loadFromContent() - enter', this.resource.toString(true)); + private resolveFromContent(content: ITextFileStreamContent, dirty: boolean, options?: ITextFileResolveOptions): void { + this.logService.trace('[text file model] resolveFromContent() - enter', this.resource.toString(true)); // Return early if we are disposed if (this.isDisposed()) { - this.logService.trace('[text file model] loadFromContent() - exit - because model is disposed', this.resource.toString(true)); + this.logService.trace('[text file model] resolveFromContent() - exit - because model is disposed', this.resource.toString(true)); - return this; + return; } // Update our resolved disk stat model @@ -498,9 +502,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil this.setDirty(!!dirty); // Emit as event - this._onDidLoad.fire(options?.reason ?? TextFileLoadReason.OTHER); - - return this; + this._onDidResolve.fire(options?.reason ?? TextFileResolveReason.OTHER); } private doCreateTextModel(resource: URI, value: ITextBufferFactory): void { @@ -550,7 +552,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } // We mark check for a dirty-state change upon model content change, unless: - // - explicitly instructed to ignore it (e.g. from model.load()) + // - 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()) { @@ -884,7 +886,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } // Subsequent resolve - make sure that we only assign it if the mtime is equal or has advanced. - // This prevents race conditions from loading and saving. If a save comes in late after a revert + // 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; @@ -914,9 +916,9 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil return this.saveSequentializer.pending ?? Promise.resolve(); } - getMode(this: IResolvedTextFileEditorModel): string; - getMode(): string | undefined; - getMode(): string | undefined { + override getMode(this: IResolvedTextFileEditorModel): string; + override getMode(): string | undefined; + override getMode(): string | undefined { if (this.textEditorModel) { return this.textEditorModel.getModeId(); } @@ -930,7 +932,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil return this.preferredEncoding || this.contentEncoding; } - setEncoding(encoding: string, mode: EncodingMode): void { + async setEncoding(encoding: string, mode: EncodingMode): Promise { if (!this.isNewEncoding(encoding)) { return; // return early if the encoding is already the same } @@ -946,22 +948,19 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } if (!this.inConflictMode) { - this.save(); + await this.save(); } } - // Decode: Load with encoding + // Decode: Resolve with encoding else { if (this.isDirty()) { - this.notificationService.info(localize('saveFileFirst', "The file is dirty. Please save it first before reopening it with another encoding.")); - - return; + await this.save(); } this.updatePreferredEncoding(encoding); - // Load - this.load({ + await this.resolve({ forceReadFromFile: true // because encoding has changed }); } @@ -992,15 +991,15 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil //#endregion - isResolved(): this is IResolvedTextFileEditorModel { + override isResolved(): this is IResolvedTextFileEditorModel { return !!this.textEditorModel; } - isReadonly(): boolean { + override isReadonly(): boolean { return this.fileService.hasCapability(this.resource, FileSystemProviderCapabilities.Readonly); } - dispose(): void { + override dispose(): void { this.logService.trace('[text file model] dispose()', this.resource.toString(true)); this.inConflictMode = false; diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts b/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts index dce72e60..1b2efab5 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts @@ -9,7 +9,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; import { dispose, IDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { ITextFileEditorModel, ITextFileEditorModelManager, ITextFileEditorModelLoadOrCreateOptions, ITextFileLoadEvent, ITextFileSaveEvent, ITextFileSaveParticipant } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileEditorModel, ITextFileEditorModelManager, ITextFileEditorModelResolveOrCreateOptions, ITextFileResolveEvent, ITextFileSaveEvent, ITextFileSaveParticipant } from 'vs/workbench/services/textfile/common/textfiles'; import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ResourceMap } from 'vs/base/common/map'; @@ -32,8 +32,8 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE private readonly _onDidCreate = this._register(new Emitter()); readonly onDidCreate = this._onDidCreate.event; - private readonly _onDidLoad = this._register(new Emitter()); - readonly onDidLoad = this._onDidLoad.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; @@ -53,9 +53,9 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE private readonly mapResourceToModel = new ResourceMap(); private readonly mapResourceToModelListeners = new ResourceMap(); private readonly mapResourceToDisposeListener = new ResourceMap(); - private readonly mapResourceToPendingModelLoaders = new ResourceMap>(); + private readonly mapResourceToPendingModelResolvers = new ResourceMap>(); - private readonly modelLoadQueue = this._register(new ResourceQueue()); + private readonly modelResolveQueue = this._register(new ResourceQueue()); saveErrorHandler = (() => { const notificationService = this.notificationService; @@ -95,7 +95,7 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE this._register(this.workingCopyFileService.onDidRunWorkingCopyFileOperation(e => this.onDidRunWorkingCopyFileOperation(e))); // Lifecycle - this.lifecycleService.onShutdown(() => this.dispose()); + this.lifecycleService.onDidShutdown(() => this.dispose()); } private onDidFilesChange(e: FileChangesEvent): void { @@ -104,25 +104,25 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE continue; // require a resolved, saved model to continue } - // Trigger a model load for any update or add event that impacts + // Trigger a model resolve for any update or add event that impacts // the model. We also consider the added event because it could // be that a file was added and updated right after. if (e.contains(model.resource, FileChangeType.UPDATED, FileChangeType.ADDED)) { - this.queueModelLoad(model); + this.queueModelResolve(model); } } } - private queueModelLoad(model: TextFileEditorModel): void { + private queueModelResolve(model: TextFileEditorModel): void { - // Load model to update (use a queue to prevent accumulation of loads - // when the load actually takes long. At most we only want the queue - // to have a size of 2 (1 running load and 1 queued load). - const queue = this.modelLoadQueue.queueFor(model.resource); + // Resolve model to update (use a queue to prevent accumulation of resolves + // when the resolve 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.modelResolveQueue.queueFor(model.resource); if (queue.size <= 1) { queue.queue(async () => { try { - await model.load(); + await model.resolve(); } catch (error) { onUnexpectedError(error); } @@ -152,7 +152,7 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE } } - // remember each source model to load again after move is done + // remember each source model to resolve again after move is done // with optional content to restore if it was dirty for (const sourceModel of sourceModels) { const sourceModelResource = sourceModel.resource; @@ -219,7 +219,7 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE })()); break; - // Move/Copy: restore models that were loaded before the operation took place + // Move/Copy: restore models that were resolved before the operation took place case FileOperation.MOVE: case FileOperation.COPY: e.waitUntil((async () => { @@ -255,17 +255,17 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE return this.mapResourceToModel.get(resource); } - async resolve(resource: URI, options?: ITextFileEditorModelLoadOrCreateOptions): Promise { + async resolve(resource: URI, options?: ITextFileEditorModelResolveOrCreateOptions): Promise { - // Await a pending model load first before proceeding - // to ensure that we never load a model more than once + // Await a pending model resolve first before proceeding + // to ensure that we never resolve a model more than once // in parallel const pendingResolve = this.joinPendingResolve(resource); if (pendingResolve) { await pendingResolve; } - let modelPromise: Promise; + let modelPromise: Promise; let model = this.get(resource); let didCreateModel = false; @@ -274,7 +274,7 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE // Always reload if contents are provided if (options?.contents) { - modelPromise = model.load(options); + modelPromise = model.resolve(options); } // Reload async or sync based on options @@ -282,19 +282,19 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE // async reload: trigger a reload but return immediately if (options.reload.async) { - modelPromise = Promise.resolve(model); - model.load(options); + modelPromise = Promise.resolve(); + model.resolve(options); } // sync reload: do not return until model reloaded else { - modelPromise = model.load(options); + modelPromise = model.resolve(options); } } // Do not reload else { - modelPromise = Promise.resolve(model); + modelPromise = Promise.resolve(); } } @@ -303,13 +303,13 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE didCreateModel = true; const newModel = model = this.instantiationService.createInstance(TextFileEditorModel, resource, options ? options.encoding : undefined, options ? options.mode : undefined); - modelPromise = model.load(options); + modelPromise = model.resolve(options); this.registerModel(newModel); } - // Store pending loads to avoid race conditions - this.mapResourceToPendingModelLoaders.set(resource, modelPromise); + // Store pending resolves to avoid race conditions + this.mapResourceToPendingModelResolvers.set(resource, modelPromise); // Make known to manager (if not already known) this.add(resource, model); @@ -326,23 +326,23 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE } try { - const resolvedModel = await modelPromise; + await modelPromise; - // Remove from pending loads - this.mapResourceToPendingModelLoaders.delete(resource); + // Remove from pending resolves + this.mapResourceToPendingModelResolvers.delete(resource); // Apply mode if provided if (options?.mode) { - resolvedModel.setMode(options.mode); + model.setMode(options.mode); } // Model can be dirty if a backup was restored, so we make sure to // have this event delivered if we created the model here - if (didCreateModel && resolvedModel.isDirty()) { - this._onDidChangeDirty.fire(resolvedModel); + if (didCreateModel && model.isDirty()) { + this._onDidChangeDirty.fire(model); } - return resolvedModel; + return model; } catch (error) { // Free resources of this invalid model @@ -350,17 +350,17 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE model.dispose(); } - // Remove from pending loads - this.mapResourceToPendingModelLoaders.delete(resource); + // Remove from pending resolves + this.mapResourceToPendingModelResolvers.delete(resource); throw error; } } private joinPendingResolve(resource: URI): Promise | undefined { - const pendingModelLoad = this.mapResourceToPendingModelLoaders.get(resource); - if (pendingModelLoad) { - return pendingModelLoad.then(undefined, error => {/* ignore any error here, it will bubble to the original requestor*/ }); + const pendingModelResolve = this.mapResourceToPendingModelResolvers.get(resource); + if (pendingModelResolve) { + return pendingModelResolve.then(undefined, error => {/* ignore any error here, it will bubble to the original requestor*/ }); } return undefined; @@ -370,7 +370,7 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE // Install model listeners const modelListeners = new DisposableStore(); - modelListeners.add(model.onDidLoad(reason => this._onDidLoad.fire({ model, reason }))); + modelListeners.add(model.onDidResolve(reason => this._onDidResolve.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, reason }))); @@ -395,7 +395,7 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE // store in cache but remove when model gets disposed this.mapResourceToModel.set(resource, model); - this.mapResourceToDisposeListener.set(resource, model.onDispose(() => this.remove(resource))); + this.mapResourceToDisposeListener.set(resource, model.onWillDispose(() => this.remove(resource))); } protected remove(resource: URI): void { @@ -432,7 +432,7 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE // model caches this.mapResourceToModel.clear(); - this.mapResourceToPendingModelLoaders.clear(); + this.mapResourceToPendingModelResolvers.clear(); // dispose the dispose listeners this.mapResourceToDisposeListener.forEach(listener => listener.dispose()); @@ -445,10 +445,10 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE canDispose(model: TextFileEditorModel): true | Promise { - // quick return if model already disposed or not dirty and not loading + // quick return if model already disposed or not dirty and not resolving if ( model.isDisposed() || - (!this.mapResourceToPendingModelLoaders.has(model.resource) && !model.isDirty()) + (!this.mapResourceToPendingModelResolvers.has(model.resource) && !model.isDirty()) ) { return true; } @@ -459,7 +459,7 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE private async doCanDispose(model: TextFileEditorModel): Promise { - // if we have a pending model load, await it first and then try again + // if we have a pending model resolve, await it first and then try again const pendingResolve = this.joinPendingResolve(model.resource); if (pendingResolve) { await pendingResolve; @@ -479,7 +479,7 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE return true; } - dispose(): void { + override dispose(): void { super.dispose(); this.clear(); diff --git a/src/vs/workbench/services/textfile/common/textFileSaveParticipant.ts b/src/vs/workbench/services/textfile/common/textFileSaveParticipant.ts index 037f389f..c2fde636 100644 --- a/src/vs/workbench/services/textfile/common/textFileSaveParticipant.ts +++ b/src/vs/workbench/services/textfile/common/textFileSaveParticipant.ts @@ -64,7 +64,7 @@ export class TextFileSaveParticipant extends Disposable { }); } - dispose(): void { + override dispose(): void { this.saveParticipants.splice(0, this.saveParticipants.length); } } diff --git a/src/vs/workbench/services/textfile/common/textfiles.ts b/src/vs/workbench/services/textfile/common/textfiles.ts index a63ba33f..d9506324 100644 --- a/src/vs/workbench/services/textfile/common/textfiles.ts +++ b/src/vs/workbench/services/textfile/common/textfiles.ts @@ -6,14 +6,15 @@ import { URI } from 'vs/base/common/uri'; import { Event } from 'vs/base/common/event'; import { IDisposable } from 'vs/base/common/lifecycle'; -import { IEncodingSupport, IModeSupport, ISaveOptions, IRevertOptions, SaveReason } from 'vs/workbench/common/editor'; +import { ISaveOptions, IRevertOptions, SaveReason } from 'vs/workbench/common/editor'; +import { ReadableStream } from 'vs/base/common/stream'; import { IBaseStatWithMetadata, IFileStatWithMetadata, IWriteFileOptions, FileOperationError, FileOperationResult, IReadFileStreamOptions } from 'vs/platform/files/common/files'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ITextEditorModel } from 'vs/editor/common/services/resolverService'; import { ITextBufferFactory, ITextModel, ITextSnapshot } from 'vs/editor/common/model'; -import { VSBuffer, VSBufferReadable } from 'vs/base/common/buffer'; +import { VSBuffer, VSBufferReadable, VSBufferReadableStream } from 'vs/base/common/buffer'; import { areFunctions, isUndefinedOrNull } from 'vs/base/common/types'; -import { IWorkingCopy } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { IWorkingCopy } from 'vs/workbench/services/workingCopy/common/workingCopy'; import { IUntitledTextEditorModelManager } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IProgress, IProgressStep } from 'vs/platform/progress/common/progress'; @@ -95,32 +96,47 @@ export interface ITextFileService extends IDisposable { * Create files. If the file exists it will be overwritten with the contents if * the options enable to overwrite. */ - create(operations: { resource: URI, value?: string | ITextSnapshot, options?: { overwrite?: boolean } }[], undoInfo?: IFileOperationUndoRedoInfo, token?: CancellationToken): Promise; + create(operations: { resource: URI, value?: string | ITextSnapshot, options?: { overwrite?: boolean } }[], undoInfo?: IFileOperationUndoRedoInfo): Promise; /** - * Returns the readable that uses the appropriate encoding. + * Returns the readable that uses the appropriate encoding. This method should + * be used whenever a `string` or `ITextSnapshot` is being persisted to the + * file system. */ + getEncodedReadable(resource: URI, value: ITextSnapshot, options?: IWriteTextFileOptions): Promise; + getEncodedReadable(resource: URI, value: string, options?: IWriteTextFileOptions): Promise; + getEncodedReadable(resource: URI, value?: ITextSnapshot, options?: IWriteTextFileOptions): Promise; + getEncodedReadable(resource: URI, value?: string, options?: IWriteTextFileOptions): Promise; getEncodedReadable(resource: URI, value?: string | ITextSnapshot, options?: IWriteTextFileOptions): Promise; + + /** + * Returns a stream of strings that uses the appropriate encoding. This method should + * be used whenever a `VSBufferReadableStream` is being loaded from the file system. + */ + getDecodedStream(resource: URI, value: VSBufferReadableStream, options?: IReadTextFileEncodingOptions): Promise>; } -export interface IReadTextFileOptions extends IReadFileStreamOptions { - - /** - * The optional acceptTextOnly parameter allows to fail this request early if the file - * contents are not textual. - */ - acceptTextOnly?: boolean; +export interface IReadTextFileEncodingOptions { /** * The optional encoding parameter allows to specify the desired encoding when resolving * the contents of the file. */ - encoding?: string; + readonly encoding?: string; /** * The optional guessEncoding parameter allows to guess encoding from content of the file. */ - autoGuessEncoding?: boolean; + readonly autoGuessEncoding?: boolean; +} + +export interface IReadTextFileOptions extends IReadTextFileEncodingOptions, IReadFileStreamOptions { + + /** + * The optional acceptTextOnly parameter allows to fail this request early if the file + * contents are not textual. + */ + readonly acceptTextOnly?: boolean; } export interface IWriteTextFileOptions extends IWriteFileOptions { @@ -128,13 +144,13 @@ export interface IWriteTextFileOptions extends IWriteFileOptions { /** * The encoding to use when updating a file. */ - encoding?: string; + readonly encoding?: string; /** * Whether to write to the file as elevated (admin) user. When setting this option a prompt will * ask the user to authenticate as super user. */ - writeElevated?: boolean; + readonly writeElevated?: boolean; } export const enum TextFileOperationResult { @@ -147,12 +163,16 @@ export class TextFileOperationError extends FileOperationError { return obj instanceof Error && !isUndefinedOrNull((obj as TextFileOperationError).textFileOperationResult); } + override readonly options?: IReadTextFileOptions & IWriteTextFileOptions; + constructor( message: string, public textFileOperationResult: TextFileOperationResult, - public options?: IReadTextFileOptions & IWriteTextFileOptions + options?: IReadTextFileOptions & IWriteTextFileOptions ) { super(message, FileOperationResult.FILE_OTHER_ERROR); + + this.options = options; } } @@ -161,8 +181,8 @@ export interface IResourceEncodings { } export interface IResourceEncoding { - encoding: string; - hasBOM: boolean; + readonly encoding: string; + readonly hasBOM: boolean; } /** @@ -214,7 +234,7 @@ export const enum TextFileEditorModelState { ERROR } -export const enum TextFileLoadReason { +export const enum TextFileResolveReason { EDITOR = 1, REFERENCE = 2, OTHER = 3 @@ -225,7 +245,7 @@ interface IBaseTextFileContent extends IBaseStatWithMetadata { /** * The encoding of the content if known. */ - encoding: string; + readonly encoding: string; } export interface ITextFileContent extends IBaseTextFileContent { @@ -233,7 +253,7 @@ export interface ITextFileContent extends IBaseTextFileContent { /** * The content of a text file. */ - value: string; + readonly value: string; } export interface ITextFileStreamContent extends IBaseTextFileContent { @@ -241,59 +261,59 @@ export interface ITextFileStreamContent extends IBaseTextFileContent { /** * The line grouped content of a text file. */ - value: ITextBufferFactory; + readonly value: ITextBufferFactory; } -export interface ITextFileEditorModelLoadOrCreateOptions { +export interface ITextFileEditorModelResolveOrCreateOptions { /** - * Context why the model is being loaded or created. + * Context why the model is being resolved or created. */ - reason?: TextFileLoadReason; + readonly reason?: TextFileResolveReason; /** * The language mode to use for the model text content. */ - mode?: string; + readonly mode?: string; /** * The encoding to use when resolving the model text content. */ - encoding?: string; + readonly encoding?: string; /** * The contents to use for the model if known. If not * provided, the contents will be retrieved from the * underlying resource or backup if present. */ - contents?: ITextBufferFactory; + readonly contents?: ITextBufferFactory; /** - * If the model was already loaded before, allows to trigger + * If the model 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 * model has finished reloading. */ - reload?: { - async: boolean + readonly reload?: { + readonly async: boolean }; /** - * Allow to load a model even if we think it is a binary file. + * Allow to resolve a model even if we think it is a binary file. */ - allowBinary?: boolean; + readonly allowBinary?: boolean; } export interface ITextFileSaveEvent { - model: ITextFileEditorModel; - reason: SaveReason; + readonly model: ITextFileEditorModel; + readonly reason: SaveReason; } -export interface ITextFileLoadEvent { - model: ITextFileEditorModel; - reason: TextFileLoadReason; +export interface ITextFileResolveEvent { + readonly model: ITextFileEditorModel; + readonly reason: TextFileResolveReason; } export interface ITextFileSaveParticipant { @@ -313,7 +333,7 @@ export interface ITextFileSaveParticipant { export interface ITextFileEditorModelManager { readonly onDidCreate: Event; - readonly onDidLoad: Event; + readonly onDidResolve: Event; readonly onDidChangeDirty: Event; readonly onDidChangeEncoding: Event; readonly onDidSaveError: Event; @@ -337,9 +357,9 @@ export interface ITextFileEditorModelManager { get(resource: URI): ITextFileEditorModel | undefined; /** - * Allows to load a text file model from disk. + * Allows to resolve a text file model from disk. */ - resolve(resource: URI, options?: ITextFileEditorModelLoadOrCreateOptions): Promise; + resolve(resource: URI, options?: ITextFileEditorModelResolveOrCreateOptions): Promise; /** * Adds a participant for saving text file models. @@ -364,24 +384,24 @@ export interface ITextFileSaveOptions extends ISaveOptions { /** * Save the file with an attempt to unlock it. */ - writeUnlock?: boolean; + readonly writeUnlock?: boolean; /** * Save the file with elevated privileges. * * Note: This may not be supported in all environments. */ - writeElevated?: boolean; + readonly writeElevated?: boolean; /** * Allows to write to a file even if it has been modified on disk. */ - ignoreModifiedSince?: boolean; + readonly ignoreModifiedSince?: boolean; /** * If set, will bubble up the error to the caller instead of handling it. */ - ignoreErrorHandler?: boolean; + readonly ignoreErrorHandler?: boolean; } export interface ITextFileSaveAsOptions extends ITextFileSaveOptions { @@ -389,32 +409,66 @@ export interface ITextFileSaveAsOptions extends ITextFileSaveOptions { /** * Optional URI to use as suggested file path to save as. */ - suggestedTarget?: URI; + readonly suggestedTarget?: URI; } -export interface ITextFileLoadOptions { +export interface ITextFileResolveOptions { /** * The contents to use for the model if known. If not * provided, the contents will be retrieved from the * underlying resource or backup if present. */ - contents?: ITextBufferFactory; + readonly contents?: ITextBufferFactory; /** * Go to file bypassing any cache of the model if any. */ - forceReadFromFile?: boolean; + readonly forceReadFromFile?: boolean; /** - * Allow to load a model even if we think it is a binary file. + * Allow to resolve a model even if we think it is a binary file. */ - allowBinary?: boolean; + readonly allowBinary?: boolean; /** - * Context why the model is being loaded. + * Context why the model is being resolved. */ - reason?: TextFileLoadReason; + readonly reason?: TextFileResolveReason; +} + +export const enum EncodingMode { + + /** + * Instructs the encoding support to encode the object with the provided encoding + */ + Encode, + + /** + * Instructs the encoding support to decode the object with the provided encoding + */ + Decode +} + +export interface IEncodingSupport { + + /** + * Gets the encoding of the object if known. + */ + getEncoding(): string | undefined; + + /** + * Sets the encoding for the object for saving. + */ + setEncoding(encoding: string, mode: EncodingMode): Promise; +} + +export interface IModeSupport { + + /** + * Sets the language mode of the object. + */ + setMode(mode: string): void; } export interface ITextFileEditorModel extends ITextEditorModel, IEncodingSupport, IModeSupport, IWorkingCopy { @@ -432,7 +486,7 @@ export interface ITextFileEditorModel extends ITextEditorModel, IEncodingSupport save(options?: ITextFileSaveOptions): Promise; revert(options?: IRevertOptions): Promise; - load(options?: ITextFileLoadOptions): Promise; + resolve(options?: ITextFileResolveOptions): Promise; isDirty(): this is IResolvedTextFileEditorModel; @@ -481,35 +535,6 @@ export function stringToSnapshot(value: string): ITextSnapshot { }; } -export class TextSnapshotReadable implements VSBufferReadable { - private preambleHandled = false; - - constructor(private snapshot: ITextSnapshot, private preamble?: string) { } - - read(): VSBuffer | null { - let value = this.snapshot.read(); - - // Handle preamble if provided - if (!this.preambleHandled) { - this.preambleHandled = true; - - if (typeof this.preamble === 'string') { - if (typeof value === 'string') { - value = this.preamble + value; - } else { - value = this.preamble; - } - } - } - - if (typeof value === 'string') { - return VSBuffer.fromString(value); - } - - return null; - } -} - export function toBufferOrReadable(value: string): VSBuffer; export function toBufferOrReadable(value: ITextSnapshot): VSBufferReadable; export function toBufferOrReadable(value: string | ITextSnapshot): VSBuffer | VSBufferReadable; @@ -523,5 +548,14 @@ export function toBufferOrReadable(value: string | ITextSnapshot | undefined): V return VSBuffer.fromString(value); } - return new TextSnapshotReadable(value); + return { + read: () => { + const chunk = value.read(); + if (typeof chunk === 'string') { + return VSBuffer.fromString(chunk); + } + + return null; + } + }; } diff --git a/src/vs/workbench/services/textfile/electron-sandbox/nativeTextFileService.ts b/src/vs/workbench/services/textfile/electron-sandbox/nativeTextFileService.ts index e6e23106..bbad7f15 100644 --- a/src/vs/workbench/services/textfile/electron-sandbox/nativeTextFileService.ts +++ b/src/vs/workbench/services/textfile/electron-sandbox/nativeTextFileService.ts @@ -5,15 +5,11 @@ import { process } from 'vs/base/parts/sandbox/electron-sandbox/globals'; import { AbstractTextFileService } from 'vs/workbench/services/textfile/browser/textFileService'; -import { ITextFileService, ITextFileStreamContent, ITextFileContent, IReadTextFileOptions, IWriteTextFileOptions, TextFileEditorModelState, ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileService, ITextFileStreamContent, ITextFileContent, IReadTextFileOptions, TextFileEditorModelState, ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { URI } from 'vs/base/common/uri'; -import { IFileStatWithMetadata, IFileService, ByteSize, getPlatformLimits, Arch } from 'vs/platform/files/common/files'; -import { Schemas } from 'vs/base/common/network'; -import { join } from 'vs/base/common/path'; +import { IFileService, ByteSize, getPlatformLimits, Arch } from 'vs/platform/files/common/files'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService'; -import { UTF8, UTF8_with_bom } from 'vs/workbench/services/textfile/common/encoding'; -import { ITextSnapshot } from 'vs/editor/common/model'; import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -27,19 +23,21 @@ import { IPathService } from 'vs/workbench/services/path/common/pathService'; import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; import { IModeService } from 'vs/editor/common/services/modeService'; -import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; +import { IElevatedFileService } from 'vs/workbench/services/files/common/elevatedFileService'; import { ILogService } from 'vs/platform/log/common/log'; import { Promises } from 'vs/base/common/async'; export class NativeTextFileService extends AbstractTextFileService { + protected override readonly environmentService: INativeWorkbenchEnvironmentService; + constructor( @IFileService fileService: IFileService, @IUntitledTextEditorService untitledTextEditorService: IUntitledTextEditorService, @ILifecycleService lifecycleService: ILifecycleService, @IInstantiationService instantiationService: IInstantiationService, @IModelService modelService: IModelService, - @INativeWorkbenchEnvironmentService protected environmentService: INativeWorkbenchEnvironmentService, + @INativeWorkbenchEnvironmentService environmentService: INativeWorkbenchEnvironmentService, @IDialogService dialogService: IDialogService, @IFileDialogService fileDialogService: IFileDialogService, @ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService, @@ -50,13 +48,15 @@ export class NativeTextFileService extends AbstractTextFileService { @IWorkingCopyFileService workingCopyFileService: IWorkingCopyFileService, @IUriIdentityService uriIdentityService: IUriIdentityService, @IModeService modeService: IModeService, - @INativeHostService private readonly nativeHostService: INativeHostService, + @IElevatedFileService elevatedFileService: IElevatedFileService, @ILogService logService: ILogService ) { - super(fileService, untitledTextEditorService, lifecycleService, instantiationService, modelService, environmentService, dialogService, fileDialogService, textResourceConfigurationService, filesConfigurationService, textModelService, codeEditorService, pathService, workingCopyFileService, uriIdentityService, modeService, logService); + super(fileService, untitledTextEditorService, lifecycleService, instantiationService, modelService, environmentService, dialogService, fileDialogService, textResourceConfigurationService, filesConfigurationService, textModelService, codeEditorService, pathService, workingCopyFileService, uriIdentityService, modeService, logService, elevatedFileService); + + this.environmentService = environmentService; } - protected registerListeners(): void { + protected override registerListeners(): void { super.registerListeners(); // Lifecycle @@ -75,7 +75,7 @@ export class NativeTextFileService extends AbstractTextFileService { } } - async read(resource: URI, options?: IReadTextFileOptions): Promise { + override async read(resource: URI, options?: IReadTextFileOptions): Promise { // ensure size & memory limits options = this.ensureLimits(options); @@ -83,7 +83,7 @@ export class NativeTextFileService extends AbstractTextFileService { return super.read(resource, options); } - async readStream(resource: URI, options?: IReadTextFileOptions): Promise { + override async readStream(resource: URI, options?: IReadTextFileOptions): Promise { // ensure size & memory limits options = this.ensureLimits(options); @@ -118,35 +118,6 @@ export class NativeTextFileService extends AbstractTextFileService { return ensuredOptions; } - - async write(resource: URI, value: string | ITextSnapshot, options?: IWriteTextFileOptions): Promise { - - // check for `writeElevated property` to write elevated - // (file:// only: https://github.com/microsoft/vscode/issues/48659) - if (options?.writeElevated && resource.scheme === Schemas.file) { - return this.writeElevated(resource, value, options); - } - - return super.write(resource, value, options); - } - - private async writeElevated(resource: URI, value: string | ITextSnapshot, options?: IWriteTextFileOptions): Promise { - const source = URI.file(join(this.environmentService.userDataPath, `code-elevated-${Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 6)}`)); - const { encoding, addBOM } = await this.encoding.getWriteEncoding(resource, options); - try { - // write into a tmp file first - await this.write(source, value, { encoding: encoding === UTF8 && addBOM ? UTF8_with_bom : encoding }); - - // then sudo prompt copy - await this.nativeHostService.writeElevated(source, resource, options); - } finally { - - // clean up - await this.fileService.del(source); - } - - return this.fileService.resolve(resource, { resolveMetadata: true }); - } } registerSingleton(ITextFileService, NativeTextFileService); diff --git a/src/vs/workbench/services/textfile/test/browser/browserTextFileService.io.test.ts b/src/vs/workbench/services/textfile/test/browser/browserTextFileService.io.test.ts index 28aeccc4..72e06225 100644 --- a/src/vs/workbench/services/textfile/test/browser/browserTextFileService.io.test.ts +++ b/src/vs/workbench/services/textfile/test/browser/browserTextFileService.io.test.ts @@ -20,7 +20,7 @@ import files from 'vs/workbench/services/textfile/test/browser/fixtures/files'; import createSuite from 'vs/workbench/services/textfile/test/common/textFileService.io.test'; import { isWeb } from 'vs/base/common/platform'; import { IWorkingCopyFileService, WorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; -import { TestWorkingCopyService } from 'vs/workbench/test/common/workbenchTestServices'; +import { WorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { UriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentityService'; // optimization: we don't need to run this suite in native environment, @@ -48,7 +48,7 @@ if (isWeb) { const collection = new ServiceCollection(); collection.set(IFileService, fileService); - collection.set(IWorkingCopyFileService, new WorkingCopyFileService(fileService, new TestWorkingCopyService(), instantiationService, new UriIdentityService(fileService))); + collection.set(IWorkingCopyFileService, new WorkingCopyFileService(fileService, new WorkingCopyService(), instantiationService, new UriIdentityService(fileService))); service = instantiationService.createChild(collection).createInstance(TestBrowserTextFileServiceWithEncodingOverrides); diff --git a/src/vs/workbench/services/textfile/test/browser/textFileEditorModel.test.ts b/src/vs/workbench/services/textfile/test/browser/textFileEditorModel.test.ts index 15f46ae8..c05e5b16 100644 --- a/src/vs/workbench/services/textfile/test/browser/textFileEditorModel.test.ts +++ b/src/vs/workbench/services/textfile/test/browser/textFileEditorModel.test.ts @@ -5,9 +5,8 @@ import * as assert from 'assert'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { EncodingMode } from 'vs/workbench/common/editor'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; -import { TextFileEditorModelState, snapshotToString, isTextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; +import { EncodingMode, TextFileEditorModelState, snapshotToString, isTextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; import { createFileEditorInput, workbenchInstantiationService, TestServiceAccessor, TestReadonlyTextFileEditorModel, getLastResolvedFileStat } from 'vs/workbench/test/browser/workbenchTestServices'; import { toResource } from 'vs/base/test/common/utils'; import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; @@ -15,7 +14,10 @@ import { FileOperationResult, FileOperationError } from 'vs/platform/files/commo import { timeout } from 'vs/base/common/async'; import { ModesRegistry } from 'vs/editor/common/modes/modesRegistry'; import { assertIsDefined } from 'vs/base/common/types'; -import { createTextBufferFactory } from 'vs/editor/common/model/textModel'; +import { createTextBufferFactory, createTextBufferFactoryFromStream } from 'vs/editor/common/model/textModel'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { URI } from 'vs/base/common/uri'; +import { bufferToStream, VSBuffer } from 'vs/base/common/buffer'; suite('Files - TextFileEditorModel', () => { @@ -43,12 +45,12 @@ suite('Files - TextFileEditorModel', () => { test('basic events', async function () { const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); - let onDidLoadCounter = 0; - model.onDidLoad(() => onDidLoadCounter++); + let onDidResolveCounter = 0; + model.onDidResolve(() => onDidResolveCounter++); - await model.load(); + await model.resolve(); - assert.strictEqual(onDidLoadCounter, 1); + assert.strictEqual(onDidResolveCounter, 1); let onDidChangeContentCounter = 0; model.onDidChangeContent(() => onDidChangeContentCounter++); @@ -84,7 +86,7 @@ suite('Files - TextFileEditorModel', () => { test('save', async function () { const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); - await model.load(); + await model.resolve(); assert.strictEqual(accessor.workingCopyService.dirtyCount, 0); @@ -99,7 +101,7 @@ suite('Files - TextFileEditorModel', () => { assert.ok(model.hasState(TextFileEditorModelState.DIRTY)); assert.strictEqual(accessor.workingCopyService.dirtyCount, 1); - assert.strictEqual(accessor.workingCopyService.isDirty(model.resource), true); + assert.strictEqual(accessor.workingCopyService.isDirty(model.resource, model.typeId), true); let workingCopyEvent = false; accessor.workingCopyService.onDidChangeDirty(e => { @@ -119,7 +121,7 @@ suite('Files - TextFileEditorModel', () => { assert.ok(workingCopyEvent); assert.strictEqual(accessor.workingCopyService.dirtyCount, 0); - assert.strictEqual(accessor.workingCopyService.isDirty(model.resource), false); + assert.strictEqual(accessor.workingCopyService.isDirty(model.resource, model.typeId), false); savedEvent = false; @@ -133,7 +135,7 @@ suite('Files - TextFileEditorModel', () => { test('save - touching also emits saved event', async function () { const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); - await model.load(); + await model.resolve(); let savedEvent = false; model.onDidSave(() => savedEvent = true); @@ -157,7 +159,7 @@ suite('Files - TextFileEditorModel', () => { test('save - touching with error turns model dirty', async function () { const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); - await model.load(); + await model.resolve(); let saveErrorEvent = false; model.onDidSaveError(() => saveErrorEvent = true); @@ -174,7 +176,7 @@ suite('Files - TextFileEditorModel', () => { assert.ok(saveErrorEvent); assert.strictEqual(accessor.workingCopyService.dirtyCount, 1); - assert.strictEqual(accessor.workingCopyService.isDirty(model.resource), true); + assert.strictEqual(accessor.workingCopyService.isDirty(model.resource, model.typeId), true); } finally { accessor.fileService.writeShouldThrowError = undefined; } @@ -191,7 +193,7 @@ suite('Files - TextFileEditorModel', () => { test('save error (generic)', async function () { const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); - await model.load(); + await model.resolve(); model.updateTextEditorModel(createTextBufferFactory('bar')); @@ -210,7 +212,7 @@ suite('Files - TextFileEditorModel', () => { assert.ok(saveErrorEvent); assert.strictEqual(accessor.workingCopyService.dirtyCount, 1); - assert.strictEqual(accessor.workingCopyService.isDirty(model.resource), true); + assert.strictEqual(accessor.workingCopyService.isDirty(model.resource, model.typeId), true); model.dispose(); } finally { @@ -221,7 +223,7 @@ suite('Files - TextFileEditorModel', () => { test('save error (conflict)', async function () { const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); - await model.load(); + await model.resolve(); model.updateTextEditorModel(createTextBufferFactory('bar')); @@ -240,7 +242,7 @@ suite('Files - TextFileEditorModel', () => { assert.ok(saveErrorEvent); assert.strictEqual(accessor.workingCopyService.dirtyCount, 1); - assert.strictEqual(accessor.workingCopyService.isDirty(model.resource), true); + assert.strictEqual(accessor.workingCopyService.isDirty(model.resource, model.typeId), true); model.dispose(); } finally { @@ -248,18 +250,18 @@ suite('Files - TextFileEditorModel', () => { } }); - test('setEncoding - encode', function () { + test('setEncoding - encode', async function () { const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); let encodingEvent = false; model.onDidChangeEncoding(() => encodingEvent = true); - model.setEncoding('utf8', EncodingMode.Encode); // no-op + await model.setEncoding('utf8', EncodingMode.Encode); // no-op assert.strictEqual(getLastModifiedTime(model), -1); assert.ok(!encodingEvent); - model.setEncoding('utf16', EncodingMode.Encode); + await model.setEncoding('utf16', EncodingMode.Encode); assert.ok(encodingEvent); @@ -271,10 +273,22 @@ suite('Files - TextFileEditorModel', () => { test('setEncoding - decode', async function () { const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); - model.setEncoding('utf16', EncodingMode.Decode); + await model.setEncoding('utf16', EncodingMode.Decode); - await timeout(0); - assert.ok(model.isResolved()); // model got loaded due to decoding + assert.ok(model.isResolved()); // model got resolved due to decoding + model.dispose(); + }); + + test('setEncoding - decode dirty file saves first', async function () { + const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); + await model.resolve(); + + model.updateTextEditorModel(createTextBufferFactory('bar')); + assert.strictEqual(model.isDirty(), true); + + await model.setEncoding('utf16', EncodingMode.Decode); + + assert.strictEqual(model.isDirty(), false); model.dispose(); }); @@ -286,7 +300,7 @@ suite('Files - TextFileEditorModel', () => { const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', mode); - await model.load(); + await model.resolve(); assert.strictEqual(model.textEditorModel!.getModeId(), mode); @@ -297,47 +311,47 @@ suite('Files - TextFileEditorModel', () => { test('disposes when underlying model is destroyed', async function () { const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); - await model.load(); + await model.resolve(); model.textEditorModel!.dispose(); assert.ok(model.isDisposed()); }); - test('Load does not trigger save', async function () { + test('Resolve does not trigger save', async function () { const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index.txt'), 'utf8', undefined); assert.ok(model.hasState(TextFileEditorModelState.SAVED)); model.onDidSave(() => assert.fail()); model.onDidChangeDirty(() => assert.fail()); - await model.load(); + await model.resolve(); assert.ok(model.isResolved()); model.dispose(); assert.ok(!accessor.modelService.getModel(model.resource)); }); - test('Load returns dirty model as long as model is dirty', async function () { + test('Resolve returns dirty model as long as model is dirty', async function () { const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); - await model.load(); + await model.resolve(); model.updateTextEditorModel(createTextBufferFactory('foo')); assert.ok(model.isDirty()); assert.ok(model.hasState(TextFileEditorModelState.DIRTY)); - await model.load(); + await model.resolve(); assert.ok(model.isDirty()); model.dispose(); }); - test('Load with contents', async function () { + test('Resolve with contents', async function () { const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); - await model.load({ contents: createTextBufferFactory('Hello World') }); + await model.resolve({ contents: createTextBufferFactory('Hello World') }); assert.strictEqual(model.textEditorModel?.getValue(), 'Hello World'); assert.strictEqual(model.isDirty(), true); - await model.load({ contents: createTextBufferFactory('Hello Changes') }); + await model.resolve({ contents: createTextBufferFactory('Hello Changes') }); assert.strictEqual(model.textEditorModel?.getValue(), 'Hello Changes'); assert.strictEqual(model.isDirty(), true); @@ -365,12 +379,12 @@ suite('Files - TextFileEditorModel', () => { } }); - await model.load(); + await model.resolve(); model.updateTextEditorModel(createTextBufferFactory('foo')); assert.ok(model.isDirty()); assert.strictEqual(accessor.workingCopyService.dirtyCount, 1); - assert.strictEqual(accessor.workingCopyService.isDirty(model.resource), true); + assert.strictEqual(accessor.workingCopyService.isDirty(model.resource, model.typeId), true); await model.revert(); assert.strictEqual(model.isDirty(), false); @@ -379,7 +393,7 @@ suite('Files - TextFileEditorModel', () => { assert.ok(workingCopyEvent); assert.strictEqual(accessor.workingCopyService.dirtyCount, 0); - assert.strictEqual(accessor.workingCopyService.isDirty(model.resource), false); + assert.strictEqual(accessor.workingCopyService.isDirty(model.resource, model.typeId), false); model.dispose(); }); @@ -398,12 +412,12 @@ suite('Files - TextFileEditorModel', () => { } }); - await model.load(); + await model.resolve(); model.updateTextEditorModel(createTextBufferFactory('foo')); assert.ok(model.isDirty()); assert.strictEqual(accessor.workingCopyService.dirtyCount, 1); - assert.strictEqual(accessor.workingCopyService.isDirty(model.resource), true); + assert.strictEqual(accessor.workingCopyService.isDirty(model.resource, model.typeId), true); await model.revert({ soft: true }); assert.strictEqual(model.isDirty(), false); @@ -412,14 +426,14 @@ suite('Files - TextFileEditorModel', () => { assert.ok(workingCopyEvent); assert.strictEqual(accessor.workingCopyService.dirtyCount, 0); - assert.strictEqual(accessor.workingCopyService.isDirty(model.resource), false); + assert.strictEqual(accessor.workingCopyService.isDirty(model.resource, model.typeId), false); model.dispose(); }); test('Undo to saved state turns model non-dirty', async function () { const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); - await model.load(); + await model.resolve(); model.updateTextEditorModel(createTextBufferFactory('Hello Text')); assert.ok(model.isDirty()); @@ -427,17 +441,17 @@ suite('Files - TextFileEditorModel', () => { assert.ok(!model.isDirty()); }); - test('Load and undo turns model dirty', async function () { + test('Resolve and undo turns model dirty', async function () { const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); - await model.load(); + await model.resolve(); accessor.fileService.setContent('Hello Change'); - await model.load(); + await model.resolve(); await model.textEditorModel!.undo(); assert.ok(model.isDirty()); assert.strictEqual(accessor.workingCopyService.dirtyCount, 1); - assert.strictEqual(accessor.workingCopyService.isDirty(model.resource), true); + assert.strictEqual(accessor.workingCopyService.isDirty(model.resource, model.typeId), true); }); test('Update Dirty', async function () { @@ -448,7 +462,7 @@ suite('Files - TextFileEditorModel', () => { model.setDirty(true); assert.ok(!model.isDirty()); // needs to be resolved - await model.load(); + await model.resolve(); model.updateTextEditorModel(createTextBufferFactory('foo')); assert.ok(model.isDirty()); @@ -491,7 +505,7 @@ suite('Files - TextFileEditorModel', () => { saveEvent = true; }); - await model.load(); + await model.resolve(); model.updateTextEditorModel(createTextBufferFactory('foo')); assert.ok(!model.isDirty()); @@ -509,25 +523,25 @@ suite('Files - TextFileEditorModel', () => { test('File not modified error is handled gracefully', async function () { let model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); - await model.load(); + await model.resolve(); const mtime = getLastModifiedTime(model); accessor.textFileService.setReadStreamErrorOnce(new FileOperationError('error', FileOperationResult.FILE_NOT_MODIFIED_SINCE)); - model = await model.load() as TextFileEditorModel; + await model.resolve(); assert.ok(model); assert.strictEqual(getLastModifiedTime(model), mtime); model.dispose(); }); - test('Load error is handled gracefully if model already exists', async function () { + test('Resolve error is handled gracefully if model already exists', async function () { let model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); - await model.load(); + await model.resolve(); accessor.textFileService.setReadStreamErrorOnce(new FileOperationError('error', FileOperationResult.FILE_NOT_FOUND)); - model = await model.load() as TextFileEditorModel; + await model.resolve(); assert.ok(model); model.dispose(); }); @@ -583,7 +597,7 @@ suite('Files - TextFileEditorModel', () => { } }); - await model.load(); + await model.resolve(); model.updateTextEditorModel(createTextBufferFactory('foo')); assert.ok(model.isDirty()); @@ -610,7 +624,7 @@ suite('Files - TextFileEditorModel', () => { } }); - await model.load(); + await model.resolve(); model.updateTextEditorModel(createTextBufferFactory('foo')); await model.save({ skipSaveParticipants: true }); @@ -640,7 +654,7 @@ suite('Files - TextFileEditorModel', () => { } }); - await model.load(); + await model.resolve(); model.updateTextEditorModel(createTextBufferFactory('foo')); const now = Date.now(); @@ -661,7 +675,7 @@ suite('Files - TextFileEditorModel', () => { } }); - await model.load(); + await model.resolve(); model.updateTextEditorModel(createTextBufferFactory('foo')); await model.save(); @@ -685,7 +699,7 @@ suite('Files - TextFileEditorModel', () => { } }); - await model.load(); + await model.resolve(); model.updateTextEditorModel(createTextBufferFactory('foo')); const p1 = model.save(); @@ -744,7 +758,7 @@ suite('Files - TextFileEditorModel', () => { } }); - await model.load(); + await model.resolve(); model.updateTextEditorModel(createTextBufferFactory('foo')); savePromise = model.save(); @@ -752,4 +766,35 @@ suite('Files - TextFileEditorModel', () => { participant.dispose(); } + + test('backup and restore (simple)', async function () { + return testBackupAndRestore(toResource.call(this, '/path/index_async.txt'), toResource.call(this, '/path/index_async2.txt'), 'Some very small file text content.'); + }); + + test('backup and restore (large, #121347)', async function () { + const largeContent = '국어한\n'.repeat(100000); + return testBackupAndRestore(toResource.call(this, '/path/index_async.txt'), toResource.call(this, '/path/index_async2.txt'), largeContent); + }); + + async function testBackupAndRestore(resourceA: URI, resourceB: URI, contents: string): Promise { + const originalModel: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, resourceA, 'utf8', undefined); + await originalModel.resolve({ + contents: await createTextBufferFactoryFromStream(await accessor.textFileService.getDecodedStream(resourceA, bufferToStream(VSBuffer.fromString(contents)))) + }); + + assert.strictEqual(originalModel.textEditorModel?.getValue(), contents); + + const backup = await originalModel.backup(CancellationToken.None); + const modelRestoredIdentifier = { typeId: originalModel.typeId, resource: resourceB }; + await accessor.workingCopyBackupService.backup(modelRestoredIdentifier, backup.content); + + const modelRestored: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, modelRestoredIdentifier.resource, 'utf8', undefined); + await modelRestored.resolve(); + + assert.strictEqual(modelRestored.textEditorModel?.getValue(), contents); + assert.strictEqual(modelRestored.isDirty(), true); + + originalModel.dispose(); + modelRestored.dispose(); + } }); diff --git a/src/vs/workbench/services/textfile/test/browser/textFileEditorModelManager.test.ts b/src/vs/workbench/services/textfile/test/browser/textFileEditorModelManager.test.ts index 396928a8..e5d979cb 100644 --- a/src/vs/workbench/services/textfile/test/browser/textFileEditorModelManager.test.ts +++ b/src/vs/workbench/services/textfile/test/browser/textFileEditorModelManager.test.ts @@ -106,7 +106,7 @@ suite('Files - TextFileEditorModelManager', () => { model1.dispose(); const model3 = await manager.resolve(resource, { encoding }); - assert.notEqual(model3, model2); + assert.notStrictEqual(model3, model2); assert.strictEqual(manager.get(resource), model3); model3.dispose(); @@ -186,16 +186,16 @@ suite('Files - TextFileEditorModelManager', () => { const resource1 = toResource.call(this, '/path/index.txt'); const resource2 = toResource.call(this, '/path/other.txt'); - let loadedCounter = 0; + let resolvedCounter = 0; let gotDirtyCounter = 0; let gotNonDirtyCounter = 0; let revertedCounter = 0; let savedCounter = 0; let encodingCounter = 0; - manager.onDidLoad(({ model }) => { + manager.onDidResolve(({ model }) => { if (model.resource.toString() === resource1.toString()) { - loadedCounter++; + resolvedCounter++; } }); @@ -228,13 +228,13 @@ suite('Files - TextFileEditorModelManager', () => { }); const model1 = await manager.resolve(resource1, { encoding: 'utf8' }); - assert.strictEqual(loadedCounter, 1); + assert.strictEqual(resolvedCounter, 1); accessor.fileService.fireFileChanges(new FileChangesEvent([{ resource: resource1, type: FileChangeType.DELETED }], false)); accessor.fileService.fireFileChanges(new FileChangesEvent([{ resource: resource1, type: FileChangeType.ADDED }], false)); const model2 = await manager.resolve(resource2, { encoding: 'utf8' }); - assert.strictEqual(loadedCounter, 2); + assert.strictEqual(resolvedCounter, 2); model1.updateTextEditorModel(createTextBufferFactory('changed')); model1.updatePreferredEncoding('utf16'); diff --git a/src/vs/workbench/services/textfile/test/browser/textFileService.test.ts b/src/vs/workbench/services/textfile/test/browser/textFileService.test.ts index 7a0f5510..9bd5ac1d 100644 --- a/src/vs/workbench/services/textfile/test/browser/textFileService.test.ts +++ b/src/vs/workbench/services/textfile/test/browser/textFileService.test.ts @@ -31,7 +31,7 @@ suite('Files - TextFileService', () => { model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); (accessor.textFileService.files).add(model.resource, model); - await model.load(); + await model.resolve(); assert.ok(!accessor.textFileService.isDirty(model.resource)); model.textEditorModel!.setValue('foo'); @@ -41,7 +41,7 @@ suite('Files - TextFileService', () => { const untitled = await accessor.textFileService.untitled.resolve(); assert.ok(!accessor.textFileService.isDirty(untitled.resource)); - untitled.textEditorModel.setValue('changed'); + untitled.textEditorModel?.setValue('changed'); assert.ok(accessor.textFileService.isDirty(untitled.resource)); @@ -53,7 +53,7 @@ suite('Files - TextFileService', () => { model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); (accessor.textFileService.files).add(model.resource, model); - await model.load(); + await model.resolve(); model.textEditorModel!.setValue('foo'); assert.ok(accessor.textFileService.isDirty(model.resource)); @@ -66,7 +66,7 @@ suite('Files - TextFileService', () => { model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); (accessor.textFileService.files).add(model.resource, model); - await model.load(); + await model.resolve(); model.textEditorModel!.setValue('foo'); assert.ok(accessor.textFileService.isDirty(model.resource)); @@ -80,7 +80,7 @@ suite('Files - TextFileService', () => { (accessor.textFileService.files).add(model.resource, model); accessor.fileDialogService.setPickFileToSave(model.resource); - await model.load(); + await model.resolve(); model.textEditorModel!.setValue('foo'); assert.ok(accessor.textFileService.isDirty(model.resource)); @@ -94,7 +94,7 @@ suite('Files - TextFileService', () => { (accessor.textFileService.files).add(model.resource, model); accessor.fileDialogService.setPickFileToSave(model.resource); - await model.load(); + await model.resolve(); model!.textEditorModel!.setValue('foo'); assert.ok(accessor.textFileService.isDirty(model.resource)); @@ -106,7 +106,7 @@ suite('Files - TextFileService', () => { model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); (accessor.textFileService.files).add(model.resource, model); - await model.load(); + await model.resolve(); model!.textEditorModel!.setValue('foo'); assert.ok(accessor.textFileService.isDirty(model.resource)); diff --git a/src/vs/workbench/services/textfile/test/common/textFileService.io.test.ts b/src/vs/workbench/services/textfile/test/common/textFileService.io.test.ts index dab4b4e6..f60ecdef 100644 --- a/src/vs/workbench/services/textfile/test/common/textFileService.io.test.ts +++ b/src/vs/workbench/services/textfile/test/common/textFileService.io.test.ts @@ -8,10 +8,11 @@ import { ITextFileService, snapshotToString, TextFileOperationError, TextFileOpe import { URI } from 'vs/base/common/uri'; import { join, basename } from 'vs/base/common/path'; import { UTF16le, UTF8_with_bom, UTF16be, UTF8, UTF16le_BOM, UTF16be_BOM, UTF8_BOM } from 'vs/workbench/services/textfile/common/encoding'; -import { VSBuffer } from 'vs/base/common/buffer'; +import { bufferToStream, VSBuffer } from 'vs/base/common/buffer'; import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; import { ITextSnapshot, DefaultEndOfLine } from 'vs/editor/common/model'; import { isWindows } from 'vs/base/common/platform'; +import { createTextBufferFactoryFromStream } from 'vs/editor/common/model/textModel'; export interface Params { setup(): Promise<{ @@ -527,10 +528,27 @@ export default function createSuite(params: Params) { async function testLargeEncoding(encoding: string, needle: string): Promise { const resource = URI.file(join(testDir, `lorem_${encoding}.txt`)); + // Verify via `ITextFileService.readStream` const result = await service.readStream(resource, { encoding }); assert.strictEqual(result.encoding, encoding); - const contents = snapshotToString(result.value.create(DefaultEndOfLine.LF).textBuffer.createSnapshot(false)); + let contents = snapshotToString(result.value.create(DefaultEndOfLine.LF).textBuffer.createSnapshot(false)); + + assert.strictEqual(contents.indexOf(needle), 0); + assert.ok(contents.indexOf(needle, 10) > 0); + + // Verify via `ITextFileService.getDecodedTextFactory` + const rawFile = await params.readFile(resource.fsPath); + let rawFileVSBuffer: VSBuffer; + if (rawFile instanceof VSBuffer) { + rawFileVSBuffer = rawFile; + } else { + rawFileVSBuffer = VSBuffer.wrap(rawFile); + } + + const factory = await createTextBufferFactoryFromStream(await service.getDecodedStream(resource, bufferToStream(rawFileVSBuffer), { encoding })); + + contents = snapshotToString(factory.create(DefaultEndOfLine.LF).textBuffer.createSnapshot(false)); assert.strictEqual(contents.indexOf(needle), 0); assert.ok(contents.indexOf(needle, 10) > 0); 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 bcefbded..57f55b51 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 @@ -20,7 +20,7 @@ import { detectEncodingByBOM } from 'vs/workbench/services/textfile/test/node/en import { workbenchInstantiationService, TestNativeTextFileServiceWithEncodingOverrides } from 'vs/workbench/test/electron-browser/workbenchTestServices'; import createSuite from 'vs/workbench/services/textfile/test/common/textFileService.io.test'; import { IWorkingCopyFileService, WorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; -import { TestWorkingCopyService } from 'vs/workbench/test/common/workbenchTestServices'; +import { WorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { UriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentityService'; flakySuite('Files - NativeTextFileService i/o', function () { @@ -30,8 +30,8 @@ flakySuite('Files - NativeTextFileService i/o', function () { let testDir: string; function readFile(path: string): Promise; - function readFile(path: string, encoding: string): Promise; - function readFile(path: string, encoding?: string): Promise { + function readFile(path: string, encoding: BufferEncoding): Promise; + function readFile(path: string, encoding?: BufferEncoding): Promise { return promises.readFile(path, encoding); } @@ -49,7 +49,7 @@ flakySuite('Files - NativeTextFileService i/o', function () { const collection = new ServiceCollection(); collection.set(IFileService, fileService); - collection.set(IWorkingCopyFileService, new WorkingCopyFileService(fileService, new TestWorkingCopyService(), instantiationService, new UriIdentityService(fileService))); + collection.set(IWorkingCopyFileService, new WorkingCopyFileService(fileService, new WorkingCopyService(), instantiationService, new UriIdentityService(fileService))); service = instantiationService.createChild(collection).createInstance(TestNativeTextFileServiceWithEncodingOverrides); diff --git a/src/vs/workbench/services/textfile/test/electron-browser/nativeTextFileService.test.ts b/src/vs/workbench/services/textfile/test/electron-browser/nativeTextFileService.test.ts index 9ac72acd..f822ddd4 100644 --- a/src/vs/workbench/services/textfile/test/electron-browser/nativeTextFileService.test.ts +++ b/src/vs/workbench/services/textfile/test/electron-browser/nativeTextFileService.test.ts @@ -14,7 +14,7 @@ import { FileService } from 'vs/platform/files/common/fileService'; import { NullLogService } from 'vs/platform/log/common/log'; import { workbenchInstantiationService, TestNativeTextFileServiceWithEncodingOverrides, TestServiceAccessor } from 'vs/workbench/test/electron-browser/workbenchTestServices'; import { IWorkingCopyFileService, WorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; -import { TestWorkingCopyService } from 'vs/workbench/test/common/workbenchTestServices'; +import { WorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { UriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentityService'; import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -40,7 +40,7 @@ suite('Files - NativeTextFileService', function () { const collection = new ServiceCollection(); collection.set(IFileService, fileService); - collection.set(IWorkingCopyFileService, new WorkingCopyFileService(fileService, new TestWorkingCopyService(), instantiationService, new UriIdentityService(fileService))); + collection.set(IWorkingCopyFileService, new WorkingCopyFileService(fileService, new WorkingCopyService(), instantiationService, new UriIdentityService(fileService))); service = instantiationService.createChild(collection).createInstance(TestNativeTextFileServiceWithEncodingOverrides); }); @@ -54,7 +54,7 @@ suite('Files - NativeTextFileService', function () { test('shutdown joins on pending saves', async function () { const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); - await model.load(); + await model.resolve(); let pendingSaveAwaited = false; model.save().then(() => pendingSaveAwaited = true); diff --git a/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts b/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts index 4ef57070..e924b129 100644 --- a/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts +++ b/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts @@ -9,7 +9,7 @@ import { ITextModel } from 'vs/editor/common/model'; import { IDisposable, toDisposable, IReference, ReferenceCollection, Disposable } from 'vs/base/common/lifecycle'; import { IModelService } from 'vs/editor/common/services/modelService'; import { ResourceEditorModel } from 'vs/workbench/common/editor/resourceEditorModel'; -import { ITextFileService, TextFileLoadReason } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileService, TextFileResolveReason } from 'vs/workbench/services/textfile/common/textfiles'; import { Schemas } from 'vs/base/common/network'; import { ITextModelService, ITextModelContentProvider, ITextEditorModel, IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; @@ -60,7 +60,7 @@ class ResourceModelCollection extends ReferenceCollection { assert.strictEqual(snapshotToString(((model as ResourceEditorModel).createSnapshot()!)), 'Hello Test'); let disposed = false; let disposedPromise = new Promise(resolve => { - Event.once(model.onDispose)(() => { + Event.once(model.onWillDispose)(() => { disposed = true; resolve(); }); @@ -73,7 +73,7 @@ suite('Workbench - TextModelResolverService', () => { const textModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file_resolver.txt'), 'utf8', undefined); (accessor.textFileService.files).add(textModel.resource, textModel); - await textModel.load(); + await textModel.resolve(); const ref = await accessor.textModelResolverService.createModelReference(textModel.resource); @@ -84,7 +84,7 @@ suite('Workbench - TextModelResolverService', () => { assert.strictEqual(editorModel.getValue(), 'Hello Html'); let disposed = false; - Event.once(model.onDispose)(() => { + Event.once(model.onWillDispose)(() => { disposed = true; }); @@ -97,14 +97,14 @@ suite('Workbench - TextModelResolverService', () => { const textModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file_resolver.txt'), 'utf8', undefined); (accessor.textFileService.files).add(textModel.resource, textModel); - const loadedModel = await textModel.load(); + await textModel.resolve(); - loadedModel.updateTextEditorModel(createTextBufferFactory('make dirty')); + textModel.updateTextEditorModel(createTextBufferFactory('make dirty')); const ref = await accessor.textModelResolverService.createModelReference(textModel.resource); let disposed = false; - Event.once(loadedModel.onDispose)(() => { + Event.once(textModel.onWillDispose)(() => { disposed = true; }); @@ -112,7 +112,7 @@ suite('Workbench - TextModelResolverService', () => { await timeout(0); assert.strictEqual(disposed, false); // not disposed because model still dirty - loadedModel.revert(); + textModel.revert(); await timeout(0); assert.strictEqual(disposed, true); // now disposed because model got reverted @@ -122,14 +122,14 @@ suite('Workbench - TextModelResolverService', () => { const textModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file_resolver.txt'), 'utf8', undefined); (accessor.textFileService.files).add(textModel.resource, textModel); - const loadedModel = await textModel.load(); + await textModel.resolve(); - loadedModel.updateTextEditorModel(createTextBufferFactory('make dirty')); + textModel.updateTextEditorModel(createTextBufferFactory('make dirty')); const ref1 = await accessor.textModelResolverService.createModelReference(textModel.resource); let disposed = false; - Event.once(loadedModel.onDispose)(() => { + Event.once(textModel.onWillDispose)(() => { disposed = true; }); @@ -139,7 +139,7 @@ suite('Workbench - TextModelResolverService', () => { const ref2 = await accessor.textModelResolverService.createModelReference(textModel.resource); - loadedModel.revert(); + textModel.revert(); await timeout(0); assert.strictEqual(disposed, false); // not disposed because we got another ref meanwhile diff --git a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts index 5611ff53..90e4c553 100644 --- a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts @@ -692,19 +692,17 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { class ThemeFileWatcher { - private inExtensionDevelopment: boolean; private watchedLocation: URI | undefined; private watcherDisposable: IDisposable | undefined; private fileChangeListener: IDisposable | undefined; - constructor(private fileService: IFileService, environmentService: IWorkbenchEnvironmentService, private onUpdate: () => void) { - this.inExtensionDevelopment = !!environmentService.extensionDevelopmentLocationURI; + constructor(private fileService: IFileService, private environmentService: IWorkbenchEnvironmentService, private onUpdate: () => void) { } update(theme: { location?: URI, watch?: boolean; }) { if (!resources.isEqual(theme.location, this.watchedLocation)) { this.dispose(); - if (theme.location && (theme.watch || this.inExtensionDevelopment)) { + if (theme.location && (theme.watch || this.environmentService.isExtensionDevelopment)) { this.watchedLocation = theme.location; this.watcherDisposable = this.fileService.watch(theme.location); this.fileService.onDidFilesChange(e => { diff --git a/src/vs/workbench/services/themes/common/themeConfiguration.ts b/src/vs/workbench/services/themes/common/themeConfiguration.ts index 36d32cc4..102b4307 100644 --- a/src/vs/workbench/services/themes/common/themeConfiguration.ts +++ b/src/vs/workbench/services/themes/common/themeConfiguration.ts @@ -70,7 +70,7 @@ const preferredHCThemeSettingSchema: IConfigurationPropertySchema = { }; const detectColorSchemeSettingSchema: IConfigurationPropertySchema = { type: 'boolean', - description: nls.localize('detectColorScheme', 'If set, automatically switch to the preferred color theme based on the OS appearance.'), + markdownDescription: nls.localize('detectColorScheme', 'If set, automatically switch to the preferred color theme based on the OS appearance. If the OS appearance is dark, the theme specified at `#{0}#` is used, for light `#{1}#`', ThemeSettings.PREFERRED_DARK_THEME, ThemeSettings.PREFERRED_LIGHT_THEME), default: false }; @@ -106,7 +106,7 @@ const productIconThemeSettingSchema: IConfigurationPropertySchema = { const detectHCSchemeSettingSchema: IConfigurationPropertySchema = { type: 'boolean', default: true, - description: nls.localize('autoDetectHighContrast', "If enabled, will automatically change to high contrast theme if the OS is using a high contrast theme."), + markdownDescription: nls.localize('autoDetectHighContrast', "If enabled, will automatically change to high contrast theme if the OS is using a high contrast theme. The high contrast theme to use is specified by `#{0}#`", ThemeSettings.PREFERRED_HC_THEME), scope: ConfigurationScope.APPLICATION }; diff --git a/src/vs/workbench/services/themes/test/electron-browser/tokenStyleResolving.test.ts b/src/vs/workbench/services/themes/test/electron-browser/tokenStyleResolving.test.ts index 581739b7..120daf55 100644 --- a/src/vs/workbench/services/themes/test/electron-browser/tokenStyleResolving.test.ts +++ b/src/vs/workbench/services/themes/test/electron-browser/tokenStyleResolving.test.ts @@ -42,12 +42,12 @@ function tokenStyleAsString(ts: TokenStyle | undefined | null) { } function assertTokenStyle(actual: TokenStyle | undefined | null, expected: TokenStyle | undefined | null, message?: string) { - assert.equal(tokenStyleAsString(actual), tokenStyleAsString(expected), message); + assert.strictEqual(tokenStyleAsString(actual), tokenStyleAsString(expected), message); } function assertTokenStyleMetaData(colorIndex: string[], actual: ITokenStyle | undefined, expected: TokenStyle | undefined | null, message = '') { if (expected === undefined || expected === null || actual === undefined) { - assert.equal(actual, expected, message); + assert.strictEqual(actual, expected, message); return; } assert.strictEqual(actual.bold, expected.bold, 'bold ' + message); @@ -56,9 +56,9 @@ function assertTokenStyleMetaData(colorIndex: string[], actual: ITokenStyle | un const actualForegroundIndex = actual.foreground; if (actualForegroundIndex && expected.foreground) { - assert.equal(colorIndex[actualForegroundIndex], Color.Format.CSS.formatHexA(expected.foreground, true).toUpperCase(), 'foreground ' + message); + assert.strictEqual(colorIndex[actualForegroundIndex], Color.Format.CSS.formatHexA(expected.foreground, true).toUpperCase(), 'foreground ' + message); } else { - assert.equal(actualForegroundIndex, expected.foreground || 0, 'foreground ' + message); + assert.strictEqual(actualForegroundIndex, expected.foreground || 0, 'foreground ' + message); } } @@ -77,21 +77,22 @@ function assertTokenStyles(themeData: ColorThemeData, expected: { [qualifiedClas } suite('Themes - TokenStyleResolving', () => { - - const fileService = new FileService(new NullLogService()); const extensionResourceLoaderService = new ExtensionResourceLoaderService(fileService); const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService()); fileService.registerProvider(Schemas.file, diskFileSystemProvider); + teardown(() => { + diskFileSystemProvider.dispose(); + }); test('color defaults', async () => { const themeData = ColorThemeData.createUnloadedTheme('foo'); themeData.location = FileAccess.asFileUri('./color-theme.json', require); await themeData.ensureLoaded(extensionResourceLoaderService); - assert.equal(themeData.isLoaded, true); + assert.strictEqual(themeData.isLoaded, true); assertTokenStyles(themeData, { 'comment': ts('#000000', undefinedStyle), @@ -335,7 +336,7 @@ suite('Themes - TokenStyleResolving', () => { registry.deregisterTokenStyleDefault(registry.parseTokenSelector('type', 'typescript1')); registry.deregisterTokenStyleDefault(registry.parseTokenSelector('type:javascript1')); - assert.equal(registry.getTokenStylingDefaultRules().length, numberOfDefaultRules); + assert.strictEqual(registry.getTokenStylingDefaultRules().length, numberOfDefaultRules); } }); }); diff --git a/src/vs/workbench/services/timer/browser/timerService.ts b/src/vs/workbench/services/timer/browser/timerService.ts index 534cc553..27eb526d 100644 --- a/src/vs/workbench/services/timer/browser/timerService.ts +++ b/src/vs/workbench/services/timer/browser/timerService.ts @@ -15,6 +15,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { Barrier } from 'vs/base/common/async'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; /* __GDPR__FRAGMENT__ "IMemoryInfo" : { @@ -41,22 +42,27 @@ export interface IMemoryInfo { "panelId": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, "editorIds": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, "timers.ellapsedAppReady" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, + "timers.ellapsedNlsGeneration" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, + "timers.ellapsedLoadMainBundle" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, + "timers.ellapsedCrashReporter" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, + "timers.ellapsedMainServer" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, + "timers.ellapsedWindowCreate" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, "timers.ellapsedWindowLoad" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, "timers.ellapsedWindowLoadToRequire" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, - "timers.ellapsedExtensions" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, - "timers.ellapsedExtensionsReady" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, - "timers.ellapsedRequire" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, + "timers.ellapsedWaitForWindowConfig" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, + "timers.ellapsedWaitForShellEnv" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, "timers.ellapsedStorageInit" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, - "timers.ellapsedSharedProcesConnected" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, "timers.ellapsedWorkspaceServiceInit" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, + "timers.ellapsedSharedProcesConnected" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, "timers.ellapsedRequiredUserDataInit" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, "timers.ellapsedOtherUserDataInit" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, + "timers.ellapsedRequire" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, + "timers.ellapsedExtensions" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, + "timers.ellapsedExtensionsReady" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, "timers.ellapsedViewletRestore" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, "timers.ellapsedPanelRestore" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, "timers.ellapsedEditorRestore" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, "timers.ellapsedWorkbench" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, - "timers.ellapsedNlsGeneration" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, - "timers.ellapsedWaitForShellEnv" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, "platform" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, "release" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, "arch" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, @@ -176,6 +182,54 @@ export interface IStartupMetrics { */ readonly ellapsedLoadMainBundle?: number; + /** + * The time it took to start the crash reporter. + * + * * Happens in the main-process + * * Measured with the `willStartCrashReporter` and `didStartCrashReporter` performance marks. + */ + readonly ellapsedCrashReporter?: number; + + /** + * The time it took to create the main instance server. + * + * * Happens in the main-process + * * Measured with the `willStartMainServer` and `didStartMainServer` performance marks. + */ + readonly ellapsedMainServer?: number; + + /** + * The time it took to create the window. + * + * * Happens in the main-process + * * Measured with the `willCreateCodeWindow` and `didCreateCodeWindow` performance marks. + */ + readonly ellapsedWindowCreate?: number; + + /** + * The time it took to create the electron browser window. + * + * * Happens in the main-process + * * Measured with the `willCreateCodeBrowserWindow` and `didCreateCodeBrowserWindow` performance marks. + */ + readonly ellapsedBrowserWindowCreate?: number; + + /** + * The time it took to restore and validate window state. + * + * * Happens in the main-process + * * Measured with the `willRestoreCodeWindowState` and `didRestoreCodeWindowState` performance marks. + */ + readonly ellapsedWindowRestoreState?: number; + + /** + * The time it took to maximize/show the window. + * + * * Happens in the main-process + * * Measured with the `willMaximizeCodeWindow` and `didMaximizeCodeWindow` performance marks. + */ + readonly ellapsedWindowMaximize?: number; + /** * The time it took to tell electron to open/restore a renderer (browser window). * @@ -198,6 +252,15 @@ export interface IStartupMetrics { */ readonly ellapsedWindowLoadToRequire: number; + /** + * The time it took to wait for resolving the window configuration. This time the workbench + * will not continue to load and be blocked entirely. + * + * * Happens in the renderer-process + * * Measured with the `willWaitForWindowConfig` and `didWaitForWindowConfig` performance marks. + */ + readonly ellapsedWaitForWindowConfig: number; + /** * The time it took to wait for resolving the shell environment. This time the workbench * will not continue to load and be blocked entirely. @@ -295,8 +358,8 @@ export interface IStartupMetrics { readonly ellapsedPanelRestore: number; /** - * The time it took to restore editors - that is text editor and complex editor likes the settings UI - * or webviews (markdown preview). + * The time it took to restore and fully resolve visible editors - that is text editor + * and complex editor likes the settings UI or webviews (markdown preview). * * * Happens in the renderer-process * * Measured with the `willRestoreEditors` and `didRestoreEditors` performance marks. @@ -420,10 +483,12 @@ export abstract class AbstractTimerService implements ITimerService { @IEditorService private readonly _editorService: IEditorService, @IAccessibilityService private readonly _accessibilityService: IAccessibilityService, @ITelemetryService private readonly _telemetryService: ITelemetryService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService ) { Promise.all([ - this._extensionService.whenInstalledExtensionsRegistered(), - _lifecycleService.when(LifecyclePhase.Restored) + this._extensionService.whenInstalledExtensionsRegistered(), // extensions registered + _lifecycleService.when(LifecyclePhase.Restored), // workbench created and parts restored + layoutService.whenRestored // layout restored (including visible editors resolved) ]).then(() => { // set perf mark from renderer this.setPerformanceMarks('renderer', perf.getMarks()); @@ -510,7 +575,7 @@ export abstract class AbstractTimerService implements ITimerService { windowKind: this._lifecycleService.startupKind, windowCount: await this._getWindowCount(), viewletId: activeViewlet?.getId(), - editorIds: this._editorService.visibleEditors.map(input => input.getTypeId()), + editorIds: this._editorService.visibleEditors.map(input => input.typeId), panelId: activePanel ? activePanel.getId() : undefined, // timers @@ -518,9 +583,16 @@ export abstract class AbstractTimerService implements ITimerService { ellapsedAppReady: initialStartup ? this._marks.getDuration('code/didStartMain', 'code/mainAppReady') : undefined, ellapsedNlsGeneration: initialStartup ? this._marks.getDuration('code/willGenerateNls', 'code/didGenerateNls') : undefined, ellapsedLoadMainBundle: initialStartup ? this._marks.getDuration('code/willLoadMainBundle', 'code/didLoadMainBundle') : undefined, + ellapsedCrashReporter: initialStartup ? this._marks.getDuration('code/willStartCrashReporter', 'code/didStartCrashReporter') : undefined, + ellapsedMainServer: initialStartup ? this._marks.getDuration('code/willStartMainServer', 'code/didStartMainServer') : undefined, + ellapsedWindowCreate: initialStartup ? this._marks.getDuration('code/willCreateCodeWindow', 'code/didCreateCodeWindow') : undefined, + ellapsedWindowRestoreState: initialStartup ? this._marks.getDuration('code/willRestoreCodeWindowState', 'code/didRestoreCodeWindowState') : undefined, + ellapsedBrowserWindowCreate: initialStartup ? this._marks.getDuration('code/willCreateCodeBrowserWindow', 'code/didCreateCodeBrowserWindow') : undefined, + ellapsedWindowMaximize: initialStartup ? this._marks.getDuration('code/willMaximizeCodeWindow', 'code/didMaximizeCodeWindow') : undefined, ellapsedWindowLoad: initialStartup ? this._marks.getDuration('code/mainAppReady', 'code/willOpenNewWindow') : undefined, ellapsedWindowLoadToRequire: this._marks.getDuration('code/willOpenNewWindow', 'code/willLoadWorkbenchMain'), ellapsedRequire: this._marks.getDuration('code/willLoadWorkbenchMain', 'code/didLoadWorkbenchMain'), + ellapsedWaitForWindowConfig: this._marks.getDuration('code/willWaitForWindowConfig', 'code/didWaitForWindowConfig'), ellapsedWaitForShellEnv: this._marks.getDuration('code/willWaitForShellEnv', 'code/didWaitForShellEnv'), ellapsedStorageInit: this._marks.getDuration('code/willInitStorage', 'code/didInitStorage'), ellapsedSharedProcesConnected: this._marks.getDuration('code/willConnectSharedProcess', 'code/didConnectSharedProcess'), diff --git a/src/vs/workbench/services/timer/electron-sandbox/timerService.ts b/src/vs/workbench/services/timer/electron-sandbox/timerService.ts index 71132fb2..3d71af64 100644 --- a/src/vs/workbench/services/timer/electron-sandbox/timerService.ts +++ b/src/vs/workbench/services/timer/electron-sandbox/timerService.ts @@ -17,6 +17,7 @@ import { IStartupMetrics, AbstractTimerService, Writeable, ITimerService } from import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { process } from 'vs/base/parts/sandbox/electron-sandbox/globals'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; export class TimerService extends AbstractTimerService { @@ -32,8 +33,9 @@ export class TimerService extends AbstractTimerService { @IEditorService editorService: IEditorService, @IAccessibilityService accessibilityService: IAccessibilityService, @ITelemetryService telemetryService: ITelemetryService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService ) { - super(lifecycleService, contextService, extensionService, updateService, viewletService, panelService, editorService, accessibilityService, telemetryService); + super(lifecycleService, contextService, extensionService, updateService, viewletService, panelService, editorService, accessibilityService, telemetryService, layoutService); this.setPerformanceMarks('main', _environmentService.configuration.perfMarks); } diff --git a/src/vs/workbench/services/untitled/common/untitledTextEditorInput.ts b/src/vs/workbench/services/untitled/common/untitledTextEditorInput.ts index 6e194ecd..21eb8784 100644 --- a/src/vs/workbench/services/untitled/common/untitledTextEditorInput.ts +++ b/src/vs/workbench/services/untitled/common/untitledTextEditorInput.ts @@ -3,12 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IEncodingSupport, EncodingMode, Verbosity, IModeSupport } from 'vs/workbench/common/editor'; +import { Verbosity } from 'vs/workbench/common/editor'; import { AbstractTextResourceEditorInput } from 'vs/workbench/common/editor/textResourceEditorInput'; import { IUntitledTextEditorModel } from 'vs/workbench/services/untitled/common/untitledTextEditorModel'; -import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { EncodingMode, IEncodingSupport, IModeSupport, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { ILabelService } from 'vs/platform/label/common/label'; -import { IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IFileService } from 'vs/platform/files/common/files'; @@ -22,7 +21,11 @@ export class UntitledTextEditorInput extends AbstractTextResourceEditorInput imp static readonly ID: string = 'workbench.editors.untitledEditorInput'; - private modelResolve: Promise | undefined = undefined; + override get typeId(): string { + return UntitledTextEditorInput.ID; + } + + private modelResolve: Promise | undefined = undefined; constructor( public readonly model: IUntitledTextEditorModel, @@ -48,15 +51,11 @@ export class UntitledTextEditorInput extends AbstractTextResourceEditorInput imp this._register(model.onDidRevert(() => this.dispose())); } - getTypeId(): string { - return UntitledTextEditorInput.ID; - } - - getName(): string { + override getName(): string { return this.model.name; } - getDescription(verbosity: Verbosity = Verbosity.MEDIUM): string | undefined { + override getDescription(verbosity: Verbosity = Verbosity.MEDIUM): string | undefined { // Without associated path: only use if name and description differ if (!this.model.hasAssociatedFilePath) { @@ -72,7 +71,7 @@ export class UntitledTextEditorInput extends AbstractTextResourceEditorInput imp return super.getDescription(verbosity); } - getTitle(verbosity: Verbosity): string { + override getTitle(verbosity: Verbosity): string { // Without associated path: check if name and description differ to decide // if description should appear besides the name to distinguish better @@ -90,7 +89,7 @@ export class UntitledTextEditorInput extends AbstractTextResourceEditorInput imp return super.getTitle(verbosity); } - isDirty(): boolean { + override isDirty(): boolean { return this.model.isDirty(); } @@ -98,8 +97,8 @@ export class UntitledTextEditorInput extends AbstractTextResourceEditorInput imp return this.model.getEncoding(); } - setEncoding(encoding: string, mode: EncodingMode /* ignored, we only have Encode */): void { - this.model.setEncoding(encoding); + setEncoding(encoding: string, mode: EncodingMode /* ignored, we only have Encode */): Promise { + return this.model.setEncoding(encoding); } setMode(mode: string): void { @@ -110,19 +109,17 @@ export class UntitledTextEditorInput extends AbstractTextResourceEditorInput imp return this.model.getMode(); } - resolve(): Promise { - - // Join a model resolve if we have had one before - if (this.modelResolve) { - return this.modelResolve; + override async resolve(): Promise { + if (!this.modelResolve) { + this.modelResolve = this.model.resolve(); } - this.modelResolve = this.model.load(); + await this.modelResolve; - return this.modelResolve; + return this.model; } - matches(otherInput: unknown): boolean { + override matches(otherInput: unknown): boolean { if (otherInput === this) { return true; } @@ -134,7 +131,7 @@ export class UntitledTextEditorInput extends AbstractTextResourceEditorInput imp return false; } - dispose(): void { + override dispose(): void { this.modelResolve = undefined; super.dispose(); diff --git a/src/vs/workbench/services/untitled/common/untitledTextEditorModel.ts b/src/vs/workbench/services/untitled/common/untitledTextEditorModel.ts index 63044396..73298524 100644 --- a/src/vs/workbench/services/untitled/common/untitledTextEditorModel.ts +++ b/src/vs/workbench/services/untitled/common/untitledTextEditorModel.ts @@ -3,19 +3,20 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IEncodingSupport, ISaveOptions, IModeSupport } from 'vs/workbench/common/editor'; +import { ISaveOptions } from 'vs/workbench/common/editor'; import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel'; import { URI } from 'vs/base/common/uri'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IModelService } from 'vs/editor/common/services/modelService'; import { Event, Emitter } from 'vs/base/common/event'; -import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; +import { IWorkingCopyBackupService } from 'vs/workbench/services/workingCopy/common/workingCopyBackup'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService'; -import { ITextBufferFactory, ITextModel } from 'vs/editor/common/model'; -import { createTextBufferFactory } from 'vs/editor/common/model/textModel'; -import { IResolvedTextEditorModel, ITextEditorModel } from 'vs/editor/common/services/resolverService'; -import { IWorkingCopyService, IWorkingCopy, WorkingCopyCapabilities, IWorkingCopyBackup } from 'vs/workbench/services/workingCopy/common/workingCopyService'; -import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextModel } from 'vs/editor/common/model'; +import { createTextBufferFactory, createTextBufferFactoryFromStream } from 'vs/editor/common/model/textModel'; +import { ITextEditorModel } from 'vs/editor/common/services/resolverService'; +import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { IWorkingCopy, WorkingCopyCapabilities, IWorkingCopyBackup, NO_TYPE_ID } from 'vs/workbench/services/workingCopy/common/workingCopy'; +import { IEncodingSupport, IModeSupport, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IModelContentChangedEvent } from 'vs/editor/common/model/textModelEvents'; import { withNullAsUndefined, assertIsDefined } from 'vs/base/common/types'; import { ILabelService } from 'vs/platform/label/common/label'; @@ -23,6 +24,8 @@ import { ensureValidWordDefinition } from 'vs/editor/common/model/wordHelper'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { CancellationToken } from 'vs/base/common/cancellation'; import { getCharContainingOffset } from 'vs/base/common/strings'; +import { UTF8 } from 'vs/workbench/services/textfile/common/encoding'; +import { bufferToStream, VSBuffer, VSBufferReadableStream } from 'vs/base/common/buffer'; export interface IUntitledTextEditorModel extends ITextEditorModel, IModeSupport, IEncodingSupport, IWorkingCopy { @@ -54,18 +57,18 @@ export interface IUntitledTextEditorModel extends ITextEditorModel, IModeSupport /** * Sets the encoding to use for this untitled model. */ - setEncoding(encoding: string): void; + setEncoding(encoding: string): Promise; /** - * Load the untitled model. + * Resolves the untitled model. */ - load(): Promise; + resolve(): Promise; /** * Updates the value of the untitled model optionally allowing to ignore dirty. * The model must be resolved for this method to work. */ - setValue(this: IResolvedTextEditorModel, value: string, ignoreDirty?: boolean): void; + setValue(value: string, ignoreDirty?: boolean): void; } export class UntitledTextEditorModel extends BaseTextEditorModel implements IUntitledTextEditorModel { @@ -73,6 +76,8 @@ export class UntitledTextEditorModel extends BaseTextEditorModel implements IUnt private static readonly FIRST_LINE_NAME_MAX_LENGTH = 40; private static readonly FIRST_LINE_NAME_CANDIDATE_MAX_LENGTH = UntitledTextEditorModel.FIRST_LINE_NAME_MAX_LENGTH * 10; + //#region Events + private readonly _onDidChangeContent = this._register(new Emitter()); readonly onDidChangeContent = this._onDidChangeContent.event; @@ -88,6 +93,10 @@ export class UntitledTextEditorModel extends BaseTextEditorModel implements IUnt private readonly _onDidRevert = this._register(new Emitter()); readonly onDidRevert = this._onDidRevert.event; + //#endregion + + readonly typeId = NO_TYPE_ID; // IMPORTANT: never change this to not break existing assumptions (e.g. backups) + readonly capabilities = WorkingCopyCapabilities.Untitled; private cachedModelFirstLineWords: string | undefined = undefined; @@ -119,7 +128,7 @@ export class UntitledTextEditorModel extends BaseTextEditorModel implements IUnt private preferredEncoding: string | undefined, @IModeService modeService: IModeService, @IModelService modelService: IModelService, - @IBackupFileService private readonly backupFileService: IBackupFileService, + @IWorkingCopyBackupService private readonly workingCopyBackupService: IWorkingCopyBackupService, @ITextResourceConfigurationService private readonly textResourceConfigurationService: ITextResourceConfigurationService, @IWorkingCopyService private readonly workingCopyService: IWorkingCopyService, @ITextFileService private readonly textFileService: ITextFileService, @@ -177,7 +186,7 @@ export class UntitledTextEditorModel extends BaseTextEditorModel implements IUnt private _hasModeSetExplicitly: boolean = false; get hasModeSetExplicitly(): boolean { return this._hasModeSetExplicitly; } - setMode(mode: string): void { + override setMode(mode: string): void { // Remember that an explicit mode was set this._hasModeSetExplicitly = true; @@ -199,7 +208,7 @@ export class UntitledTextEditorModel extends BaseTextEditorModel implements IUnt } } - getMode(): string | undefined { + override getMode(): string | undefined { if (this.textEditorModel) { return this.textEditorModel.getModeId(); } @@ -211,7 +220,7 @@ export class UntitledTextEditorModel extends BaseTextEditorModel implements IUnt return this.preferredEncoding || this.configuredEncoding; } - setEncoding(encoding: string): void { + async setEncoding(encoding: string): Promise { const oldEncoding = this.getEncoding(); this.preferredEncoding = encoding; @@ -233,10 +242,13 @@ export class UntitledTextEditorModel extends BaseTextEditorModel implements IUnt } } - isReadonly(): boolean { + override isReadonly(): boolean { return false; } + + //#region Dirty + isDirty(): boolean { return this.dirty; } @@ -250,6 +262,11 @@ export class UntitledTextEditorModel extends BaseTextEditorModel implements IUnt this._onDidChangeDirty.fire(); } + //#endregion + + + //#region Save / Revert / Backup + async save(options?: ISaveOptions): Promise { const target = await this.textFileService.save(this.resource, options); @@ -269,25 +286,44 @@ export class UntitledTextEditorModel extends BaseTextEditorModel implements IUnt } async backup(token: CancellationToken): Promise { - return { content: withNullAsUndefined(this.createSnapshot()) }; + + // Fill in content the same way we would do when + // saving the file via the text file service + // encoding support (hardcode UTF-8) + const content = await this.textFileService.getEncodedReadable(this.resource, withNullAsUndefined(this.createSnapshot()), { encoding: UTF8 }); + + return { content }; } - async load(): Promise { + //#endregion - // Check for backups - const backup = await this.backupFileService.resolve(this.resource); - let untitledContents: ITextBufferFactory; - if (backup) { - untitledContents = backup.value; - } else { - untitledContents = createTextBufferFactory(this.initialValue || ''); - } + //#region Resolve + + override async resolve(): Promise { // Create text editor model if not yet done let createdUntitledModel = false; + let hasBackup = false; if (!this.textEditorModel) { - this.createTextEditorModel(untitledContents, this.resource, this.preferredMode); + let untitledContents: VSBufferReadableStream; + + // Check for backups or use initial value or empty + const backup = await this.workingCopyBackupService.resolve(this); + if (backup) { + untitledContents = backup.value; + hasBackup = true; + } else { + untitledContents = bufferToStream(VSBuffer.fromString(this.initialValue || '')); + } + + // Determine untitled contents based on backup + // or initial value. We must use text file service + // to create the text factory to respect encodings + // accordingly. + const untitledContentsFactory = await createTextBufferFactoryFromStream(await this.textFileService.getDecodedStream(this.resource, untitledContents, { encoding: UTF8 })); + + this.createTextEditorModel(untitledContentsFactory, this.resource, this.preferredMode); createdUntitledModel = true; } @@ -308,21 +344,19 @@ export class UntitledTextEditorModel extends BaseTextEditorModel implements IUnt if (createdUntitledModel) { // Name - if (backup || this.initialValue) { + if (hasBackup || this.initialValue) { this.updateNameFromFirstLine(textEditorModel); } // Untitled associated to file path are dirty right away as well as untitled with content - this.setDirty(this.hasAssociatedFilePath || !!backup || !!this.initialValue); + this.setDirty(this.hasAssociatedFilePath || !!hasBackup || !!this.initialValue); // If we have initial contents, make sure to emit this // as the appropiate events to the outside. - if (backup || this.initialValue) { + if (hasBackup || this.initialValue) { this._onDidChangeContent.fire(); } } - - return this as UntitledTextEditorModel & IResolvedTextEditorModel; } private onModelContentChanged(textEditorModel: ITextModel, e: IModelContentChangedEvent): void { @@ -385,4 +419,6 @@ export class UntitledTextEditorModel extends BaseTextEditorModel implements IUnt this._onDidChangeName.fire(); } } + + //#endregion } diff --git a/src/vs/workbench/services/untitled/common/untitledTextEditorService.ts b/src/vs/workbench/services/untitled/common/untitledTextEditorService.ts index 0180d8ee..7c9e2dca 100644 --- a/src/vs/workbench/services/untitled/common/untitledTextEditorService.ts +++ b/src/vs/workbench/services/untitled/common/untitledTextEditorService.ts @@ -13,7 +13,6 @@ import { ResourceMap } from 'vs/base/common/map'; import { Schemas } from 'vs/base/common/network'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService'; export const IUntitledTextEditorService = createDecorator('untitledTextEditorService'); @@ -80,9 +79,9 @@ export interface IUntitledTextEditorModelManager { readonly onDidChangeLabel: Event; /** - * Events for when untitled text editors are disposed. + * Events for when untitled text editors are about to be disposed. */ - readonly onDidDispose: Event; + readonly onWillDispose: Event; /** * Creates a new untitled editor model with the provided options. If the `untitledResource` @@ -110,9 +109,9 @@ export interface IUntitledTextEditorModelManager { * property is provided and the untitled editor exists, it will return that existing * instance instead of creating a new one. */ - resolve(options?: INewUntitledTextEditorOptions): Promise; - resolve(options?: INewUntitledTextEditorWithAssociatedResourceOptions): Promise; - resolve(options?: IExistingUntitledTextEditorOptions): Promise; + resolve(options?: INewUntitledTextEditorOptions): Promise; + resolve(options?: INewUntitledTextEditorWithAssociatedResourceOptions): Promise; + resolve(options?: IExistingUntitledTextEditorOptions): Promise; } export interface IUntitledTextEditorService extends IUntitledTextEditorModelManager { @@ -130,8 +129,8 @@ export class UntitledTextEditorService extends Disposable implements IUntitledTe private readonly _onDidChangeEncoding = this._register(new Emitter()); readonly onDidChangeEncoding = this._onDidChangeEncoding.event; - private readonly _onDidDispose = this._register(new Emitter()); - readonly onDidDispose = this._onDidDispose.event; + private readonly _onWillDispose = this._register(new Emitter()); + readonly onWillDispose = this._onWillDispose.event; private readonly _onDidChangeLabel = this._register(new Emitter()); readonly onDidChangeLabel = this._onDidChangeLabel.event; @@ -153,8 +152,11 @@ export class UntitledTextEditorService extends Disposable implements IUntitledTe return this.get(resource)?.textEditorModel?.getValue(); } - resolve(options?: IInternalUntitledTextEditorOptions): Promise { - return this.doCreateOrGet(options).load(); + async resolve(options?: IInternalUntitledTextEditorOptions): Promise { + const model = this.doCreateOrGet(options); + await model.resolve(); + + return model; } create(options?: IInternalUntitledTextEditorOptions): UntitledTextEditorModel { @@ -236,10 +238,10 @@ export class UntitledTextEditorService extends Disposable implements IUntitledTe modelListeners.add(model.onDidChangeDirty(() => this._onDidChangeDirty.fire(model))); modelListeners.add(model.onDidChangeName(() => this._onDidChangeLabel.fire(model))); modelListeners.add(model.onDidChangeEncoding(() => this._onDidChangeEncoding.fire(model))); - modelListeners.add(model.onDispose(() => this._onDidDispose.fire(model))); + modelListeners.add(model.onWillDispose(() => this._onWillDispose.fire(model))); // Remove from cache on dispose - Event.once(model.onDispose)(() => { + Event.once(model.onWillDispose)(() => { // Registry this.mapResourceToModel.delete(model.resource); diff --git a/src/vs/workbench/services/untitled/test/browser/untitledTextEditor.test.ts b/src/vs/workbench/services/untitled/test/browser/untitledTextEditor.test.ts index fa16a78c..e3abf193 100644 --- a/src/vs/workbench/services/untitled/test/browser/untitledTextEditor.test.ts +++ b/src/vs/workbench/services/untitled/test/browser/untitledTextEditor.test.ts @@ -15,6 +15,7 @@ import { IIdentifiedSingleEditOperation } from 'vs/editor/common/model'; import { Range } from 'vs/editor/common/core/range'; import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; import { IUntitledTextEditorModel } from 'vs/workbench/services/untitled/common/untitledTextEditorModel'; +import { CancellationToken } from 'vs/base/common/cancellation'; suite('Untitled text editors', () => { @@ -62,7 +63,7 @@ suite('Untitled text editors', () => { const resourcePromise = awaitDidChangeDirty(accessor.untitledTextEditorService); - model.textEditorModel.setValue('foo bar'); + model.textEditorModel?.setValue('foo bar'); const resource = await resourcePromise; @@ -147,12 +148,12 @@ suite('Untitled text editors', () => { // dirty const model = await input.resolve(); - model.textEditorModel.setValue('foo bar'); + model.textEditorModel?.setValue('foo bar'); assert.ok(model.isDirty()); - assert.ok(workingCopyService.isDirty(model.resource)); - model.textEditorModel.setValue(''); + assert.ok(workingCopyService.isDirty(model.resource, model.typeId)); + model.textEditorModel?.setValue(''); assert.ok(!model.isDirty()); - assert.ok(!workingCopyService.isDirty(model.resource)); + assert.ok(!workingCopyService.isDirty(model.resource, model.typeId)); input.dispose(); model.dispose(); }); @@ -196,9 +197,9 @@ suite('Untitled text editors', () => { // dirty const model = await input.resolve(); - model.textEditorModel.setValue('foo bar'); + model.textEditorModel?.setValue('foo bar'); assert.ok(model.isDirty()); - model.textEditorModel.setValue(''); + model.textEditorModel?.setValue(''); assert.ok(model.isDirty()); input.dispose(); model.dispose(); @@ -326,7 +327,7 @@ suite('Untitled text editors', () => { // encoding const model = await input.resolve(); - model.setEncoding('utf16'); + await model.setEncoding('utf16'); assert.strictEqual(counter, 1); input.dispose(); model.dispose(); @@ -345,19 +346,19 @@ suite('Untitled text editors', () => { // label const model = await input.resolve(); - model.textEditorModel.setValue('Foo Bar'); + model.textEditorModel?.setValue('Foo Bar'); assert.strictEqual(counter, 1); input.dispose(); model.dispose(); }); - test('service#onDidDisposeModel', async () => { + test('service#onWillDispose', async () => { const service = accessor.untitledTextEditorService; const input = instantiationService.createInstance(UntitledTextEditorInput, service.create()); let counter = 0; - service.onDidDispose(model => { + service.onWillDispose(model => { counter++; assert.strictEqual(model.resource.toString(), input.resource.toString()); }); @@ -391,16 +392,16 @@ suite('Untitled text editors', () => { const model = await input.resolve(); model.onDidChangeContent(() => counter++); - model.textEditorModel.setValue('foo'); + model.textEditorModel?.setValue('foo'); assert.strictEqual(counter, 1, 'Dirty model should trigger event'); - model.textEditorModel.setValue('bar'); + model.textEditorModel?.setValue('bar'); assert.strictEqual(counter, 2, 'Content change when dirty should trigger event'); - model.textEditorModel.setValue(''); + model.textEditorModel?.setValue(''); assert.strictEqual(counter, 3, 'Manual revert should trigger event'); - model.textEditorModel.setValue('foo'); + model.textEditorModel?.setValue('foo'); assert.strictEqual(counter, 4, 'Dirty model should trigger event'); @@ -417,7 +418,7 @@ suite('Untitled text editors', () => { const model = await input.resolve(); model.onDidRevert(() => counter++); - model.textEditorModel.setValue('foo'); + model.textEditorModel?.setValue('foo'); await model.revert(); @@ -434,43 +435,43 @@ suite('Untitled text editors', () => { let model = await input.resolve(); model.onDidChangeName(() => counter++); - model.textEditorModel.setValue('foo'); + model.textEditorModel?.setValue('foo'); assert.strictEqual(input.getName(), 'foo'); assert.strictEqual(model.name, 'foo'); assert.strictEqual(counter, 1); - model.textEditorModel.setValue('bar'); + model.textEditorModel?.setValue('bar'); assert.strictEqual(input.getName(), 'bar'); assert.strictEqual(model.name, 'bar'); assert.strictEqual(counter, 2); - model.textEditorModel.setValue(''); + model.textEditorModel?.setValue(''); assert.strictEqual(input.getName(), 'Untitled-1'); assert.strictEqual(model.name, 'Untitled-1'); - model.textEditorModel.setValue(' '); + model.textEditorModel?.setValue(' '); assert.strictEqual(input.getName(), 'Untitled-1'); assert.strictEqual(model.name, 'Untitled-1'); - model.textEditorModel.setValue('([]}'); // require actual words + model.textEditorModel?.setValue('([]}'); // require actual words assert.strictEqual(input.getName(), 'Untitled-1'); assert.strictEqual(model.name, 'Untitled-1'); - model.textEditorModel.setValue('([]}hello '); // require actual words + model.textEditorModel?.setValue('([]}hello '); // require actual words assert.strictEqual(input.getName(), '([]}hello'); assert.strictEqual(model.name, '([]}hello'); - model.textEditorModel.setValue('12345678901234567890123456789012345678901234567890'); // trimmed at 40chars max + model.textEditorModel?.setValue('12345678901234567890123456789012345678901234567890'); // trimmed at 40chars max assert.strictEqual(input.getName(), '1234567890123456789012345678901234567890'); assert.strictEqual(model.name, '1234567890123456789012345678901234567890'); - model.textEditorModel.setValue('123456789012345678901234567890123456789🌞'); // do not break grapehems (#111235) + model.textEditorModel?.setValue('123456789012345678901234567890123456789🌞'); // do not break grapehems (#111235) assert.strictEqual(input.getName(), '123456789012345678901234567890123456789'); assert.strictEqual(model.name, '123456789012345678901234567890123456789'); assert.strictEqual(counter, 6); - model.textEditorModel.setValue('Hello\nWorld'); + model.textEditorModel?.setValue('Hello\nWorld'); assert.strictEqual(counter, 7); function createSingleEditOp(text: string, positionLineNumber: number, positionColumn: number, selectionLineNumber: number = positionLineNumber, selectionColumn: number = positionColumn): IIdentifiedSingleEditOperation { @@ -489,7 +490,7 @@ suite('Untitled text editors', () => { }; } - model.textEditorModel.applyEdits([createSingleEditOp('hello', 2, 2)]); + model.textEditorModel?.applyEdits([createSingleEditOp('hello', 2, 2)]); assert.strictEqual(counter, 7); // change was not on first line input.dispose(); @@ -513,10 +514,10 @@ suite('Untitled text editors', () => { const model = await input.resolve(); model.onDidChangeDirty(() => counter++); - model.textEditorModel.setValue('foo'); + model.textEditorModel?.setValue('foo'); assert.strictEqual(counter, 1, 'Dirty model should trigger event'); - model.textEditorModel.setValue('bar'); + model.textEditorModel?.setValue('bar'); assert.strictEqual(counter, 1, 'Another change does not fire event'); @@ -533,14 +534,46 @@ suite('Untitled text editors', () => { const model = await input.resolve(); model.onDidChangeEncoding(() => counter++); - model.setEncoding('utf16'); + await model.setEncoding('utf16'); assert.strictEqual(counter, 1, 'Dirty model should trigger event'); - model.setEncoding('utf16'); + await model.setEncoding('utf16'); assert.strictEqual(counter, 1, 'Another change to same encoding does not fire event'); input.dispose(); model.dispose(); }); + + test('backup and restore (simple)', async function () { + return testBackupAndRestore('Some very small file text content.'); + }); + + test('backup and restore (large, #121347)', async function () { + const largeContent = '국어한\n'.repeat(100000); + return testBackupAndRestore(largeContent); + }); + + async function testBackupAndRestore(content: string) { + const service = accessor.untitledTextEditorService; + const originalInput = instantiationService.createInstance(UntitledTextEditorInput, service.create()); + const restoredInput = instantiationService.createInstance(UntitledTextEditorInput, service.create()); + + const originalModel = await originalInput.resolve(); + originalModel.textEditorModel?.setValue(content); + + const backup = await originalModel.backup(CancellationToken.None); + const modelRestoredIdentifier = { typeId: originalModel.typeId, resource: restoredInput.resource }; + await accessor.workingCopyBackupService.backup(modelRestoredIdentifier, backup.content); + + const restoredModel = await restoredInput.resolve(); + + assert.strictEqual(restoredModel.textEditorModel?.getValue(), content); + assert.strictEqual(restoredModel.isDirty(), true); + + originalInput.dispose(); + originalModel.dispose(); + restoredInput.dispose(); + restoredModel.dispose(); + } }); diff --git a/src/vs/workbench/services/update/browser/updateService.ts b/src/vs/workbench/services/update/browser/updateService.ts index 83064efa..6e47235d 100644 --- a/src/vs/workbench/services/update/browser/updateService.ts +++ b/src/vs/workbench/services/update/browser/updateService.ts @@ -44,25 +44,25 @@ export class BrowserUpdateService extends Disposable implements IUpdateService { ) { super(); - this.checkForUpdates(); + this.checkForUpdates(false); } async isLatestVersion(): Promise { - const update = await this.doCheckForUpdates(); + const update = await this.doCheckForUpdates(false); return !!update; } - async checkForUpdates(): Promise { - await this.doCheckForUpdates(); + async checkForUpdates(explicit: boolean): Promise { + await this.doCheckForUpdates(explicit); } - private async doCheckForUpdates(): Promise { + private async doCheckForUpdates(explicit: boolean): Promise { if (this.environmentService.options && this.environmentService.options.updateProvider) { const updateProvider = this.environmentService.options.updateProvider; // State -> Checking for Updates - this.state = State.CheckingForUpdates(null); + this.state = State.CheckingForUpdates(explicit); const update = await updateProvider.checkForUpdate(); if (update) { diff --git a/src/vs/workbench/services/uriIdentity/test/common/uriIdentityService.test.ts b/src/vs/workbench/services/uriIdentity/test/common/uriIdentityService.test.ts index 683fb667..ad03c1f0 100644 --- a/src/vs/workbench/services/uriIdentity/test/common/uriIdentityService.test.ts +++ b/src/vs/workbench/services/uriIdentity/test/common/uriIdentityService.test.ts @@ -14,16 +14,16 @@ suite('URI Identity', function () { class FakeFileService extends mock() { - onDidChangeFileSystemProviderCapabilities = Event.None; - onDidChangeFileSystemProviderRegistrations = Event.None; + override onDidChangeFileSystemProviderCapabilities = Event.None; + override onDidChangeFileSystemProviderRegistrations = Event.None; constructor(readonly data: Map) { super(); } - canHandleResource(uri: URI) { + override canHandleResource(uri: URI) { return this.data.has(uri.scheme); } - hasCapability(uri: URI, flag: FileSystemProviderCapabilities): boolean { + override hasCapability(uri: URI, flag: FileSystemProviderCapabilities): boolean { const mask = this.data.get(uri.scheme) ?? 0; return Boolean(mask & flag); } @@ -40,7 +40,7 @@ suite('URI Identity', function () { function assertCanonical(input: URI, expected: URI, service: UriIdentityService = _service) { const actual = service.asCanonicalUri(input); - assert.equal(actual.toString(), expected.toString()); + assert.strictEqual(actual.toString(), expected.toString()); assert.ok(service.extUri.isEqual(actual, expected)); } @@ -50,11 +50,11 @@ suite('URI Identity', function () { let b = URI.parse('bar://bar/bang'); let b1 = URI.parse('bar://bar/BANG'); - assert.equal(_service.extUri.isEqual(a, a1), true); - assert.equal(_service.extUri.isEqual(a1, a), true); + assert.strictEqual(_service.extUri.isEqual(a, a1), true); + assert.strictEqual(_service.extUri.isEqual(a1, a), true); - assert.equal(_service.extUri.isEqual(b, b1), false); - assert.equal(_service.extUri.isEqual(b1, b), false); + assert.strictEqual(_service.extUri.isEqual(b, b1), false); + assert.strictEqual(_service.extUri.isEqual(b1, b), false); }); test('asCanonicalUri (casing)', function () { diff --git a/src/vs/workbench/services/url/browser/urlService.ts b/src/vs/workbench/services/url/browser/urlService.ts index 9d3ef46d..bb54701d 100644 --- a/src/vs/workbench/services/url/browser/urlService.ts +++ b/src/vs/workbench/services/url/browser/urlService.ts @@ -38,8 +38,6 @@ export interface IURLCallbackProvider { export class BrowserURLService extends AbstractURLService { - declare readonly _serviceBrand: undefined; - private provider: IURLCallbackProvider | undefined; constructor( diff --git a/src/vs/workbench/services/url/electron-sandbox/urlService.ts b/src/vs/workbench/services/url/electron-sandbox/urlService.ts index 79220df3..f1d70dcd 100644 --- a/src/vs/workbench/services/url/electron-sandbox/urlService.ts +++ b/src/vs/workbench/services/url/electron-sandbox/urlService.ts @@ -37,7 +37,7 @@ export class RelayURLService extends NativeURLService implements IURLHandler, IO openerService.registerOpener(this); } - create(options?: Partial): URI { + override create(options?: Partial): URI { const uri = super.create(options); let query = uri.query; @@ -50,7 +50,7 @@ export class RelayURLService extends NativeURLService implements IURLHandler, IO return uri.with({ query }); } - async open(resource: URI | string, options?: IRelayOpenURLOptions): Promise { + override async open(resource: URI | string, options?: IRelayOpenURLOptions): Promise { if (!matchesScheme(resource, this.productService.urlProtocol)) { return false; diff --git a/src/vs/workbench/services/userData/browser/userDataInit.ts b/src/vs/workbench/services/userData/browser/userDataInit.ts index 45a05b80..b34f4dba 100644 --- a/src/vs/workbench/services/userData/browser/userDataInit.ts +++ b/src/vs/workbench/services/userData/browser/userDataInit.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; -import { ExtensionsInitializer } from 'vs/platform/userDataSync/common/extensionsSync'; +import { AbstractExtensionsInitializer, getExtensionStorageState, IExtensionsInitializerPreviewResult, storeExtensionStorageState } from 'vs/platform/userDataSync/common/extensionsSync'; import { GlobalStateInitializer, UserDataSyncStoreTypeSynchronizer } from 'vs/platform/userDataSync/common/globalStateSync'; import { KeybindingsInitializer } from 'vs/platform/userDataSync/common/keybindingsSync'; import { SettingsInitializer } from 'vs/platform/userDataSync/common/settingsSync'; @@ -16,7 +16,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { UserDataSyncStoreClient } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; import { IProductService } from 'vs/platform/product/common/productService'; import { IRequestService } from 'vs/platform/request/common/request'; -import { ISyncExtension, IUserData, IUserDataInitializer, IUserDataSyncLogService, IUserDataSyncStoreClient, IUserDataSyncStoreManagementService, SyncResource } from 'vs/platform/userDataSync/common/userDataSync'; +import { IRemoteUserData, IUserData, IUserDataInitializer, IUserDataSyncLogService, IUserDataSyncStoreClient, IUserDataSyncStoreManagementService, SyncResource } from 'vs/platform/userDataSync/common/userDataSync'; import { AuthenticationSessionInfo, getCurrentAuthenticationSessionInfo } from 'vs/workbench/services/authentication/browser/authenticationService'; import { getSyncAreaLabel } from 'vs/workbench/services/userDataSync/common/userDataSync'; import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions } from 'vs/workbench/common/contributions'; @@ -32,6 +32,7 @@ import { mark } from 'vs/base/common/performance'; import { IIgnoredExtensionsManagementService } from 'vs/platform/userDataSync/common/ignoredExtensions'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { isEqual } from 'vs/base/common/resources'; +import { CancellationToken } from 'vs/base/common/cancellation'; export const IUserDataInitializationService = createDecorator('IUserDataInitializationService'); export interface IUserDataInitializationService { @@ -40,6 +41,7 @@ export interface IUserDataInitializationService { requiresInitialization(): Promise; whenInitializationFinished(): Promise; initializeRequiredResources(): Promise; + initializeInstalledExtensions(instantiationService: IInstantiationService): Promise; initializeOtherResources(instantiationService: IInstantiationService): Promise; } @@ -169,13 +171,64 @@ export class UserDataInitializationService implements IUserDataInitializationSer async initializeOtherResources(instantiationService: IInstantiationService): Promise { try { this.logService.trace(`UserDataInitializationService#initializeOtherResources`); - await this.initialize([SyncResource.Extensions, SyncResource.Keybindings, SyncResource.Snippets], instantiationService); + await Promises.allSettled([this.initialize([SyncResource.Keybindings, SyncResource.Snippets]), this.initializeExtensions(instantiationService)]); } finally { this.initializationFinished.open(); } } - private async initialize(syncResources: SyncResource[], instantiationService?: IInstantiationService): Promise { + private async initializeExtensions(instantiationService: IInstantiationService): Promise { + try { + await Promise.all([this.initializeInstalledExtensions(instantiationService), this.initializeNewExtensions(instantiationService)]); + } finally { + this.initialized.push(SyncResource.Extensions); + } + } + + private initializeInstalledExtensionsPromise: Promise | undefined; + async initializeInstalledExtensions(instantiationService: IInstantiationService): Promise { + if (!this.initializeInstalledExtensionsPromise) { + this.initializeInstalledExtensionsPromise = (async () => { + this.logService.trace(`UserDataInitializationService#initializeInstalledExtensions`); + const extensionsPreviewInitializer = await this.getExtensionsPreviewInitializer(instantiationService); + if (extensionsPreviewInitializer) { + await instantiationService.createInstance(InstalledExtensionsInitializer, extensionsPreviewInitializer).initialize(); + } + })(); + } + return this.initializeInstalledExtensionsPromise; + } + + private initializeNewExtensionsPromise: Promise | undefined; + private async initializeNewExtensions(instantiationService: IInstantiationService): Promise { + if (!this.initializeNewExtensionsPromise) { + this.initializeNewExtensionsPromise = (async () => { + this.logService.trace(`UserDataInitializationService#initializeNewExtensions`); + const extensionsPreviewInitializer = await this.getExtensionsPreviewInitializer(instantiationService); + if (extensionsPreviewInitializer) { + await instantiationService.createInstance(NewExtensionsInitializer, extensionsPreviewInitializer).initialize(); + } + })(); + } + return this.initializeNewExtensionsPromise; + } + + private extensionsPreviewInitializerPromise: Promise | undefined; + private getExtensionsPreviewInitializer(instantiationService: IInstantiationService): Promise { + if (!this.extensionsPreviewInitializerPromise) { + this.extensionsPreviewInitializerPromise = (async () => { + const userDataSyncStoreClient = await this.createUserDataSyncStoreClient(); + if (!userDataSyncStoreClient) { + return null; + } + const userData = await userDataSyncStoreClient.read(SyncResource.Extensions, null); + return instantiationService.createInstance(ExtensionsPreviewInitializer, userData); + })(); + } + return this.extensionsPreviewInitializerPromise; + } + + private async initialize(syncResources: SyncResource[]): Promise { const userDataSyncStoreClient = await this.createUserDataSyncStoreClient(); if (!userDataSyncStoreClient) { return; @@ -189,7 +242,7 @@ export class UserDataInitializationService implements IUserDataInitializationSer } this.initialized.push(syncResource); this.logService.trace(`Initializing ${getSyncAreaLabel(syncResource)}`); - const initializer = this.createSyncResourceInitializer(syncResource, instantiationService); + const initializer = this.createSyncResourceInitializer(syncResource); const userData = await userDataSyncStoreClient.read(syncResource, syncResource === SyncResource.GlobalState ? this.globalStateUserData : null); await initializer.initialize(userData); this.logService.info(`Initialized ${getSyncAreaLabel(syncResource)}`); @@ -200,40 +253,141 @@ export class UserDataInitializationService implements IUserDataInitializationSer })); } - private createSyncResourceInitializer(syncResource: SyncResource, instantiationService?: IInstantiationService): IUserDataInitializer { + private createSyncResourceInitializer(syncResource: SyncResource): IUserDataInitializer { switch (syncResource) { case SyncResource.Settings: return new SettingsInitializer(this.fileService, this.environmentService, this.logService); case SyncResource.Keybindings: return new KeybindingsInitializer(this.fileService, this.environmentService, this.logService); case SyncResource.Snippets: return new SnippetsInitializer(this.fileService, this.environmentService, this.logService); case SyncResource.GlobalState: return new GlobalStateInitializer(this.storageService, this.fileService, this.environmentService, this.logService); - case SyncResource.Extensions: - if (!instantiationService) { - throw new Error('Instantiation Service is required to initialize extension'); - } - return instantiationService.createInstance(WorkbenchExtensionsInitializer); } + throw new Error(`Cannot create initializer for ${syncResource}`); } } -class WorkbenchExtensionsInitializer extends ExtensionsInitializer { +class ExtensionsPreviewInitializer extends AbstractExtensionsInitializer { + + private previewPromise: Promise | undefined; + private preview: IExtensionsInitializerPreviewResult | null = null; constructor( - @IExtensionService private readonly extensionService: IExtensionService, + private readonly extensionsData: IUserData, @IExtensionManagementService extensionManagementService: IExtensionManagementService, - @IExtensionGalleryService galleryService: IExtensionGalleryService, - @IGlobalExtensionEnablementService extensionEnablementService: IGlobalExtensionEnablementService, - @IStorageService storageService: IStorageService, @IIgnoredExtensionsManagementService ignoredExtensionsManagementService: IIgnoredExtensionsManagementService, @IFileService fileService: IFileService, @IEnvironmentService environmentService: IEnvironmentService, @IUserDataSyncLogService logService: IUserDataSyncLogService, ) { - super(extensionManagementService, galleryService, extensionEnablementService, storageService, ignoredExtensionsManagementService, fileService, environmentService, logService); + super(extensionManagementService, ignoredExtensionsManagementService, fileService, environmentService, logService); } - protected async initializeRemoteExtensions(remoteExtensions: ISyncExtension[]): Promise { - const newlyEnabledExtensions = (await super.initializeRemoteExtensions(remoteExtensions)); + getPreview(): Promise { + if (!this.previewPromise) { + this.previewPromise = super.initialize(this.extensionsData).then(() => this.preview); + } + return this.previewPromise; + } + + override initialize(): Promise { + throw new Error('should not be called directly'); + } + + protected override async doInitialize(remoteUserData: IRemoteUserData): Promise { + const remoteExtensions = await this.parseExtensions(remoteUserData); + if (!remoteExtensions) { + this.logService.info('Skipping initializing extensions because remote extensions does not exist.'); + return; + } + const installedExtensions = await this.extensionManagementService.getInstalled(); + this.preview = this.generatePreview(remoteExtensions, installedExtensions); + } +} + +class InstalledExtensionsInitializer implements IUserDataInitializer { + + constructor( + private readonly extensionsPreviewInitializer: ExtensionsPreviewInitializer, + @IGlobalExtensionEnablementService private readonly extensionEnablementService: IGlobalExtensionEnablementService, + @IStorageService private readonly storageService: IStorageService, + @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, + ) { + } + + async initialize(): Promise { + const preview = await this.extensionsPreviewInitializer.getPreview(); + if (!preview) { + return; + } + + // 1. Initialise already installed extensions state + for (const installedExtension of preview.installedExtensions) { + const syncExtension = preview.remoteExtensions.find(({ identifier }) => areSameExtensions(identifier, installedExtension.identifier)); + if (syncExtension?.state) { + const extensionState = getExtensionStorageState(installedExtension.manifest.publisher, installedExtension.manifest.name, this.storageService); + Object.keys(syncExtension.state).forEach(key => extensionState[key] = syncExtension.state![key]); + storeExtensionStorageState(installedExtension.manifest.publisher, installedExtension.manifest.name, extensionState, this.storageService); + } + } + + // 2. Initialise extensions enablement + if (preview.disabledExtensions.length) { + for (const identifier of preview.disabledExtensions) { + this.logService.trace(`Disabling extension...`, identifier.id); + await this.extensionEnablementService.disableExtension(identifier); + this.logService.info(`Disabling extension`, identifier.id); + } + } + } +} + +class NewExtensionsInitializer implements IUserDataInitializer { + + constructor( + private readonly extensionsPreviewInitializer: ExtensionsPreviewInitializer, + @IExtensionService private readonly extensionService: IExtensionService, + @IStorageService private readonly storageService: IStorageService, + @IExtensionGalleryService private readonly galleryService: IExtensionGalleryService, + @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, + @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, + ) { + } + + async initialize(): Promise { + const preview = await this.extensionsPreviewInitializer.getPreview(); + if (!preview) { + return; + } + + const newlyEnabledExtensions: ILocalExtension[] = []; + const uuids: string[] = [], names: string[] = []; + for (const { uuid, id } of preview.newExtensions) { + if (uuid) { + uuids.push(uuid); + } else { + names.push(id); + } + } + const galleryExtensions = (await this.galleryService.query({ ids: uuids, names: names, pageSize: uuids.length + names.length }, CancellationToken.None)).firstPage; + for (const galleryExtension of galleryExtensions) { + try { + const extensionToSync = preview.remoteExtensions.find(({ identifier }) => areSameExtensions(identifier, galleryExtension.identifier)); + if (!extensionToSync) { + continue; + } + if (extensionToSync.state) { + storeExtensionStorageState(galleryExtension.publisher, galleryExtension.name, extensionToSync.state, this.storageService); + } + this.logService.trace(`Installing extension...`, galleryExtension.identifier.id); + const local = await this.extensionManagementService.installFromGallery(galleryExtension, { isMachineScoped: false } /* pass options to prevent install and sync dialog in web */); + if (!preview.disabledExtensions.some(identifier => areSameExtensions(identifier, galleryExtension.identifier))) { + newlyEnabledExtensions.push(local); + } + this.logService.info(`Installed extension.`, galleryExtension.identifier.id); + } catch (error) { + this.logService.error(error); + } + } + const canEnabledExtensions = newlyEnabledExtensions.filter(e => this.extensionService.canAddExtension(toExtensionDescription(e))); if (!(await this.areExtensionsRunning(canEnabledExtensions))) { await new Promise((c, e) => { @@ -249,7 +403,6 @@ class WorkbenchExtensionsInitializer extends ExtensionsInitializer { }); }); } - return newlyEnabledExtensions; } private async areExtensionsRunning(extensions: ILocalExtension[]): Promise { @@ -261,9 +414,10 @@ class WorkbenchExtensionsInitializer extends ExtensionsInitializer { class InitializeOtherResourcesContribution implements IWorkbenchContribution { constructor( @IUserDataInitializationService userDataInitializeService: IUserDataInitializationService, - @IInstantiationService instantiationService: IInstantiationService + @IInstantiationService instantiationService: IInstantiationService, + @IExtensionService extensionService: IExtensionService ) { - this.initializeOtherResource(userDataInitializeService, instantiationService); + extensionService.whenInstalledExtensionsRegistered().then(() => this.initializeOtherResource(userDataInitializeService, instantiationService)); } private async initializeOtherResource(userDataInitializeService: IUserDataInitializationService, instantiationService: IInstantiationService): Promise { diff --git a/src/vs/workbench/services/userData/test/browser/fileUserDataProvider.test.ts b/src/vs/workbench/services/userData/test/browser/fileUserDataProvider.test.ts index 0f30300b..0c8685ce 100644 --- a/src/vs/workbench/services/userData/test/browser/fileUserDataProvider.test.ts +++ b/src/vs/workbench/services/userData/test/browser/fileUserDataProvider.test.ts @@ -25,7 +25,7 @@ class TestWorkbenchEnvironmentService extends BrowserWorkbenchEnvironmentService constructor(private readonly appSettingsHome: URI) { super(Object.create(null), TestProductService); } - get userRoamingDataHome() { return this.appSettingsHome.with({ scheme: Schemas.userData }); } + override get userRoamingDataHome() { return this.appSettingsHome.with({ scheme: Schemas.userData }); } } suite('FileUserDataProvider', () => { @@ -60,7 +60,7 @@ suite('FileUserDataProvider', () => { test('exists return false when file does not exist', async () => { const exists = await testObject.exists(environmentService.settingsResource); - assert.equal(exists, false); + assert.strictEqual(exists, false); }); test('read file throws error if not exist', async () => { @@ -73,39 +73,39 @@ suite('FileUserDataProvider', () => { test('read existing file', async () => { await testObject.writeFile(joinPath(userDataHomeOnDisk, 'settings.json'), VSBuffer.fromString('{}')); const result = await testObject.readFile(environmentService.settingsResource); - assert.equal(result.value, '{}'); + assert.strictEqual(result.value.toString(), '{}'); }); test('create file', async () => { const resource = environmentService.settingsResource; const actual1 = await testObject.createFile(resource, VSBuffer.fromString('{}')); - assert.equal(actual1.resource.toString(), resource.toString()); + assert.strictEqual(actual1.resource.toString(), resource.toString()); const actual2 = await testObject.readFile(joinPath(userDataHomeOnDisk, 'settings.json')); - assert.equal(actual2.value.toString(), '{}'); + assert.strictEqual(actual2.value.toString(), '{}'); }); test('write file creates the file if not exist', async () => { const resource = environmentService.settingsResource; const actual1 = await testObject.writeFile(resource, VSBuffer.fromString('{}')); - assert.equal(actual1.resource.toString(), resource.toString()); + assert.strictEqual(actual1.resource.toString(), resource.toString()); const actual2 = await testObject.readFile(joinPath(userDataHomeOnDisk, 'settings.json')); - assert.equal(actual2.value.toString(), '{}'); + assert.strictEqual(actual2.value.toString(), '{}'); }); test('write to existing file', async () => { const resource = environmentService.settingsResource; await testObject.writeFile(joinPath(userDataHomeOnDisk, 'settings.json'), VSBuffer.fromString('{}')); const actual1 = await testObject.writeFile(resource, VSBuffer.fromString('{a:1}')); - assert.equal(actual1.resource.toString(), resource.toString()); + assert.strictEqual(actual1.resource.toString(), resource.toString()); const actual2 = await testObject.readFile(joinPath(userDataHomeOnDisk, 'settings.json')); - assert.equal(actual2.value.toString(), '{a:1}'); + assert.strictEqual(actual2.value.toString(), '{a:1}'); }); test('delete file', async () => { await testObject.writeFile(joinPath(userDataHomeOnDisk, 'settings.json'), VSBuffer.fromString('')); await testObject.del(environmentService.settingsResource); const result = await testObject.exists(joinPath(userDataHomeOnDisk, 'settings.json')); - assert.equal(false, result); + assert.strictEqual(false, result); }); test('resolve file', async () => { @@ -117,13 +117,13 @@ suite('FileUserDataProvider', () => { test('exists return false for folder that does not exist', async () => { const exists = await testObject.exists(environmentService.snippetsHome); - assert.equal(exists, false); + assert.strictEqual(exists, false); }); test('exists return true for folder that exists', async () => { await testObject.createFolder(joinPath(userDataHomeOnDisk, 'snippets')); const exists = await testObject.exists(environmentService.snippetsHome); - assert.equal(exists, true); + assert.strictEqual(exists, true); }); test('read file throws error for folder', async () => { @@ -139,8 +139,8 @@ suite('FileUserDataProvider', () => { await testObject.writeFile(joinPath(userDataHomeOnDisk, 'snippets', 'settings.json'), VSBuffer.fromString('{}')); const resource = joinPath(environmentService.snippetsHome, 'settings.json'); const actual = await testObject.readFile(resource); - assert.equal(actual.resource.toString(), resource.toString()); - assert.equal(actual.value, '{}'); + assert.strictEqual(actual.resource.toString(), resource.toString()); + assert.strictEqual(actual.value.toString(), '{}'); }); test('read file under sub folder', async () => { @@ -148,42 +148,42 @@ suite('FileUserDataProvider', () => { await testObject.writeFile(joinPath(userDataHomeOnDisk, 'snippets', 'java', 'settings.json'), VSBuffer.fromString('{}')); const resource = joinPath(environmentService.snippetsHome, 'java/settings.json'); const actual = await testObject.readFile(resource); - assert.equal(actual.resource.toString(), resource.toString()); - assert.equal(actual.value, '{}'); + assert.strictEqual(actual.resource.toString(), resource.toString()); + assert.strictEqual(actual.value.toString(), '{}'); }); test('create file under folder that exists', async () => { await testObject.createFolder(joinPath(userDataHomeOnDisk, 'snippets')); const resource = joinPath(environmentService.snippetsHome, 'settings.json'); const actual1 = await testObject.createFile(resource, VSBuffer.fromString('{}')); - assert.equal(actual1.resource.toString(), resource.toString()); + assert.strictEqual(actual1.resource.toString(), resource.toString()); const actual2 = await testObject.readFile(joinPath(userDataHomeOnDisk, 'snippets', 'settings.json')); - assert.equal(actual2.value.toString(), '{}'); + assert.strictEqual(actual2.value.toString(), '{}'); }); test('create file under folder that does not exist', async () => { const resource = joinPath(environmentService.snippetsHome, 'settings.json'); const actual1 = await testObject.createFile(resource, VSBuffer.fromString('{}')); - assert.equal(actual1.resource.toString(), resource.toString()); + assert.strictEqual(actual1.resource.toString(), resource.toString()); const actual2 = await testObject.readFile(joinPath(userDataHomeOnDisk, 'snippets', 'settings.json')); - assert.equal(actual2.value.toString(), '{}'); + assert.strictEqual(actual2.value.toString(), '{}'); }); test('write to not existing file under container that exists', async () => { await testObject.createFolder(joinPath(userDataHomeOnDisk, 'snippets')); const resource = joinPath(environmentService.snippetsHome, 'settings.json'); const actual1 = await testObject.writeFile(resource, VSBuffer.fromString('{}')); - assert.equal(actual1.resource.toString(), resource.toString()); + assert.strictEqual(actual1.resource.toString(), resource.toString()); const actual = await testObject.readFile(joinPath(userDataHomeOnDisk, 'snippets', 'settings.json')); - assert.equal(actual.value.toString(), '{}'); + assert.strictEqual(actual.value.toString(), '{}'); }); test('write to not existing file under container that does not exists', async () => { const resource = joinPath(environmentService.snippetsHome, 'settings.json'); const actual1 = await testObject.writeFile(resource, VSBuffer.fromString('{}')); - assert.equal(actual1.resource.toString(), resource.toString()); + assert.strictEqual(actual1.resource.toString(), resource.toString()); const actual = await testObject.readFile(joinPath(userDataHomeOnDisk, 'snippets', 'settings.json')); - assert.equal(actual.value.toString(), '{}'); + assert.strictEqual(actual.value.toString(), '{}'); }); test('write to existing file under container', async () => { @@ -191,17 +191,17 @@ suite('FileUserDataProvider', () => { await testObject.writeFile(joinPath(userDataHomeOnDisk, 'snippets', 'settings.json'), VSBuffer.fromString('{}')); const resource = joinPath(environmentService.snippetsHome, 'settings.json'); const actual1 = await testObject.writeFile(resource, VSBuffer.fromString('{a:1}')); - assert.equal(actual1.resource.toString(), resource.toString()); + assert.strictEqual(actual1.resource.toString(), resource.toString()); const actual = await testObject.readFile(joinPath(userDataHomeOnDisk, 'snippets', 'settings.json')); - assert.equal(actual.value.toString(), '{a:1}'); + assert.strictEqual(actual.value.toString(), '{a:1}'); }); test('write file under sub container', async () => { const resource = joinPath(environmentService.snippetsHome, 'java/settings.json'); const actual1 = await testObject.writeFile(resource, VSBuffer.fromString('{}')); - assert.equal(actual1.resource.toString(), resource.toString()); + assert.strictEqual(actual1.resource.toString(), resource.toString()); const actual = await testObject.readFile(joinPath(userDataHomeOnDisk, 'snippets', 'java', 'settings.json')); - assert.equal(actual.value.toString(), '{}'); + assert.strictEqual(actual.value.toString(), '{}'); }); test('delete throws error for folder that does not exist', async () => { @@ -231,7 +231,7 @@ suite('FileUserDataProvider', () => { await testObject.writeFile(joinPath(userDataHomeOnDisk, 'snippets', 'settings.json'), VSBuffer.fromString('{}')); await testObject.del(joinPath(environmentService.snippetsHome, 'settings.json')); const exists = await testObject.exists(joinPath(userDataHomeOnDisk, 'snippets', 'settings.json')); - assert.equal(exists, false); + assert.strictEqual(exists, false); }); test('resolve folder', async () => { @@ -240,27 +240,27 @@ suite('FileUserDataProvider', () => { const result = await testObject.resolve(environmentService.snippetsHome); assert.ok(result.isDirectory); assert.ok(result.children !== undefined); - assert.equal(result.children!.length, 1); - assert.equal(result.children![0].resource.toString(), joinPath(environmentService.snippetsHome, 'settings.json').toString()); + assert.strictEqual(result.children!.length, 1); + assert.strictEqual(result.children![0].resource.toString(), joinPath(environmentService.snippetsHome, 'settings.json').toString()); }); test('read backup file', async () => { await testObject.writeFile(joinPath(backupWorkspaceHomeOnDisk, 'backup.json'), VSBuffer.fromString('{}')); const result = await testObject.readFile(joinPath(backupWorkspaceHomeOnDisk.with({ scheme: environmentService.userRoamingDataHome.scheme }), `backup.json`)); - assert.equal(result.value, '{}'); + assert.strictEqual(result.value.toString(), '{}'); }); test('create backup file', async () => { await testObject.createFile(joinPath(backupWorkspaceHomeOnDisk.with({ scheme: environmentService.userRoamingDataHome.scheme }), `backup.json`), VSBuffer.fromString('{}')); const result = await testObject.readFile(joinPath(backupWorkspaceHomeOnDisk, 'backup.json')); - assert.equal(result.value.toString(), '{}'); + assert.strictEqual(result.value.toString(), '{}'); }); test('write backup file', async () => { await testObject.writeFile(joinPath(backupWorkspaceHomeOnDisk, 'backup.json'), VSBuffer.fromString('{}')); await testObject.writeFile(joinPath(backupWorkspaceHomeOnDisk.with({ scheme: environmentService.userRoamingDataHome.scheme }), `backup.json`), VSBuffer.fromString('{a:1}')); const result = await testObject.readFile(joinPath(backupWorkspaceHomeOnDisk, 'backup.json')); - assert.equal(result.value.toString(), '{a:1}'); + assert.strictEqual(result.value.toString(), '{a:1}'); }); test('resolve backups folder', async () => { @@ -268,8 +268,8 @@ suite('FileUserDataProvider', () => { const result = await testObject.resolve(backupWorkspaceHomeOnDisk.with({ scheme: environmentService.userRoamingDataHome.scheme })); assert.ok(result.isDirectory); assert.ok(result.children !== undefined); - assert.equal(result.children!.length, 1); - assert.equal(result.children![0].resource.toString(), joinPath(backupWorkspaceHomeOnDisk.with({ scheme: environmentService.userRoamingDataHome.scheme }), `backup.json`).toString()); + assert.strictEqual(result.children!.length, 1); + assert.strictEqual(result.children![0].resource.toString(), joinPath(backupWorkspaceHomeOnDisk.with({ scheme: environmentService.userRoamingDataHome.scheme }), `backup.json`).toString()); }); }); diff --git a/src/vs/workbench/services/userDataSync/browser/userDataAutoSyncEnablementService.ts b/src/vs/workbench/services/userDataSync/browser/userDataAutoSyncEnablementService.ts index 9684c842..7d4c6ffb 100644 --- a/src/vs/workbench/services/userDataSync/browser/userDataAutoSyncEnablementService.ts +++ b/src/vs/workbench/services/userDataSync/browser/userDataAutoSyncEnablementService.ts @@ -13,11 +13,11 @@ export class WebUserDataAutoSyncEnablementService extends UserDataAutoSyncEnable private get workbenchEnvironmentService(): IWorkbenchEnvironmentService { return this.environmentService; } private enabled: boolean | undefined = undefined; - canToggleEnablement(): boolean { + override canToggleEnablement(): boolean { return this.isTrusted() && super.canToggleEnablement(); } - isEnabled(): boolean { + override isEnabled(): boolean { if (!this.isTrusted()) { return false; } @@ -30,7 +30,7 @@ export class WebUserDataAutoSyncEnablementService extends UserDataAutoSyncEnable return this.enabled; } - setEnablement(enabled: boolean) { + override setEnablement(enabled: boolean) { if (enabled && !this.canToggleEnablement()) { return; } diff --git a/src/vs/workbench/services/userDataSync/browser/userDataSyncResourceEnablementService.ts b/src/vs/workbench/services/userDataSync/browser/userDataSyncResourceEnablementService.ts index 8c361845..203ff7eb 100644 --- a/src/vs/workbench/services/userDataSync/browser/userDataSyncResourceEnablementService.ts +++ b/src/vs/workbench/services/userDataSync/browser/userDataSyncResourceEnablementService.ts @@ -20,7 +20,7 @@ export class UserDataSyncResourceEnablementService extends BaseUserDataSyncResou super(storageService, telemetryService); } - getResourceSyncStateVersion(resource: SyncResource): string | undefined { + override getResourceSyncStateVersion(resource: SyncResource): string | undefined { return resource === SyncResource.Extensions ? this.environmentService.options?.settingsSyncOptions?.extensionsSyncStateVersion : undefined; } diff --git a/src/vs/workbench/services/views/test/browser/viewContainerModel.test.ts b/src/vs/workbench/services/views/test/browser/viewContainerModel.test.ts index e567ef94..5b22e632 100644 --- a/src/vs/workbench/services/views/test/browser/viewContainerModel.test.ts +++ b/src/vs/workbench/services/views/test/browser/viewContainerModel.test.ts @@ -63,7 +63,7 @@ suite('ViewContainerModel', () => { test('empty model', function () { container = ViewContainerRegistry.registerViewContainer({ id: 'test', title: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); const testObject = viewDescriptorService.getViewContainerModel(container); - assert.equal(testObject.visibleViewDescriptors.length, 0); + assert.strictEqual(testObject.visibleViewDescriptors.length, 0); }); test('register/unregister', () => { @@ -71,8 +71,8 @@ suite('ViewContainerModel', () => { const testObject = viewDescriptorService.getViewContainerModel(container); const target = disposableStore.add(new ViewDescriptorSequence(testObject)); - assert.equal(testObject.visibleViewDescriptors.length, 0); - assert.equal(target.elements.length, 0); + assert.strictEqual(testObject.visibleViewDescriptors.length, 0); + assert.strictEqual(target.elements.length, 0); const viewDescriptor: IViewDescriptor = { id: 'view1', @@ -82,23 +82,23 @@ suite('ViewContainerModel', () => { ViewsRegistry.registerViews([viewDescriptor], container); - assert.equal(testObject.visibleViewDescriptors.length, 1); - assert.equal(target.elements.length, 1); - assert.deepEqual(testObject.visibleViewDescriptors[0], viewDescriptor); - assert.deepEqual(target.elements[0], viewDescriptor); + assert.strictEqual(testObject.visibleViewDescriptors.length, 1); + assert.strictEqual(target.elements.length, 1); + assert.deepStrictEqual(testObject.visibleViewDescriptors[0], viewDescriptor); + assert.deepStrictEqual(target.elements[0], viewDescriptor); ViewsRegistry.deregisterViews([viewDescriptor], container); - assert.equal(testObject.visibleViewDescriptors.length, 0); - assert.equal(target.elements.length, 0); + assert.strictEqual(testObject.visibleViewDescriptors.length, 0); + assert.strictEqual(target.elements.length, 0); }); test('when contexts', async function () { container = ViewContainerRegistry.registerViewContainer({ id: 'test', title: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); const testObject = viewDescriptorService.getViewContainerModel(container); const target = disposableStore.add(new ViewDescriptorSequence(testObject)); - assert.equal(testObject.visibleViewDescriptors.length, 0); - assert.equal(target.elements.length, 0); + assert.strictEqual(testObject.visibleViewDescriptors.length, 0); + assert.strictEqual(target.elements.length, 0); const viewDescriptor: IViewDescriptor = { id: 'view1', @@ -108,33 +108,33 @@ suite('ViewContainerModel', () => { }; ViewsRegistry.registerViews([viewDescriptor], container); - assert.equal(testObject.visibleViewDescriptors.length, 0, 'view should not appear since context isnt in'); - assert.equal(target.elements.length, 0); + assert.strictEqual(testObject.visibleViewDescriptors.length, 0, 'view should not appear since context isnt in'); + assert.strictEqual(target.elements.length, 0); const key = contextKeyService.createKey('showview1', false); - assert.equal(testObject.visibleViewDescriptors.length, 0, 'view should still not appear since showview1 isnt true'); - assert.equal(target.elements.length, 0); + assert.strictEqual(testObject.visibleViewDescriptors.length, 0, 'view should still not appear since showview1 isnt true'); + assert.strictEqual(target.elements.length, 0); key.set(true); await new Promise(c => setTimeout(c, 30)); - assert.equal(testObject.visibleViewDescriptors.length, 1, 'view should appear'); - assert.equal(target.elements.length, 1); - assert.deepEqual(testObject.visibleViewDescriptors[0], viewDescriptor); - assert.equal(target.elements[0], viewDescriptor); + assert.strictEqual(testObject.visibleViewDescriptors.length, 1, 'view should appear'); + assert.strictEqual(target.elements.length, 1); + assert.deepStrictEqual(testObject.visibleViewDescriptors[0], viewDescriptor); + assert.strictEqual(target.elements[0], viewDescriptor); key.set(false); await new Promise(c => setTimeout(c, 30)); - assert.equal(testObject.visibleViewDescriptors.length, 0, 'view should disappear'); - assert.equal(target.elements.length, 0); + assert.strictEqual(testObject.visibleViewDescriptors.length, 0, 'view should disappear'); + assert.strictEqual(target.elements.length, 0); ViewsRegistry.deregisterViews([viewDescriptor], container); - assert.equal(testObject.visibleViewDescriptors.length, 0, 'view should not be there anymore'); - assert.equal(target.elements.length, 0); + assert.strictEqual(testObject.visibleViewDescriptors.length, 0, 'view should not be there anymore'); + assert.strictEqual(target.elements.length, 0); key.set(true); await new Promise(c => setTimeout(c, 30)); - assert.equal(testObject.visibleViewDescriptors.length, 0, 'view should not be there anymore'); - assert.equal(target.elements.length, 0); + assert.strictEqual(testObject.visibleViewDescriptors.length, 0, 'view should not be there anymore'); + assert.strictEqual(target.elements.length, 0); }); test('when contexts - multiple', async function () { @@ -145,17 +145,17 @@ suite('ViewContainerModel', () => { const view2: IViewDescriptor = { id: 'view2', ctorDescriptor: null!, name: 'Test View 2', when: ContextKeyExpr.equals('showview2', true) }; ViewsRegistry.registerViews([view1, view2], container); - assert.deepEqual(testObject.visibleViewDescriptors, [view1], 'only view1 should be visible'); - assert.deepEqual(target.elements, [view1], 'only view1 should be visible'); + assert.deepStrictEqual(testObject.visibleViewDescriptors, [view1], 'only view1 should be visible'); + assert.deepStrictEqual(target.elements, [view1], 'only view1 should be visible'); const key = contextKeyService.createKey('showview2', false); - assert.deepEqual(testObject.visibleViewDescriptors, [view1], 'still only view1 should be visible'); - assert.deepEqual(target.elements, [view1], 'still only view1 should be visible'); + assert.deepStrictEqual(testObject.visibleViewDescriptors, [view1], 'still only view1 should be visible'); + assert.deepStrictEqual(target.elements, [view1], 'still only view1 should be visible'); key.set(true); await new Promise(c => setTimeout(c, 30)); - assert.deepEqual(testObject.visibleViewDescriptors, [view1, view2], 'both views should be visible'); - assert.deepEqual(target.elements, [view1, view2], 'both views should be visible'); + assert.deepStrictEqual(testObject.visibleViewDescriptors, [view1, view2], 'both views should be visible'); + assert.deepStrictEqual(target.elements, [view1, view2], 'both views should be visible'); ViewsRegistry.deregisterViews([view1, view2], container); }); @@ -168,17 +168,17 @@ suite('ViewContainerModel', () => { const view2: IViewDescriptor = { id: 'view2', ctorDescriptor: null!, name: 'Test View 2' }; ViewsRegistry.registerViews([view1, view2], container); - assert.deepEqual(testObject.visibleViewDescriptors, [view2], 'only view2 should be visible'); - assert.deepEqual(target.elements, [view2], 'only view2 should be visible'); + assert.deepStrictEqual(testObject.visibleViewDescriptors, [view2], 'only view2 should be visible'); + assert.deepStrictEqual(target.elements, [view2], 'only view2 should be visible'); const key = contextKeyService.createKey('showview1', false); - assert.deepEqual(testObject.visibleViewDescriptors, [view2], 'still only view2 should be visible'); - assert.deepEqual(target.elements, [view2], 'still only view2 should be visible'); + assert.deepStrictEqual(testObject.visibleViewDescriptors, [view2], 'still only view2 should be visible'); + assert.deepStrictEqual(target.elements, [view2], 'still only view2 should be visible'); key.set(true); await new Promise(c => setTimeout(c, 30)); - assert.deepEqual(testObject.visibleViewDescriptors, [view1, view2], 'both views should be visible'); - assert.deepEqual(target.elements, [view1, view2], 'both views should be visible'); + assert.deepStrictEqual(testObject.visibleViewDescriptors, [view1, view2], 'both views should be visible'); + assert.deepStrictEqual(target.elements, [view1, view2], 'both views should be visible'); ViewsRegistry.deregisterViews([view1, view2], container); }); @@ -192,40 +192,40 @@ suite('ViewContainerModel', () => { const view3: IViewDescriptor = { id: 'view3', ctorDescriptor: null!, name: 'Test View 3', canToggleVisibility: true }; ViewsRegistry.registerViews([view1, view2, view3], container); - assert.deepEqual(testObject.visibleViewDescriptors, [view1, view2, view3]); - assert.deepEqual(target.elements, [view1, view2, view3]); + assert.deepStrictEqual(testObject.visibleViewDescriptors, [view1, view2, view3]); + assert.deepStrictEqual(target.elements, [view1, view2, view3]); testObject.setVisible('view2', true); - assert.deepEqual(testObject.visibleViewDescriptors, [view1, view2, view3], 'nothing should happen'); - assert.deepEqual(target.elements, [view1, view2, view3]); + assert.deepStrictEqual(testObject.visibleViewDescriptors, [view1, view2, view3], 'nothing should happen'); + assert.deepStrictEqual(target.elements, [view1, view2, view3]); testObject.setVisible('view2', false); - assert.deepEqual(testObject.visibleViewDescriptors, [view1, view3], 'view2 should hide'); - assert.deepEqual(target.elements, [view1, view3]); + assert.deepStrictEqual(testObject.visibleViewDescriptors, [view1, view3], 'view2 should hide'); + assert.deepStrictEqual(target.elements, [view1, view3]); testObject.setVisible('view1', false); - assert.deepEqual(testObject.visibleViewDescriptors, [view3], 'view1 should hide'); - assert.deepEqual(target.elements, [view3]); + assert.deepStrictEqual(testObject.visibleViewDescriptors, [view3], 'view1 should hide'); + assert.deepStrictEqual(target.elements, [view3]); testObject.setVisible('view3', false); - assert.deepEqual(testObject.visibleViewDescriptors, [], 'view3 shoud hide'); - assert.deepEqual(target.elements, []); + assert.deepStrictEqual(testObject.visibleViewDescriptors, [], 'view3 shoud hide'); + assert.deepStrictEqual(target.elements, []); testObject.setVisible('view1', true); - assert.deepEqual(testObject.visibleViewDescriptors, [view1], 'view1 should show'); - assert.deepEqual(target.elements, [view1]); + assert.deepStrictEqual(testObject.visibleViewDescriptors, [view1], 'view1 should show'); + assert.deepStrictEqual(target.elements, [view1]); testObject.setVisible('view3', true); - assert.deepEqual(testObject.visibleViewDescriptors, [view1, view3], 'view3 should show'); - assert.deepEqual(target.elements, [view1, view3]); + assert.deepStrictEqual(testObject.visibleViewDescriptors, [view1, view3], 'view3 should show'); + assert.deepStrictEqual(target.elements, [view1, view3]); testObject.setVisible('view2', true); - assert.deepEqual(testObject.visibleViewDescriptors, [view1, view2, view3], 'view2 should show'); - assert.deepEqual(target.elements, [view1, view2, view3]); + assert.deepStrictEqual(testObject.visibleViewDescriptors, [view1, view2, view3], 'view2 should show'); + assert.deepStrictEqual(target.elements, [view1, view2, view3]); ViewsRegistry.deregisterViews([view1, view2, view3], container); - assert.deepEqual(testObject.visibleViewDescriptors, []); - assert.deepEqual(target.elements, []); + assert.deepStrictEqual(testObject.visibleViewDescriptors, []); + assert.deepStrictEqual(target.elements, []); }); test('move', () => { @@ -237,24 +237,24 @@ suite('ViewContainerModel', () => { const view3: IViewDescriptor = { id: 'view3', ctorDescriptor: null!, name: 'Test View 3' }; ViewsRegistry.registerViews([view1, view2, view3], container); - assert.deepEqual(testObject.visibleViewDescriptors, [view1, view2, view3], 'model views should be OK'); - assert.deepEqual(target.elements, [view1, view2, view3], 'sql views should be OK'); + assert.deepStrictEqual(testObject.visibleViewDescriptors, [view1, view2, view3], 'model views should be OK'); + assert.deepStrictEqual(target.elements, [view1, view2, view3], 'sql views should be OK'); testObject.move('view3', 'view1'); - assert.deepEqual(testObject.visibleViewDescriptors, [view3, view1, view2], 'view3 should go to the front'); - assert.deepEqual(target.elements, [view3, view1, view2]); + assert.deepStrictEqual(testObject.visibleViewDescriptors, [view3, view1, view2], 'view3 should go to the front'); + assert.deepStrictEqual(target.elements, [view3, view1, view2]); testObject.move('view1', 'view2'); - assert.deepEqual(testObject.visibleViewDescriptors, [view3, view2, view1], 'view1 should go to the end'); - assert.deepEqual(target.elements, [view3, view2, view1]); + assert.deepStrictEqual(testObject.visibleViewDescriptors, [view3, view2, view1], 'view1 should go to the end'); + assert.deepStrictEqual(target.elements, [view3, view2, view1]); testObject.move('view1', 'view3'); - assert.deepEqual(testObject.visibleViewDescriptors, [view1, view3, view2], 'view1 should go to the front'); - assert.deepEqual(target.elements, [view1, view3, view2]); + assert.deepStrictEqual(testObject.visibleViewDescriptors, [view1, view3, view2], 'view1 should go to the front'); + assert.deepStrictEqual(target.elements, [view1, view3, view2]); testObject.move('view2', 'view3'); - assert.deepEqual(testObject.visibleViewDescriptors, [view1, view2, view3], 'view2 should go to the middle'); - assert.deepEqual(target.elements, [view1, view2, view3]); + assert.deepStrictEqual(testObject.visibleViewDescriptors, [view1, view2, view3], 'view2 should go to the middle'); + assert.deepStrictEqual(target.elements, [view1, view2, view3]); }); test('view states', async function () { @@ -263,8 +263,8 @@ suite('ViewContainerModel', () => { const testObject = viewDescriptorService.getViewContainerModel(container); const target = disposableStore.add(new ViewDescriptorSequence(testObject)); - assert.equal(testObject.visibleViewDescriptors.length, 0); - assert.equal(target.elements.length, 0); + assert.strictEqual(testObject.visibleViewDescriptors.length, 0); + assert.strictEqual(target.elements.length, 0); const viewDescriptor: IViewDescriptor = { id: 'view1', @@ -273,8 +273,8 @@ suite('ViewContainerModel', () => { }; ViewsRegistry.registerViews([viewDescriptor], container); - assert.equal(testObject.visibleViewDescriptors.length, 0, 'view should not appear since it was set not visible in view state'); - assert.equal(target.elements.length, 0); + assert.strictEqual(testObject.visibleViewDescriptors.length, 0, 'view should not appear since it was set not visible in view state'); + assert.strictEqual(target.elements.length, 0); }); test('view states and when contexts', async function () { @@ -283,8 +283,8 @@ suite('ViewContainerModel', () => { const testObject = viewDescriptorService.getViewContainerModel(container); const target = disposableStore.add(new ViewDescriptorSequence(testObject)); - assert.equal(testObject.visibleViewDescriptors.length, 0); - assert.equal(target.elements.length, 0); + assert.strictEqual(testObject.visibleViewDescriptors.length, 0); + assert.strictEqual(target.elements.length, 0); const viewDescriptor: IViewDescriptor = { id: 'view1', @@ -294,17 +294,17 @@ suite('ViewContainerModel', () => { }; ViewsRegistry.registerViews([viewDescriptor], container); - assert.equal(testObject.visibleViewDescriptors.length, 0, 'view should not appear since context isnt in'); - assert.equal(target.elements.length, 0); + assert.strictEqual(testObject.visibleViewDescriptors.length, 0, 'view should not appear since context isnt in'); + assert.strictEqual(target.elements.length, 0); const key = contextKeyService.createKey('showview1', false); - assert.equal(testObject.visibleViewDescriptors.length, 0, 'view should still not appear since showview1 isnt true'); - assert.equal(target.elements.length, 0); + assert.strictEqual(testObject.visibleViewDescriptors.length, 0, 'view should still not appear since showview1 isnt true'); + assert.strictEqual(target.elements.length, 0); key.set(true); await new Promise(c => setTimeout(c, 30)); - assert.equal(testObject.visibleViewDescriptors.length, 0, 'view should still not appear since it was set not visible in view state'); - assert.equal(target.elements.length, 0); + assert.strictEqual(testObject.visibleViewDescriptors.length, 0, 'view should still not appear since it was set not visible in view state'); + assert.strictEqual(target.elements.length, 0); }); test('view states and when contexts multiple views', async function () { @@ -313,8 +313,8 @@ suite('ViewContainerModel', () => { const testObject = viewDescriptorService.getViewContainerModel(container); const target = disposableStore.add(new ViewDescriptorSequence(testObject)); - assert.equal(testObject.visibleViewDescriptors.length, 0); - assert.equal(target.elements.length, 0); + assert.strictEqual(testObject.visibleViewDescriptors.length, 0); + assert.strictEqual(target.elements.length, 0); const view1: IViewDescriptor = { id: 'view1', @@ -335,22 +335,22 @@ suite('ViewContainerModel', () => { }; ViewsRegistry.registerViews([view1, view2, view3], container); - assert.deepEqual(testObject.visibleViewDescriptors, [view2], 'Only view2 should be visible'); - assert.deepEqual(target.elements, [view2]); + assert.deepStrictEqual(testObject.visibleViewDescriptors, [view2], 'Only view2 should be visible'); + assert.deepStrictEqual(target.elements, [view2]); const key = contextKeyService.createKey('showview', false); - assert.deepEqual(testObject.visibleViewDescriptors, [view2], 'Only view2 should be visible'); - assert.deepEqual(target.elements, [view2]); + assert.deepStrictEqual(testObject.visibleViewDescriptors, [view2], 'Only view2 should be visible'); + assert.deepStrictEqual(target.elements, [view2]); key.set(true); await new Promise(c => setTimeout(c, 30)); - assert.deepEqual(testObject.visibleViewDescriptors, [view2, view3], 'view3 should be visible'); - assert.deepEqual(target.elements, [view2, view3]); + assert.deepStrictEqual(testObject.visibleViewDescriptors, [view2, view3], 'view3 should be visible'); + assert.deepStrictEqual(target.elements, [view2, view3]); key.set(false); await new Promise(c => setTimeout(c, 30)); - assert.deepEqual(testObject.visibleViewDescriptors, [view2], 'Only view2 should be visible'); - assert.deepEqual(target.elements, [view2]); + assert.deepStrictEqual(testObject.visibleViewDescriptors, [view2], 'Only view2 should be visible'); + assert.deepStrictEqual(target.elements, [view2]); }); test('remove event is not triggered if view was hidden and removed', async function () { @@ -369,12 +369,12 @@ suite('ViewContainerModel', () => { const key = contextKeyService.createKey('showview1', true); await new Promise(c => setTimeout(c, 30)); - assert.equal(testObject.visibleViewDescriptors.length, 1, 'view should appear after context is set'); - assert.equal(target.elements.length, 1); + assert.strictEqual(testObject.visibleViewDescriptors.length, 1, 'view should appear after context is set'); + assert.strictEqual(target.elements.length, 1); testObject.setVisible('view1', false); - assert.equal(testObject.visibleViewDescriptors.length, 0, 'view should disappear after setting visibility to false'); - assert.equal(target.elements.length, 0); + assert.strictEqual(testObject.visibleViewDescriptors.length, 0, 'view should disappear after setting visibility to false'); + assert.strictEqual(target.elements.length, 0); const targetEvent = sinon.spy(testObject.onDidRemoveVisibleViewDescriptors); key.set(false); @@ -398,14 +398,14 @@ suite('ViewContainerModel', () => { key.set(false); ViewsRegistry.registerViews([viewDescriptor], container); - assert.equal(testObject.visibleViewDescriptors.length, 0); - assert.equal(target.elements.length, 0); + assert.strictEqual(testObject.visibleViewDescriptors.length, 0); + assert.strictEqual(target.elements.length, 0); const targetEvent = sinon.spy(testObject.onDidAddVisibleViewDescriptors); testObject.setVisible('view1', true); assert.ok(!targetEvent.called, 'add event should not be called since it is already visible'); - assert.equal(testObject.visibleViewDescriptors.length, 0); - assert.equal(target.elements.length, 0); + assert.strictEqual(testObject.visibleViewDescriptors.length, 0); + assert.strictEqual(target.elements.length, 0); }); test('remove event is not triggered if view was hidden and not active', async function () { @@ -424,14 +424,14 @@ suite('ViewContainerModel', () => { key.set(false); ViewsRegistry.registerViews([viewDescriptor], container); - assert.equal(testObject.visibleViewDescriptors.length, 0); - assert.equal(target.elements.length, 0); + assert.strictEqual(testObject.visibleViewDescriptors.length, 0); + assert.strictEqual(target.elements.length, 0); const targetEvent = sinon.spy(testObject.onDidAddVisibleViewDescriptors); testObject.setVisible('view1', false); assert.ok(!targetEvent.called, 'add event should not be called since it is disabled'); - assert.equal(testObject.visibleViewDescriptors.length, 0); - assert.equal(target.elements.length, 0); + assert.strictEqual(testObject.visibleViewDescriptors.length, 0); + assert.strictEqual(target.elements.length, 0); }); test('add event is not triggered if view was set visible (when not visible) and not active', async function () { @@ -450,18 +450,18 @@ suite('ViewContainerModel', () => { key.set(false); ViewsRegistry.registerViews([viewDescriptor], container); - assert.equal(testObject.visibleViewDescriptors.length, 0); - assert.equal(target.elements.length, 0); + assert.strictEqual(testObject.visibleViewDescriptors.length, 0); + assert.strictEqual(target.elements.length, 0); testObject.setVisible('view1', false); - assert.equal(testObject.visibleViewDescriptors.length, 0); - assert.equal(target.elements.length, 0); + assert.strictEqual(testObject.visibleViewDescriptors.length, 0); + assert.strictEqual(target.elements.length, 0); const targetEvent = sinon.spy(testObject.onDidAddVisibleViewDescriptors); testObject.setVisible('view1', true); assert.ok(!targetEvent.called, 'add event should not be called since it is disabled'); - assert.equal(testObject.visibleViewDescriptors.length, 0); - assert.equal(target.elements.length, 0); + assert.strictEqual(testObject.visibleViewDescriptors.length, 0); + assert.strictEqual(target.elements.length, 0); }); test('added view descriptors are in ascending order in the event', async function () { @@ -483,9 +483,9 @@ suite('ViewContainerModel', () => { order: 2 }], container); - assert.equal(target.elements.length, 2); - assert.equal(target.elements[0].id, 'view2'); - assert.equal(target.elements[1].id, 'view5'); + assert.strictEqual(target.elements.length, 2); + assert.strictEqual(target.elements[0].id, 'view2'); + assert.strictEqual(target.elements[1].id, 'view5'); ViewsRegistry.registerViews([{ id: 'view4', @@ -507,12 +507,12 @@ suite('ViewContainerModel', () => { order: 1 }], container); - assert.equal(target.elements.length, 5); - assert.equal(target.elements[0].id, 'view1'); - assert.equal(target.elements[1].id, 'view2'); - assert.equal(target.elements[2].id, 'view3'); - assert.equal(target.elements[3].id, 'view4'); - assert.equal(target.elements[4].id, 'view5'); + assert.strictEqual(target.elements.length, 5); + assert.strictEqual(target.elements[0].id, 'view1'); + assert.strictEqual(target.elements[1].id, 'view2'); + assert.strictEqual(target.elements[2].id, 'view3'); + assert.strictEqual(target.elements[3].id, 'view4'); + assert.strictEqual(target.elements[4].id, 'view5'); }); }); diff --git a/src/vs/workbench/services/views/test/browser/viewDescriptorService.test.ts b/src/vs/workbench/services/views/test/browser/viewDescriptorService.test.ts index 11312c8d..84a4102c 100644 --- a/src/vs/workbench/services/views/test/browser/viewDescriptorService.test.ts +++ b/src/vs/workbench/services/views/test/browser/viewDescriptorService.test.ts @@ -36,8 +36,8 @@ suite('ViewDescriptorService', () => { test('Empty Containers', function () { const sidebarViews = viewDescriptorService.getViewContainerModel(sidebarContainer); const panelViews = viewDescriptorService.getViewContainerModel(panelContainer); - assert.equal(sidebarViews.allViewDescriptors.length, 0, 'The sidebar container should have no views yet.'); - assert.equal(panelViews.allViewDescriptors.length, 0, 'The panel container should have no views yet.'); + assert.strictEqual(sidebarViews.allViewDescriptors.length, 0, 'The sidebar container should have no views yet.'); + assert.strictEqual(panelViews.allViewDescriptors.length, 0, 'The panel container should have no views yet.'); }); test('Register/Deregister', () => { @@ -70,8 +70,8 @@ suite('ViewDescriptorService', () => { let sidebarViews = viewDescriptorService.getViewContainerModel(sidebarContainer); let panelViews = viewDescriptorService.getViewContainerModel(panelContainer); - assert.equal(sidebarViews.activeViewDescriptors.length, 2, 'Sidebar should have 2 views'); - assert.equal(panelViews.activeViewDescriptors.length, 1, 'Panel should have 1 view'); + assert.strictEqual(sidebarViews.activeViewDescriptors.length, 2, 'Sidebar should have 2 views'); + assert.strictEqual(panelViews.activeViewDescriptors.length, 1, 'Panel should have 1 view'); ViewsRegistry.deregisterViews(viewDescriptors.slice(0, 2), sidebarContainer); ViewsRegistry.deregisterViews(viewDescriptors.slice(2), panelContainer); @@ -80,8 +80,8 @@ suite('ViewDescriptorService', () => { sidebarViews = viewDescriptorService.getViewContainerModel(sidebarContainer); panelViews = viewDescriptorService.getViewContainerModel(panelContainer); - assert.equal(sidebarViews.activeViewDescriptors.length, 0, 'Sidebar should have no views'); - assert.equal(panelViews.activeViewDescriptors.length, 0, 'Panel should have no views'); + assert.strictEqual(sidebarViews.activeViewDescriptors.length, 0, 'Sidebar should have no views'); + assert.strictEqual(panelViews.activeViewDescriptors.length, 0, 'Panel should have no views'); }); test('move views to existing containers', async function () { @@ -115,12 +115,12 @@ suite('ViewDescriptorService', () => { let sidebarViews = viewDescriptorService.getViewContainerModel(sidebarContainer); let panelViews = viewDescriptorService.getViewContainerModel(panelContainer); - assert.equal(sidebarViews.activeViewDescriptors.length, 1, 'Sidebar should have 2 views'); - assert.equal(panelViews.activeViewDescriptors.length, 2, 'Panel should have 1 view'); + assert.strictEqual(sidebarViews.activeViewDescriptors.length, 1, 'Sidebar should have 2 views'); + assert.strictEqual(panelViews.activeViewDescriptors.length, 2, 'Panel should have 1 view'); - assert.notEqual(sidebarViews.activeViewDescriptors.indexOf(viewDescriptors[2]), -1, `Sidebar should have ${viewDescriptors[2].name}`); - assert.notEqual(panelViews.activeViewDescriptors.indexOf(viewDescriptors[0]), -1, `Panel should have ${viewDescriptors[0].name}`); - assert.notEqual(panelViews.activeViewDescriptors.indexOf(viewDescriptors[1]), -1, `Panel should have ${viewDescriptors[1].name}`); + assert.notStrictEqual(sidebarViews.activeViewDescriptors.indexOf(viewDescriptors[2]), -1, `Sidebar should have ${viewDescriptors[2].name}`); + assert.notStrictEqual(panelViews.activeViewDescriptors.indexOf(viewDescriptors[0]), -1, `Panel should have ${viewDescriptors[0].name}`); + assert.notStrictEqual(panelViews.activeViewDescriptors.indexOf(viewDescriptors[1]), -1, `Panel should have ${viewDescriptors[1].name}`); }); test('move views to generated containers', async function () { @@ -154,20 +154,20 @@ suite('ViewDescriptorService', () => { let sidebarViews = viewDescriptorService.getViewContainerModel(sidebarContainer); let panelViews = viewDescriptorService.getViewContainerModel(panelContainer); - assert.equal(sidebarViews.activeViewDescriptors.length, 1, 'Sidebar container should have 1 view'); - assert.equal(panelViews.activeViewDescriptors.length, 0, 'Panel container should have no views'); + assert.strictEqual(sidebarViews.activeViewDescriptors.length, 1, 'Sidebar container should have 1 view'); + assert.strictEqual(panelViews.activeViewDescriptors.length, 0, 'Panel container should have no views'); const generatedPanel = assertIsDefined(viewDescriptorService.getViewContainerByViewId(viewDescriptors[0].id)); const generatedSidebar = assertIsDefined(viewDescriptorService.getViewContainerByViewId(viewDescriptors[2].id)); - assert.equal(viewDescriptorService.getViewContainerLocation(generatedPanel), ViewContainerLocation.Panel, 'Generated Panel should be in located in the panel'); - assert.equal(viewDescriptorService.getViewContainerLocation(generatedSidebar), ViewContainerLocation.Sidebar, 'Generated Sidebar should be in located in the sidebar'); + assert.strictEqual(viewDescriptorService.getViewContainerLocation(generatedPanel), ViewContainerLocation.Panel, 'Generated Panel should be in located in the panel'); + assert.strictEqual(viewDescriptorService.getViewContainerLocation(generatedSidebar), ViewContainerLocation.Sidebar, 'Generated Sidebar should be in located in the sidebar'); - assert.equal(viewDescriptorService.getViewContainerLocation(generatedPanel), viewDescriptorService.getViewLocationById(viewDescriptors[0].id), 'Panel view location and container location should match'); - assert.equal(viewDescriptorService.getViewContainerLocation(generatedSidebar), viewDescriptorService.getViewLocationById(viewDescriptors[2].id), 'Sidebar view location and container location should match'); + assert.strictEqual(viewDescriptorService.getViewContainerLocation(generatedPanel), viewDescriptorService.getViewLocationById(viewDescriptors[0].id), 'Panel view location and container location should match'); + assert.strictEqual(viewDescriptorService.getViewContainerLocation(generatedSidebar), viewDescriptorService.getViewLocationById(viewDescriptors[2].id), 'Sidebar view location and container location should match'); - assert.equal(viewDescriptorService.getDefaultContainerById(viewDescriptors[2].id), panelContainer, `${viewDescriptors[2].name} has wrong default container`); - assert.equal(viewDescriptorService.getDefaultContainerById(viewDescriptors[0].id), sidebarContainer, `${viewDescriptors[0].name} has wrong default container`); + assert.strictEqual(viewDescriptorService.getDefaultContainerById(viewDescriptors[2].id), panelContainer, `${viewDescriptors[2].name} has wrong default container`); + assert.strictEqual(viewDescriptorService.getDefaultContainerById(viewDescriptors[0].id), sidebarContainer, `${viewDescriptors[0].name} has wrong default container`); viewDescriptorService.moveViewToLocation(viewDescriptors[0], ViewContainerLocation.Sidebar); viewDescriptorService.moveViewToLocation(viewDescriptors[2], ViewContainerLocation.Panel); @@ -175,11 +175,11 @@ suite('ViewDescriptorService', () => { sidebarViews = viewDescriptorService.getViewContainerModel(sidebarContainer); panelViews = viewDescriptorService.getViewContainerModel(panelContainer); - assert.equal(sidebarViews.activeViewDescriptors.length, 1, 'Sidebar should have 2 views'); - assert.equal(panelViews.activeViewDescriptors.length, 0, 'Panel should have 1 view'); + assert.strictEqual(sidebarViews.activeViewDescriptors.length, 1, 'Sidebar should have 2 views'); + assert.strictEqual(panelViews.activeViewDescriptors.length, 0, 'Panel should have 1 view'); - assert.equal(viewDescriptorService.getViewLocationById(viewDescriptors[0].id), ViewContainerLocation.Sidebar, 'View should be located in the sidebar'); - assert.equal(viewDescriptorService.getViewLocationById(viewDescriptors[2].id), ViewContainerLocation.Panel, 'View should be located in the panel'); + assert.strictEqual(viewDescriptorService.getViewLocationById(viewDescriptors[0].id), ViewContainerLocation.Sidebar, 'View should be located in the sidebar'); + assert.strictEqual(viewDescriptorService.getViewLocationById(viewDescriptors[2].id), ViewContainerLocation.Panel, 'View should be located in the panel'); }); test('move view events', async function () { @@ -262,7 +262,7 @@ suite('ViewDescriptorService', () => { expectedSequence += containerMoveString(viewDescriptors[2], sidebarContainer, panelContainer); viewDescriptorService.moveViewsToContainer([viewDescriptors[1], viewDescriptors[2]], panelContainer); - assert.equal(actualSequence, expectedSequence, 'Event sequence not matching expected sequence'); + assert.strictEqual(actualSequence, expectedSequence, 'Event sequence not matching expected sequence'); }); }); diff --git a/src/vs/workbench/services/backup/browser/backupFileService.ts b/src/vs/workbench/services/workingCopy/browser/workingCopyBackupService.ts similarity index 55% rename from src/vs/workbench/services/backup/browser/backupFileService.ts rename to src/vs/workbench/services/workingCopy/browser/workingCopyBackupService.ts index cdc7d84d..e4d32112 100644 --- a/src/vs/workbench/services/backup/browser/backupFileService.ts +++ b/src/vs/workbench/services/workingCopy/browser/workingCopyBackupService.ts @@ -6,15 +6,17 @@ import { IFileService } from 'vs/platform/files/common/files'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { ILogService } from 'vs/platform/log/common/log'; -import { BackupFileService } from 'vs/workbench/services/backup/common/backupFileService'; +import { WorkingCopyBackupService } from 'vs/workbench/services/workingCopy/common/workingCopyBackupService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; +import { IWorkingCopyBackupService } from 'vs/workbench/services/workingCopy/common/workingCopyBackup'; import { joinPath } from 'vs/base/common/resources'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { BrowserWorkingCopyBackupTracker } from 'vs/workbench/services/workingCopy/browser/workingCopyBackupTracker'; -export class BrowserBackupFileService extends BackupFileService { - - declare readonly _serviceBrand: undefined; +export class BrowserWorkingCopyBackupService extends WorkingCopyBackupService { constructor( @IWorkspaceContextService contextService: IWorkspaceContextService, @@ -26,4 +28,8 @@ export class BrowserBackupFileService extends BackupFileService { } } -registerSingleton(IBackupFileService, BrowserBackupFileService); +// Register Service +registerSingleton(IWorkingCopyBackupService, BrowserWorkingCopyBackupService); + +// Register Backup Tracker +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(BrowserWorkingCopyBackupTracker, LifecyclePhase.Starting); diff --git a/src/vs/workbench/contrib/backup/browser/backupTracker.ts b/src/vs/workbench/services/workingCopy/browser/workingCopyBackupTracker.ts similarity index 63% rename from src/vs/workbench/contrib/backup/browser/backupTracker.ts rename to src/vs/workbench/services/workingCopy/browser/workingCopyBackupTracker.ts index 88f79e72..18ff62b8 100644 --- a/src/vs/workbench/contrib/backup/browser/backupTracker.ts +++ b/src/vs/workbench/services/workingCopy/browser/workingCopyBackupTracker.ts @@ -3,24 +3,28 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; +import { IWorkingCopyBackupService } from 'vs/workbench/services/workingCopy/common/workingCopyBackup'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { ILifecycleService, ShutdownReason } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { ILogService } from 'vs/platform/log/common/log'; -import { BackupTracker } from 'vs/workbench/contrib/backup/common/backupTracker'; +import { WorkingCopyBackupTracker } from 'vs/workbench/services/workingCopy/common/workingCopyBackupTracker'; +import { IWorkingCopyEditorService } from 'vs/workbench/services/workingCopy/common/workingCopyEditorService'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -export class BrowserBackupTracker extends BackupTracker implements IWorkbenchContribution { +export class BrowserWorkingCopyBackupTracker extends WorkingCopyBackupTracker implements IWorkbenchContribution { constructor( - @IBackupFileService backupFileService: IBackupFileService, + @IWorkingCopyBackupService workingCopyBackupService: IWorkingCopyBackupService, @IFilesConfigurationService filesConfigurationService: IFilesConfigurationService, @IWorkingCopyService workingCopyService: IWorkingCopyService, @ILifecycleService lifecycleService: ILifecycleService, - @ILogService logService: ILogService + @ILogService logService: ILogService, + @IWorkingCopyEditorService workingCopyEditorService: IWorkingCopyEditorService, + @IEditorService editorService: IEditorService ) { - super(backupFileService, workingCopyService, logService, lifecycleService, filesConfigurationService); + super(workingCopyBackupService, workingCopyService, logService, lifecycleService, filesConfigurationService, workingCopyEditorService, editorService); } protected onBeforeShutdown(reason: ShutdownReason): boolean | Promise { @@ -40,7 +44,7 @@ export class BrowserBackupTracker extends BackupTracker implements IWorkbenchCon } for (const dirtyWorkingCopy of dirtyWorkingCopies) { - if (!this.backupFileService.hasBackupSync(dirtyWorkingCopy.resource, this.getContentVersion(dirtyWorkingCopy))) { + if (!this.workingCopyBackupService.hasBackupSync(dirtyWorkingCopy, this.getContentVersion(dirtyWorkingCopy))) { this.logService.warn('Unload veto: pending backups'); return true; // dirty without backup: veto diff --git a/src/vs/workbench/services/workingCopy/common/fileWorkingCopy.ts b/src/vs/workbench/services/workingCopy/common/fileWorkingCopy.ts index 1eb6cbb3..0dba1971 100644 --- a/src/vs/workbench/services/workingCopy/common/fileWorkingCopy.ts +++ b/src/vs/workbench/services/workingCopy/common/fileWorkingCopy.ts @@ -3,22 +3,30 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { localize } from 'vs/nls'; 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, IDisposable } from 'vs/base/common/lifecycle'; -import { ETAG_DISABLED, FileChangesEvent, FileChangeType, FileOperationError, FileOperationResult, FileSystemProviderCapabilities, IFileService, IFileStatWithMetadata, IFileStreamContent } from 'vs/platform/files/common/files'; +import { ETAG_DISABLED, FileChangesEvent, FileChangeType, FileOperationError, FileOperationResult, FileSystemProviderCapabilities, IFileService, IFileStatWithMetadata, IFileStreamContent, IWriteFileOptions } 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 { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { IWorkingCopy, IWorkingCopyBackup, IWorkingCopyBackupMeta, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopy'; +import { raceCancellation, 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, stringToSnapshot } from 'vs/workbench/services/textfile/common/textfiles'; -import { newWriteableBufferStream, streamToBuffer, VSBuffer, VSBufferReadableStream } from 'vs/base/common/buffer'; +import { ITextFileEditorModel, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { 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 { IWorkingCopyBackupService, IResolvedWorkingCopyBackup } from 'vs/workbench/services/workingCopy/common/workingCopyBackup'; +import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; +import { hash } from 'vs/base/common/hash'; +import { toErrorMessage } from 'vs/base/common/errorMessage'; +import { IAction, toAction } from 'vs/base/common/actions'; import { isWindows } from 'vs/base/common/platform'; +import { IWorkingCopyEditorService } from 'vs/workbench/services/workingCopy/common/workingCopyEditorService'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IElevatedFileService } from 'vs/workbench/services/files/common/elevatedFileService'; export interface IFileWorkingCopyModelFactory { @@ -67,6 +75,21 @@ export interface IFileWorkingCopyModel extends IDisposable { */ readonly onWillDispose: Event; + /** + * A version ID of the model. If a `onDidChangeContent` is fired + * from the model and the last known saved `versionId` matches + * with the `model.versionId`, the file working copy will discard + * any dirty state. + * + * A use case is the following: + * - a file working copy gets edited and thus dirty + * - the user triggers undo to revert the changes + * - at this point the `versionId` should match the one we had saved + * + * This requires the model to be aware of undo/redo operations. + */ + readonly versionId: unknown; + /** * Snapshots the model's current content for writing. This must include * any changes that were made to the model that are in memory. @@ -89,17 +112,6 @@ export interface IFileWorkingCopyModel extends IDisposable { */ update(contents: VSBufferReadableStream, token: CancellationToken): Promise; - /** - * Get the alternative version id of the model. This alternative version - * id is not always incremented, it will return the same values in the - * case of undo-redo. - * - * 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. @@ -107,10 +119,6 @@ export interface IFileWorkingCopyModel extends IDisposable { * 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 should find a better name here maybe together - * with the `getAlternativeVersionId` concept since this is around - * undo/redo? */ pushStackElement(): void; } @@ -309,7 +317,7 @@ export interface IFileWorkingCopyResolveOptions { /** * Metadata associated with a file working copy backup. */ -interface IFileWorkingCopyBackupMetaData { +interface IFileWorkingCopyBackupMetaData extends IWorkingCopyBackupMeta { mtime: number; ctime: number; size: number; @@ -353,6 +361,7 @@ export class FileWorkingCopy extends Disposable //#endregion constructor( + readonly typeId: string, readonly resource: URI, readonly name: string, private readonly modelFactory: IFileWorkingCopyModelFactory, @@ -360,8 +369,12 @@ export class FileWorkingCopy extends Disposable @ILogService private readonly logService: ILogService, @ITextFileService private readonly textFileService: ITextFileService, @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService, - @IBackupFileService private readonly backupFileService: IBackupFileService, - @IWorkingCopyService workingCopyService: IWorkingCopyService + @IWorkingCopyBackupService private readonly workingCopyBackupService: IWorkingCopyBackupService, + @IWorkingCopyService workingCopyService: IWorkingCopyService, + @INotificationService private readonly notificationService: INotificationService, + @IWorkingCopyEditorService private readonly workingCopyEditorService: IWorkingCopyEditorService, + @IEditorService private readonly editorService: IEditorService, + @IElevatedFileService private readonly elevatedFileService: IElevatedFileService ) { super(); @@ -442,7 +455,7 @@ export class FileWorkingCopy extends Disposable //#region Dirty private dirty = false; - private savedVersionId: number | undefined; + private savedVersionId: unknown; isDirty(): this is IResolvedFileWorkingCopy { return this.dirty; @@ -477,7 +490,15 @@ export class FileWorkingCopy extends Disposable this.dirty = false; this.inConflictMode = false; this.inErrorMode = false; - this.updateSavedVersionId(); + + // 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.savedVersionId = this.model.versionId; + } } else { this.dirty = true; } @@ -498,11 +519,11 @@ export class FileWorkingCopy extends Disposable private lastResolvedFileStat: IFileStatWithMetadata | undefined; async resolve(options?: IFileWorkingCopyResolveOptions): Promise { - this.logService.trace('[file working copy] resolve() - enter', this.resource.toString(true)); + this.trace('[file working copy] resolve() - enter'); // 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)); + this.trace('[file working copy] resolve() - exit - without resolving because file working copy is disposed'); return; } @@ -511,7 +532,7 @@ export class FileWorkingCopy extends Disposable // 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)); + this.trace('[file working copy] resolve() - exit - without resolving because file working copy is dirty or being saved'); return; } @@ -540,7 +561,7 @@ export class FileWorkingCopy extends Disposable } private async resolveFromBuffer(buffer: VSBufferReadableStream): Promise { - this.logService.trace('[file working copy] resolveFromBuffer()', this.resource.toString(true)); + this.trace('[file working copy] resolveFromBuffer()'); // Try to resolve metdata from disk let mtime: number; @@ -583,12 +604,12 @@ export class FileWorkingCopy extends Disposable private async resolveFromBackup(): Promise { // Resolve backup if any - const backup = await this.backupFileService.resolve(this.resource); + const backup = await this.workingCopyBackupService.resolve(this); // 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)); + this.trace('[file working copy] resolveFromBackup() - exit - withoutresolving because previously new file working copy got created meanwhile'); return true; // imply that resolving has happened in another operation } @@ -604,8 +625,8 @@ export class FileWorkingCopy extends Disposable return false; } - private async doResolveFromBackup(backup: IResolvedBackup): Promise { - this.logService.trace('[file working copy] doResolveFromBackup()', this.resource.toString(true)); + private async doResolveFromBackup(backup: IResolvedWorkingCopyBackup): Promise { + this.trace('[file working copy] doResolveFromBackup()'); // Resolve with backup await this.resolveFromContent({ @@ -615,7 +636,7 @@ export class FileWorkingCopy extends Disposable 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) + value: backup.value }, true /* dirty (resolved from backup) */); // Restore orphaned flag based on state @@ -625,7 +646,7 @@ export class FileWorkingCopy extends Disposable } private async resolveFromFile(options?: IFileWorkingCopyResolveOptions): Promise { - this.logService.trace('[file working copy] resolveFromFile()', this.resource.toString(true)); + this.trace('[file working copy] resolveFromFile()'); const forceReadFromFile = options?.forceReadFromFile; @@ -652,7 +673,7 @@ export class FileWorkingCopy extends Disposable // 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)); + this.trace('[file working copy] resolveFromFile() - exit - without resolving because file working copy content changed'); return; } @@ -684,11 +705,11 @@ export class FileWorkingCopy extends Disposable } private async resolveFromContent(content: IFileStreamContent, dirty: boolean): Promise { - this.logService.trace('[file working copy] resolveFromContent() - enter', this.resource.toString(true)); + this.trace('[file working copy] resolveFromContent() - enter'); // 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)); + this.trace('[file working copy] resolveFromContent() - exit - because working copy is disposed'); return; } @@ -728,7 +749,7 @@ export class FileWorkingCopy extends Disposable } private async doCreateModel(contents: VSBufferReadableStream): Promise { - this.logService.trace('[file working copy] doCreateModel()', this.resource.toString(true)); + this.trace('[file working copy] doCreateModel()'); // Create model and dispose it when we get disposed this._model = this._register(await this.modelFactory.createModel(this.resource, contents, CancellationToken.None)); @@ -740,7 +761,7 @@ export class FileWorkingCopy extends Disposable private ignoreDirtyOnModelContentChange = false; private async doUpdateModel(contents: VSBufferReadableStream): Promise { - this.logService.trace('[file working copy] doUpdateModel()', this.resource.toString(true)); + this.trace('[file working copy] doUpdateModel()'); // Update model value in a block that ignores content change events for dirty tracking this.ignoreDirtyOnModelContentChange = true; @@ -765,11 +786,11 @@ export class FileWorkingCopy extends Disposable } private onModelContentChanged(model: IFileWorkingCopyModel, isUndoingOrRedoing: boolean): void { - this.logService.trace(`[file working copy] onModelContentChanged() - enter`, this.resource.toString(true)); + this.trace(`[file working copy] onModelContentChanged() - enter`); // 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)); + this.trace(`[file working copy] onModelContentChanged() - new versionId ${this.versionId}`); // Remember when the user changed the model through a undo/redo operation. // We need this information to throttle save participants to fix @@ -785,8 +806,8 @@ export class FileWorkingCopy extends Disposable // 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.savedVersionId) { - this.logService.trace('[file working copy] onModelContentChanged() - model content changed back to last saved version', this.resource.toString(true)); + if (model.versionId === this.savedVersionId) { + this.trace('[file working copy] onModelContentChanged() - model content changed back to last saved version'); // Clear flags const wasDirty = this.dirty; @@ -800,7 +821,7 @@ export class FileWorkingCopy extends Disposable // 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)); + this.trace('[file working copy] onModelContentChanged() - model content changed and marked as dirty'); // Mark as dirty this.setDirty(true); @@ -829,7 +850,13 @@ export class FileWorkingCopy extends Disposable }; } - return { meta, content: await this.modelTextSnapshot(token) }; + // Fill in content if we are resolved + let content: VSBufferReadableStream | undefined = undefined; + if (this.isResolved()) { + content = await raceCancellation(this.model.snapshot(token), token); + } + + return { meta, content }; } //#endregion @@ -849,7 +876,7 @@ export class FileWorkingCopy extends Disposable } if (this.isReadonly()) { - this.logService.trace('[file working copy] save() - ignoring request for readonly resource', this.resource.toString(true)); + this.trace('[file working copy] save() - ignoring request for readonly resource'); return false; // if working copy is readonly we do not attempt to save at all } @@ -858,15 +885,15 @@ export class FileWorkingCopy extends Disposable (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)); + this.trace('[file working copy] save() - ignoring auto save request for file working copy that is in conflict or error'); 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)); + this.trace('[file working copy] save() - enter'); await this.doSave(options); - this.logService.trace('[file working copy] save() - exit', this.resource.toString(true)); + this.trace('[file working copy] save() - exit'); return true; } @@ -877,7 +904,7 @@ export class FileWorkingCopy extends Disposable } let versionId = this.versionId; - this.logService.trace(`[file working copy] doSave(${versionId}) - enter with versionId ${versionId}`, this.resource.toString(true)); + this.trace(`[file working copy] doSave(${versionId}) - enter with versionId ${versionId}`); // Lookup any running pending save for this versionId and return it if found // @@ -885,7 +912,7 @@ export class FileWorkingCopy extends Disposable // 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)); + this.trace(`[file working copy] doSave(${versionId}) - exit - found a pending save for versionId ${versionId}`); return this.saveSequentializer.pending; } @@ -894,7 +921,7 @@ export class FileWorkingCopy extends Disposable // // 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)); + this.trace(`[file working copy] doSave(${versionId}) - exit - because not dirty and/or versionId is different (this.isDirty: ${this.dirty}, this.versionId: ${this.versionId})`); return; } @@ -908,13 +935,15 @@ export class FileWorkingCopy extends Disposable // 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)); + this.trace(`[file working copy] doSave(${versionId}) - exit - because busy saving`); // 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. + // participants and pending snapshots from the + // save operation, but not the actual save which does + // not support cancellation yet. this.saveSequentializer.cancelPending(); // Register this as the next upcoming save and return @@ -965,20 +994,14 @@ export class FileWorkingCopy extends Disposable 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)); + this.logService.error(`[file working copy] runSaveParticipants(${versionId}) - resulted in an error: ${error.toString()}`, this.resource.toString(true), this.typeId); } } // 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. + // running save. As such we return early when we detect that. 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 @@ -1005,27 +1028,46 @@ export class FileWorkingCopy extends Disposable // 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)); + this.trace(`[file working copy] doSave(${versionId}) - before write()`); const lastResolvedFileStat = assertIsDefined(this.lastResolvedFileStat); const resolvedFileWorkingCopy = this; return this.saveSequentializer.setPending(versionId, (async () => { try { // Snapshot working copy model contents - const snapshot = await resolvedFileWorkingCopy.model.snapshot(CancellationToken.None); + const snapshot = await raceCancellation(resolvedFileWorkingCopy.model.snapshot(saveCancellation.token), saveCancellation.token); - // Write them to disk - const stat = await this.fileService.writeFile(lastResolvedFileStat.resource, snapshot, { + // 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(); + } + + const writeFileOptions: IWriteFileOptions = { mtime: lastResolvedFileStat.mtime, etag: (options.ignoreModifiedSince || !this.filesConfigurationService.preventSaveConflicts(lastResolvedFileStat.resource)) ? ETAG_DISABLED : lastResolvedFileStat.etag, unlock: options.writeUnlock - }); + }; + + // Write them to disk + let stat: IFileStatWithMetadata; + if (options?.writeElevated && this.elevatedFileService.isSupported(lastResolvedFileStat.resource)) { + stat = await this.elevatedFileService.writeFileElevated(lastResolvedFileStat.resource, assertIsDefined(snapshot), writeFileOptions); + } else { + stat = await this.fileService.writeFile(lastResolvedFileStat.resource, assertIsDefined(snapshot), writeFileOptions); + } this.handleSaveSuccess(stat, versionId, options); } catch (error) { this.handleSaveError(error, versionId, options); } - })()); + })(), () => saveCancellation.cancel()); })(), () => saveCancellation.cancel()); } @@ -1036,10 +1078,10 @@ export class FileWorkingCopy extends Disposable // 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.trace(`[file working copy] handleSaveSuccess(${versionId}) - setting dirty to false because versionId did not change`); 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)); + this.trace(`[file working copy] handleSaveSuccess(${versionId}) - not setting dirty to false because versionId did change meanwhile`); } // Update orphan state given save was successful @@ -1050,7 +1092,7 @@ export class FileWorkingCopy extends Disposable } 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)); + this.logService.error(`[file working copy] handleSaveError(${versionId}) - exit - resulted in a save error: ${error.toString()}`, this.resource.toString(true), this.typeId); // Return early if the save() call was made asking to // handle the save error itself. @@ -1073,30 +1115,99 @@ export class FileWorkingCopy extends Disposable } // Delegate to save error handler - let throwError = true; if (this.isTextFileModel(this.model)) { this.textFileService.files.saveErrorHandler.onSaveError(error, this.model); - throwError = false; + } else { + this.doHandleSaveError(error); } // Emit as event this._onDidSaveError.fire(); - - if (throwError) { - throw error; - } } - private updateSavedVersionId(): void { + private doHandleSaveError(error: Error): void { + const fileOperationError = error as FileOperationError; + const primaryActions: IAction[] = []; - // 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.savedVersionId = this.model.getAlternativeVersionId(); + let message: string; + + // Dirty write prevention + if (fileOperationError.fileOperationResult === FileOperationResult.FILE_MODIFIED_SINCE) { + message = localize('staleSaveError', "Failed to save '{0}': The content of the file is newer. Do you want to overwrite the file with your changes?", this.name); + + primaryActions.push(toAction({ id: 'fileWorkingCopy.overwrite', label: localize('overwrite', "Overwrite"), run: () => this.save({ ignoreModifiedSince: true }) })); + primaryActions.push(toAction({ id: 'fileWorkingCopy.revert', label: localize('discard', "Discard"), run: () => this.revert() })); } + + // Any other save error + else { + const isWriteLocked = fileOperationError.fileOperationResult === FileOperationResult.FILE_WRITE_LOCKED; + const triedToUnlock = isWriteLocked && fileOperationError.options?.unlock; + const isPermissionDenied = fileOperationError.fileOperationResult === FileOperationResult.FILE_PERMISSION_DENIED; + const canSaveElevated = this.elevatedFileService.isSupported(this.resource); + + // Save Elevated + if (canSaveElevated && (isPermissionDenied || triedToUnlock)) { + primaryActions.push(toAction({ + id: 'fileWorkingCopy.saveElevated', + label: triedToUnlock ? + isWindows ? localize('overwriteElevated', "Overwrite as Admin...") : localize('overwriteElevatedSudo', "Overwrite as Sudo...") : + isWindows ? localize('saveElevated', "Retry as Admin...") : localize('saveElevatedSudo', "Retry as Sudo..."), + run: () => { + this.save({ writeElevated: true, writeUnlock: triedToUnlock, reason: SaveReason.EXPLICIT }); + } + })); + } + + // Unlock + else if (isWriteLocked) { + primaryActions.push(toAction({ id: 'fileWorkingCopy.unlock', label: localize('overwrite', "Overwrite"), run: () => this.save({ writeUnlock: true, reason: SaveReason.EXPLICIT }) })); + } + + // Retry + else { + primaryActions.push(toAction({ id: 'fileWorkingCopy.retry', label: localize('retry', "Retry"), run: () => this.save({ reason: SaveReason.EXPLICIT }) })); + } + + // Save As + primaryActions.push(toAction({ + id: 'fileWorkingCopy.saveAs', + label: localize('saveAs', "Save As..."), + run: () => { + const editor = this.workingCopyEditorService.findEditor(this); + if (editor) { + this.editorService.save(editor, { saveAs: true, reason: SaveReason.EXPLICIT }); + } + } + })); + + // Discard + primaryActions.push(toAction({ id: 'fileWorkingCopy.revert', label: localize('discard', "Discard"), run: () => this.revert() })); + + // Message + if (isWriteLocked) { + if (triedToUnlock && canSaveElevated) { + message = isWindows ? + localize('readonlySaveErrorAdmin', "Failed to save '{0}': File is read-only. Select 'Overwrite as Admin' to retry as administrator.", this.name) : + localize('readonlySaveErrorSudo', "Failed to save '{0}': File is read-only. Select 'Overwrite as Sudo' to retry as superuser.", this.name); + } else { + message = localize('readonlySaveError', "Failed to save '{0}': File is read-only. Select 'Overwrite' to attempt to make it writeable.", this.name); + } + } else if (canSaveElevated && isPermissionDenied) { + message = isWindows ? + localize('permissionDeniedSaveError', "Failed to save '{0}': Insufficient permissions. Select 'Retry as Admin' to retry as administrator.", this.name) : + localize('permissionDeniedSaveErrorSudo', "Failed to save '{0}': Insufficient permissions. Select 'Retry as Sudo' to retry as superuser.", this.name); + } else { + message = localize({ key: 'genericSaveError', comment: ['{0} is the resource that failed to save and {1} the error message'] }, "Failed to save '{0}': {1}", this.name, toErrorMessage(error, false)); + } + } + + // Show to the user as notification + const handle = this.notificationService.notify({ id: `${hash(this.resource.toString())}`, severity: Severity.Error, message, actions: { primary: primaryActions } }); + + // Remove automatically when we get saved/reverted + const listener = Event.once(Event.any(this.onDidSave, this.onDidRevert))(() => handle.close()); + Event.once(handle.onDidClose)(() => listener.dispose()); } private updateLastResolvedFileStat(newFileStat: IFileStatWithMetadata): void { @@ -1196,6 +1307,10 @@ export class FileWorkingCopy extends Disposable return this.fileService.hasCapability(this.resource, FileSystemProviderCapabilities.Readonly); } + private trace(msg: string): void { + this.logService.trace(msg, this.resource.toString(true), this.typeId); + } + //#endregion //#region Dispose @@ -1206,8 +1321,8 @@ export class FileWorkingCopy extends Disposable return this.disposed; } - dispose(): void { - this.logService.trace('[file working copy] dispose()', this.resource.toString(true)); + override dispose(): void { + this.trace('[file working copy] dispose()'); // State this.disposed = true; @@ -1231,28 +1346,5 @@ export class FileWorkingCopy extends Disposable return !!(textFileModel && this.model && (textFileModel as unknown) === (this.model as unknown)); } - // TODO@bpasero backups should account for binary data, - // and be able to deal with VSBuffer directly - - 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; - } - - private async modelTextSnapshot(token: CancellationToken): Promise { - if (!this.isResolved()) { - return undefined; - } - - const snapshot = await this.model.snapshot(token); - const contents = await streamToBuffer(snapshot); - - return stringToSnapshot(contents.toString()); - } - //#endregion } diff --git a/src/vs/workbench/services/workingCopy/common/fileWorkingCopyManager.ts b/src/vs/workbench/services/workingCopy/common/fileWorkingCopyManager.ts index 542e84d9..8e135787 100644 --- a/src/vs/workbench/services/workingCopy/common/fileWorkingCopyManager.ts +++ b/src/vs/workbench/services/workingCopy/common/fileWorkingCopyManager.ts @@ -5,7 +5,7 @@ import { Disposable, DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle'; import { Event, Emitter } from 'vs/base/common/event'; -import { FileWorkingCopy, IFileWorkingCopy, IFileWorkingCopyModel, IFileWorkingCopyModelFactory, IFileWorkingCopySaveOptions } from 'vs/workbench/services/workingCopy/common/fileWorkingCopy'; +import { FileWorkingCopy, FileWorkingCopyState, IFileWorkingCopy, IFileWorkingCopyModel, IFileWorkingCopyModelFactory, IFileWorkingCopySaveOptions } from 'vs/workbench/services/workingCopy/common/fileWorkingCopy'; import { SaveReason } from 'vs/workbench/common/editor'; import { ResourceMap } from 'vs/base/common/map'; import { Promises, ResourceQueue } from 'vs/base/common/async'; @@ -62,7 +62,7 @@ export interface IFileWorkingCopyManager extend /** * Access to all known file working copies within the manager. */ - readonly workingCopies: IFileWorkingCopy[]; + readonly workingCopies: readonly IFileWorkingCopy[]; /** * Returns the file working copy for the provided resource @@ -101,6 +101,8 @@ export interface IFileWorkingCopyManager extend * copy may chose to return an existing file working copy with different casing * to respect file systems that are case insensitive. * + * Note: Callers must `dispose` the working copy when no longer needed. + * * @param source the source resource to save as * @param target the optional target resource to save to. if not defined, the user * will be asked for input @@ -114,9 +116,6 @@ export interface IFileWorkingCopyManager extend * 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. - * - * TODO@bpasero this is a bit fishy, should this not be inside the working copy - * itself? */ canDispose(workingCopy: IFileWorkingCopy): true | Promise; } @@ -200,6 +199,7 @@ export class FileWorkingCopyManager extends Dis private readonly workingCopyResolveQueue = this._register(new ResourceQueue()); constructor( + private readonly workingCopyTypeId: string, private readonly modelFactory: IFileWorkingCopyModelFactory, @IFileService private readonly fileService: IFileService, @ILifecycleService private readonly lifecycleService: ILifecycleService, @@ -226,7 +226,19 @@ export class FileWorkingCopyManager extends Dis this._register(this.workingCopyFileService.onDidRunWorkingCopyFileOperation(e => this.onDidRunWorkingCopyFileOperation(e))); // Lifecycle - this.lifecycleService.onShutdown(() => this.dispose()); + this.lifecycleService.onWillShutdown(event => event.join(this.onWillShutdown(), 'join.fileWorkingCopyManager')); + this.lifecycleService.onDidShutdown(() => this.dispose()); + } + + private async onWillShutdown(): Promise { + let fileWorkingCopies: IFileWorkingCopy[]; + + // As long as file working copies are pending to be saved, we prolong the shutdown + // until that has happened to ensure we are not shutting down in the middle of + // writing to the working copy (https://github.com/microsoft/vscode/issues/116600). + while ((fileWorkingCopies = this.workingCopies.filter(workingCopy => workingCopy.hasState(FileWorkingCopyState.PENDING_SAVE))).length > 0) { + await Promises.settled(fileWorkingCopies.map(workingCopy => workingCopy.joinState(FileWorkingCopyState.PENDING_SAVE))); + } } //#region Resolve from file changes @@ -447,7 +459,7 @@ export class FileWorkingCopyManager extends Dis else { didCreateWorkingCopy = true; - const newWorkingCopy = workingCopy = this.instantiationService.createInstance(FileWorkingCopy, resource, this.labelService.getUriBasenameLabel(resource), this.modelFactory) as unknown as IFileWorkingCopy; + const newWorkingCopy = workingCopy = this.instantiationService.createInstance(FileWorkingCopy, this.workingCopyTypeId, resource, this.labelService.getUriBasenameLabel(resource), this.modelFactory) as unknown as IFileWorkingCopy; workingCopyResolve = workingCopy.resolve(options); this.registerWorkingCopy(newWorkingCopy); @@ -626,7 +638,7 @@ export class FileWorkingCopyManager extends Dis // Create target file adhoc if it does not exist yet if (!targetExists) { - await this.workingCopyFileService.create([{ resource: target }]); + await this.workingCopyFileService.create([{ resource: target }], CancellationToken.None); } // At this point we need to resolve the target working copy @@ -703,7 +715,7 @@ export class FileWorkingCopyManager extends Dis return true; } - dispose(): void { + override dispose(): void { super.dispose(); this.clear(); diff --git a/src/vs/workbench/services/workingCopy/common/legacyBackupRestorer.ts b/src/vs/workbench/services/workingCopy/common/legacyBackupRestorer.ts new file mode 100644 index 00000000..99ef284a --- /dev/null +++ b/src/vs/workbench/services/workingCopy/common/legacyBackupRestorer.ts @@ -0,0 +1,136 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { IWorkingCopyBackupService } from 'vs/workbench/services/workingCopy/common/workingCopyBackup'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IResourceEditorInput } from 'vs/platform/editor/common/editor'; +import { Schemas } from 'vs/base/common/network'; +import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { IUntitledTextResourceEditorInput, IEditorInput, IEditorInputFactoryRegistry, EditorExtensions, IEditorInputWithOptions } from 'vs/workbench/common/editor'; +import { toLocalResource, isEqual } from 'vs/base/common/resources'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IPathService } from 'vs/workbench/services/path/common/pathService'; +import { ILogService } from 'vs/platform/log/common/log'; +import { Promises } from 'vs/base/common/async'; +import { IWorkingCopyIdentifier } from 'vs/workbench/services/workingCopy/common/workingCopy'; + +/** + * @deprecated TODO@bpasero remove me once all backups are handled properly + */ +export class LegacyWorkingCopyBackupRestorer implements IWorkbenchContribution { + + private static readonly UNTITLED_REGEX = /Untitled-\d+/; + + private readonly editorInputFactories = Registry.as(EditorExtensions.EditorInputFactories); + + constructor( + @IEditorService private readonly editorService: IEditorService, + @IWorkingCopyBackupService private readonly workingCopyBackupService: IWorkingCopyBackupService, + @ILifecycleService private readonly lifecycleService: ILifecycleService, + @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IPathService private readonly pathService: IPathService, + @ILogService private readonly logService: ILogService + ) { + this.restoreLegacyBackups(); + } + + private restoreLegacyBackups(): void { + this.lifecycleService.when(LifecyclePhase.Restored).then(() => this.doRestoreLegacyBackups()); + } + + protected async doRestoreLegacyBackups(): Promise { + + // Resolve all backup resources that exist for this window + // that have not yet adopted the working copy editor handler + // - any working copy without `typeId` + // - not `search-edior:/` (supports migration to typeId) + const backups = (await this.workingCopyBackupService.getBackups()) + .filter(backup => backup.typeId.length === 0) + .filter(backup => backup.resource.scheme !== 'search-editor'); + + // Trigger `resolve` in each opened editor that can be found + // for the given resource and keep track of backups that are + // not opened. + const unresolvedBackups = await this.resolveOpenedBackupEditors(backups); + + // For remaining unresolved backups, explicitly open an editor + if (unresolvedBackups.length > 0) { + try { + await this.openEditors(unresolvedBackups); + } catch (error) { + this.logService.error(error); + } + + // Finally trigger `resolve` in the newly opened editors + await this.resolveOpenedBackupEditors(unresolvedBackups); + } + } + + private async resolveOpenedBackupEditors(backups: readonly IWorkingCopyIdentifier[]): Promise { + const unresolvedBackups: IWorkingCopyIdentifier[] = []; + + await Promises.settled(backups.map(async backup => { + const openedEditor = this.findOpenedEditor(backup); + if (openedEditor) { + try { + await openedEditor.resolve(); + } catch (error) { + unresolvedBackups.push(backup); // ignore error and remember as unresolved + } + } else { + unresolvedBackups.push(backup); + } + })); + + return unresolvedBackups; + } + + private findOpenedEditor(backup: IWorkingCopyIdentifier): IEditorInput | undefined { + for (const editor of this.editorService.editors) { + const customFactory = this.editorInputFactories.getCustomEditorInputFactory(backup.resource.scheme); + if (customFactory?.canResolveBackup(editor, backup.resource) || isEqual(editor.resource, backup.resource)) { + return editor; + } + } + + return undefined; + } + + private async openEditors(backups: IWorkingCopyIdentifier[]): Promise { + const hasOpenedEditors = this.editorService.visibleEditors.length > 0; + const editors = await Promises.settled(backups.map((backup, index) => this.resolveEditor(backup, index, hasOpenedEditors))); + + await this.editorService.openEditors(editors); + } + + private async resolveEditor(backup: IWorkingCopyIdentifier, index: number, hasOpenedEditors: boolean): Promise { + + // Set editor as `inactive` if we have other editors + const options = { pinned: true, preserveFocus: true, inactive: index > 0 || hasOpenedEditors }; + + // This is a (weak) strategy to find out if the untitled input had + // an associated file path or not by just looking at the path. and + // if so, we must ensure to restore the local resource it had. + if (backup.resource.scheme === Schemas.untitled && !LegacyWorkingCopyBackupRestorer.UNTITLED_REGEX.test(backup.resource.path)) { + return { resource: toLocalResource(backup.resource, this.environmentService.remoteAuthority, this.pathService.defaultUriScheme), options, forceUntitled: true }; + } + + // Handle custom editors by asking the custom editor input factory + // to create the input. + const customFactory = this.editorInputFactories.getCustomEditorInputFactory(backup.resource.scheme); + if (customFactory) { + const editor = await customFactory.createCustomEditorInput(backup.resource, this.instantiationService); + + return { editor, options }; + } + + // Finally return with a simple resource based input + return { resource: backup.resource, options }; + } +} diff --git a/src/vs/workbench/services/workingCopy/common/workingCopy.ts b/src/vs/workbench/services/workingCopy/common/workingCopy.ts new file mode 100644 index 00000000..419f6a12 --- /dev/null +++ b/src/vs/workbench/services/workingCopy/common/workingCopy.ts @@ -0,0 +1,174 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Event } from 'vs/base/common/event'; +import { URI } from 'vs/base/common/uri'; +import { ISaveOptions, IRevertOptions } from 'vs/workbench/common/editor'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { VSBufferReadable, VSBufferReadableStream } from 'vs/base/common/buffer'; + +export const enum WorkingCopyCapabilities { + + /** + * Signals no specific capability for the working copy. + */ + None = 0, + + /** + * Signals that the working copy requires + * additional input when saving, e.g. an + * associated path to save to. + */ + Untitled = 1 << 1 +} + +/** + * Data to be associated with working copy backups. Use + * `IWorkingCopyBackupService.resolve(workingCopy)` to + * retrieve the backup when loading the working copy. + */ +export interface IWorkingCopyBackup { + + /** + * Any serializable metadata to be associated with the backup. + */ + meta?: IWorkingCopyBackupMeta; + + /** + * The actual snapshot of the contents of the working copy at + * the time the backup was made. + */ + content?: VSBufferReadable | VSBufferReadableStream; +} + +/** + * Working copy backup metadata that can be associated + * with the backup. + * + * Some properties may be reserved as outlined here and + * cannot be used. + */ +export interface IWorkingCopyBackupMeta { + + /** + * Any property needs to be serializable through JSON. + */ + [key: string]: unknown; + + /** + * `typeId` is a reverved property that cannot be used + * as backup metadata. + */ + typeId?: never; +} + +/** + * @deprecated it is important to provide a type identifier + * for working copies to enable all capabilities. + */ +export const NO_TYPE_ID = ''; + +/** + * Every working copy has in common that it is identified by + * a resource `URI` and a `typeId`. There can only be one + * working copy registered with the same `URI` and `typeId`. + */ +export interface IWorkingCopyIdentifier { + + /** + * The type identifier of the working copy for grouping + * working copies of the same domain together. + * + * There can only be one working copy for a given resource + * and type identifier. + */ + readonly typeId: string; + + /** + * The resource of the working copy must be unique for + * working copies of the same `typeId`. + */ + readonly resource: URI; +} + +/** + * A working copy is an abstract concept to unify handling of + * data that can be worked on (e.g. edited) in an editor. + * + * + * A working copy resource may be the backing store of the data + * (e.g. a file on disk), but that is not a requirement. If + * your working copy is file based, consider to use the + * `IFileWorkingCopy` instead that simplifies a lot of things + * when working with file based working copies. + */ +export interface IWorkingCopy extends IWorkingCopyIdentifier { + + /** + * Human readable name of the working copy. + */ + readonly name: string; + + /** + * The capabilities of the working copy. + */ + readonly capabilities: WorkingCopyCapabilities; + + + //#region Events + + /** + * Used by the workbench to signal if the working copy + * is dirty or not. Typically a working copy is dirty + * once changed until saved or reverted. + */ + readonly onDidChangeDirty: Event; + + /** + * Used by the workbench e.g. to trigger auto-save + * (unless this working copy is untitled) and backups. + */ + readonly onDidChangeContent: Event; + + //#endregion + + + //#region Dirty Tracking + + isDirty(): boolean; + + //#endregion + + + //#region Save / Backup + + /** + * The workbench may call this method often after it receives + * the `onDidChangeContent` event for the working copy. The motivation + * is to allow to quit VSCode with dirty working copies present. + * + * Providers of working copies should use `IWorkingCopyBackupService.resolve(workingCopy)` + * to retrieve the backup metadata associated when loading the working copy. + * + * @param token support for cancellation + */ + backup(token: CancellationToken): Promise; + + /** + * Asks the working copy to save. If the working copy was dirty, it is + * expected to be non-dirty after this operation has finished. + * + * @returns `true` if the operation was successful and `false` otherwise. + */ + save(options?: ISaveOptions): Promise; + + /** + * Asks the working copy to revert. If the working copy was dirty, it is + * expected to be non-dirty after this operation has finished. + */ + revert(options?: IRevertOptions): Promise; + + //#endregion +} diff --git a/src/vs/workbench/services/workingCopy/common/workingCopyBackup.ts b/src/vs/workbench/services/workingCopy/common/workingCopyBackup.ts new file mode 100644 index 00000000..ca0f2f2e --- /dev/null +++ b/src/vs/workbench/services/workingCopy/common/workingCopyBackup.ts @@ -0,0 +1,83 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { VSBufferReadable, VSBufferReadableStream } from 'vs/base/common/buffer'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { IWorkingCopyBackupMeta, IWorkingCopyIdentifier } from 'vs/workbench/services/workingCopy/common/workingCopy'; + +export const IWorkingCopyBackupService = createDecorator('workingCopyBackupService'); + +/** + * A resolved working copy backup carries the backup value + * as well as associated metadata with it. + */ +export interface IResolvedWorkingCopyBackup { + + /** + * The content of the working copy backup. + */ + readonly value: VSBufferReadableStream; + + /** + * Additional metadata that is associated with + * the working copy backup. + */ + readonly meta?: T; +} + +/** + * The working copy backup service is the main service to handle backups + * for working copies. + * Methods allow to persist and resolve working copy backups from the file + * system. + */ +export interface IWorkingCopyBackupService { + + readonly _serviceBrand: undefined; + + /** + * Finds out if there are any working copy backups stored. + */ + hasBackups(): Promise; + + /** + * Finds out if a working copy backup with the given identifier + * and optional version exists. + * + * Note: if the backup service has not been initialized yet, this may return + * the wrong result. Always use `resolve()` if you can do a long running + * operation. + */ + hasBackupSync(identifier: IWorkingCopyIdentifier, versionId?: number): boolean; + + /** + * Gets a list of working copy backups for the current workspace. + */ + getBackups(): Promise; + + /** + * Resolves the working copy backup for the given identifier if that exists. + */ + resolve(identifier: IWorkingCopyIdentifier): Promise | undefined>; + + /** + * Stores a new working copy backup for the given identifier. + */ + backup(identifier: IWorkingCopyIdentifier, content?: VSBufferReadable | VSBufferReadableStream, versionId?: number, meta?: IWorkingCopyBackupMeta, token?: CancellationToken): Promise; + + /** + * Discards the working copy backup associated with the identifier if it exists. + */ + discardBackup(identifier: IWorkingCopyIdentifier): Promise; + + /** + * Discards all working copy backups. + * + * The optional set of identifiers can be provided to discard all but the + * provided ones. + */ + discardBackups(except?: IWorkingCopyIdentifier[]): Promise; +} diff --git a/src/vs/workbench/services/workingCopy/common/workingCopyBackupService.ts b/src/vs/workbench/services/workingCopy/common/workingCopyBackupService.ts new file mode 100644 index 00000000..9fa72082 --- /dev/null +++ b/src/vs/workbench/services/workingCopy/common/workingCopyBackupService.ts @@ -0,0 +1,596 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { basename, isEqual, joinPath } from 'vs/base/common/resources'; +import { URI } from 'vs/base/common/uri'; +import { coalesce } from 'vs/base/common/arrays'; +import { equals, deepClone } from 'vs/base/common/objects'; +import { Promises, ResourceQueue } from 'vs/base/common/async'; +import { IResolvedWorkingCopyBackup, IWorkingCopyBackupService } from 'vs/workbench/services/workingCopy/common/workingCopyBackup'; +import { IFileService, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; +import { ResourceMap } from 'vs/base/common/map'; +import { isReadableStream, peekStream } from 'vs/base/common/stream'; +import { bufferToStream, prefixedBufferReadable, prefixedBufferStream, readableToBuffer, streamToBuffer, VSBuffer, VSBufferReadable, VSBufferReadableStream } from 'vs/base/common/buffer'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { ILogService } from 'vs/platform/log/common/log'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { Schemas } from 'vs/base/common/network'; +import { hash } from 'vs/base/common/hash'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { LegacyWorkingCopyBackupRestorer } from 'vs/workbench/services/workingCopy/common/legacyBackupRestorer'; +import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { isEmptyObject } from 'vs/base/common/types'; +import { IWorkingCopyBackupMeta, IWorkingCopyIdentifier } from 'vs/workbench/services/workingCopy/common/workingCopy'; + +export class WorkingCopyBackupsModel { + + private readonly cache = new ResourceMap<{ versionId?: number, meta?: IWorkingCopyBackupMeta }>(); + + static async create(backupRoot: URI, fileService: IFileService): Promise { + const model = new WorkingCopyBackupsModel(backupRoot, fileService); + + await model.resolve(); + + return model; + } + + private constructor(private backupRoot: URI, private fileService: IFileService) { } + + private async resolve(): Promise { + try { + const backupRootStat = await this.fileService.resolve(this.backupRoot); + if (backupRootStat.children) { + await Promises.settled(backupRootStat.children + .filter(child => child.isDirectory) + .map(async backupSchemaFolder => { + + // Read backup directory for backups + const backupSchemaFolderStat = await this.fileService.resolve(backupSchemaFolder.resource); + + // Remember known backups in our caches + if (backupSchemaFolderStat.children) { + for (const backupForSchema of backupSchemaFolderStat.children) { + if (!backupForSchema.isDirectory) { + this.add(backupForSchema.resource); + } + } + } + })); + } + } catch (error) { + // ignore any errors + } + } + + add(resource: URI, versionId = 0, meta?: IWorkingCopyBackupMeta): void { + this.cache.set(resource, { versionId, meta: deepClone(meta) }); // make sure to not store original meta in our cache... + } + + count(): number { + return this.cache.size; + } + + has(resource: URI, versionId?: number, meta?: IWorkingCopyBackupMeta): boolean { + const entry = this.cache.get(resource); + if (!entry) { + return false; // unknown resource + } + + if (typeof versionId === 'number' && versionId !== entry.versionId) { + return false; // different versionId + } + + if (meta && !equals(meta, entry.meta)) { + return false; // different metadata + } + + return true; + } + + get(): URI[] { + return Array.from(this.cache.keys()); + } + + remove(resource: URI): void { + this.cache.delete(resource); + } + + move(source: URI, target: URI): void { + const entry = this.cache.get(source); + if (entry) { + this.cache.delete(source); + this.cache.set(target, entry); + } + } + + clear(): void { + this.cache.clear(); + } +} + +export abstract class WorkingCopyBackupService implements IWorkingCopyBackupService { + + declare readonly _serviceBrand: undefined; + + private impl: NativeWorkingCopyBackupServiceImpl | InMemoryWorkingCopyBackupService; + + constructor( + backupWorkspaceHome: URI | undefined, + @IFileService protected fileService: IFileService, + @ILogService private readonly logService: ILogService + ) { + this.impl = this.initialize(backupWorkspaceHome); + } + + private initialize(backupWorkspaceHome: URI | undefined): NativeWorkingCopyBackupServiceImpl | InMemoryWorkingCopyBackupService { + if (backupWorkspaceHome) { + return new NativeWorkingCopyBackupServiceImpl(backupWorkspaceHome, this.fileService, this.logService); + } + + return new InMemoryWorkingCopyBackupService(); + } + + reinitialize(backupWorkspaceHome: URI | undefined): void { + + // Re-init implementation (unless we are running in-memory) + if (this.impl instanceof NativeWorkingCopyBackupServiceImpl) { + if (backupWorkspaceHome) { + this.impl.initialize(backupWorkspaceHome); + } else { + this.impl = new InMemoryWorkingCopyBackupService(); + } + } + } + + hasBackups(): Promise { + return this.impl.hasBackups(); + } + + hasBackupSync(identifier: IWorkingCopyIdentifier, versionId?: number): boolean { + return this.impl.hasBackupSync(identifier, versionId); + } + + backup(identifier: IWorkingCopyIdentifier, content?: VSBufferReadableStream | VSBufferReadable, versionId?: number, meta?: IWorkingCopyBackupMeta, token?: CancellationToken): Promise { + return this.impl.backup(identifier, content, versionId, meta, token); + } + + discardBackup(identifier: IWorkingCopyIdentifier): Promise { + return this.impl.discardBackup(identifier); + } + + discardBackups(except?: IWorkingCopyIdentifier[]): Promise { + return this.impl.discardBackups(except); + } + + getBackups(): Promise { + return this.impl.getBackups(); + } + + resolve(identifier: IWorkingCopyIdentifier): Promise | undefined> { + return this.impl.resolve(identifier); + } + + toBackupResource(identifier: IWorkingCopyIdentifier): URI { + return this.impl.toBackupResource(identifier); + } +} + +class NativeWorkingCopyBackupServiceImpl extends Disposable implements IWorkingCopyBackupService { + + private static readonly PREAMBLE_END_MARKER = '\n'; + private static readonly PREAMBLE_END_MARKER_CHARCODE = '\n'.charCodeAt(0); + private static readonly PREAMBLE_META_SEPARATOR = ' '; // using a character that is know to be escaped in a URI as separator + private static readonly PREAMBLE_MAX_LENGTH = 10000; + + declare readonly _serviceBrand: undefined; + + private readonly ioOperationQueues = this._register(new ResourceQueue()); // queue IO operations to ensure write/delete file order + + private ready!: Promise; + private model: WorkingCopyBackupsModel | undefined = undefined; + + constructor( + private backupWorkspaceHome: URI, + @IFileService private readonly fileService: IFileService, + @ILogService private readonly logService: ILogService + ) { + super(); + + this.initialize(backupWorkspaceHome); + } + + initialize(backupWorkspaceResource: URI): void { + this.backupWorkspaceHome = backupWorkspaceResource; + + this.ready = this.doInitialize(); + } + + private async doInitialize(): Promise { + + // Create backup model + this.model = await WorkingCopyBackupsModel.create(this.backupWorkspaceHome, this.fileService); + + // Migrate hashes as needed. We used to hash with a MD5 + // sum of the path but switched to our own simpler hash + // to avoid a node.js dependency. We still want to + // support the older hash so we: + // - iterate over all backups + // - detect if the file name length is 32 (MD5 length) + // - read the backup's target file path + // - rename the backup to the new hash + // - update the backup in our model + // + // TODO@bpasero remove me eventually + for (const backupResource of this.model.get()) { + if (basename(backupResource).length !== 32) { + continue; // not a MD5 hash, already uses new hash function + } + + try { + const identifier = await this.resolveIdentifier(backupResource); + if (!identifier) { + this.logService.warn(`Backup: Unable to read target URI of backup ${backupResource} for migration to new hash.`); + continue; + } + + const expectedBackupResource = this.toBackupResource(identifier); + if (!isEqual(expectedBackupResource, backupResource)) { + await this.fileService.move(backupResource, expectedBackupResource, true); + this.model.move(backupResource, expectedBackupResource); + } + } catch (error) { + this.logService.error(`Backup: Unable to migrate backup ${backupResource} to new hash.`); + } + } + + return this.model; + } + + async hasBackups(): Promise { + const model = await this.ready; + + return model.count() > 0; + } + + hasBackupSync(identifier: IWorkingCopyIdentifier, versionId?: number): boolean { + if (!this.model) { + return false; + } + + const backupResource = this.toBackupResource(identifier); + + return this.model.has(backupResource, versionId); + } + + async backup(identifier: IWorkingCopyIdentifier, content?: VSBufferReadable | VSBufferReadableStream, versionId?: number, meta?: IWorkingCopyBackupMeta, token?: CancellationToken): Promise { + const model = await this.ready; + if (token?.isCancellationRequested) { + return; + } + + const backupResource = this.toBackupResource(identifier); + if (model.has(backupResource, versionId, meta)) { + return; // return early if backup version id matches requested one + } + + return this.ioOperationQueues.queueFor(backupResource).queue(async () => { + if (token?.isCancellationRequested) { + return; + } + + // Encode as: Resource + META-START + Meta + END + // and respect max length restrictions in case + // meta is too large. + let preamble = this.createPreamble(identifier, meta); + if (preamble.length >= NativeWorkingCopyBackupServiceImpl.PREAMBLE_MAX_LENGTH) { + preamble = this.createPreamble(identifier); + } + + // Update backup with value + const preambleBuffer = VSBuffer.fromString(preamble); + let backupBuffer: VSBuffer | VSBufferReadableStream | VSBufferReadable; + if (isReadableStream(content)) { + backupBuffer = prefixedBufferStream(preambleBuffer, content); + } else if (content) { + backupBuffer = prefixedBufferReadable(preambleBuffer, content); + } else { + backupBuffer = VSBuffer.concat([preambleBuffer, VSBuffer.fromString('')]); + } + + await this.fileService.writeFile(backupResource, backupBuffer); + + // Update model + model.add(backupResource, versionId, meta); + }); + } + + private createPreamble(identifier: IWorkingCopyIdentifier, meta?: IWorkingCopyBackupMeta): string { + return `${identifier.resource.toString()}${NativeWorkingCopyBackupServiceImpl.PREAMBLE_META_SEPARATOR}${JSON.stringify({ ...meta, typeId: identifier.typeId })}${NativeWorkingCopyBackupServiceImpl.PREAMBLE_END_MARKER}`; + } + + async discardBackups(except?: IWorkingCopyIdentifier[]): Promise { + const model = await this.ready; + + // Discard all but some backups + if (Array.isArray(except) && except.length > 0) { + const exceptMap = new ResourceMap(); + for (const exceptWorkingCopy of except) { + exceptMap.set(this.toBackupResource(exceptWorkingCopy), true); + } + + for (const backupResource of model.get()) { + if (!exceptMap.has(backupResource)) { + await this.doDiscardBackup(backupResource); + } + } + } + + // Discard all backups + else { + await this.deleteIgnoreFileNotFound(this.backupWorkspaceHome); + + model.clear(); + } + } + + discardBackup(identifier: IWorkingCopyIdentifier): Promise { + const backupResource = this.toBackupResource(identifier); + + return this.doDiscardBackup(backupResource); + } + + private async doDiscardBackup(backupResource: URI): Promise { + const model = await this.ready; + + return this.ioOperationQueues.queueFor(backupResource).queue(async () => { + await this.deleteIgnoreFileNotFound(backupResource); + + model.remove(backupResource); + }); + } + + private async deleteIgnoreFileNotFound(backupResource: URI): Promise { + try { + await this.fileService.del(backupResource, { recursive: true }); + } catch (error) { + if ((error).fileOperationResult !== FileOperationResult.FILE_NOT_FOUND) { + throw error; // re-throw any other error than file not found which is OK + } + } + } + + async getBackups(): Promise { + const model = await this.ready; + + const backups = await Promise.all(model.get().map(backupResource => this.resolveIdentifier(backupResource))); + + return coalesce(backups); + } + + private async resolveIdentifier(backupResource: URI): Promise { + + // Read the entire backup preamble by reading up to + // `PREAMBLE_MAX_LENGTH` in the backup file until + // the `PREAMBLE_END_MARKER` is found + const backupPreamble = await this.readToMatchingString(backupResource, NativeWorkingCopyBackupServiceImpl.PREAMBLE_END_MARKER, NativeWorkingCopyBackupServiceImpl.PREAMBLE_MAX_LENGTH); + if (!backupPreamble) { + return undefined; + } + + // Figure out the offset in the preamble where meta + // information possibly starts. This can be `-1` for + // older backups without meta. + const metaStartIndex = backupPreamble.indexOf(NativeWorkingCopyBackupServiceImpl.PREAMBLE_META_SEPARATOR); + + // Extract the preamble content for resource and meta + let resourcePreamble: string; + let metaPreamble: string | undefined; + if (metaStartIndex > 0) { + resourcePreamble = backupPreamble.substring(0, metaStartIndex); + metaPreamble = backupPreamble.substr(metaStartIndex + 1); + } else { + resourcePreamble = backupPreamble; + metaPreamble = undefined; + } + + // Try to find the `typeId` in the meta data if possible + let typeId: string | undefined = undefined; + if (metaPreamble) { + try { + typeId = JSON.parse(metaPreamble).typeId; + } catch (error) { + // ignore JSON parse errors + } + } + + return { + typeId: typeId ?? '', // Fallback for previous backups that do not encode the typeId (TODO@bpasero remove me eventually) + resource: URI.parse(resourcePreamble) + }; + } + + private async readToMatchingString(backupResource: URI, matchingString: string, maximumBytesToRead: number): Promise { + const contents = (await this.fileService.readFile(backupResource, { length: maximumBytesToRead })).value.toString(); + + const matchingStringIndex = contents.indexOf(matchingString); + if (matchingStringIndex >= 0) { + return contents.substr(0, matchingStringIndex); + } + + // Unable to find matching string in file + return undefined; + } + + async resolve(identifier: IWorkingCopyIdentifier): Promise | undefined> { + const backupResource = this.toBackupResource(identifier); + + const model = await this.ready; + if (!model.has(backupResource)) { + return undefined; // require backup to be present + } + + // Load the backup content and peek into the first chunk + // to be able to resolve the meta data + const backupStream = await this.fileService.readFileStream(backupResource); + const peekedBackupStream = await peekStream(backupStream.value, 1); + const firstBackupChunk = VSBuffer.concat(peekedBackupStream.buffer); + + // We have seen reports (e.g. https://github.com/microsoft/vscode/issues/78500) where + // if VSCode goes down while writing the backup file, the file can turn empty because + // it always first gets truncated and then written to. In this case, we will not find + // the meta-end marker ('\n') and as such the backup can only be invalid. We bail out + // here if that is the case. + const preambleEndIndex = firstBackupChunk.buffer.indexOf(NativeWorkingCopyBackupServiceImpl.PREAMBLE_END_MARKER_CHARCODE); + if (preambleEndIndex === -1) { + this.logService.trace(`Backup: Could not find meta end marker in ${backupResource}. The file is probably corrupt (filesize: ${backupStream.size}).`); + + return undefined; + } + + const preambelRaw = firstBackupChunk.slice(0, preambleEndIndex).toString(); + + // Extract meta data (if any) + let meta: T | undefined; + const metaStartIndex = preambelRaw.indexOf(NativeWorkingCopyBackupServiceImpl.PREAMBLE_META_SEPARATOR); + if (metaStartIndex !== -1) { + try { + meta = JSON.parse(preambelRaw.substr(metaStartIndex + 1)); + + // `typeId` is a property that we add so we + // remove it when returning to clients. + if (typeof meta?.typeId === 'string') { + delete meta.typeId; + + if (isEmptyObject(meta)) { + meta = undefined; + } + } + } catch (error) { + // ignore JSON parse errors + } + } + + // Build a new stream without the preamble + const firstBackupChunkWithoutPreamble = firstBackupChunk.slice(preambleEndIndex + 1); + let value: VSBufferReadableStream; + if (peekedBackupStream.ended) { + value = bufferToStream(firstBackupChunkWithoutPreamble); + } else { + value = prefixedBufferStream(firstBackupChunkWithoutPreamble, peekedBackupStream.stream); + } + + return { value, meta }; + } + + toBackupResource(identifier: IWorkingCopyIdentifier): URI { + return joinPath(this.backupWorkspaceHome, identifier.resource.scheme, hashIdentifier(identifier)); + } +} + +export class InMemoryWorkingCopyBackupService implements IWorkingCopyBackupService { + + declare readonly _serviceBrand: undefined; + + private backups = new ResourceMap<{ typeId: string, content: VSBuffer, meta?: IWorkingCopyBackupMeta }>(); + + constructor() { } + + async hasBackups(): Promise { + return this.backups.size > 0; + } + + hasBackupSync(identifier: IWorkingCopyIdentifier, versionId?: number): boolean { + const backupResource = this.toBackupResource(identifier); + + return this.backups.has(backupResource); + } + + async backup(identifier: IWorkingCopyIdentifier, content?: VSBufferReadable | VSBufferReadableStream, versionId?: number, meta?: IWorkingCopyBackupMeta, token?: CancellationToken): Promise { + const backupResource = this.toBackupResource(identifier); + this.backups.set(backupResource, { + typeId: identifier.typeId, + content: content instanceof VSBuffer ? content : content ? isReadableStream(content) ? await streamToBuffer(content) : readableToBuffer(content) : VSBuffer.fromString(''), + meta + }); + } + + async resolve(identifier: IWorkingCopyIdentifier): Promise | undefined> { + const backupResource = this.toBackupResource(identifier); + const backup = this.backups.get(backupResource); + if (backup) { + return { value: bufferToStream(backup.content), meta: backup.meta as T | undefined }; + } + + return undefined; + } + + async getBackups(): Promise { + return Array.from(this.backups.entries()).map(([resource, backup]) => ({ typeId: backup.typeId, resource })); + } + + async discardBackup(identifier: IWorkingCopyIdentifier): Promise { + this.backups.delete(this.toBackupResource(identifier)); + } + + async discardBackups(except?: IWorkingCopyIdentifier[]): Promise { + if (Array.isArray(except) && except.length > 0) { + const exceptMap = new ResourceMap(); + for (const exceptWorkingCopy of except) { + exceptMap.set(this.toBackupResource(exceptWorkingCopy), true); + } + + for (const backup of await this.getBackups()) { + if (!exceptMap.has(this.toBackupResource(backup))) { + await this.discardBackup(backup); + } + } + } else { + this.backups.clear(); + } + } + + toBackupResource(identifier: IWorkingCopyIdentifier): URI { + return URI.from({ scheme: Schemas.inMemory, path: hashIdentifier(identifier) }); + } +} + +/* + * Exported only for testing + */ +export function hashIdentifier(identifier: IWorkingCopyIdentifier): string { + + // IMPORTANT: for backwards compatibility, ensure that + // we ignore the `typeId` unless a value is provided. + // To preserve previous backups without type id, we + // need to just hash the resource. Otherwise we use + // the type id as a seed to the resource path. + let resource: URI; + if (identifier.typeId.length > 0) { + const typeIdHash = hashString(identifier.typeId); + if (identifier.resource.path) { + resource = joinPath(identifier.resource, typeIdHash); + } else { + resource = identifier.resource.with({ path: typeIdHash }); + } + } else { + resource = identifier.resource; + } + + return hashPath(resource); +} + +function hashPath(resource: URI): string { + const str = resource.scheme === Schemas.file || resource.scheme === Schemas.untitled ? resource.fsPath : resource.toString(); + + return hashString(str); +} + +function hashString(str: string): string { + return hash(str).toString(16); +} + +// Register Backup Restorer +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(LegacyWorkingCopyBackupRestorer, LifecyclePhase.Starting); diff --git a/src/vs/workbench/contrib/backup/common/backupTracker.ts b/src/vs/workbench/services/workingCopy/common/workingCopyBackupTracker.ts similarity index 54% rename from src/vs/workbench/contrib/backup/common/backupTracker.ts rename to src/vs/workbench/services/workingCopy/common/workingCopyBackupTracker.ts index 6aa29c32..e071b467 100644 --- a/src/vs/workbench/contrib/backup/common/backupTracker.ts +++ b/src/vs/workbench/services/workingCopy/common/workingCopyBackupTracker.ts @@ -3,15 +3,63 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; +import { IWorkingCopyBackupService } from 'vs/workbench/services/workingCopy/common/workingCopyBackup'; import { Disposable, IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; -import { IWorkingCopyService, IWorkingCopy, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { IWorkingCopy, IWorkingCopyIdentifier, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopy'; import { ILogService } from 'vs/platform/log/common/log'; -import { ShutdownReason, ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { ShutdownReason, ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { AutoSaveMode, IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; +import { IWorkingCopyEditorHandler, IWorkingCopyEditorService } from 'vs/workbench/services/workingCopy/common/workingCopyEditorService'; +import { Promises } from 'vs/base/common/async'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IEditorInput } from 'vs/workbench/common/editor'; +import { EditorOverride } from 'vs/platform/editor/common/editor'; -export abstract class BackupTracker extends Disposable { +/** + * The working copy backup tracker deals with: + * - restoring backups that exist + * - creating backups for dirty working copies + * - deleting backups for saved working copies + * - handling backups on shutdown + */ +export abstract class WorkingCopyBackupTracker extends Disposable { + + constructor( + protected readonly workingCopyBackupService: IWorkingCopyBackupService, + protected readonly workingCopyService: IWorkingCopyService, + protected readonly logService: ILogService, + private readonly lifecycleService: ILifecycleService, + protected readonly filesConfigurationService: IFilesConfigurationService, + private readonly workingCopyEditorService: IWorkingCopyEditorService, + protected readonly editorService: IEditorService + ) { + super(); + + // Fill in initial dirty working copies + this.workingCopyService.dirtyWorkingCopies.forEach(workingCopy => this.onDidRegister(workingCopy)); + + this.registerListeners(); + } + + private registerListeners() { + + // Working Copy events + this._register(this.workingCopyService.onDidRegister(workingCopy => this.onDidRegister(workingCopy))); + this._register(this.workingCopyService.onDidUnregister(workingCopy => this.onDidUnregister(workingCopy))); + this._register(this.workingCopyService.onDidChangeDirty(workingCopy => this.onDidChangeDirty(workingCopy))); + this._register(this.workingCopyService.onDidChangeContent(workingCopy => this.onDidChangeContent(workingCopy))); + + // Lifecycle (handled in subclasses) + this.lifecycleService.onBeforeShutdown(event => event.veto(this.onBeforeShutdown(event.reason), 'veto.backups')); + + // Once a handler registers, restore backups + this._register(this.workingCopyEditorService.onDidRegisterHandler(handler => this.restoreBackups(handler))); + } + + + //#region Backup Creator // A map from working copy to a version ID we compute on each content // change. This version ID allows to e.g. ask if a backup for a specific @@ -35,33 +83,6 @@ export abstract class BackupTracker extends Disposable { [AutoSaveMode.AFTER_LONG_DELAY]: 1000 }; - constructor( - protected readonly backupFileService: IBackupFileService, - protected readonly workingCopyService: IWorkingCopyService, - protected readonly logService: ILogService, - protected readonly lifecycleService: ILifecycleService, - protected readonly filesConfigurationService: IFilesConfigurationService - ) { - super(); - - // Fill in initial dirty working copies - this.workingCopyService.dirtyWorkingCopies.forEach(workingCopy => this.onDidRegister(workingCopy)); - - this.registerListeners(); - } - - private registerListeners() { - - // Working Copy events - this._register(this.workingCopyService.onDidRegister(workingCopy => this.onDidRegister(workingCopy))); - this._register(this.workingCopyService.onDidUnregister(workingCopy => this.onDidUnregister(workingCopy))); - this._register(this.workingCopyService.onDidChangeDirty(workingCopy => this.onDidChangeDirty(workingCopy))); - this._register(this.workingCopyService.onDidChangeContent(workingCopy => this.onDidChangeContent(workingCopy))); - - // Lifecycle (handled in subclasses) - this.lifecycleService.onBeforeShutdown(event => event.veto(this.onBeforeShutdown(event.reason), 'veto.backups')); - } - private onDidRegister(workingCopy: IWorkingCopy): void { if (workingCopy.isDirty()) { this.scheduleBackup(workingCopy); @@ -105,7 +126,7 @@ export abstract class BackupTracker extends Disposable { // Clear any running backup operation this.cancelBackup(workingCopy); - this.logService.trace(`[backup tracker] scheduling backup`, workingCopy.resource.toString()); + this.logService.trace(`[backup tracker] scheduling backup`, workingCopy.resource.toString(true), workingCopy.typeId); // Schedule new backup const cts = new CancellationTokenSource(); @@ -116,7 +137,7 @@ export abstract class BackupTracker extends Disposable { // Backup if dirty if (workingCopy.isDirty()) { - this.logService.trace(`[backup tracker] creating backup`, workingCopy.resource.toString()); + this.logService.trace(`[backup tracker] creating backup`, workingCopy.resource.toString(true), workingCopy.typeId); try { const backup = await workingCopy.backup(cts.token); @@ -125,9 +146,9 @@ export abstract class BackupTracker extends Disposable { } if (workingCopy.isDirty()) { - this.logService.trace(`[backup tracker] storing backup`, workingCopy.resource.toString()); + this.logService.trace(`[backup tracker] storing backup`, workingCopy.resource.toString(true), workingCopy.typeId); - await this.backupFileService.backup(workingCopy.resource, backup.content, this.getContentVersion(workingCopy), backup.meta, cts.token); + await this.workingCopyBackupService.backup(workingCopy, backup.content, this.getContentVersion(workingCopy), backup.meta, cts.token); } } catch (error) { this.logService.error(error); @@ -145,7 +166,7 @@ export abstract class BackupTracker extends Disposable { // Keep in map for disposal as needed this.pendingBackups.set(workingCopy, toDisposable(() => { - this.logService.trace(`[backup tracker] clearing pending backup`, workingCopy.resource.toString()); + this.logService.trace(`[backup tracker] clearing pending backup`, workingCopy.resource.toString(true), workingCopy.typeId); cts.dispose(true); clearTimeout(handle); @@ -158,7 +179,7 @@ export abstract class BackupTracker extends Disposable { autoSaveMode = AutoSaveMode.OFF; // auto-save is never on for untitled working copies } - return BackupTracker.BACKUP_SCHEDULE_DELAYS[autoSaveMode]; + return WorkingCopyBackupTracker.BACKUP_SCHEDULE_DELAYS[autoSaveMode]; } protected getContentVersion(workingCopy: IWorkingCopy): number { @@ -166,13 +187,13 @@ export abstract class BackupTracker extends Disposable { } private discardBackup(workingCopy: IWorkingCopy): void { - this.logService.trace(`[backup tracker] discarding backup`, workingCopy.resource.toString()); + this.logService.trace(`[backup tracker] discarding backup`, workingCopy.resource.toString(true), workingCopy.typeId); // Clear any running backup operation this.cancelBackup(workingCopy); - // Forward to backup file service - this.backupFileService.discardBackup(workingCopy.resource); + // Forward to working copy backup service + this.workingCopyBackupService.discardBackup(workingCopy); } private cancelBackup(workingCopy: IWorkingCopy): void { @@ -181,4 +202,90 @@ export abstract class BackupTracker extends Disposable { } protected abstract onBeforeShutdown(reason: ShutdownReason): boolean | Promise; + + //#endregion + + + //#region Backup Restorer + + protected readonly unrestoredBackups = new Set(); + private readonly whenReady = this.resolveBackupsToRestore(); + + private async resolveBackupsToRestore(): Promise { + + // Wait for resolving backups until we are restored to reduce startup pressure + await this.lifecycleService.when(LifecyclePhase.Restored); + + // Remember each backup that needs to restore + for (const backup of await this.workingCopyBackupService.getBackups()) { + this.unrestoredBackups.add(backup); + } + } + + protected async restoreBackups(handler: IWorkingCopyEditorHandler): Promise { + + // Wait for backups to be resolved + await this.whenReady; + + // Figure out already opened editors for backups vs + // non-opened. + const openedEditorsForBackups: IEditorInput[] = []; + const nonOpenedEditorsForBackups: IEditorInput[] = []; + + // Ensure each backup that can be handled has an + // associated editor. + const restoredBackups = new Set(); + for (const unrestoredBackup of this.unrestoredBackups) { + const canHandleUnrestoredBackup = handler.handles(unrestoredBackup); + if (!canHandleUnrestoredBackup) { + continue; + } + + // Collect already opened editors for backup + let hasOpenedEditorForBackup = false; + for (const editor of this.editorService.editors) { + const isUnrestoredBackupOpened = handler.isOpen(unrestoredBackup, editor); + if (isUnrestoredBackupOpened) { + openedEditorsForBackups.push(editor); + hasOpenedEditorForBackup = true; + } + } + + // Otherwise, make sure to create at least one editor + // for the backup to show + if (!hasOpenedEditorForBackup) { + nonOpenedEditorsForBackups.push(handler.createEditor(unrestoredBackup)); + } + + // Remember as (potentially) restored + restoredBackups.add(unrestoredBackup); + } + + // Ensure editors are opened for each backup without editor + // in the background without stealing focus + if (nonOpenedEditorsForBackups.length > 0) { + await this.editorService.openEditors(nonOpenedEditorsForBackups.map(nonOpenedEditorForBackup => ({ + editor: nonOpenedEditorForBackup, + options: { + pinned: true, + preserveFocus: true, + inactive: true, + override: EditorOverride.DISABLED + } + }))); + + openedEditorsForBackups.push(...nonOpenedEditorsForBackups); + } + + // Then, resolve each editor to make sure the working copy + // is loaded and the dirty editor appears properly + await Promises.settled(openedEditorsForBackups.map(openedEditorsForBackup => openedEditorsForBackup.resolve())); + + // Finally, remove all handled backups from the list + for (const restoredBackup of restoredBackups) { + this.unrestoredBackups.delete(restoredBackup); + } + } + + //#endregion } diff --git a/src/vs/workbench/services/workingCopy/common/workingCopyEditorService.ts b/src/vs/workbench/services/workingCopy/common/workingCopyEditorService.ts new file mode 100644 index 00000000..5a5494ae --- /dev/null +++ b/src/vs/workbench/services/workingCopy/common/workingCopyEditorService.ts @@ -0,0 +1,99 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Emitter, Event } from 'vs/base/common/event'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { EditorsOrder, IEditorIdentifier, IEditorInput } from 'vs/workbench/common/editor'; +import { IWorkingCopy, IWorkingCopyIdentifier } from 'vs/workbench/services/workingCopy/common/workingCopy'; +import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; + +export const IWorkingCopyEditorService = createDecorator('workingCopyEditorService'); + +export interface IWorkingCopyEditorHandler { + + /** + * Whether the handler is capable of opening the specific backup in + * an editor. + */ + handles(workingCopy: IWorkingCopyIdentifier): boolean; + + /** + * Whether the provided working copy is opened in the provided editor. + */ + isOpen(workingCopy: IWorkingCopyIdentifier, editor: IEditorInput): boolean; + + /** + * Create an editor that is suitable of opening the provided working copy. + */ + createEditor(workingCopy: IWorkingCopyIdentifier): IEditorInput; +} + +export interface IWorkingCopyEditorService { + + readonly _serviceBrand: undefined; + + /** + * An event fired whenever a handler is registered. + */ + readonly onDidRegisterHandler: Event; + + /** + * Register a handler to the working copy editor service. + */ + registerHandler(handler: IWorkingCopyEditorHandler): IDisposable; + + /** + * Finds the first editor that can handle the provided working copy. + */ + findEditor(workingCopy: IWorkingCopy): IEditorIdentifier | undefined; +} + +export class WorkingCopyEditorService extends Disposable implements IWorkingCopyEditorService { + + declare readonly _serviceBrand: undefined; + + private readonly _onDidRegisterHandler = this._register(new Emitter()); + readonly onDidRegisterHandler = this._onDidRegisterHandler.event; + + private readonly handlers = new Set(); + + constructor(@IEditorService private readonly editorService: IEditorService) { + super(); + } + + registerHandler(handler: IWorkingCopyEditorHandler): IDisposable { + + // Add to registry and emit as event + this.handlers.add(handler); + this._onDidRegisterHandler.fire(handler); + + return toDisposable(() => this.handlers.delete(handler)); + } + + findEditor(workingCopy: IWorkingCopy): IEditorIdentifier | undefined { + for (const editorIdentifier of this.editorService.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE)) { + if (this.isOpen(workingCopy, editorIdentifier.editor)) { + return editorIdentifier; + } + } + + return undefined; + } + + private isOpen(workingCopy: IWorkingCopy, editor: IEditorInput): boolean { + for (const handler of this.handlers) { + if (handler.handles(workingCopy) && handler.isOpen(workingCopy, editor)) { + return true; + } + } + + return false; + } +} + +// Register Service +registerSingleton(IWorkingCopyEditorService, WorkingCopyEditorService); diff --git a/src/vs/workbench/services/workingCopy/common/workingCopyFileOperationParticipant.ts b/src/vs/workbench/services/workingCopy/common/workingCopyFileOperationParticipant.ts index 87d5005d..741f1d51 100644 --- a/src/vs/workbench/services/workingCopy/common/workingCopyFileOperationParticipant.ts +++ b/src/vs/workbench/services/workingCopy/common/workingCopyFileOperationParticipant.ts @@ -27,7 +27,7 @@ export class WorkingCopyFileOperationParticipant extends Disposable { return toDisposable(() => remove()); } - async participate(files: SourceTargetPair[], operation: FileOperation, undoInfo: IFileOperationUndoRedoInfo | undefined, token: CancellationToken | undefined): Promise { + async participate(files: SourceTargetPair[], operation: FileOperation, undoInfo: IFileOperationUndoRedoInfo | undefined, token: CancellationToken): Promise { const timeout = this.configurationService.getValue('files.participants.timeout'); if (timeout <= 0) { return; // disabled @@ -36,14 +36,14 @@ export class WorkingCopyFileOperationParticipant extends Disposable { // For each participant for (const participant of this.participants) { try { - await participant.participate(files, operation, undoInfo, timeout, token ?? CancellationToken.None); + await participant.participate(files, operation, undoInfo, timeout, token); } catch (err) { this.logService.warn(err); } } } - dispose(): void { + override dispose(): void { this.participants.clear(); } } diff --git a/src/vs/workbench/services/workingCopy/common/workingCopyFileService.ts b/src/vs/workbench/services/workingCopy/common/workingCopyFileService.ts index 97865f08..a9cb9a7c 100644 --- a/src/vs/workbench/services/workingCopy/common/workingCopyFileService.ts +++ b/src/vs/workbench/services/workingCopy/common/workingCopyFileService.ts @@ -12,7 +12,8 @@ import { URI } from 'vs/base/common/uri'; import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { IFileService, FileOperation, IFileStatWithMetadata } from 'vs/platform/files/common/files'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { IWorkingCopyService, IWorkingCopy } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { IWorkingCopy } from 'vs/workbench/services/workingCopy/common/workingCopy'; import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; import { WorkingCopyFileOperationParticipant } from 'vs/workbench/services/workingCopy/common/workingCopyFileOperationParticipant'; import { VSBuffer, VSBufferReadable, VSBufferReadableStream } from 'vs/base/common/buffer'; @@ -61,7 +62,7 @@ export interface WorkingCopyFileEvent extends IWaitUntil { /** * The array of source/target pair of files involved in given operation. */ - readonly files: SourceTargetPair[] + readonly files: readonly SourceTargetPair[]; } export interface IWorkingCopyFileOperationParticipant { @@ -165,7 +166,7 @@ export interface IWorkingCopyFileService { * Working copy owners can listen to the `onWillRunWorkingCopyFileOperation` and * `onDidRunWorkingCopyFileOperation` events to participate. */ - create(operations: ICreateFileOperation[], undoInfo?: IFileOperationUndoRedoInfo, token?: CancellationToken): Promise; + create(operations: ICreateFileOperation[], token: CancellationToken, undoInfo?: IFileOperationUndoRedoInfo): Promise; /** * Will create a folder and any parent folder that needs to be created. @@ -176,7 +177,7 @@ export interface IWorkingCopyFileService { * Note: events will only be emitted for the provided resource, but not any * parent folders that are being created as part of the operation. */ - createFolder(operations: ICreateOperation[], undoInfo?: IFileOperationUndoRedoInfo, token?: CancellationToken): Promise; + createFolder(operations: ICreateOperation[], token: CancellationToken, undoInfo?: IFileOperationUndoRedoInfo): Promise; /** * Will move working copies matching the provided resources and corresponding children @@ -185,7 +186,7 @@ export interface IWorkingCopyFileService { * Working copy owners can listen to the `onWillRunWorkingCopyFileOperation` and * `onDidRunWorkingCopyFileOperation` events to participate. */ - move(operations: IMoveOperation[], undoInfo?: IFileOperationUndoRedoInfo, token?: CancellationToken): Promise; + move(operations: IMoveOperation[], token: CancellationToken, undoInfo?: IFileOperationUndoRedoInfo): Promise; /** * Will copy working copies matching the provided resources and corresponding children @@ -194,7 +195,7 @@ export interface IWorkingCopyFileService { * Working copy owners can listen to the `onWillRunWorkingCopyFileOperation` and * `onDidRunWorkingCopyFileOperation` events to participate. */ - copy(operations: ICopyOperation[], undoInfo?: IFileOperationUndoRedoInfo, token?: CancellationToken): Promise; + copy(operations: ICopyOperation[], token: CancellationToken, undoInfo?: IFileOperationUndoRedoInfo): Promise; /** * Will delete working copies matching the provided resources and children @@ -203,7 +204,7 @@ export interface IWorkingCopyFileService { * Working copy owners can listen to the `onWillRunWorkingCopyFileOperation` and * `onDidRunWorkingCopyFileOperation` events to participate. */ - delete(operations: IDeleteOperation[], undoInfo?: IFileOperationUndoRedoInfo, token?: CancellationToken): Promise; + delete(operations: IDeleteOperation[], token: CancellationToken, undoInfo?: IFileOperationUndoRedoInfo): Promise; //#endregion @@ -222,7 +223,7 @@ export interface IWorkingCopyFileService { * If the resource is a folder and the scheme supports file operations, a working * copy that is dirty and is a child of that folder will also be returned. */ - getDirty(resource: URI): IWorkingCopy[]; + getDirty(resource: URI): readonly IWorkingCopy[]; //#endregion } @@ -272,15 +273,15 @@ export class WorkingCopyFileService extends Disposable implements IWorkingCopyFi //#region File operations - create(operations: ICreateFileOperation[], undoInfo?: IFileOperationUndoRedoInfo, token?: CancellationToken): Promise { - return this.doCreateFileOrFolder(operations, true, undoInfo, token); + create(operations: ICreateFileOperation[], token: CancellationToken, undoInfo?: IFileOperationUndoRedoInfo): Promise { + return this.doCreateFileOrFolder(operations, true, token, undoInfo); } - createFolder(operations: ICreateOperation[], undoInfo?: IFileOperationUndoRedoInfo, token?: CancellationToken): Promise { - return this.doCreateFileOrFolder(operations, false, undoInfo, token); + createFolder(operations: ICreateOperation[], token: CancellationToken, undoInfo?: IFileOperationUndoRedoInfo): Promise { + return this.doCreateFileOrFolder(operations, false, token, undoInfo); } - async doCreateFileOrFolder(operations: (ICreateFileOperation | ICreateOperation)[], isFile: boolean, undoInfo?: IFileOperationUndoRedoInfo, token?: CancellationToken): Promise { + async doCreateFileOrFolder(operations: (ICreateFileOperation | ICreateOperation)[], isFile: boolean, token: CancellationToken, undoInfo?: IFileOperationUndoRedoInfo): Promise { if (operations.length === 0) { return []; } @@ -300,7 +301,7 @@ export class WorkingCopyFileService extends Disposable implements IWorkingCopyFi // before events const event = { correlationId: this.correlationIds++, operation: FileOperation.CREATE, files }; - await this._onWillRunWorkingCopyFileOperation.fireAsync(event, CancellationToken.None); + await this._onWillRunWorkingCopyFileOperation.fireAsync(event, CancellationToken.None /* intentional: we currently only forward cancellation to participants */); // now actually create on disk let stats: IFileStatWithMetadata[]; @@ -313,26 +314,26 @@ export class WorkingCopyFileService extends Disposable implements IWorkingCopyFi } catch (error) { // error event - await this._onDidFailWorkingCopyFileOperation.fireAsync(event, CancellationToken.None); + await this._onDidFailWorkingCopyFileOperation.fireAsync(event, CancellationToken.None /* intentional: we currently only forward cancellation to participants */); throw error; } // after event - await this._onDidRunWorkingCopyFileOperation.fireAsync(event, CancellationToken.None); + await this._onDidRunWorkingCopyFileOperation.fireAsync(event, CancellationToken.None /* intentional: we currently only forward cancellation to participants */); return stats; } - async move(operations: IMoveOperation[], undoInfo?: IFileOperationUndoRedoInfo, token?: CancellationToken): Promise { - return this.doMoveOrCopy(operations, true, undoInfo, token); + async move(operations: IMoveOperation[], token: CancellationToken, undoInfo?: IFileOperationUndoRedoInfo): Promise { + return this.doMoveOrCopy(operations, true, token, undoInfo); } - async copy(operations: ICopyOperation[], undoInfo?: IFileOperationUndoRedoInfo, token?: CancellationToken): Promise { - return this.doMoveOrCopy(operations, false, undoInfo, token); + async copy(operations: ICopyOperation[], token: CancellationToken, undoInfo?: IFileOperationUndoRedoInfo): Promise { + return this.doMoveOrCopy(operations, false, token, undoInfo); } - private async doMoveOrCopy(operations: IMoveOperation[] | ICopyOperation[], move: boolean, undoInfo?: IFileOperationUndoRedoInfo, token?: CancellationToken): Promise { + private async doMoveOrCopy(operations: IMoveOperation[] | ICopyOperation[], move: boolean, token: CancellationToken, undoInfo?: IFileOperationUndoRedoInfo): Promise { const stats: IFileStatWithMetadata[] = []; // validate move/copy operation before starting @@ -349,7 +350,7 @@ export class WorkingCopyFileService extends Disposable implements IWorkingCopyFi // before event const event = { correlationId: this.correlationIds++, operation: move ? FileOperation.MOVE : FileOperation.COPY, files }; - await this._onWillRunWorkingCopyFileOperation.fireAsync(event, CancellationToken.None); + await this._onWillRunWorkingCopyFileOperation.fireAsync(event, CancellationToken.None /* intentional: we currently only forward cancellation to participants */); try { for (const { file: { source, target }, overwrite } of operations) { @@ -372,18 +373,18 @@ export class WorkingCopyFileService extends Disposable implements IWorkingCopyFi } catch (error) { // error event - await this._onDidFailWorkingCopyFileOperation.fireAsync(event, CancellationToken.None); + await this._onDidFailWorkingCopyFileOperation.fireAsync(event, CancellationToken.None /* intentional: we currently only forward cancellation to participants */); throw error; } // after event - await this._onDidRunWorkingCopyFileOperation.fireAsync(event, CancellationToken.None); + await this._onDidRunWorkingCopyFileOperation.fireAsync(event, CancellationToken.None /* intentional: we currently only forward cancellation to participants */); return stats; } - async delete(operations: IDeleteOperation[], undoInfo?: IFileOperationUndoRedoInfo, token?: CancellationToken): Promise { + async delete(operations: IDeleteOperation[], token: CancellationToken, undoInfo?: IFileOperationUndoRedoInfo): Promise { // validate delete operation before starting for (const operation of operations) { @@ -399,7 +400,7 @@ export class WorkingCopyFileService extends Disposable implements IWorkingCopyFi // before events const event = { correlationId: this.correlationIds++, operation: FileOperation.DELETE, files }; - await this._onWillRunWorkingCopyFileOperation.fireAsync(event, CancellationToken.None); + await this._onWillRunWorkingCopyFileOperation.fireAsync(event, CancellationToken.None /* intentional: we currently only forward cancellation to participants */); // check for any existing dirty working copies for the resource // and do a soft revert before deleting to be able to close @@ -417,13 +418,13 @@ export class WorkingCopyFileService extends Disposable implements IWorkingCopyFi } catch (error) { // error event - await this._onDidFailWorkingCopyFileOperation.fireAsync(event, CancellationToken.None); + await this._onDidFailWorkingCopyFileOperation.fireAsync(event, CancellationToken.None /* intentional: we currently only forward cancellation to participants */); throw error; } // after event - await this._onDidRunWorkingCopyFileOperation.fireAsync(event, CancellationToken.None); + await this._onDidRunWorkingCopyFileOperation.fireAsync(event, CancellationToken.None /* intentional: we currently only forward cancellation to participants */); } //#endregion @@ -437,7 +438,7 @@ export class WorkingCopyFileService extends Disposable implements IWorkingCopyFi return this.fileOperationParticipants.addFileOperationParticipant(participant); } - private runFileOperationParticipants(files: SourceTargetPair[], operation: FileOperation, undoInfo: IFileOperationUndoRedoInfo | undefined, token: CancellationToken | undefined): Promise { + private runFileOperationParticipants(files: SourceTargetPair[], operation: FileOperation, undoInfo: IFileOperationUndoRedoInfo | undefined, token: CancellationToken): Promise { return this.fileOperationParticipants.participate(files, operation, undoInfo, token); } diff --git a/src/vs/workbench/services/workingCopy/common/workingCopyService.ts b/src/vs/workbench/services/workingCopy/common/workingCopyService.ts index 09f1680e..61957986 100644 --- a/src/vs/workbench/services/workingCopy/common/workingCopyService.ts +++ b/src/vs/workbench/services/workingCopy/common/workingCopyService.ts @@ -9,130 +9,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; import { Disposable, IDisposable, toDisposable, DisposableStore, dispose } from 'vs/base/common/lifecycle'; import { ResourceMap } from 'vs/base/common/map'; -import { ISaveOptions, IRevertOptions } from 'vs/workbench/common/editor'; -import { ITextSnapshot } from 'vs/editor/common/model'; -import { CancellationToken } from 'vs/base/common/cancellation'; - -export const enum WorkingCopyCapabilities { - - /** - * Signals no specific capability for the working copy. - */ - None = 0, - - /** - * Signals that the working copy requires - * additional input when saving, e.g. an - * associated path to save to. - */ - Untitled = 1 << 1 -} - -/** - * Data to be associated with working copy backups. Use - * `IBackupFileService.resolve(workingCopy.resource)` to - * retrieve the backup when loading the working copy. - */ -export interface IWorkingCopyBackup { - - /** - * Any serializable metadata to be associated with the backup. - */ - meta?: MetaType; - - /** - * Use this for larger textual content of the backup. - */ - 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 { - - /** - * The unique resource of the working copy. There can only be one - * working copy in the system with the same URI. - */ - readonly resource: URI; - - /** - * Human readable name of the working copy. - */ - readonly name: string; - - /** - * The capabilities of the working copy. - */ - readonly capabilities: WorkingCopyCapabilities; - - - //#region Events - - /** - * Used by the workbench to signal if the working copy - * is dirty or not. Typically a working copy is dirty - * once changed until saved or reverted. - */ - readonly onDidChangeDirty: Event; - - /** - * Used by the workbench e.g. to trigger auto-save - * (unless this working copy is untitled) and backups. - */ - readonly onDidChangeContent: Event; - - //#endregion - - - //#region Dirty Tracking - - isDirty(): boolean; - - //#endregion - - - //#region Save / Backup - - /** - * The workbench may call this method often after it receives - * the `onDidChangeContent` event for the working copy. The motivation - * is to allow to quit VSCode with dirty working copies present. - * - * Providers of working copies should use `IBackupFileService.resolve(workingCopy.resource)` - * to retrieve the backup metadata associated when loading the working copy. - * - * @param token support for cancellation - */ - backup(token: CancellationToken): Promise; - - /** - * Asks the working copy to save. If the working copy was dirty, it is - * expected to be non-dirty after this operation has finished. - * - * @returns `true` if the operation was successful and `false` otherwise. - */ - save(options?: ISaveOptions): Promise; - - /** - * Asks the working copy to revert. If the working copy was dirty, it is - * expected to be non-dirty after this operation has finished. - */ - revert(options?: IRevertOptions): Promise; - - //#endregion -} +import { IWorkingCopy } from 'vs/workbench/services/workingCopy/common/workingCopy'; export const IWorkingCopyService = createDecorator('workingCopyService'); @@ -143,12 +20,24 @@ export interface IWorkingCopyService { //#region Events + /** + * An event for when a working copy was registered. + */ readonly onDidRegister: Event; + /** + * An event for when a working copy was unregistered. + */ readonly onDidUnregister: Event; + /** + * An event for when a working copy dirty state changed. + */ readonly onDidChangeDirty: Event; + /** + * An event for when a working copy's content changed. + */ readonly onDidChangeContent: Event; //#endregion @@ -156,20 +45,40 @@ export interface IWorkingCopyService { //#region Dirty Tracking + /** + * The number of dirty working copies that are registered. + */ readonly dirtyCount: number; - readonly dirtyWorkingCopies: IWorkingCopy[]; + /** + * All dirty working copies that are registered. + */ + readonly dirtyWorkingCopies: readonly IWorkingCopy[]; + /** + * Whether there is any registered working copy that is dirty. + */ readonly hasDirty: boolean; - isDirty(resource: URI): boolean; + /** + * Figure out if working copies with the given + * resource are dirty or not. + * + * @param resource the URI of the working copy + * @param typeId optional type identifier to only + * consider working copies of that type. + */ + isDirty(resource: URI, typeId?: string): boolean; //#endregion //#region Registry - readonly workingCopies: IWorkingCopy[]; + /** + * All working copies that are registered. + */ + readonly workingCopies: readonly IWorkingCopy[]; /** * Register a new working copy with the service. This method will @@ -210,20 +119,26 @@ export class WorkingCopyService extends Disposable implements IWorkingCopyServic get workingCopies(): IWorkingCopy[] { return Array.from(this._workingCopies.values()); } private _workingCopies = new Set(); - private readonly mapResourceToWorkingCopy = new ResourceMap(); + private readonly mapResourceToWorkingCopies = new ResourceMap>(); registerWorkingCopy(workingCopy: IWorkingCopy): IDisposable { - if (this.mapResourceToWorkingCopy.has(workingCopy.resource)) { - throw new Error(`Cannot register more than one working copy with the same resource ${workingCopy.resource.toString(true)}.`); + let workingCopiesForResource = this.mapResourceToWorkingCopies.get(workingCopy.resource); + if (workingCopiesForResource?.has(workingCopy.typeId)) { + throw new Error(`Cannot register more than one working copy with the same resource ${workingCopy.resource.toString(true)} and type ${workingCopy.typeId}.`); } - const disposables = new DisposableStore(); - - // Registry + // Registry (all) this._workingCopies.add(workingCopy); - this.mapResourceToWorkingCopy.set(workingCopy.resource, workingCopy); + + // Registry (type based) + if (!workingCopiesForResource) { + workingCopiesForResource = new Map(); + this.mapResourceToWorkingCopies.set(workingCopy.resource, workingCopiesForResource); + } + workingCopiesForResource.set(workingCopy.typeId, workingCopy); // Wire in Events + const disposables = new DisposableStore(); disposables.add(workingCopy.onDidChangeContent(() => this._onDidChangeContent.fire(workingCopy))); disposables.add(workingCopy.onDidChangeDirty(() => this._onDidChangeDirty.fire(workingCopy))); @@ -244,9 +159,14 @@ export class WorkingCopyService extends Disposable implements IWorkingCopyServic private unregisterWorkingCopy(workingCopy: IWorkingCopy): void { - // Remove from registry + // Registry (all) this._workingCopies.delete(workingCopy); - this.mapResourceToWorkingCopy.delete(workingCopy.resource); + + // Registry (type based) + const workingCopiesForResource = this.mapResourceToWorkingCopies.get(workingCopy.resource); + if (workingCopiesForResource?.delete(workingCopy.typeId) && workingCopiesForResource.size === 0) { + this.mapResourceToWorkingCopies.delete(workingCopy.resource); + } // If copy is dirty, ensure to fire an event to signal the dirty change // (a disposed working copy cannot account for being dirty in our model) @@ -286,10 +206,23 @@ export class WorkingCopyService extends Disposable implements IWorkingCopyServic return this.workingCopies.filter(workingCopy => workingCopy.isDirty()); } - isDirty(resource: URI): boolean { - const workingCopy = this.mapResourceToWorkingCopy.get(resource); - if (workingCopy) { - return workingCopy.isDirty(); + isDirty(resource: URI, typeId?: string): boolean { + const workingCopies = this.mapResourceToWorkingCopies.get(resource); + if (workingCopies) { + + // For a specific type + if (typeof typeId === 'string') { + return workingCopies.get(typeId)?.isDirty() ?? false; + } + + // Across all working copies + else { + for (const [, workingCopy] of workingCopies) { + if (workingCopy.isDirty()) { + return true; + } + } + } } return false; diff --git a/src/vs/workbench/services/backup/electron-sandbox/backupFileService.ts b/src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupService.ts similarity index 53% rename from src/vs/workbench/services/backup/electron-sandbox/backupFileService.ts rename to src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupService.ts index 5f4dbeff..758acf32 100644 --- a/src/vs/workbench/services/backup/electron-sandbox/backupFileService.ts +++ b/src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupService.ts @@ -3,15 +3,19 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { BackupFileService } from 'vs/workbench/services/backup/common/backupFileService'; +import { WorkingCopyBackupService } from 'vs/workbench/services/workingCopy/common/workingCopyBackupService'; import { URI } from 'vs/base/common/uri'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; +import { IWorkingCopyBackupService } from 'vs/workbench/services/workingCopy/common/workingCopyBackup'; import { IFileService } from 'vs/platform/files/common/files'; import { ILogService } from 'vs/platform/log/common/log'; import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { NativeWorkingCopyBackupTracker } from 'vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupTracker'; -export class NativeBackupFileService extends BackupFileService { +export class NativeWorkingCopyBackupService extends WorkingCopyBackupService { constructor( @INativeWorkbenchEnvironmentService environmentService: INativeWorkbenchEnvironmentService, @@ -22,4 +26,8 @@ export class NativeBackupFileService extends BackupFileService { } } -registerSingleton(IBackupFileService, NativeBackupFileService); +// Register Service +registerSingleton(IWorkingCopyBackupService, NativeWorkingCopyBackupService); + +// Register Backup Tracker +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(NativeWorkingCopyBackupTracker, LifecyclePhase.Starting); diff --git a/src/vs/workbench/contrib/backup/electron-sandbox/backupTracker.ts b/src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupTracker.ts similarity index 76% rename from src/vs/workbench/contrib/backup/electron-sandbox/backupTracker.ts rename to src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupTracker.ts index 2eaff593..c69ddb41 100644 --- a/src/vs/workbench/contrib/backup/electron-sandbox/backupTracker.ts +++ b/src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupTracker.ts @@ -4,18 +4,19 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; -import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; +import { IWorkingCopyBackupService } from 'vs/workbench/services/workingCopy/common/workingCopyBackup'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IFilesConfigurationService, AutoSaveMode } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; -import { IWorkingCopyService, IWorkingCopy, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopyService'; -import { ILifecycleService, LifecyclePhase, ShutdownReason } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { IWorkingCopy, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopy'; +import { ILifecycleService, ShutdownReason } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { ConfirmResult, IFileDialogService, IDialogService, getFileNamesMessage } from 'vs/platform/dialogs/common/dialogs'; import Severity from 'vs/base/common/severity'; import { WorkbenchState, IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { isMacintosh } from 'vs/base/common/platform'; import { HotExitConfiguration } from 'vs/platform/files/common/files'; import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; -import { BackupTracker } from 'vs/workbench/contrib/backup/common/backupTracker'; +import { WorkingCopyBackupTracker } from 'vs/workbench/services/workingCopy/common/workingCopyBackupTracker'; import { ILogService } from 'vs/platform/log/common/log'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { SaveReason } from 'vs/workbench/common/editor'; @@ -23,11 +24,13 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; import { Promises, raceCancellation } from 'vs/base/common/async'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IWorkingCopyEditorService } from 'vs/workbench/services/workingCopy/common/workingCopyEditorService'; -export class NativeBackupTracker extends BackupTracker implements IWorkbenchContribution { +export class NativeWorkingCopyBackupTracker extends WorkingCopyBackupTracker implements IWorkbenchContribution { constructor( - @IBackupFileService backupFileService: IBackupFileService, + @IWorkingCopyBackupService workingCopyBackupService: IWorkingCopyBackupService, @IFilesConfigurationService filesConfigurationService: IFilesConfigurationService, @IWorkingCopyService workingCopyService: IWorkingCopyService, @ILifecycleService lifecycleService: ILifecycleService, @@ -36,11 +39,13 @@ export class NativeBackupTracker extends BackupTracker implements IWorkbenchCont @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @INativeHostService private readonly nativeHostService: INativeHostService, @ILogService logService: ILogService, - @IEditorService private readonly editorService: IEditorService, @IEnvironmentService private readonly environmentService: IEnvironmentService, - @IProgressService private readonly progressService: IProgressService + @IProgressService private readonly progressService: IProgressService, + @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, + @IWorkingCopyEditorService workingCopyEditorService: IWorkingCopyEditorService, + @IEditorService editorService: IEditorService ) { - super(backupFileService, workingCopyService, logService, lifecycleService, filesConfigurationService); + super(workingCopyBackupService, workingCopyService, logService, lifecycleService, filesConfigurationService, workingCopyEditorService, editorService); } protected onBeforeShutdown(reason: ShutdownReason): boolean | Promise { @@ -55,7 +60,7 @@ export class NativeBackupTracker extends BackupTracker implements IWorkbenchCont return this.onBeforeShutdownWithoutDirty(); } - protected async onBeforeShutdownWithDirty(reason: ShutdownReason, dirtyWorkingCopies: IWorkingCopy[]): Promise { + protected async onBeforeShutdownWithDirty(reason: ShutdownReason, dirtyWorkingCopies: readonly IWorkingCopy[]): Promise { // If auto save is enabled, save all non-untitled working copies // and then check again for dirty copies @@ -81,14 +86,17 @@ export class NativeBackupTracker extends BackupTracker implements IWorkbenchCont return this.handleDirtyBeforeShutdown(dirtyWorkingCopies, reason); } - private async handleDirtyBeforeShutdown(dirtyWorkingCopies: IWorkingCopy[], reason: ShutdownReason): Promise { + private async handleDirtyBeforeShutdown(dirtyWorkingCopies: readonly IWorkingCopy[], reason: ShutdownReason): Promise { // Trigger backup if configured let backups: IWorkingCopy[] = []; let backupError: Error | undefined = undefined; if (this.filesConfigurationService.isHotExitEnabled) { try { - backups = await this.backupBeforeShutdown(dirtyWorkingCopies, reason); + const backupResult = await this.backupBeforeShutdown(dirtyWorkingCopies, reason); + backups = backupResult.backups; + backupError = backupResult.error; + if (backups.length === dirtyWorkingCopies.length) { return false; // no veto (backup was successful for all working copies) } @@ -97,7 +105,9 @@ export class NativeBackupTracker extends BackupTracker implements IWorkbenchCont } } - // we ran a backup but received an error that we show to the user + const remainingDirtyWorkingCopies = dirtyWorkingCopies.filter(workingCopy => !backups.includes(workingCopy)); + + // We ran a backup but received an error that we show to the user if (backupError) { if (this.environmentService.isExtensionDevelopment) { this.logService.error(`[backup tracker] error creating backups: ${backupError}`); @@ -105,15 +115,15 @@ export class NativeBackupTracker extends BackupTracker implements IWorkbenchCont return false; // do not block shutdown during extension development (https://github.com/microsoft/vscode/issues/115028) } - this.showErrorDialog(localize('backupTrackerBackupFailed', "The following dirty editors could not be saved to the back up location."), dirtyWorkingCopies, backupError); + this.showErrorDialog(localize('backupTrackerBackupFailed', "The following dirty editors could not be saved to the back up location."), remainingDirtyWorkingCopies, backupError); return true; // veto (the backup failed) } - // since a backup did not happen, we have to confirm for + // Since a backup did not happen, we have to confirm for // the working copies that did not successfully backup try { - return await this.confirmBeforeShutdown(dirtyWorkingCopies.filter(workingCopy => !backups.includes(workingCopy))); + return await this.confirmBeforeShutdown(remainingDirtyWorkingCopies); } catch (error) { if (this.environmentService.isExtensionDevelopment) { this.logService.error(`[backup tracker] error saving or reverting dirty working copies: ${error}`); @@ -121,7 +131,7 @@ export class NativeBackupTracker extends BackupTracker implements IWorkbenchCont return false; // do not block shutdown during extension development (https://github.com/microsoft/vscode/issues/115028) } - this.showErrorDialog(localize('backupTrackerConfirmFailed', "The following dirty editors could not be saved or reverted."), dirtyWorkingCopies, error); + this.showErrorDialog(localize('backupTrackerConfirmFailed', "The following dirty editors could not be saved or reverted."), remainingDirtyWorkingCopies, error); return true; // veto (save or revert failed) } @@ -140,7 +150,7 @@ export class NativeBackupTracker extends BackupTracker implements IWorkbenchCont this.logService.error(error ? `[backup tracker] ${msg}: ${error}` : `[backup tracker] ${msg}`); } - private async backupBeforeShutdown(dirtyWorkingCopies: IWorkingCopy[], reason: ShutdownReason): Promise { + private async backupBeforeShutdown(dirtyWorkingCopies: readonly IWorkingCopy[], reason: ShutdownReason): Promise<{ backups: IWorkingCopy[], error?: Error }> { // When quit is requested skip the confirm callback and attempt to backup all workspaces. // When quit is not requested the confirm callback should be shown when the window being @@ -181,37 +191,42 @@ export class NativeBackupTracker extends BackupTracker implements IWorkbenchCont } if (!doBackup) { - return []; + return { backups: [] }; } return this.doBackupBeforeShutdown(dirtyWorkingCopies); } - private async doBackupBeforeShutdown(dirtyWorkingCopies: IWorkingCopy[]): Promise { + private async doBackupBeforeShutdown(dirtyWorkingCopies: readonly IWorkingCopy[]): Promise<{ backups: IWorkingCopy[], error?: Error }> { const backups: IWorkingCopy[] = []; + let error: Error | undefined = undefined; await this.withProgressAndCancellation(async token => { // Perform a backup of all dirty working copies unless a backup already exists - await Promises.settled(dirtyWorkingCopies.map(async workingCopy => { - const contentVersion = this.getContentVersion(workingCopy); + try { + await Promises.settled(dirtyWorkingCopies.map(async workingCopy => { + const contentVersion = this.getContentVersion(workingCopy); - // Backup exists - if (this.backupFileService.hasBackupSync(workingCopy.resource, contentVersion)) { - backups.push(workingCopy); - } + // Backup exists + if (this.workingCopyBackupService.hasBackupSync(workingCopy, contentVersion)) { + backups.push(workingCopy); + } - // Backup does not exist - else { - const backup = await workingCopy.backup(token); - await this.backupFileService.backup(workingCopy.resource, backup.content, contentVersion, backup.meta); + // Backup does not exist + else { + const backup = await workingCopy.backup(token); + await this.workingCopyBackupService.backup(workingCopy, backup.content, contentVersion, backup.meta, token); - backups.push(workingCopy); - } - })); + backups.push(workingCopy); + } + })); + } catch (backupError) { + error = backupError; + } }, localize('backupBeforeShutdown', "Waiting for dirty editors to backup...")); - return backups; + return { backups, error }; } private async confirmBeforeShutdown(dirtyWorkingCopies: IWorkingCopy[]): Promise { @@ -310,22 +325,32 @@ export class NativeBackupTracker extends BackupTracker implements IWorkbenchCont } private noVeto(backupsToDiscard: IWorkingCopy[]): boolean | Promise { - if (this.lifecycleService.phase < LifecyclePhase.Restored) { - return false; // if editors have not restored, we are not up to speed with backups and thus should not discard them + if (!this.editorGroupService.isRestored()) { + return false; // if editors have not restored, we are very likely not up to speed with backups and thus should not discard them } - return Promises.settled(backupsToDiscard.map(workingCopy => this.backupFileService.discardBackup(workingCopy.resource))).then(() => false, () => false); + return Promises.settled(backupsToDiscard.map(workingCopy => this.workingCopyBackupService.discardBackup(workingCopy))).then(() => false, () => false); } private async onBeforeShutdownWithoutDirty(): Promise { + // If we have proceeded enough that editors and dirty state // has restored, we make sure that no backups lure around // given we have no known dirty working copy. This helps // to clean up stale backups as for example reported in // https://github.com/microsoft/vscode/issues/92962 - if (this.lifecycleService.phase >= LifecyclePhase.Restored) { + // + // However, we never want to discard backups that we know + // were not restored in the session. + if (this.editorGroupService.isRestored()) { try { - await this.backupFileService.discardBackups(); + + // Backups without `typeId` are handed in the legacy backup + // restorer still and thus we explicitly don't want to keep + // them on shutdown, otherwise they would always come back. + // TODO@bpasero remove this check once typeId has been adopted. + const backupsToKeep = Array.from(this.unrestoredBackups).filter(unrestoredBackup => unrestoredBackup.typeId.length > 0); + await this.workingCopyBackupService.discardBackups(backupsToKeep); } catch (error) { this.logService.error(`[backup tracker] error discarding backups: ${error}`); } diff --git a/src/vs/workbench/services/workingCopy/test/browser/fileWorkingCopy.test.ts b/src/vs/workbench/services/workingCopy/test/browser/fileWorkingCopy.test.ts index 20ea9ca7..8ed08c10 100644 --- a/src/vs/workbench/services/workingCopy/test/browser/fileWorkingCopy.test.ts +++ b/src/vs/workbench/services/workingCopy/test/browser/fileWorkingCopy.test.ts @@ -16,6 +16,7 @@ import { basename } from 'vs/base/common/resources'; import { FileChangesEvent, FileChangeType, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; import { SaveReason } from 'vs/workbench/common/editor'; import { Promises } from 'vs/base/common/async'; +import { consumeReadable, consumeStream, isReadableStream } from 'vs/base/common/stream'; export class TestFileWorkingCopyModel extends Disposable implements IFileWorkingCopyModel { @@ -51,16 +52,12 @@ export class TestFileWorkingCopyModel extends Disposable implements IFileWorking private doUpdate(newContents: string): void { this.contents = newContents; - this.alternateVersionId++; + this.versionId++; this._onDidChangeContent.fire({ isRedoing: false, isUndoing: false }); } - alternateVersionId = 0; - - getAlternativeVersionId(): number { - return this.alternateVersionId; - } + versionId = 0; pushedStackElement = false; @@ -68,7 +65,7 @@ export class TestFileWorkingCopyModel extends Disposable implements IFileWorking this.pushedStackElement = true; } - dispose(): void { + override dispose(): void { this._onWillDispose.fire(); super.dispose(); @@ -91,8 +88,8 @@ suite('FileWorkingCopy', function () { let accessor: TestServiceAccessor; let workingCopy: FileWorkingCopy; - function createWorkingCopy() { - return new FileWorkingCopy(resource, basename(resource), factory, accessor.fileService, accessor.logService, accessor.textFileService, accessor.filesConfigurationService, accessor.backupFileService, accessor.workingCopyService); + function createWorkingCopy(uri: URI = resource) { + return new FileWorkingCopy('testWorkingCopyType', uri, basename(uri), factory, accessor.fileService, accessor.logService, accessor.textFileService, accessor.filesConfigurationService, accessor.workingCopyBackupService, accessor.workingCopyService, accessor.notificationService, accessor.workingCopyEditorService, accessor.editorService, accessor.elevatedFileService); } setup(() => { @@ -106,6 +103,10 @@ suite('FileWorkingCopy', function () { workingCopy.dispose(); }); + test('requires good file system URI', async () => { + assert.throws(() => createWorkingCopy(URI.from({ scheme: 'unknown', path: 'somePath' }))); + }); + test('orphaned tracking', async () => { assert.strictEqual(workingCopy.hasState(FileWorkingCopyState.ORPHAN), false); @@ -194,7 +195,7 @@ suite('FileWorkingCopy', function () { assert.strictEqual(workingCopy.isDirty(), true); // Simulate an undo that goes back to the last (saved) version ID - workingCopy.model!.alternateVersionId--; + workingCopy.model!.versionId--; workingCopy.model?.fireContentChangeEvent({ isRedoing: false, isUndoing: true }); assert.strictEqual(workingCopy.isDirty(), false); @@ -244,7 +245,9 @@ suite('FileWorkingCopy', function () { await workingCopy.resolve({ contents: bufferToStream(VSBuffer.fromString('hello backup')) }); const backup = await workingCopy.backup(CancellationToken.None); - accessor.backupFileService.backup(workingCopy.resource, backup.content, undefined, backup.meta); + await accessor.workingCopyBackupService.backup(workingCopy, backup.content, undefined, backup.meta); + + assert.strictEqual(accessor.workingCopyBackupService.hasBackupSync(workingCopy), true); workingCopy.dispose(); @@ -277,7 +280,9 @@ suite('FileWorkingCopy', function () { assert.strictEqual(workingCopy.hasState(FileWorkingCopyState.ORPHAN), true); const backup = await workingCopy.backup(CancellationToken.None); - accessor.backupFileService.backup(workingCopy.resource, backup.content, undefined, backup.meta); + await accessor.workingCopyBackupService.backup(workingCopy, backup.content, undefined, backup.meta); + + assert.strictEqual(accessor.workingCopyBackupService.hasBackupSync(workingCopy), true); workingCopy.dispose(); @@ -349,7 +354,17 @@ suite('FileWorkingCopy', function () { const backup = await workingCopy.backup(CancellationToken.None); assert.ok(backup.meta); - assert.strictEqual(backup.content?.read(), 'hello backup'); + + let backupContents: string | undefined = undefined; + if (backup.content instanceof VSBuffer) { + backupContents = backup.content.toString(); + } else if (isReadableStream(backup.content)) { + backupContents = (await consumeStream(backup.content, chunks => VSBuffer.concat(chunks))).toString(); + } else if (backup.content) { + backupContents = consumeReadable(backup.content, chunks => VSBuffer.concat(chunks)).toString(); + } + + assert.strictEqual(backupContents, 'hello backup'); }); test('save (no errors)', async () => { @@ -404,26 +419,27 @@ suite('FileWorkingCopy', function () { assert.strictEqual(workingCopy.isDirty(), false); // multiple saves in parallel are fine and result - // in individual save operations when content changes + // in just one save operation (the second one + // cancels the first) workingCopy.model?.updateContents('hello save'); const firstSave = workingCopy.save(); workingCopy.model?.updateContents('hello save more'); const secondSave = workingCopy.save(); await Promises.settled([firstSave, secondSave]); - assert.strictEqual(savedCounter, 5); + assert.strictEqual(savedCounter, 4); assert.strictEqual(saveErrorCounter, 0); assert.strictEqual(workingCopy.isDirty(), false); // no save when not forced and not dirty await workingCopy.save(); - assert.strictEqual(savedCounter, 5); + assert.strictEqual(savedCounter, 4); assert.strictEqual(saveErrorCounter, 0); assert.strictEqual(workingCopy.isDirty(), false); // save when forced even when not dirty await workingCopy.save({ force: true }); - assert.strictEqual(savedCounter, 6); + assert.strictEqual(savedCounter, 5); assert.strictEqual(saveErrorCounter, 0); assert.strictEqual(workingCopy.isDirty(), false); @@ -437,7 +453,7 @@ suite('FileWorkingCopy', function () { assert.strictEqual(workingCopy.hasState(FileWorkingCopyState.ORPHAN), true); await workingCopy.save({ force: true }); - assert.strictEqual(savedCounter, 7); + assert.strictEqual(savedCounter, 6); assert.strictEqual(saveErrorCounter, 0); assert.strictEqual(workingCopy.isDirty(), false); assert.strictEqual(workingCopy.hasState(FileWorkingCopyState.ORPHAN), false); diff --git a/src/vs/workbench/services/workingCopy/test/browser/fileWorkingCopyManager.test.ts b/src/vs/workbench/services/workingCopy/test/browser/fileWorkingCopyManager.test.ts index 19b66526..9370d2f4 100644 --- a/src/vs/workbench/services/workingCopy/test/browser/fileWorkingCopyManager.test.ts +++ b/src/vs/workbench/services/workingCopy/test/browser/fileWorkingCopyManager.test.ts @@ -6,13 +6,14 @@ import * as assert from 'assert'; import { URI } from 'vs/base/common/uri'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { workbenchInstantiationService, TestServiceAccessor } from 'vs/workbench/test/browser/workbenchTestServices'; +import { workbenchInstantiationService, TestServiceAccessor, TestWillShutdownEvent } from 'vs/workbench/test/browser/workbenchTestServices'; import { FileWorkingCopyManager, IFileWorkingCopyManager } from 'vs/workbench/services/workingCopy/common/fileWorkingCopyManager'; import { IFileWorkingCopy, IFileWorkingCopyModel } from 'vs/workbench/services/workingCopy/common/fileWorkingCopy'; import { bufferToStream, VSBuffer } from 'vs/base/common/buffer'; import { FileChangesEvent, FileChangeType } from 'vs/platform/files/common/files'; import { timeout } from 'vs/base/common/async'; import { TestFileWorkingCopyModel, TestFileWorkingCopyModelFactory } from 'vs/workbench/services/workingCopy/test/browser/fileWorkingCopy.test'; +import { CancellationToken } from 'vs/base/common/cancellation'; suite('FileWorkingCopyManager', () => { @@ -26,7 +27,7 @@ suite('FileWorkingCopyManager', () => { accessor = instantiationService.createInstance(TestServiceAccessor); const factory = new TestFileWorkingCopyModelFactory(); - manager = new FileWorkingCopyManager(factory, accessor.fileService, accessor.lifecycleService, accessor.labelService, instantiationService, accessor.logService, accessor.fileDialogService, accessor.workingCopyFileService, accessor.uriIdentityService); + manager = new FileWorkingCopyManager('testWorkingCopyType', factory, accessor.fileService, accessor.lifecycleService, accessor.labelService, instantiationService, accessor.logService, accessor.fileDialogService, accessor.workingCopyFileService, accessor.uriIdentityService); }); teardown(() => { @@ -48,6 +49,7 @@ suite('FileWorkingCopyManager', () => { const workingCopy1 = await resolvePromise; assert.ok(workingCopy1); assert.ok(workingCopy1.model); + assert.strictEqual(workingCopy1.typeId, 'testWorkingCopyType'); assert.strictEqual(manager.get(resource), workingCopy1); const workingCopy2 = await manager.resolve(resource); @@ -110,7 +112,7 @@ suite('FileWorkingCopyManager', () => { workingCopy.dispose(); }); - test('multiple resolves execute in sequence', async () => { + test('multiple resolves execute in sequence (same resources)', async () => { const resource = URI.file('/test.html'); const firstPromise = manager.resolve(resource); @@ -127,6 +129,27 @@ suite('FileWorkingCopyManager', () => { workingCopy.dispose(); }); + test('multiple resolves execute in parallel (different resources)', async () => { + const resource1 = URI.file('/test1.html'); + const resource2 = URI.file('/test2.html'); + const resource3 = URI.file('/test3.html'); + + const firstPromise = manager.resolve(resource1); + const secondPromise = manager.resolve(resource2); + const thirdPromise = manager.resolve(resource3); + + const [workingCopy1, workingCopy2, workingCopy3] = await Promise.all([firstPromise, secondPromise, thirdPromise]); + + assert.strictEqual(manager.workingCopies.length, 3); + assert.strictEqual(workingCopy1.resource.toString(), resource1.toString()); + assert.strictEqual(workingCopy2.resource.toString(), resource2.toString()); + assert.strictEqual(workingCopy3.resource.toString(), resource3.toString()); + + workingCopy1.dispose(); + workingCopy2.dispose(); + workingCopy3.dispose(); + }); + test('removed from cache when working copy or model gets disposed', async () => { const resource = URI.file('/test.html'); @@ -248,7 +271,7 @@ suite('FileWorkingCopyManager', () => { workingCopy.model?.updateContents('hello create'); assert.strictEqual(workingCopy.isDirty(), true); - await accessor.workingCopyFileService.create([{ resource }]); + await accessor.workingCopyFileService.create([{ resource }], CancellationToken.None); assert.strictEqual(workingCopy.isDirty(), false); }); @@ -269,9 +292,9 @@ suite('FileWorkingCopyManager', () => { assert.strictEqual(sourceWorkingCopy.isDirty(), true); if (move) { - await accessor.workingCopyFileService.move([{ file: { source, target } }]); + await accessor.workingCopyFileService.move([{ file: { source, target } }], CancellationToken.None); } else { - await accessor.workingCopyFileService.copy([{ file: { source, target } }]); + await accessor.workingCopyFileService.copy([{ file: { source, target } }], CancellationToken.None); } const targetWorkingCopy = await manager.resolve(target); @@ -286,7 +309,7 @@ suite('FileWorkingCopyManager', () => { workingCopy.model?.updateContents('hello delete'); assert.strictEqual(workingCopy.isDirty(), true); - await accessor.workingCopyFileService.delete([{ resource }]); + await accessor.workingCopyFileService.delete([{ resource }], CancellationToken.None); assert.strictEqual(workingCopy.isDirty(), false); }); @@ -297,7 +320,7 @@ suite('FileWorkingCopyManager', () => { sourceWorkingCopy.model?.updateContents('hello move'); assert.strictEqual(sourceWorkingCopy.isDirty(), true); - await accessor.workingCopyFileService.move([{ file: { source, target: source } }]); + await accessor.workingCopyFileService.move([{ file: { source, target: source } }], CancellationToken.None); assert.strictEqual(sourceWorkingCopy.isDirty(), true); assert.strictEqual(sourceWorkingCopy.model?.contents, 'hello move'); @@ -443,4 +466,34 @@ suite('FileWorkingCopyManager', () => { let canDispose2 = manager.canDispose(workingCopy); assert.strictEqual(canDispose2, true); }); + + test('pending saves join on shutdown', async () => { + const resource1 = URI.file('/path/index_something1.txt'); + const resource2 = URI.file('/path/index_something2.txt'); + + const workingCopy1 = await manager.resolve(resource1); + workingCopy1.model?.updateContents('make dirty'); + + const workingCopy2 = await manager.resolve(resource2); + workingCopy2.model?.updateContents('make dirty'); + + let saved1 = false; + workingCopy1.save().then(() => { + saved1 = true; + }); + + let saved2 = false; + workingCopy2.save().then(() => { + saved2 = true; + }); + + const event = new TestWillShutdownEvent(); + accessor.lifecycleService.fireWillShutdown(event); + + assert.ok(event.value.length > 0); + await Promise.all(event.value); + + assert.strictEqual(saved1, true); + assert.strictEqual(saved2, true); + }); }); diff --git a/src/vs/workbench/contrib/backup/test/browser/backupRestorer.test.ts b/src/vs/workbench/services/workingCopy/test/browser/legacyBackupRestorer.test.ts similarity index 58% rename from src/vs/workbench/contrib/backup/test/browser/backupRestorer.test.ts rename to src/vs/workbench/services/workingCopy/test/browser/legacyBackupRestorer.test.ts index 0671325a..42e44905 100644 --- a/src/vs/workbench/contrib/backup/test/browser/backupRestorer.test.ts +++ b/src/vs/workbench/services/workingCopy/test/browser/legacyBackupRestorer.test.ts @@ -6,24 +6,23 @@ import * as assert from 'assert'; import { isWindows } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; -import { createTextBufferFactory } from 'vs/editor/common/model/textModel'; -import { DefaultEndOfLine } from 'vs/editor/common/model'; +import { bufferToReadable, VSBuffer } from 'vs/base/common/buffer'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { EditorService } from 'vs/workbench/services/editor/browser/editorService'; -import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; +import { IWorkingCopyBackupService } from 'vs/workbench/services/workingCopy/common/workingCopyBackup'; import { Schemas } from 'vs/base/common/network'; import { isEqual } from 'vs/base/common/resources'; -import { createEditorPart, InMemoryTestBackupFileService, registerTestResourceEditor, TestServiceAccessor, workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; -import { BackupRestorer } from 'vs/workbench/contrib/backup/common/backupRestorer'; -import { BrowserBackupTracker } from 'vs/workbench/contrib/backup/browser/backupTracker'; +import { createEditorPart, InMemoryTestWorkingCopyBackupService, registerTestResourceEditor, TestServiceAccessor, toUntypedWorkingCopyId, workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { LegacyWorkingCopyBackupRestorer } from 'vs/workbench/services/workingCopy/common/legacyBackupRestorer'; +import { BrowserWorkingCopyBackupTracker } from 'vs/workbench/services/workingCopy/browser/workingCopyBackupTracker'; import { DisposableStore } from 'vs/base/common/lifecycle'; -suite('BackupRestorer', () => { +suite('LegacyWorkingCopyBackupRestorer', () => { - class TestBackupRestorer extends BackupRestorer { - async doRestoreBackups(): Promise { - return super.doRestoreBackups(); + class TestBackupRestorer extends LegacyWorkingCopyBackupRestorer { + override async doRestoreLegacyBackups(): Promise { + return super.doRestoreLegacyBackups(); } } @@ -44,11 +43,11 @@ suite('BackupRestorer', () => { }); test('Restore backups', async function () { - const backupFileService = new InMemoryTestBackupFileService(); + const workingCopyBackupService = new InMemoryTestWorkingCopyBackupService(); const instantiationService = workbenchInstantiationService(); - instantiationService.stub(IBackupFileService, backupFileService); + instantiationService.stub(IWorkingCopyBackupService, workingCopyBackupService); - const part = createEditorPart(instantiationService, disposables); + const part = await createEditorPart(instantiationService, disposables); instantiationService.stub(IEditorGroupsService, part); @@ -57,19 +56,17 @@ suite('BackupRestorer', () => { accessor = instantiationService.createInstance(TestServiceAccessor); - await part.whenRestored; - - disposables.add(instantiationService.createInstance(BrowserBackupTracker)); + disposables.add(instantiationService.createInstance(BrowserWorkingCopyBackupTracker)); const restorer = instantiationService.createInstance(TestBackupRestorer); - // Backup 2 normal files and 2 untitled file - await backupFileService.backup(untitledFile1, createTextBufferFactory('untitled-1').create(DefaultEndOfLine.LF).textBuffer.createSnapshot(false)); - await backupFileService.backup(untitledFile2, createTextBufferFactory('untitled-2').create(DefaultEndOfLine.LF).textBuffer.createSnapshot(false)); - await backupFileService.backup(fooFile, createTextBufferFactory('fooFile').create(DefaultEndOfLine.LF).textBuffer.createSnapshot(false)); - await backupFileService.backup(barFile, createTextBufferFactory('barFile').create(DefaultEndOfLine.LF).textBuffer.createSnapshot(false)); + // Backup 2 normal files and 2 untitled files + await workingCopyBackupService.backup(toUntypedWorkingCopyId(untitledFile1), bufferToReadable(VSBuffer.fromString('untitled-1'))); + await workingCopyBackupService.backup(toUntypedWorkingCopyId(untitledFile2), bufferToReadable(VSBuffer.fromString('untitled-2'))); + await workingCopyBackupService.backup(toUntypedWorkingCopyId(fooFile), bufferToReadable(VSBuffer.fromString('fooFile'))); + await workingCopyBackupService.backup(toUntypedWorkingCopyId(barFile), bufferToReadable(VSBuffer.fromString('barFile'))); // Verify backups restored and opened as dirty - await restorer.doRestoreBackups(); + await restorer.doRestoreLegacyBackups(); assert.strictEqual(editorService.count, 4); assert.ok(editorService.editors.every(editor => editor.isDirty())); @@ -78,31 +75,33 @@ suite('BackupRestorer', () => { const resource = editor.resource; if (isEqual(resource, untitledFile1)) { const model = await accessor.textFileService.untitled.resolve({ untitledResource: resource }); - if (model.textEditorModel.getValue() !== 'untitled-1') { - const backupContents = await backupFileService.getBackupContents(untitledFile1); + if (model.textEditorModel?.getValue() !== 'untitled-1') { + const backupContents = await workingCopyBackupService.getBackupContents(model); assert.fail(`Unable to restore backup for resource ${untitledFile1.toString()}. Backup contents: ${backupContents}`); } model.dispose(); counter++; } else if (isEqual(resource, untitledFile2)) { const model = await accessor.textFileService.untitled.resolve({ untitledResource: resource }); - if (model.textEditorModel.getValue() !== 'untitled-2') { - const backupContents = await backupFileService.getBackupContents(untitledFile2); + if (model.textEditorModel?.getValue() !== 'untitled-2') { + const backupContents = await workingCopyBackupService.getBackupContents(model); assert.fail(`Unable to restore backup for resource ${untitledFile2.toString()}. Backup contents: ${backupContents}`); } model.dispose(); counter++; } else if (isEqual(resource, fooFile)) { - const model = await accessor.textFileService.files.get(fooFile!)?.load(); + const model = accessor.textFileService.files.get(fooFile); + await model?.resolve(); if (model?.textEditorModel?.getValue() !== 'fooFile') { - const backupContents = await backupFileService.getBackupContents(fooFile); + const backupContents = await workingCopyBackupService.getBackupContents(model!); assert.fail(`Unable to restore backup for resource ${fooFile.toString()}. Backup contents: ${backupContents}`); } counter++; } else { - const model = await accessor.textFileService.files.get(barFile!)?.load(); + const model = accessor.textFileService.files.get(barFile); + await model?.resolve(); if (model?.textEditorModel?.getValue() !== 'barFile') { - const backupContents = await backupFileService.getBackupContents(barFile); + const backupContents = await workingCopyBackupService.getBackupContents(model!); assert.fail(`Unable to restore backup for resource ${barFile.toString()}. Backup contents: ${backupContents}`); } counter++; diff --git a/src/vs/workbench/services/workingCopy/test/browser/workingCopyBackupTracker.test.ts b/src/vs/workbench/services/workingCopy/test/browser/workingCopyBackupTracker.test.ts new file mode 100644 index 00000000..5b06acb2 --- /dev/null +++ b/src/vs/workbench/services/workingCopy/test/browser/workingCopyBackupTracker.test.ts @@ -0,0 +1,386 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { URI } from 'vs/base/common/uri'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { EditorService } from 'vs/workbench/services/editor/browser/editorService'; +import { IUntitledTextResourceEditorInput } from 'vs/workbench/common/editor'; +import { IWorkingCopyBackupService } from 'vs/workbench/services/workingCopy/common/workingCopyBackup'; +import { toResource } from 'vs/base/test/common/utils'; +import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; +import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { IWorkingCopyBackup } from 'vs/workbench/services/workingCopy/common/workingCopy'; +import { ILogService } from 'vs/platform/log/common/log'; +import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { WorkingCopyBackupTracker } from 'vs/workbench/services/workingCopy/common/workingCopyBackupTracker'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; +import { createEditorPart, InMemoryTestWorkingCopyBackupService, registerTestResourceEditor, TestServiceAccessor, toTypedWorkingCopyId, toUntypedWorkingCopyId, workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestWorkingCopy } from 'vs/workbench/test/common/workbenchTestServices'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { timeout } from 'vs/base/common/async'; +import { BrowserWorkingCopyBackupTracker } from 'vs/workbench/services/workingCopy/browser/workingCopyBackupTracker'; +import { DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { IWorkingCopyEditorHandler, IWorkingCopyEditorService } from 'vs/workbench/services/workingCopy/common/workingCopyEditorService'; +import { bufferToReadable, VSBuffer } from 'vs/base/common/buffer'; +import { isWindows } from 'vs/base/common/platform'; +import { Schemas } from 'vs/base/common/network'; + +suite('WorkingCopyBackupTracker (browser)', function () { + let accessor: TestServiceAccessor; + let disposables = new DisposableStore(); + + setup(() => { + disposables.add(registerTestResourceEditor()); + }); + + teardown(() => { + disposables.clear(); + }); + + class TestWorkingCopyBackupTracker extends BrowserWorkingCopyBackupTracker { + + constructor( + @IWorkingCopyBackupService workingCopyBackupService: IWorkingCopyBackupService, + @IFilesConfigurationService filesConfigurationService: IFilesConfigurationService, + @IWorkingCopyService workingCopyService: IWorkingCopyService, + @ILifecycleService lifecycleService: ILifecycleService, + @ILogService logService: ILogService, + @IWorkingCopyEditorService workingCopyEditorService: IWorkingCopyEditorService, + @IEditorService editorService: IEditorService + ) { + super(workingCopyBackupService, filesConfigurationService, workingCopyService, lifecycleService, logService, workingCopyEditorService, editorService); + } + + protected override getBackupScheduleDelay(): number { + return 10; // Reduce timeout for tests + } + + getUnrestoredBackups() { + return this.unrestoredBackups; + } + + override async restoreBackups(handler: IWorkingCopyEditorHandler): Promise { + return super.restoreBackups(handler); + } + } + + class TestUntitledTextEditorInput extends UntitledTextEditorInput { + + resolved = false; + + override resolve() { + this.resolved = true; + + return super.resolve(); + } + } + + async function createTracker(): Promise<{ accessor: TestServiceAccessor, part: EditorPart, tracker: WorkingCopyBackupTracker, workingCopyBackupService: InMemoryTestWorkingCopyBackupService, instantiationService: IInstantiationService, cleanup: () => void }> { + const disposables = new DisposableStore(); + + const workingCopyBackupService = new InMemoryTestWorkingCopyBackupService(); + const instantiationService = workbenchInstantiationService(); + instantiationService.stub(IWorkingCopyBackupService, workingCopyBackupService); + + const part = await createEditorPart(instantiationService, disposables); + + disposables.add(registerTestResourceEditor()); + + instantiationService.stub(IEditorGroupsService, part); + + const editorService: EditorService = instantiationService.createInstance(EditorService); + instantiationService.stub(IEditorService, editorService); + + accessor = instantiationService.createInstance(TestServiceAccessor); + + const tracker = disposables.add(instantiationService.createInstance(TestWorkingCopyBackupTracker)); + + return { accessor, part, tracker, workingCopyBackupService: workingCopyBackupService, instantiationService, cleanup: () => disposables.dispose() }; + } + + async function untitledBackupTest(untitled: IUntitledTextResourceEditorInput = {}): Promise { + const { accessor, cleanup, workingCopyBackupService } = await createTracker(); + + const untitledEditor = (await accessor.editorService.openEditor(untitled))?.input as UntitledTextEditorInput; + + const untitledModel = await untitledEditor.resolve(); + + if (!untitled?.contents) { + untitledModel.textEditorModel?.setValue('Super Good'); + } + + await workingCopyBackupService.joinBackupResource(); + + assert.strictEqual(workingCopyBackupService.hasBackupSync(untitledModel), true); + + untitledModel.dispose(); + + await workingCopyBackupService.joinDiscardBackup(); + + assert.strictEqual(workingCopyBackupService.hasBackupSync(untitledModel), false); + + cleanup(); + } + + test('Track backups (untitled)', function () { + return untitledBackupTest(); + }); + + test('Track backups (untitled with initial contents)', function () { + return untitledBackupTest({ contents: 'Foo Bar' }); + }); + + test('Track backups (custom)', async function () { + const { accessor, cleanup, workingCopyBackupService } = await createTracker(); + + class TestBackupWorkingCopy extends TestWorkingCopy { + + backupDelay = 0; + + constructor(resource: URI) { + super(resource); + + accessor.workingCopyService.registerWorkingCopy(this); + } + + override async backup(token: CancellationToken): Promise { + await timeout(this.backupDelay); + + return {}; + } + } + + const resource = toResource.call(this, '/path/custom.txt'); + const customWorkingCopy = new TestBackupWorkingCopy(resource); + + // Normal + customWorkingCopy.setDirty(true); + await workingCopyBackupService.joinBackupResource(); + assert.strictEqual(workingCopyBackupService.hasBackupSync(customWorkingCopy), true); + + customWorkingCopy.setDirty(false); + customWorkingCopy.setDirty(true); + await workingCopyBackupService.joinBackupResource(); + assert.strictEqual(workingCopyBackupService.hasBackupSync(customWorkingCopy), true); + + customWorkingCopy.setDirty(false); + await workingCopyBackupService.joinDiscardBackup(); + assert.strictEqual(workingCopyBackupService.hasBackupSync(customWorkingCopy), false); + + // Cancellation + customWorkingCopy.setDirty(true); + await timeout(0); + customWorkingCopy.setDirty(false); + await workingCopyBackupService.joinDiscardBackup(); + assert.strictEqual(workingCopyBackupService.hasBackupSync(customWorkingCopy), false); + + customWorkingCopy.dispose(); + cleanup(); + }); + + async function restoreBackupsInit(): Promise<[TestWorkingCopyBackupTracker, TestServiceAccessor, IDisposable]> { + const fooFile = URI.file(isWindows ? 'c:\\Foo' : '/Foo'); + const barFile = URI.file(isWindows ? 'c:\\Bar' : '/Bar'); + const untitledFile1 = URI.from({ scheme: Schemas.untitled, path: 'Untitled-1' }); + const untitledFile2 = URI.from({ scheme: Schemas.untitled, path: 'Untitled-2' }); + + const disposables = new DisposableStore(); + + const workingCopyBackupService = new InMemoryTestWorkingCopyBackupService(); + const instantiationService = workbenchInstantiationService(); + instantiationService.stub(IWorkingCopyBackupService, workingCopyBackupService); + + const part = await createEditorPart(instantiationService, disposables); + + instantiationService.stub(IEditorGroupsService, part); + + const editorService: EditorService = instantiationService.createInstance(EditorService); + instantiationService.stub(IEditorService, editorService); + + accessor = instantiationService.createInstance(TestServiceAccessor); + + // Backup 2 normal files and 2 untitled files + const untitledFile1WorkingCopyId = toUntypedWorkingCopyId(untitledFile1); + const untitledFile2WorkingCopyId = toTypedWorkingCopyId(untitledFile2); + await workingCopyBackupService.backup(untitledFile1WorkingCopyId, bufferToReadable(VSBuffer.fromString('untitled-1'))); + await workingCopyBackupService.backup(untitledFile2WorkingCopyId, bufferToReadable(VSBuffer.fromString('untitled-2'))); + + const fooFileWorkingCopyId = toUntypedWorkingCopyId(fooFile); + const barFileWorkingCopyId = toTypedWorkingCopyId(barFile); + await workingCopyBackupService.backup(fooFileWorkingCopyId, bufferToReadable(VSBuffer.fromString('fooFile'))); + await workingCopyBackupService.backup(barFileWorkingCopyId, bufferToReadable(VSBuffer.fromString('barFile'))); + + const tracker = disposables.add(instantiationService.createInstance(TestWorkingCopyBackupTracker)); + + accessor.lifecycleService.phase = LifecyclePhase.Restored; + + return [tracker, accessor, disposables]; + } + + test('Restore backups (basics, some handled)', async function () { + const [tracker, accessor, disposables] = await restoreBackupsInit(); + + assert.strictEqual(tracker.getUnrestoredBackups().size, 0); + + let handlesCounter = 0; + let isOpenCounter = 0; + let createEditorCounter = 0; + + await tracker.restoreBackups({ + handles: workingCopy => { + handlesCounter++; + + return workingCopy.typeId === 'testBackupTypeId'; + }, + isOpen: (workingCopy, editor) => { + isOpenCounter++; + + return false; + }, + createEditor: workingCopy => { + createEditorCounter++; + + return accessor.instantiationService.createInstance(TestUntitledTextEditorInput, accessor.untitledTextEditorService.create({ initialValue: 'foo' })); + } + }); + + assert.strictEqual(handlesCounter, 4); + assert.strictEqual(isOpenCounter, 0); + assert.strictEqual(createEditorCounter, 2); + + assert.strictEqual(accessor.editorService.count, 2); + assert.ok(accessor.editorService.editors.every(editor => editor.isDirty())); + assert.strictEqual(tracker.getUnrestoredBackups().size, 2); + + for (const editor of accessor.editorService.editors) { + assert.ok(editor instanceof TestUntitledTextEditorInput); + assert.strictEqual(editor.resolved, true); + } + + dispose(disposables); + }); + + test('Restore backups (basics, none handled)', async function () { + const [tracker, accessor, disposables] = await restoreBackupsInit(); + + await tracker.restoreBackups({ + handles: workingCopy => false, + isOpen: (workingCopy, editor) => { throw new Error('unexpected'); }, + createEditor: workingCopy => { throw new Error('unexpected'); } + }); + + assert.strictEqual(accessor.editorService.count, 0); + assert.strictEqual(tracker.getUnrestoredBackups().size, 4); + + dispose(disposables); + }); + + test('Restore backups (basics, error case)', async function () { + const [tracker, , disposables] = await restoreBackupsInit(); + + try { + await tracker.restoreBackups({ + handles: workingCopy => true, + isOpen: (workingCopy, editor) => { throw new Error('unexpected'); }, + createEditor: workingCopy => { throw new Error('unexpected'); } + }); + } catch (error) { + // ignore + } + + assert.strictEqual(tracker.getUnrestoredBackups().size, 4); + + dispose(disposables); + }); + + test('Restore backups (multiple handlers)', async function () { + const [tracker, accessor, disposables] = await restoreBackupsInit(); + + const firstHandler = tracker.restoreBackups({ + handles: workingCopy => { + return workingCopy.typeId === 'testBackupTypeId'; + }, + isOpen: (workingCopy, editor) => { + return false; + }, + createEditor: workingCopy => { + return accessor.instantiationService.createInstance(TestUntitledTextEditorInput, accessor.untitledTextEditorService.create({ initialValue: 'foo' })); + } + }); + + const secondHandler = tracker.restoreBackups({ + handles: workingCopy => { + return workingCopy.typeId.length === 0; + }, + isOpen: (workingCopy, editor) => { + return false; + }, + createEditor: workingCopy => { + return accessor.instantiationService.createInstance(TestUntitledTextEditorInput, accessor.untitledTextEditorService.create({ initialValue: 'foo' })); + } + }); + + await Promise.all([firstHandler, secondHandler]); + + assert.strictEqual(accessor.editorService.count, 4); + assert.ok(accessor.editorService.editors.every(editor => editor.isDirty())); + assert.strictEqual(tracker.getUnrestoredBackups().size, 0); + + for (const editor of accessor.editorService.editors) { + assert.ok(editor instanceof TestUntitledTextEditorInput); + assert.strictEqual(editor.resolved, true); + } + + dispose(disposables); + }); + + test('Restore backups (editors already opened)', async function () { + const [tracker, accessor, disposables] = await restoreBackupsInit(); + + assert.strictEqual(tracker.getUnrestoredBackups().size, 0); + + let handlesCounter = 0; + let isOpenCounter = 0; + + const editor1 = accessor.instantiationService.createInstance(TestUntitledTextEditorInput, accessor.untitledTextEditorService.create({ initialValue: 'foo' })); + const editor2 = accessor.instantiationService.createInstance(TestUntitledTextEditorInput, accessor.untitledTextEditorService.create({ initialValue: 'foo' })); + + await accessor.editorService.openEditors([{ editor: editor1 }, { editor: editor2 }]); + + editor1.resolved = false; + editor2.resolved = false; + + await tracker.restoreBackups({ + handles: workingCopy => { + handlesCounter++; + + return workingCopy.typeId === 'testBackupTypeId'; + }, + isOpen: (workingCopy, editor) => { + isOpenCounter++; + + return true; + }, + createEditor: workingCopy => { throw new Error('unexpected'); } + }); + + assert.strictEqual(handlesCounter, 4); + assert.strictEqual(isOpenCounter, 4); + + assert.strictEqual(accessor.editorService.count, 2); + assert.strictEqual(tracker.getUnrestoredBackups().size, 2); + + for (const editor of accessor.editorService.editors) { + assert.ok(editor instanceof TestUntitledTextEditorInput); + assert.strictEqual(editor.resolved, true); + } + + dispose(disposables); + }); +}); diff --git a/src/vs/workbench/services/workingCopy/test/browser/workingCopyEditorService.test.ts b/src/vs/workbench/services/workingCopy/test/browser/workingCopyEditorService.test.ts new file mode 100644 index 00000000..80c23994 --- /dev/null +++ b/src/vs/workbench/services/workingCopy/test/browser/workingCopyEditorService.test.ts @@ -0,0 +1,82 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { DisposableStore } from 'vs/base/common/lifecycle'; +import { URI } from 'vs/base/common/uri'; +import { EditorService } from 'vs/workbench/services/editor/browser/editorService'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; +import { IWorkingCopyEditorHandler, WorkingCopyEditorService } from 'vs/workbench/services/workingCopy/common/workingCopyEditorService'; +import { createEditorPart, registerTestResourceEditor, TestEditorService, TestServiceAccessor, workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestWorkingCopy } from 'vs/workbench/test/common/workbenchTestServices'; + +suite('WorkingCopyEditorService', () => { + + let disposables = new DisposableStore(); + + setup(() => { + disposables.add(registerTestResourceEditor()); + }); + + teardown(() => { + disposables.clear(); + }); + + test('registry - basics', () => { + const service = new WorkingCopyEditorService(new TestEditorService()); + + let handlerEvent: IWorkingCopyEditorHandler | undefined = undefined; + service.onDidRegisterHandler(handler => { + handlerEvent = handler; + }); + + const editorHandler: IWorkingCopyEditorHandler = { + handles: workingCopy => false, + isOpen: () => false, + createEditor: workingCopy => { throw new Error(); } + }; + + const disposable = service.registerHandler(editorHandler); + + assert.strictEqual(handlerEvent, editorHandler); + + disposable.dispose(); + }); + + test('findEditor', async () => { + const disposables = new DisposableStore(); + + const instantiationService = workbenchInstantiationService(); + const part = await createEditorPart(instantiationService, disposables); + instantiationService.stub(IEditorGroupsService, part); + const editorService = instantiationService.createInstance(EditorService); + const accessor = instantiationService.createInstance(TestServiceAccessor); + + const service = new WorkingCopyEditorService(editorService); + + const resource = URI.parse('custom://some/folder/custom.txt'); + const testWorkingCopy = new TestWorkingCopy(resource, false, 'testWorkingCopyTypeId1'); + + assert.strictEqual(service.findEditor(testWorkingCopy), undefined); + + const editorHandler: IWorkingCopyEditorHandler = { + handles: workingCopy => workingCopy === testWorkingCopy, + isOpen: (workingCopy, editor) => workingCopy === testWorkingCopy, + createEditor: workingCopy => { throw new Error(); } + }; + + disposables.add(service.registerHandler(editorHandler)); + + const editor1 = instantiationService.createInstance(UntitledTextEditorInput, accessor.untitledTextEditorService.create({ initialValue: 'foo' })); + const editor2 = instantiationService.createInstance(UntitledTextEditorInput, accessor.untitledTextEditorService.create({ initialValue: 'foo' })); + + await editorService.openEditors([{ editor: editor1 }, { editor: editor2 }]); + + assert.ok(service.findEditor(testWorkingCopy)); + + disposables.dispose(); + }); +}); diff --git a/src/vs/workbench/services/workingCopy/test/browser/workingCopyFileService.test.ts b/src/vs/workbench/services/workingCopy/test/browser/workingCopyFileService.test.ts index 09fe8d06..5021663c 100644 --- a/src/vs/workbench/services/workingCopy/test/browser/workingCopyFileService.test.ts +++ b/src/vs/workbench/services/workingCopy/test/browser/workingCopyFileService.test.ts @@ -14,6 +14,8 @@ import { FileOperation } from 'vs/platform/files/common/files'; import { TestWorkingCopy } from 'vs/workbench/test/common/workbenchTestServices'; import { VSBuffer } from 'vs/base/common/buffer'; import { ICopyOperation } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; +import { timeout } from 'vs/base/common/async'; suite('WorkingCopyFileService', () => { @@ -144,7 +146,7 @@ suite('WorkingCopyFileService', () => { let dirty = accessor.workingCopyFileService.getDirty(model1.resource); assert.strictEqual(dirty.length, 0); - await model1.load(); + await model1.resolve(); model1.textEditorModel!.setValue('foo'); dirty = accessor.workingCopyFileService.getDirty(model1.resource); @@ -155,7 +157,7 @@ suite('WorkingCopyFileService', () => { assert.strictEqual(dirty.length, 1); assert.strictEqual(dirty[0], model1); - await model2.load(); + await model2.resolve(); model2.textEditorModel!.setValue('bar'); dirty = accessor.workingCopyFileService.getDirty(toResource.call(this, '/path')); @@ -168,7 +170,7 @@ suite('WorkingCopyFileService', () => { test('registerWorkingCopyProvider', async function () { const model1 = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file-1.txt'), 'utf8', undefined); (accessor.textFileService.files).add(model1.resource, model1); - await model1.load(); + await model1.resolve(); model1.textEditorModel!.setValue('foo'); const testWorkingCopy = new TestWorkingCopy(toResource.call(this, '/path/file-2.txt'), true); @@ -224,7 +226,7 @@ suite('WorkingCopyFileService', () => { eventCounter++; }); - await accessor.workingCopyFileService.createFolder([{ resource }]); + await accessor.workingCopyFileService.createFolder([{ resource }], CancellationToken.None); assert.strictEqual(eventCounter, 3); @@ -233,6 +235,60 @@ suite('WorkingCopyFileService', () => { listener2.dispose(); }); + test('cancellation of participants', async function () { + const resource = toResource.call(this, '/path/folder'); + + let canceled = false; + const participant = accessor.workingCopyFileService.addFileOperationParticipant({ + participate: async (files, operation, info, t, token) => { + await timeout(0); + canceled = token.isCancellationRequested; + } + }); + + // Create + let cts = new CancellationTokenSource(); + let promise: Promise = accessor.workingCopyFileService.create([{ resource }], cts.token); + cts.cancel(); + await promise; + assert.strictEqual(canceled, true); + canceled = false; + + // Create Folder + cts = new CancellationTokenSource(); + promise = accessor.workingCopyFileService.createFolder([{ resource }], cts.token); + cts.cancel(); + await promise; + assert.strictEqual(canceled, true); + canceled = false; + + // Move + cts = new CancellationTokenSource(); + promise = accessor.workingCopyFileService.move([{ file: { source: resource, target: resource } }], cts.token); + cts.cancel(); + await promise; + assert.strictEqual(canceled, true); + canceled = false; + + // Copy + cts = new CancellationTokenSource(); + promise = accessor.workingCopyFileService.copy([{ file: { source: resource, target: resource } }], cts.token); + cts.cancel(); + await promise; + assert.strictEqual(canceled, true); + canceled = false; + + // Delete + cts = new CancellationTokenSource(); + promise = accessor.workingCopyFileService.delete([{ resource }], cts.token); + cts.cancel(); + await promise; + assert.strictEqual(canceled, true); + canceled = false; + + participant.dispose(); + }); + async function testEventsMoveOrCopy(files: ICopyOperation[], move?: boolean): Promise { let eventCounter = 0; @@ -251,9 +307,9 @@ suite('WorkingCopyFileService', () => { }); if (move) { - await accessor.workingCopyFileService.move(files); + await accessor.workingCopyFileService.move(files, CancellationToken.None); } else { - await accessor.workingCopyFileService.copy(files); + await accessor.workingCopyFileService.copy(files, CancellationToken.None); } participant.dispose(); @@ -271,11 +327,11 @@ suite('WorkingCopyFileService', () => { (accessor.textFileService.files).add(sourceModel.resource, sourceModel); (accessor.textFileService.files).add(targetModel.resource, targetModel); - await sourceModel.load(); + await sourceModel.resolve(); sourceModel.textEditorModel!.setValue('foo' + i); assert.ok(accessor.textFileService.isDirty(sourceModel.resource)); if (targetDirty) { - await targetModel.load(); + await targetModel.resolve(); targetModel.textEditorModel!.setValue('bar' + i); assert.ok(accessor.textFileService.isDirty(targetModel.resource)); } @@ -331,9 +387,9 @@ suite('WorkingCopyFileService', () => { }); if (move) { - await accessor.workingCopyFileService.move(models.map(model => ({ file: { source: model.sourceModel.resource, target: model.targetModel.resource }, options: { overwrite: true } }))); + await accessor.workingCopyFileService.move(models.map(model => ({ file: { source: model.sourceModel.resource, target: model.targetModel.resource }, options: { overwrite: true } })), CancellationToken.None); } else { - await accessor.workingCopyFileService.copy(models.map(model => ({ file: { source: model.sourceModel.resource, target: model.targetModel.resource }, options: { overwrite: true } }))); + await accessor.workingCopyFileService.copy(models.map(model => ({ file: { source: model.sourceModel.resource, target: model.targetModel.resource }, options: { overwrite: true } })), CancellationToken.None); } for (let i = 0; i < models.length; i++) { @@ -364,7 +420,7 @@ suite('WorkingCopyFileService', () => { const model = instantiationService.createInstance(TextFileEditorModel, resource, 'utf8', undefined); (accessor.textFileService.files).add(model.resource, model); - await model.load(); + await model.resolve(); model!.textEditorModel!.setValue('foo'); assert.ok(accessor.workingCopyService.isDirty(model.resource)); return model; @@ -407,7 +463,7 @@ suite('WorkingCopyFileService', () => { eventCounter++; }); - await accessor.workingCopyFileService.delete(models.map(m => ({ resource: m.resource }))); + await accessor.workingCopyFileService.delete(models.map(model => ({ resource: model.resource })), CancellationToken.None); for (const model of models) { assert.ok(!accessor.workingCopyService.isDirty(model.resource)); model.dispose(); @@ -424,7 +480,7 @@ suite('WorkingCopyFileService', () => { const model = instantiationService.createInstance(TextFileEditorModel, resource, 'utf8', undefined); (accessor.textFileService.files).add(model.resource, model); - await model.load(); + await model.resolve(); model!.textEditorModel!.setValue('foo'); assert.ok(accessor.workingCopyService.isDirty(model.resource)); @@ -459,7 +515,7 @@ suite('WorkingCopyFileService', () => { eventCounter++; }); - await accessor.workingCopyFileService.create([{ resource, contents }]); + await accessor.workingCopyFileService.create([{ resource, contents }], CancellationToken.None); assert.ok(!accessor.workingCopyService.isDirty(model.resource)); model.dispose(); diff --git a/src/vs/workbench/services/workingCopy/test/common/workingCopyService.test.ts b/src/vs/workbench/services/workingCopy/test/common/workingCopyService.test.ts index 314cd18f..9da4f35c 100644 --- a/src/vs/workbench/services/workingCopy/test/common/workingCopyService.test.ts +++ b/src/vs/workbench/services/workingCopy/test/common/workingCopyService.test.ts @@ -4,14 +4,15 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { IWorkingCopy } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { IWorkingCopy } from 'vs/workbench/services/workingCopy/common/workingCopy'; import { URI } from 'vs/base/common/uri'; -import { TestWorkingCopy, TestWorkingCopyService } from 'vs/workbench/test/common/workbenchTestServices'; +import { TestWorkingCopy } from 'vs/workbench/test/common/workbenchTestServices'; +import { WorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; suite('WorkingCopyService', () => { test('registry - basics', () => { - const service = new TestWorkingCopyService(); + const service = new WorkingCopyService(); const onDidChangeDirty: IWorkingCopy[] = []; service.onDidChangeDirty(copy => onDidChangeDirty.push(copy)); @@ -104,11 +105,8 @@ suite('WorkingCopyService', () => { assert.strictEqual(onDidChangeDirty[3], copy2); }); - test('registry - multiple copies on same resource throws', () => { - const service = new TestWorkingCopyService(); - - const onDidChangeDirty: IWorkingCopy[] = []; - service.onDidChangeDirty(copy => onDidChangeDirty.push(copy)); + test('registry - multiple copies on same resource throws (same type ID)', () => { + const service = new WorkingCopyService(); const resource = URI.parse('custom://some/folder/custom.txt'); @@ -119,4 +117,64 @@ suite('WorkingCopyService', () => { assert.throws(() => service.registerWorkingCopy(copy2)); }); + + test('registry - multiple copies on same resource is supported (different type ID)', () => { + const service = new WorkingCopyService(); + + const resource = URI.parse('custom://some/folder/custom.txt'); + + const typeId1 = 'testWorkingCopyTypeId1'; + let copy1 = new TestWorkingCopy(resource, false, typeId1); + let dispose1 = service.registerWorkingCopy(copy1); + + const typeId2 = 'testWorkingCopyTypeId2'; + const copy2 = new TestWorkingCopy(resource, false, typeId2); + const dispose2 = service.registerWorkingCopy(copy2); + + const typeId3 = 'testWorkingCopyTypeId3'; + const copy3 = new TestWorkingCopy(resource, false, typeId3); + const dispose3 = service.registerWorkingCopy(copy3); + + assert.strictEqual(service.dirtyCount, 0); + assert.strictEqual(service.isDirty(resource), false); + assert.strictEqual(service.isDirty(resource, typeId1), false); + + copy1.setDirty(true); + assert.strictEqual(service.dirtyCount, 1); + assert.strictEqual(service.isDirty(resource), true); + assert.strictEqual(service.isDirty(resource, typeId1), true); + assert.strictEqual(service.isDirty(resource, typeId2), false); + + copy2.setDirty(true); + assert.strictEqual(service.dirtyCount, 2); + assert.strictEqual(service.isDirty(resource), true); + assert.strictEqual(service.isDirty(resource, typeId1), true); + assert.strictEqual(service.isDirty(resource, typeId2), true); + + copy3.setDirty(true); + assert.strictEqual(service.dirtyCount, 3); + assert.strictEqual(service.isDirty(resource), true); + assert.strictEqual(service.isDirty(resource, typeId1), true); + assert.strictEqual(service.isDirty(resource, typeId2), true); + assert.strictEqual(service.isDirty(resource, typeId3), true); + + copy1.setDirty(false); + copy2.setDirty(false); + copy3.setDirty(false); + assert.strictEqual(service.dirtyCount, 0); + assert.strictEqual(service.isDirty(resource), false); + assert.strictEqual(service.isDirty(resource, typeId1), false); + assert.strictEqual(service.isDirty(resource, typeId2), false); + assert.strictEqual(service.isDirty(resource, typeId3), false); + + dispose1.dispose(); + copy1 = new TestWorkingCopy(resource, false, typeId1); + dispose1 = service.registerWorkingCopy(copy1); + + dispose1.dispose(); + dispose2.dispose(); + dispose3.dispose(); + + assert.strictEqual(service.workingCopies.length, 0); + }); }); diff --git a/src/vs/workbench/services/workingCopy/test/electron-browser/fixtures/binary.txt b/src/vs/workbench/services/workingCopy/test/electron-browser/fixtures/binary.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc30693d792253bf83b60e6f9bac20311570a186 GIT binary patch literal 274 zcmeAS@N?(olHy`uVBq!ia0vp^oNn{1`ISV`@iy0XB4ude`@%$AjKtcBs*NBqf{L-T2REFS;{F0JNg)$>O13e=> zBSSL<4QEY-kc|A?#9{@f#M0cvygUV6g^ZGt0xNy}Vz6qxl+?0f-TXYgywnn%7SXFf zBSSo0978H@y*=+J$iTqEq;UDB{Up_Iw;Y)-?SJ%)JW!Uhl4UK2&W{}$K=T[]; + private diskFileSystemProvider: DiskFileSystemProvider; + + constructor(testDir: string, workspaceBackupPath: string) { + const environmentService = new TestWorkbenchEnvironmentService(testDir, workspaceBackupPath); + const logService = new NullLogService(); + const fileService = new FileService(logService); + super(environmentService, fileService, logService); + + this.diskFileSystemProvider = new DiskFileSystemProvider(logService); + fileService.registerProvider(Schemas.file, this.diskFileSystemProvider); + fileService.registerProvider(Schemas.userData, new FileUserDataProvider(Schemas.file, this.diskFileSystemProvider, Schemas.userData, logService)); + + this.fileService = fileService; + this.backupResourceJoiners = []; + this.discardBackupJoiners = []; + this.discardedBackups = []; + this.pendingBackupsArr = []; + } + + async waitForAllBackups(): Promise { + await Promise.all(this.pendingBackupsArr); + } + + joinBackupResource(): Promise { + return new Promise(resolve => this.backupResourceJoiners.push(resolve)); + } + + override async backup(identifier: IWorkingCopyIdentifier, content?: VSBufferReadableStream | VSBufferReadable, versionId?: number, meta?: any, token?: CancellationToken): Promise { + const p = super.backup(identifier, content, versionId, meta, token); + const removeFromPendingBackups = insert(this.pendingBackupsArr, p.then(undefined, undefined)); + + try { + await p; + } finally { + removeFromPendingBackups(); + } + + while (this.backupResourceJoiners.length) { + this.backupResourceJoiners.pop()!(); + } + } + + joinDiscardBackup(): Promise { + return new Promise(resolve => this.discardBackupJoiners.push(resolve)); + } + + override async discardBackup(identifier: IWorkingCopyIdentifier): Promise { + await super.discardBackup(identifier); + this.discardedBackups.push(identifier); + + while (this.discardBackupJoiners.length) { + this.discardBackupJoiners.pop()!(); + } + } + + async getBackupContents(identifier: IWorkingCopyIdentifier): Promise { + const backupResource = this.toBackupResource(identifier); + + const fileContents = await this.fileService.readFile(backupResource); + + return fileContents.value.toString(); + } + + dispose() { + this.diskFileSystemProvider.dispose(); + } +} + +suite('WorkingCopyBackupService', () => { + + let testDir: string; + let backupHome: string; + let workspacesJsonPath: string; + let workspaceBackupPath: string; + + let service: NodeTestWorkingCopyBackupService; + + let workspaceResource = URI.file(isWindows ? 'c:\\workspace' : '/workspace'); + let fooFile = URI.file(isWindows ? 'c:\\Foo' : '/Foo'); + let customFile = URI.parse('customScheme://some/path'); + let customFileWithFragment = URI.parse('customScheme2://some/path#fragment'); + let barFile = URI.file(isWindows ? 'c:\\Bar' : '/Bar'); + let fooBarFile = URI.file(isWindows ? 'c:\\Foo Bar' : '/Foo Bar'); + let untitledFile = URI.from({ scheme: Schemas.untitled, path: 'Untitled-1' }); + + setup(async () => { + testDir = getRandomTestPath(tmpdir(), 'vsctests', 'workingcopybackupservice'); + backupHome = join(testDir, 'Backups'); + workspacesJsonPath = join(backupHome, 'workspaces.json'); + workspaceBackupPath = join(backupHome, hash(workspaceResource.fsPath).toString(16)); + + service = new NodeTestWorkingCopyBackupService(testDir, workspaceBackupPath); + + await promises.mkdir(backupHome, { recursive: true }); + + return writeFile(workspacesJsonPath, ''); + }); + + teardown(() => { + service.dispose(); + return rimraf(testDir); + }); + + suite('hashIdentifier', () => { + test('should correctly hash the identifier for untitled scheme URIs', () => { + const uri = URI.from({ scheme: Schemas.untitled, path: 'Untitled-1' }); + + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // If these hashes change people will lose their backed up files + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + const untypedBackupHash = hashIdentifier(toUntypedWorkingCopyId(uri)); + assert.strictEqual(untypedBackupHash, '-7f9c1a2e'); + assert.strictEqual(untypedBackupHash, hash(uri.fsPath).toString(16)); + + const typedBackupHash = hashIdentifier({ typeId: 'hashTest', resource: uri }); + if (isWindows) { + assert.strictEqual(typedBackupHash, '-17c47cdc'); + } else { + assert.strictEqual(typedBackupHash, '-8ad5f4f'); + } + + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // If these hashes collide people will lose their backed up files + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + assert.notStrictEqual(untypedBackupHash, typedBackupHash); + }); + + test('should correctly hash the identifier for file scheme URIs', () => { + const uri = URI.file('/foo'); + + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // If these hashes change people will lose their backed up files + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + const untypedBackupHash = hashIdentifier(toUntypedWorkingCopyId(uri)); + if (isWindows) { + assert.strictEqual(untypedBackupHash, '20ffaa13'); + } else { + assert.strictEqual(untypedBackupHash, '20eb3560'); + } + assert.strictEqual(untypedBackupHash, hash(uri.fsPath).toString(16)); + + const typedBackupHash = hashIdentifier({ typeId: 'hashTest', resource: uri }); + if (isWindows) { + assert.strictEqual(typedBackupHash, '-55fc55db'); + } else { + assert.strictEqual(typedBackupHash, '51e56bf'); + } + + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // If these hashes collide people will lose their backed up files + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + assert.notStrictEqual(untypedBackupHash, typedBackupHash); + }); + + test('should correctly hash the identifier for custom scheme URIs', () => { + const uri = URI.from({ + scheme: 'vscode-custom', + path: 'somePath' + }); + + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // If these hashes change people will lose their backed up files + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + const untypedBackupHash = hashIdentifier(toUntypedWorkingCopyId(uri)); + assert.strictEqual(untypedBackupHash, '-44972d98'); + assert.strictEqual(untypedBackupHash, hash(uri.toString()).toString(16)); + + const typedBackupHash = hashIdentifier({ typeId: 'hashTest', resource: uri }); + assert.strictEqual(typedBackupHash, '502149c7'); + + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // If these hashes collide people will lose their backed up files + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + assert.notStrictEqual(untypedBackupHash, typedBackupHash); + }); + + test('should not fail for URIs without path', () => { + const uri = URI.from({ + scheme: 'vscode-fragment', + fragment: 'frag' + }); + + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // If these hashes change people will lose their backed up files + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + const untypedBackupHash = hashIdentifier(toUntypedWorkingCopyId(uri)); + assert.strictEqual(untypedBackupHash, '-2f6b2f1b'); + assert.strictEqual(untypedBackupHash, hash(uri.toString()).toString(16)); + + const typedBackupHash = hashIdentifier({ typeId: 'hashTest', resource: uri }); + assert.strictEqual(typedBackupHash, '6e82ca57'); + + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // If these hashes collide people will lose their backed up files + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + assert.notStrictEqual(untypedBackupHash, typedBackupHash); + }); + }); + + suite('getBackupResource', () => { + test('should get the correct backup path for text files', () => { + + // Format should be: /// + const backupResource = fooFile; + const workspaceHash = hash(workspaceResource.fsPath).toString(16); + + // No Type ID + let backupId = toUntypedWorkingCopyId(backupResource); + let filePathHash = hashIdentifier(backupId); + let expectedPath = URI.file(join(backupHome, workspaceHash, Schemas.file, filePathHash)).with({ scheme: Schemas.userData }).toString(); + assert.strictEqual(service.toBackupResource(backupId).toString(), expectedPath); + + // With Type ID + backupId = toTypedWorkingCopyId(backupResource); + filePathHash = hashIdentifier(backupId); + expectedPath = URI.file(join(backupHome, workspaceHash, Schemas.file, filePathHash)).with({ scheme: Schemas.userData }).toString(); + assert.strictEqual(service.toBackupResource(backupId).toString(), expectedPath); + }); + + test('should get the correct backup path for untitled files', () => { + + // Format should be: /// + const backupResource = URI.from({ scheme: Schemas.untitled, path: 'Untitled-1' }); + const workspaceHash = hash(workspaceResource.fsPath).toString(16); + + // No Type ID + let backupId = toUntypedWorkingCopyId(backupResource); + let filePathHash = hashIdentifier(backupId); + let expectedPath = URI.file(join(backupHome, workspaceHash, Schemas.untitled, filePathHash)).with({ scheme: Schemas.userData }).toString(); + assert.strictEqual(service.toBackupResource(backupId).toString(), expectedPath); + + // With Type ID + backupId = toTypedWorkingCopyId(backupResource); + filePathHash = hashIdentifier(backupId); + expectedPath = URI.file(join(backupHome, workspaceHash, Schemas.untitled, filePathHash)).with({ scheme: Schemas.userData }).toString(); + assert.strictEqual(service.toBackupResource(backupId).toString(), expectedPath); + }); + + test('should get the correct backup path for custom files', () => { + + // Format should be: /// + const backupResource = URI.from({ scheme: 'custom', path: 'custom/file.txt' }); + const workspaceHash = hash(workspaceResource.fsPath).toString(16); + + // No Type ID + let backupId = toUntypedWorkingCopyId(backupResource); + let filePathHash = hashIdentifier(backupId); + let expectedPath = URI.file(join(backupHome, workspaceHash, 'custom', filePathHash)).with({ scheme: Schemas.userData }).toString(); + assert.strictEqual(service.toBackupResource(backupId).toString(), expectedPath); + + // With Type ID + backupId = toTypedWorkingCopyId(backupResource); + filePathHash = hashIdentifier(backupId); + expectedPath = URI.file(join(backupHome, workspaceHash, 'custom', filePathHash)).with({ scheme: Schemas.userData }).toString(); + assert.strictEqual(service.toBackupResource(backupId).toString(), expectedPath); + }); + }); + + suite('backup', () => { + + function toExpectedPreamble(identifier: IWorkingCopyIdentifier, content = '', meta?: object): string { + return `${identifier.resource.toString()} ${JSON.stringify({ ...meta, typeId: identifier.typeId })}\n${content}`; + } + + test('no text', async () => { + const identifier = toUntypedWorkingCopyId(fooFile); + const backupPath = join(workspaceBackupPath, identifier.resource.scheme, hashIdentifier(identifier)); + + await service.backup(identifier); + assert.strictEqual(readdirSync(join(workspaceBackupPath, 'file')).length, 1); + assert.strictEqual(existsSync(backupPath), true); + assert.strictEqual(readFileSync(backupPath).toString(), toExpectedPreamble(identifier)); + assert.ok(service.hasBackupSync(identifier)); + }); + + test('text file', async () => { + const identifier = toUntypedWorkingCopyId(fooFile); + const backupPath = join(workspaceBackupPath, identifier.resource.scheme, hashIdentifier(identifier)); + + await service.backup(identifier, bufferToReadable(VSBuffer.fromString('test'))); + assert.strictEqual(readdirSync(join(workspaceBackupPath, 'file')).length, 1); + assert.strictEqual(existsSync(backupPath), true); + assert.strictEqual(readFileSync(backupPath).toString(), toExpectedPreamble(identifier, 'test')); + assert.ok(service.hasBackupSync(identifier)); + }); + + test('text file (with version)', async () => { + const identifier = toUntypedWorkingCopyId(fooFile); + const backupPath = join(workspaceBackupPath, identifier.resource.scheme, hashIdentifier(identifier)); + + await service.backup(identifier, bufferToReadable(VSBuffer.fromString('test')), 666); + assert.strictEqual(readdirSync(join(workspaceBackupPath, 'file')).length, 1); + assert.strictEqual(existsSync(backupPath), true); + assert.strictEqual(readFileSync(backupPath).toString(), toExpectedPreamble(identifier, 'test')); + assert.ok(!service.hasBackupSync(identifier, 555)); + assert.ok(service.hasBackupSync(identifier, 666)); + }); + + test('text file (with meta)', async () => { + const identifier = toUntypedWorkingCopyId(fooFile); + const backupPath = join(workspaceBackupPath, identifier.resource.scheme, hashIdentifier(identifier)); + const meta = { etag: '678', orphaned: true }; + + await service.backup(identifier, bufferToReadable(VSBuffer.fromString('test')), undefined, meta); + assert.strictEqual(readdirSync(join(workspaceBackupPath, 'file')).length, 1); + assert.strictEqual(existsSync(backupPath), true); + assert.strictEqual(readFileSync(backupPath).toString(), toExpectedPreamble(identifier, 'test', meta)); + assert.ok(service.hasBackupSync(identifier)); + }); + + test('text file with whitespace in name and type (with meta)', async () => { + let fileWithSpace = URI.file(isWindows ? 'c:\\Foo \n Bar' : '/Foo \n Bar'); + const identifier = toTypedWorkingCopyId(fileWithSpace, ' test id \n'); + const backupPath = join(workspaceBackupPath, identifier.resource.scheme, hashIdentifier(identifier)); + const meta = { etag: '678 \n k', orphaned: true }; + + await service.backup(identifier, bufferToReadable(VSBuffer.fromString('test')), undefined, meta); + assert.strictEqual(readdirSync(join(workspaceBackupPath, 'file')).length, 1); + assert.strictEqual(existsSync(backupPath), true); + assert.strictEqual(readFileSync(backupPath).toString(), toExpectedPreamble(identifier, 'test', meta)); + assert.ok(service.hasBackupSync(identifier)); + }); + + test('text file with unicode character in name and type (with meta)', async () => { + let fileWithUnicode = URI.file(isWindows ? 'c:\\so𒀅meࠄ' : '/so𒀅meࠄ'); + const identifier = toTypedWorkingCopyId(fileWithUnicode, ' test so𒀅meࠄ id \n'); + const backupPath = join(workspaceBackupPath, identifier.resource.scheme, hashIdentifier(identifier)); + const meta = { etag: '678so𒀅meࠄ', orphaned: true }; + + await service.backup(identifier, bufferToReadable(VSBuffer.fromString('test')), undefined, meta); + assert.strictEqual(readdirSync(join(workspaceBackupPath, 'file')).length, 1); + assert.strictEqual(existsSync(backupPath), true); + assert.strictEqual(readFileSync(backupPath).toString(), toExpectedPreamble(identifier, 'test', meta)); + assert.ok(service.hasBackupSync(identifier)); + }); + + test('untitled file', async () => { + const identifier = toUntypedWorkingCopyId(untitledFile); + const backupPath = join(workspaceBackupPath, identifier.resource.scheme, hashIdentifier(identifier)); + + await service.backup(identifier, bufferToReadable(VSBuffer.fromString('test'))); + assert.strictEqual(readdirSync(join(workspaceBackupPath, 'untitled')).length, 1); + assert.strictEqual(existsSync(backupPath), true); + assert.strictEqual(readFileSync(backupPath).toString(), toExpectedPreamble(identifier, 'test')); + assert.ok(service.hasBackupSync(identifier)); + }); + + test('text file (readable)', async () => { + const identifier = toUntypedWorkingCopyId(fooFile); + const backupPath = join(workspaceBackupPath, identifier.resource.scheme, hashIdentifier(identifier)); + const model = createTextModel('test'); + + await service.backup(identifier, toBufferOrReadable(model.createSnapshot())); + assert.strictEqual(readdirSync(join(workspaceBackupPath, 'file')).length, 1); + assert.strictEqual(existsSync(backupPath), true); + assert.strictEqual(readFileSync(backupPath).toString(), toExpectedPreamble(identifier, 'test')); + assert.ok(service.hasBackupSync(identifier)); + + model.dispose(); + }); + + test('untitled file (readable)', async () => { + const identifier = toUntypedWorkingCopyId(untitledFile); + const backupPath = join(workspaceBackupPath, identifier.resource.scheme, hashIdentifier(identifier)); + const model = createTextModel('test'); + + await service.backup(identifier, toBufferOrReadable(model.createSnapshot())); + assert.strictEqual(readdirSync(join(workspaceBackupPath, 'untitled')).length, 1); + assert.strictEqual(existsSync(backupPath), true); + assert.strictEqual(readFileSync(backupPath).toString(), toExpectedPreamble(identifier, 'test')); + + model.dispose(); + }); + + test('text file (large file, stream)', () => { + const largeString = (new Array(30 * 1024)).join('Large String\n'); + + return testLargeTextFile(largeString, bufferToStream(VSBuffer.fromString(largeString))); + }); + + test('text file (large file, readable)', async () => { + const largeString = (new Array(30 * 1024)).join('Large String\n'); + const model = createTextModel(largeString); + + await testLargeTextFile(largeString, toBufferOrReadable(model.createSnapshot())); + + model.dispose(); + }); + + async function testLargeTextFile(largeString: string, buffer: VSBufferReadable | VSBufferReadableStream) { + const identifier = toUntypedWorkingCopyId(fooFile); + const backupPath = join(workspaceBackupPath, identifier.resource.scheme, hashIdentifier(identifier)); + + await service.backup(identifier, buffer, undefined, { largeTest: true }); + assert.strictEqual(readdirSync(join(workspaceBackupPath, 'file')).length, 1); + assert.strictEqual(existsSync(backupPath), true); + assert.strictEqual(readFileSync(backupPath).toString(), toExpectedPreamble(identifier, largeString, { largeTest: true })); + assert.ok(service.hasBackupSync(identifier)); + } + + test('untitled file (large file, readable)', async () => { + const identifier = toUntypedWorkingCopyId(untitledFile); + const backupPath = join(workspaceBackupPath, identifier.resource.scheme, hashIdentifier(identifier)); + const largeString = (new Array(30 * 1024)).join('Large String\n'); + const model = createTextModel(largeString); + + await service.backup(identifier, toBufferOrReadable(model.createSnapshot())); + assert.strictEqual(readdirSync(join(workspaceBackupPath, 'untitled')).length, 1); + assert.strictEqual(existsSync(backupPath), true); + assert.strictEqual(readFileSync(backupPath).toString(), toExpectedPreamble(identifier, largeString)); + assert.ok(service.hasBackupSync(identifier)); + + model.dispose(); + }); + + test('cancellation', async () => { + const identifier = toUntypedWorkingCopyId(fooFile); + const backupPath = join(workspaceBackupPath, identifier.resource.scheme, hashIdentifier(identifier)); + + const cts = new CancellationTokenSource(); + const promise = service.backup(identifier, undefined, undefined, undefined, cts.token); + cts.cancel(); + await promise; + + assert.strictEqual(existsSync(backupPath), false); + assert.ok(!service.hasBackupSync(identifier)); + }); + + test('multiple same resource, different type id', async () => { + const backupId1 = toUntypedWorkingCopyId(fooFile); + const backupId2 = toTypedWorkingCopyId(fooFile, 'type1'); + const backupId3 = toTypedWorkingCopyId(fooFile, 'type2'); + + await service.backup(backupId1); + await service.backup(backupId2); + await service.backup(backupId3); + + assert.strictEqual(readdirSync(join(workspaceBackupPath, 'file')).length, 3); + + for (const backupId of [backupId1, backupId2, backupId3]) { + const fooBackupPath = join(workspaceBackupPath, backupId.resource.scheme, hashIdentifier(backupId)); + assert.strictEqual(existsSync(fooBackupPath), true); + assert.strictEqual(readFileSync(fooBackupPath).toString(), toExpectedPreamble(backupId)); + assert.ok(service.hasBackupSync(backupId)); + } + }); + }); + + suite('discardBackup', () => { + + test('text file', async () => { + const identifier = toUntypedWorkingCopyId(fooFile); + const backupPath = join(workspaceBackupPath, identifier.resource.scheme, hashIdentifier(identifier)); + + await service.backup(identifier, bufferToReadable(VSBuffer.fromString('test'))); + assert.strictEqual(readdirSync(join(workspaceBackupPath, 'file')).length, 1); + assert.ok(service.hasBackupSync(identifier)); + + await service.discardBackup(identifier); + assert.strictEqual(existsSync(backupPath), false); + assert.strictEqual(readdirSync(join(workspaceBackupPath, 'file')).length, 0); + assert.ok(!service.hasBackupSync(identifier)); + }); + + test('untitled file', async () => { + const identifier = toUntypedWorkingCopyId(untitledFile); + const backupPath = join(workspaceBackupPath, identifier.resource.scheme, hashIdentifier(identifier)); + + await service.backup(identifier, bufferToReadable(VSBuffer.fromString('test'))); + assert.strictEqual(readdirSync(join(workspaceBackupPath, 'untitled')).length, 1); + + await service.discardBackup(identifier); + assert.strictEqual(existsSync(backupPath), false); + assert.strictEqual(readdirSync(join(workspaceBackupPath, 'untitled')).length, 0); + }); + + test('multiple same resource, different type id', async () => { + const backupId1 = toUntypedWorkingCopyId(fooFile); + const backupId2 = toTypedWorkingCopyId(fooFile, 'type1'); + const backupId3 = toTypedWorkingCopyId(fooFile, 'type2'); + + await service.backup(backupId1); + await service.backup(backupId2); + await service.backup(backupId3); + + assert.strictEqual(readdirSync(join(workspaceBackupPath, 'file')).length, 3); + + for (const backupId of [backupId1, backupId2, backupId3]) { + const backupPath = join(workspaceBackupPath, backupId.resource.scheme, hashIdentifier(backupId)); + await service.discardBackup(backupId); + assert.strictEqual(existsSync(backupPath), false); + } + assert.strictEqual(readdirSync(join(workspaceBackupPath, 'file')).length, 0); + }); + }); + + suite('discardBackups (all)', () => { + test('text file', async () => { + const backupId1 = toUntypedWorkingCopyId(fooFile); + const backupId2 = toUntypedWorkingCopyId(barFile); + const backupId3 = toTypedWorkingCopyId(barFile); + + await service.backup(backupId1, bufferToReadable(VSBuffer.fromString('test'))); + assert.strictEqual(readdirSync(join(workspaceBackupPath, 'file')).length, 1); + + await service.backup(backupId2, bufferToReadable(VSBuffer.fromString('test'))); + assert.strictEqual(readdirSync(join(workspaceBackupPath, 'file')).length, 2); + + await service.backup(backupId3, bufferToReadable(VSBuffer.fromString('test'))); + assert.strictEqual(readdirSync(join(workspaceBackupPath, 'file')).length, 3); + + await service.discardBackups(); + for (const backupId of [backupId1, backupId2, backupId3]) { + const backupPath = join(workspaceBackupPath, backupId.resource.scheme, hashIdentifier(backupId)); + assert.strictEqual(existsSync(backupPath), false); + } + + assert.strictEqual(existsSync(join(workspaceBackupPath, 'file')), false); + }); + + test('untitled file', async () => { + const backupId = toUntypedWorkingCopyId(untitledFile); + const backupPath = join(workspaceBackupPath, backupId.resource.scheme, hashIdentifier(backupId)); + + await service.backup(backupId, bufferToReadable(VSBuffer.fromString('test'))); + assert.strictEqual(readdirSync(join(workspaceBackupPath, 'untitled')).length, 1); + + await service.discardBackups(); + assert.strictEqual(existsSync(backupPath), false); + assert.strictEqual(existsSync(join(workspaceBackupPath, 'untitled')), false); + }); + + test('can backup after discarding all', async () => { + await service.discardBackups(); + await service.backup(toUntypedWorkingCopyId(untitledFile), bufferToReadable(VSBuffer.fromString('test'))); + assert.strictEqual(existsSync(workspaceBackupPath), true); + }); + }); + + suite('discardBackups (except some)', () => { + test('text file', async () => { + const backupId1 = toUntypedWorkingCopyId(fooFile); + const backupId2 = toUntypedWorkingCopyId(barFile); + const backupId3 = toTypedWorkingCopyId(barFile); + + await service.backup(backupId1, bufferToReadable(VSBuffer.fromString('test'))); + assert.strictEqual(readdirSync(join(workspaceBackupPath, 'file')).length, 1); + + await service.backup(backupId2, bufferToReadable(VSBuffer.fromString('test'))); + assert.strictEqual(readdirSync(join(workspaceBackupPath, 'file')).length, 2); + + await service.backup(backupId3, bufferToReadable(VSBuffer.fromString('test'))); + assert.strictEqual(readdirSync(join(workspaceBackupPath, 'file')).length, 3); + + await service.discardBackups([backupId2, backupId3]); + + let backupPath = join(workspaceBackupPath, backupId1.resource.scheme, hashIdentifier(backupId1)); + assert.strictEqual(existsSync(backupPath), false); + + backupPath = join(workspaceBackupPath, backupId2.resource.scheme, hashIdentifier(backupId2)); + assert.strictEqual(existsSync(backupPath), true); + + backupPath = join(workspaceBackupPath, backupId3.resource.scheme, hashIdentifier(backupId3)); + assert.strictEqual(existsSync(backupPath), true); + + await service.discardBackups([backupId1]); + + for (const backupId of [backupId1, backupId2, backupId3]) { + const backupPath = join(workspaceBackupPath, backupId.resource.scheme, hashIdentifier(backupId)); + assert.strictEqual(existsSync(backupPath), false); + } + }); + + test('untitled file', async () => { + const backupId = toUntypedWorkingCopyId(untitledFile); + const backupPath = join(workspaceBackupPath, backupId.resource.scheme, hashIdentifier(backupId)); + + await service.backup(backupId, bufferToReadable(VSBuffer.fromString('test'))); + assert.strictEqual(existsSync(backupPath), true); + assert.strictEqual(readdirSync(join(workspaceBackupPath, 'untitled')).length, 1); + + await service.discardBackups([backupId]); + assert.strictEqual(existsSync(backupPath), true); + }); + }); + + suite('getBackups', () => { + test('text file', async () => { + await service.backup(toUntypedWorkingCopyId(fooFile), bufferToReadable(VSBuffer.fromString('test'))); + await service.backup(toTypedWorkingCopyId(fooFile, 'type1'), bufferToReadable(VSBuffer.fromString('test'))); + await service.backup(toTypedWorkingCopyId(fooFile, 'type2'), bufferToReadable(VSBuffer.fromString('test'))); + + let backups = await service.getBackups(); + assert.strictEqual(backups.length, 3); + + for (const backup of backups) { + if (backup.typeId === '') { + assert.strictEqual(backup.resource.toString(), fooFile.toString()); + } else if (backup.typeId === 'type1') { + assert.strictEqual(backup.resource.toString(), fooFile.toString()); + } else if (backup.typeId === 'type2') { + assert.strictEqual(backup.resource.toString(), fooFile.toString()); + } else { + assert.fail('Unexpected backup'); + } + } + + await service.backup(toUntypedWorkingCopyId(barFile), bufferToReadable(VSBuffer.fromString('test'))); + + backups = await service.getBackups(); + assert.strictEqual(backups.length, 4); + }); + + test('untitled file', async () => { + await service.backup(toUntypedWorkingCopyId(untitledFile), bufferToReadable(VSBuffer.fromString('test'))); + await service.backup(toTypedWorkingCopyId(untitledFile, 'type1'), bufferToReadable(VSBuffer.fromString('test'))); + await service.backup(toTypedWorkingCopyId(untitledFile, 'type2'), bufferToReadable(VSBuffer.fromString('test'))); + + const backups = await service.getBackups(); + assert.strictEqual(backups.length, 3); + + for (const backup of backups) { + if (backup.typeId === '') { + assert.strictEqual(backup.resource.toString(), untitledFile.toString()); + } else if (backup.typeId === 'type1') { + assert.strictEqual(backup.resource.toString(), untitledFile.toString()); + } else if (backup.typeId === 'type2') { + assert.strictEqual(backup.resource.toString(), untitledFile.toString()); + } else { + assert.fail('Unexpected backup'); + } + } + }); + }); + + suite('resolve', () => { + + interface IBackupTestMetaData extends IWorkingCopyBackupMeta { + mtime?: number; + size?: number; + etag?: string; + orphaned?: boolean; + } + + test('should restore the original contents (untitled file)', async () => { + const contents = 'test\nand more stuff'; + + await testResolveBackup(untitledFile, contents); + }); + + test('should restore the original contents (untitled file with metadata)', async () => { + const contents = 'test\nand more stuff'; + + const meta = { + etag: 'the Etag', + size: 666, + mtime: Date.now(), + orphaned: true + }; + + await testResolveBackup(untitledFile, contents, meta); + }); + + test('should restore the original contents (untitled file empty with metadata)', async () => { + const contents = ''; + + const meta = { + etag: 'the Etag', + size: 666, + mtime: Date.now(), + orphaned: true + }; + + await testResolveBackup(untitledFile, contents, meta); + }); + + test('should restore the original contents (untitled large file with metadata)', async () => { + const contents = (new Array(30 * 1024)).join('Large String\n'); + + const meta = { + etag: 'the Etag', + size: 666, + mtime: Date.now(), + orphaned: true + }; + + await testResolveBackup(untitledFile, contents, meta); + }); + + test('should restore the original contents (text file)', async () => { + const contents = [ + 'Lorem ipsum ', + 'dolor öäü sit amet ', + 'consectetur ', + 'adipiscing ßß elit' + ].join(''); + + await testResolveBackup(fooFile, contents); + }); + + test('should restore the original contents (text file - custom scheme)', async () => { + const contents = [ + 'Lorem ipsum ', + 'dolor öäü sit amet ', + 'consectetur ', + 'adipiscing ßß elit' + ].join(''); + + await testResolveBackup(customFile, contents); + }); + + test('should restore the original contents (text file with metadata)', async () => { + const contents = [ + 'Lorem ipsum ', + 'dolor öäü sit amet ', + 'adipiscing ßß elit', + 'consectetur ' + ].join(''); + + const meta = { + etag: 'theEtag', + size: 888, + mtime: Date.now(), + orphaned: false + }; + + await testResolveBackup(fooFile, contents, meta); + }); + + test('should restore the original contents (empty text file with metadata)', async () => { + const contents = ''; + + const meta = { + etag: 'theEtag', + size: 888, + mtime: Date.now(), + orphaned: false + }; + + await testResolveBackup(fooFile, contents, meta); + }); + + test('should restore the original contents (large text file with metadata)', async () => { + const contents = (new Array(30 * 1024)).join('Large String\n'); + + const meta = { + etag: 'theEtag', + size: 888, + mtime: Date.now(), + orphaned: false + }; + + await testResolveBackup(fooFile, contents, meta); + }); + + test('should restore the original contents (text file with metadata changed once)', async () => { + const contents = [ + 'Lorem ipsum ', + 'dolor öäü sit amet ', + 'adipiscing ßß elit', + 'consectetur ' + ].join(''); + + const meta = { + etag: 'theEtag', + size: 888, + mtime: Date.now(), + orphaned: false + }; + + await testResolveBackup(fooFile, contents, meta); + + // Change meta and test again + meta.size = 999; + await testResolveBackup(fooFile, contents, meta); + }); + + test('should restore the original contents (text file with metadata and fragment URI)', async () => { + const contents = [ + 'Lorem ipsum ', + 'dolor öäü sit amet ', + 'adipiscing ßß elit', + 'consectetur ' + ].join(''); + + const meta = { + etag: 'theEtag', + size: 888, + mtime: Date.now(), + orphaned: false + }; + + await testResolveBackup(customFileWithFragment, contents, meta); + }); + + test('should restore the original contents (text file with space in name with metadata)', async () => { + const contents = [ + 'Lorem ipsum ', + 'dolor öäü sit amet ', + 'adipiscing ßß elit', + 'consectetur ' + ].join(''); + + const meta = { + etag: 'theEtag', + size: 888, + mtime: Date.now(), + orphaned: false + }; + + await testResolveBackup(fooBarFile, contents, meta); + }); + + test('should restore the original contents (text file with too large metadata to persist)', async () => { + const contents = [ + 'Lorem ipsum ', + 'dolor öäü sit amet ', + 'adipiscing ßß elit', + 'consectetur ' + ].join(''); + + const meta = { + etag: (new Array(100 * 1024)).join('Large String'), + size: 888, + mtime: Date.now(), + orphaned: false + }; + + await testResolveBackup(fooFile, contents, meta, true); + }); + + async function testResolveBackup(resource: URI, contents: string, meta?: IBackupTestMetaData, expectNoMeta?: boolean) { + await doTestResolveBackup(toUntypedWorkingCopyId(resource), contents, meta, expectNoMeta); + await doTestResolveBackup(toTypedWorkingCopyId(resource), contents, meta, expectNoMeta); + } + + async function doTestResolveBackup(identifier: IWorkingCopyIdentifier, contents: string, meta?: IBackupTestMetaData, expectNoMeta?: boolean) { + await service.backup(identifier, bufferToReadable(VSBuffer.fromString(contents)), 1, meta); + + const backup = await service.resolve(identifier); + assert.ok(backup); + assert.strictEqual(contents, (await streamToBuffer(backup.value)).toString()); + + if (expectNoMeta || !meta) { + assert.strictEqual(backup.meta, undefined); + } else { + assert.ok(backup.meta); + assert.strictEqual(backup.meta.etag, meta.etag); + assert.strictEqual(backup.meta.size, meta.size); + assert.strictEqual(backup.meta.mtime, meta.mtime); + assert.strictEqual(backup.meta.orphaned, meta.orphaned); + + assert.strictEqual(Object.keys(meta).length, Object.keys(backup.meta).length); + } + } + + test('should restore the original contents (text file with broken metadata)', async () => { + await testShouldRestoreOriginalContentsWithBrokenBackup(toUntypedWorkingCopyId(fooFile)); + await testShouldRestoreOriginalContentsWithBrokenBackup(toTypedWorkingCopyId(fooFile)); + }); + + async function testShouldRestoreOriginalContentsWithBrokenBackup(identifier: IWorkingCopyIdentifier): Promise { + const contents = [ + 'Lorem ipsum ', + 'dolor öäü sit amet ', + 'adipiscing ßß elit', + 'consectetur ' + ].join(''); + + const meta = { + etag: 'theEtag', + size: 888, + mtime: Date.now(), + orphaned: false + }; + + await service.backup(identifier, bufferToReadable(VSBuffer.fromString(contents)), 1, meta); + + const backupPath = join(workspaceBackupPath, identifier.resource.scheme, hashIdentifier(identifier)); + + const fileContents = readFileSync(backupPath).toString(); + assert.strictEqual(fileContents.indexOf(identifier.resource.toString()), 0); + + const metaIndex = fileContents.indexOf('{'); + const newFileContents = fileContents.substring(0, metaIndex) + '{{' + fileContents.substr(metaIndex); + writeFileSync(backupPath, newFileContents); + + const backup = await service.resolve(identifier); + assert.ok(backup); + assert.strictEqual(contents, (await streamToBuffer(backup.value)).toString()); + assert.strictEqual(backup.meta, undefined); + } + + test('should ignore invalid backups (empty file)', async () => { + const contents = 'test\nand more stuff'; + + await service.backup(toUntypedWorkingCopyId(fooFile), bufferToReadable(VSBuffer.fromString(contents)), 1); + + let backup = await service.resolve(toUntypedWorkingCopyId(fooFile)); + assert.ok(backup); + + await service.fileService.writeFile(service.toBackupResource(toUntypedWorkingCopyId(fooFile)), VSBuffer.fromString('')); + + backup = await service.resolve(toUntypedWorkingCopyId(fooFile)); + assert.ok(!backup); + }); + + test('should ignore invalid backups (no preamble)', async () => { + const contents = 'testand more stuff'; + + await service.backup(toUntypedWorkingCopyId(fooFile), bufferToReadable(VSBuffer.fromString(contents)), 1); + + let backup = await service.resolve(toUntypedWorkingCopyId(fooFile)); + assert.ok(backup); + + await service.fileService.writeFile(service.toBackupResource(toUntypedWorkingCopyId(fooFile)), VSBuffer.fromString(contents)); + + backup = await service.resolve(toUntypedWorkingCopyId(fooFile)); + assert.ok(!backup); + }); + + test('file with binary data', async () => { + const identifier = toUntypedWorkingCopyId(fooFile); + + const sourceDir = getPathFromAmdModule(require, './fixtures'); + + const buffer = await promises.readFile(join(sourceDir, 'binary.txt')); + const hash = createHash('md5').update(buffer).digest('base64'); + + await service.backup(identifier, bufferToReadable(VSBuffer.wrap(buffer)), undefined, { binaryTest: 'true' }); + + const backup = await service.resolve(toUntypedWorkingCopyId(fooFile)); + assert.ok(backup); + + const backupBuffer = await consumeStream(backup.value, chunks => VSBuffer.concat(chunks)); + assert.strictEqual(backupBuffer.buffer.byteLength, buffer.byteLength); + + const backupHash = createHash('md5').update(backupBuffer.buffer).digest('base64'); + + assert.strictEqual(hash, backupHash); + }); + }); + + suite('WorkingCopyBackupsModel', () => { + + test('simple', async () => { + const model = await WorkingCopyBackupsModel.create(URI.file(workspaceBackupPath), service.fileService); + + const resource1 = URI.file('test.html'); + + assert.strictEqual(model.has(resource1), false); + + model.add(resource1); + + assert.strictEqual(model.has(resource1), true); + assert.strictEqual(model.has(resource1, 0), true); + assert.strictEqual(model.has(resource1, 1), false); + assert.strictEqual(model.has(resource1, 1, { foo: 'bar' }), false); + + model.remove(resource1); + + assert.strictEqual(model.has(resource1), false); + + model.add(resource1); + + assert.strictEqual(model.has(resource1), true); + assert.strictEqual(model.has(resource1, 0), true); + assert.strictEqual(model.has(resource1, 1), false); + + model.clear(); + + assert.strictEqual(model.has(resource1), false); + + model.add(resource1, 1); + + assert.strictEqual(model.has(resource1), true); + assert.strictEqual(model.has(resource1, 0), false); + assert.strictEqual(model.has(resource1, 1), true); + + const resource2 = URI.file('test1.html'); + const resource3 = URI.file('test2.html'); + const resource4 = URI.file('test3.html'); + + model.add(resource2); + model.add(resource3); + model.add(resource4, undefined, { foo: 'bar' }); + + assert.strictEqual(model.has(resource1), true); + assert.strictEqual(model.has(resource2), true); + assert.strictEqual(model.has(resource3), true); + + assert.strictEqual(model.has(resource4), true); + assert.strictEqual(model.has(resource4, undefined, { foo: 'bar' }), true); + assert.strictEqual(model.has(resource4, undefined, { bar: 'foo' }), false); + + const resource5 = URI.file('test4.html'); + model.move(resource4, resource5); + assert.strictEqual(model.has(resource4), false); + assert.strictEqual(model.has(resource5), true); + }); + + test('create', async () => { + const fooBackupPath = join(workspaceBackupPath, fooFile.scheme, hashIdentifier(toUntypedWorkingCopyId(fooFile))); + await promises.mkdir(dirname(fooBackupPath), { recursive: true }); + writeFileSync(fooBackupPath, 'foo'); + const model = await WorkingCopyBackupsModel.create(URI.file(workspaceBackupPath), service.fileService); + + assert.strictEqual(model.has(URI.file(fooBackupPath)), true); + }); + + test('get', async () => { + const model = await WorkingCopyBackupsModel.create(URI.file(workspaceBackupPath), service.fileService); + + assert.deepStrictEqual(model.get(), []); + + const file1 = URI.file('/root/file/foo.html'); + const file2 = URI.file('/root/file/bar.html'); + const untitled = URI.file('/root/untitled/bar.html'); + + model.add(file1); + model.add(file2); + model.add(untitled); + + assert.deepStrictEqual(model.get().map(f => f.fsPath), [file1.fsPath, file2.fsPath, untitled.fsPath]); + }); + }); + + suite('Hash migration', () => { + + test('works', async () => { + const fooBackupId = toUntypedWorkingCopyId(fooFile); + const untitledBackupId = toUntypedWorkingCopyId(untitledFile); + const customBackupId = toUntypedWorkingCopyId(customFile); + + const fooBackupPath = join(workspaceBackupPath, fooFile.scheme, hashIdentifier(fooBackupId)); + const untitledBackupPath = join(workspaceBackupPath, untitledFile.scheme, hashIdentifier(untitledBackupId)); + const customFileBackupPath = join(workspaceBackupPath, customFile.scheme, hashIdentifier(customBackupId)); + + // Prepare backups of the old MD5 hash format + mkdirSync(join(workspaceBackupPath, fooFile.scheme), { recursive: true }); + mkdirSync(join(workspaceBackupPath, untitledFile.scheme), { recursive: true }); + mkdirSync(join(workspaceBackupPath, customFile.scheme), { recursive: true }); + writeFileSync(join(workspaceBackupPath, fooFile.scheme, '8a8589a2f1c9444b89add38166f50229'), `${fooFile.toString()}\ntest file`); + writeFileSync(join(workspaceBackupPath, untitledFile.scheme, '13264068d108c6901b3592ea654fcd57'), `${untitledFile.toString()}\ntest untitled`); + writeFileSync(join(workspaceBackupPath, customFile.scheme, 'bf018572af7b38746b502893bd0adf6c'), `${customFile.toString()}\ntest custom`); + + service.reinitialize(URI.file(workspaceBackupPath)); + + const backups = await service.getBackups(); + assert.strictEqual(backups.length, 3); + assert.ok(backups.some(backup => isEqual(backup.resource, fooFile))); + assert.ok(backups.some(backup => isEqual(backup.resource, untitledFile))); + assert.ok(backups.some(backup => isEqual(backup.resource, customFile))); + + assert.strictEqual(readdirSync(join(workspaceBackupPath, fooFile.scheme)).length, 1); + assert.strictEqual(existsSync(fooBackupPath), true); + assert.strictEqual(readFileSync(fooBackupPath).toString(), `${fooFile.toString()}\ntest file`); + assert.ok(service.hasBackupSync(fooBackupId)); + + assert.strictEqual(readdirSync(join(workspaceBackupPath, untitledFile.scheme)).length, 1); + assert.strictEqual(existsSync(untitledBackupPath), true); + assert.strictEqual(readFileSync(untitledBackupPath).toString(), `${untitledFile.toString()}\ntest untitled`); + assert.ok(service.hasBackupSync(untitledBackupId)); + + assert.strictEqual(readdirSync(join(workspaceBackupPath, customFile.scheme)).length, 1); + assert.strictEqual(existsSync(customFileBackupPath), true); + assert.strictEqual(readFileSync(customFileBackupPath).toString(), `${customFile.toString()}\ntest custom`); + assert.ok(service.hasBackupSync(customBackupId)); + }); + }); + + suite('typeId migration', () => { + + test('works (when meta is missing)', async () => { + const fooBackupId = toUntypedWorkingCopyId(fooFile); + const untitledBackupId = toUntypedWorkingCopyId(untitledFile); + const customBackupId = toUntypedWorkingCopyId(customFile); + + const fooBackupPath = join(workspaceBackupPath, fooFile.scheme, hashIdentifier(fooBackupId)); + const untitledBackupPath = join(workspaceBackupPath, untitledFile.scheme, hashIdentifier(untitledBackupId)); + const customFileBackupPath = join(workspaceBackupPath, customFile.scheme, hashIdentifier(customBackupId)); + + // Prepare backups of the old format without meta + mkdirSync(join(workspaceBackupPath, fooFile.scheme), { recursive: true }); + mkdirSync(join(workspaceBackupPath, untitledFile.scheme), { recursive: true }); + mkdirSync(join(workspaceBackupPath, customFile.scheme), { recursive: true }); + writeFileSync(fooBackupPath, `${fooFile.toString()}\ntest file`); + writeFileSync(untitledBackupPath, `${untitledFile.toString()}\ntest untitled`); + writeFileSync(customFileBackupPath, `${customFile.toString()}\ntest custom`); + + service.reinitialize(URI.file(workspaceBackupPath)); + + const backups = await service.getBackups(); + assert.strictEqual(backups.length, 3); + assert.ok(backups.some(backup => isEqual(backup.resource, fooFile))); + assert.ok(backups.some(backup => isEqual(backup.resource, untitledFile))); + assert.ok(backups.some(backup => isEqual(backup.resource, customFile))); + assert.ok(backups.every(backup => backup.typeId === '')); + }); + + test('works (when typeId in meta is missing)', async () => { + const fooBackupId = toUntypedWorkingCopyId(fooFile); + const untitledBackupId = toUntypedWorkingCopyId(untitledFile); + const customBackupId = toUntypedWorkingCopyId(customFile); + + const fooBackupPath = join(workspaceBackupPath, fooFile.scheme, hashIdentifier(fooBackupId)); + const untitledBackupPath = join(workspaceBackupPath, untitledFile.scheme, hashIdentifier(untitledBackupId)); + const customFileBackupPath = join(workspaceBackupPath, customFile.scheme, hashIdentifier(customBackupId)); + + // Prepare backups of the old format without meta + mkdirSync(join(workspaceBackupPath, fooFile.scheme), { recursive: true }); + mkdirSync(join(workspaceBackupPath, untitledFile.scheme), { recursive: true }); + mkdirSync(join(workspaceBackupPath, customFile.scheme), { recursive: true }); + writeFileSync(fooBackupPath, `${fooFile.toString()} ${JSON.stringify({ foo: 'bar' })}\ntest file`); + writeFileSync(untitledBackupPath, `${untitledFile.toString()} ${JSON.stringify({ foo: 'bar' })}\ntest untitled`); + writeFileSync(customFileBackupPath, `${customFile.toString()} ${JSON.stringify({ foo: 'bar' })}\ntest custom`); + + service.reinitialize(URI.file(workspaceBackupPath)); + + const backups = await service.getBackups(); + assert.strictEqual(backups.length, 3); + assert.ok(backups.some(backup => isEqual(backup.resource, fooFile))); + assert.ok(backups.some(backup => isEqual(backup.resource, untitledFile))); + assert.ok(backups.some(backup => isEqual(backup.resource, customFile))); + assert.ok(backups.every(backup => backup.typeId === '')); + }); + }); +}); diff --git a/src/vs/workbench/contrib/backup/test/electron-browser/backupTracker.test.ts b/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyBackupTracker.test.ts similarity index 78% rename from src/vs/workbench/contrib/backup/test/electron-browser/backupTracker.test.ts rename to src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyBackupTracker.test.ts index abb2ae3b..852d30fe 100644 --- a/src/vs/workbench/contrib/backup/test/electron-browser/backupTracker.test.ts +++ b/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyBackupTracker.test.ts @@ -11,43 +11,47 @@ import { join } from 'vs/base/common/path'; import { rimraf, writeFile } from 'vs/base/node/pfs'; import { URI } from 'vs/base/common/uri'; import { flakySuite, getRandomTestPath } from 'vs/base/test/node/testUtils'; -import { hashPath } from 'vs/workbench/services/backup/common/backupFileService'; -import { NativeBackupTracker } from 'vs/workbench/contrib/backup/electron-sandbox/backupTracker'; +import { hash } from 'vs/base/common/hash'; +import { NativeWorkingCopyBackupTracker } from 'vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupTracker'; import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { EditorService } from 'vs/workbench/services/editor/browser/editorService'; -import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; -import { NodeTestBackupFileService } from 'vs/workbench/services/backup/test/electron-browser/backupFileService.test'; +import { IWorkingCopyBackupService } from 'vs/workbench/services/workingCopy/common/workingCopyBackup'; +import { NodeTestWorkingCopyBackupService } from 'vs/workbench/services/workingCopy/test/electron-browser/workingCopyBackupService.test'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { toResource } from 'vs/base/test/common/utils'; import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { ILogService } from 'vs/platform/log/common/log'; import { HotExitConfiguration } from 'vs/platform/files/common/files'; -import { ShutdownReason, ILifecycleService, BeforeShutdownEvent } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { ShutdownReason, ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IFileDialogService, ConfirmResult, IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; -import { BackupTracker } from 'vs/workbench/contrib/backup/common/backupTracker'; +import { WorkingCopyBackupTracker } from 'vs/workbench/services/workingCopy/common/workingCopyBackupTracker'; import { workbenchInstantiationService, TestServiceAccessor } from 'vs/workbench/test/electron-browser/workbenchTestServices'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { createEditorPart, registerTestFileEditor, TestFilesConfigurationService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { createEditorPart, registerTestFileEditor, TestBeforeShutdownEvent, TestFilesConfigurationService } from 'vs/workbench/test/browser/workbenchTestServices'; import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { Workspace } from 'vs/platform/workspace/test/common/testWorkspace'; import { IProgressService } from 'vs/platform/progress/common/progress'; +import { IWorkingCopyEditorService } from 'vs/workbench/services/workingCopy/common/workingCopyEditorService'; +import { TestWorkingCopy } from 'vs/workbench/test/common/workbenchTestServices'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { IWorkingCopyBackup } from 'vs/workbench/services/workingCopy/common/workingCopy'; -flakySuite('BackupTracker (native)', function () { +flakySuite('WorkingCopyBackupTracker (native)', function () { - class TestBackupTracker extends NativeBackupTracker { + class TestBackupTracker extends NativeWorkingCopyBackupTracker { constructor( - @IBackupFileService backupFileService: IBackupFileService, + @IWorkingCopyBackupService workingCopyBackupService: IWorkingCopyBackupService, @IFilesConfigurationService filesConfigurationService: IFilesConfigurationService, @IWorkingCopyService workingCopyService: IWorkingCopyService, @ILifecycleService lifecycleService: ILifecycleService, @@ -58,16 +62,18 @@ flakySuite('BackupTracker (native)', function () { @ILogService logService: ILogService, @IEditorService editorService: IEditorService, @IEnvironmentService environmentService: IEnvironmentService, - @IProgressService progressService: IProgressService + @IProgressService progressService: IProgressService, + @IEditorGroupsService editorGroupService: IEditorGroupsService, + @IWorkingCopyEditorService workingCopyEditorService: IWorkingCopyEditorService ) { - super(backupFileService, filesConfigurationService, workingCopyService, lifecycleService, fileDialogService, dialogService, contextService, nativeHostService, logService, editorService, environmentService, progressService); + super(workingCopyBackupService, filesConfigurationService, workingCopyService, lifecycleService, fileDialogService, dialogService, contextService, nativeHostService, logService, environmentService, progressService, editorGroupService, workingCopyEditorService, editorService); } - protected getBackupScheduleDelay(): number { + protected override getBackupScheduleDelay(): number { return 10; // Reduce timeout for tests } - dispose() { + override dispose() { super.dispose(); for (const [_, disposable] of this.pendingBackups) { @@ -76,16 +82,6 @@ flakySuite('BackupTracker (native)', function () { } } - class BeforeShutdownEventImpl implements BeforeShutdownEvent { - - value: boolean | Promise | undefined; - reason = ShutdownReason.CLOSE; - - veto(value: boolean | Promise): void { - this.value = value; - } - } - let testDir: string; let backupHome: string; let workspaceBackupPath: string; @@ -99,7 +95,7 @@ flakySuite('BackupTracker (native)', function () { const workspacesJsonPath = join(backupHome, 'workspaces.json'); const workspaceResource = URI.file(isWindows ? 'c:\\workspace' : '/workspace'); - workspaceBackupPath = join(backupHome, hashPath(workspaceResource)); + workspaceBackupPath = join(backupHome, hash(workspaceResource.fsPath).toString(16)); const instantiationService = workbenchInstantiationService(); accessor = instantiationService.createInstance(TestServiceAccessor); @@ -119,10 +115,10 @@ flakySuite('BackupTracker (native)', function () { return rimraf(testDir); }); - async function createTracker(autoSaveEnabled = false): Promise<{ accessor: TestServiceAccessor, part: EditorPart, tracker: BackupTracker, instantiationService: IInstantiationService, cleanup: () => Promise }> { - const backupFileService = new NodeTestBackupFileService(testDir, workspaceBackupPath); + async function createTracker(autoSaveEnabled = false): Promise<{ accessor: TestServiceAccessor, part: EditorPart, tracker: WorkingCopyBackupTracker, instantiationService: IInstantiationService, cleanup: () => Promise }> { + const workingCopyBackupService = new NodeTestWorkingCopyBackupService(testDir, workspaceBackupPath); const instantiationService = workbenchInstantiationService(); - instantiationService.stub(IBackupFileService, backupFileService); + instantiationService.stub(IWorkingCopyBackupService, workingCopyBackupService); const configurationService = new TestConfigurationService(); if (autoSaveEnabled) { @@ -135,7 +131,7 @@ flakySuite('BackupTracker (native)', function () { configurationService )); - const part = createEditorPart(instantiationService, disposables); + const part = await createEditorPart(instantiationService, disposables); instantiationService.stub(IEditorGroupsService, part); @@ -144,13 +140,11 @@ flakySuite('BackupTracker (native)', function () { accessor = instantiationService.createInstance(TestServiceAccessor); - await part.whenRestored; - const tracker = instantiationService.createInstance(TestBackupTracker); const cleanup = async () => { // File changes could also schedule some backup operations so we need to wait for them before finishing the test - await accessor.backupFileService.waitForAllBackups(); + await accessor.workingCopyBackupService.waitForAllBackups(); part.dispose(); tracker.dispose(); @@ -173,17 +167,18 @@ flakySuite('BackupTracker (native)', function () { await accessor.editorService.openEditor({ resource, options: { pinned: true } }); const fileModel = accessor.textFileService.files.get(resource); - fileModel?.textEditorModel?.setValue('Super Good'); + assert.ok(fileModel); + fileModel.textEditorModel?.setValue('Super Good'); - await accessor.backupFileService.joinBackupResource(); + await accessor.workingCopyBackupService.joinBackupResource(); - assert.strictEqual(accessor.backupFileService.hasBackupSync(resource), true); + assert.strictEqual(accessor.workingCopyBackupService.hasBackupSync(fileModel), true); - fileModel?.dispose(); + fileModel.dispose(); - await accessor.backupFileService.joinDiscardBackup(); + await accessor.workingCopyBackupService.joinDiscardBackup(); - assert.strictEqual(accessor.backupFileService.hasBackupSync(resource), false); + assert.strictEqual(accessor.workingCopyBackupService.hasBackupSync(fileModel), false); await cleanup(); } @@ -194,8 +189,8 @@ flakySuite('BackupTracker (native)', function () { const resource = toResource.call(this, '/path/index.txt'); await accessor.editorService.openEditor({ resource, options: { pinned: true } }); - const event = new BeforeShutdownEventImpl(); - accessor.lifecycleService.fireWillShutdown(event); + const event = new TestBeforeShutdownEvent(); + accessor.lifecycleService.fireBeforeShutdown(event); const veto = await event.value; assert.ok(!veto); @@ -214,12 +209,12 @@ flakySuite('BackupTracker (native)', function () { accessor.fileDialogService.setConfirmResult(ConfirmResult.CANCEL); accessor.filesConfigurationService.onFilesConfigurationChange({ files: { hotExit: 'off' } }); - await model?.load(); + await model?.resolve(); model?.textEditorModel?.setValue('foo'); assert.strictEqual(accessor.workingCopyService.dirtyCount, 1); - const event = new BeforeShutdownEventImpl(); - accessor.lifecycleService.fireWillShutdown(event); + const event = new TestBeforeShutdownEvent(); + accessor.lifecycleService.fireBeforeShutdown(event); const veto = await event.value; assert.ok(veto); @@ -235,12 +230,12 @@ flakySuite('BackupTracker (native)', function () { const model = accessor.textFileService.files.get(resource); - await model?.load(); + await model?.resolve(); model?.textEditorModel?.setValue('foo'); assert.strictEqual(accessor.workingCopyService.dirtyCount, 1); - const event = new BeforeShutdownEventImpl(); - accessor.lifecycleService.fireWillShutdown(event); + const event = new TestBeforeShutdownEvent(); + accessor.lifecycleService.fireBeforeShutdown(event); const veto = await event.value; assert.ok(!veto); @@ -261,15 +256,15 @@ flakySuite('BackupTracker (native)', function () { accessor.fileDialogService.setConfirmResult(ConfirmResult.DONT_SAVE); accessor.filesConfigurationService.onFilesConfigurationChange({ files: { hotExit: 'off' } }); - await model?.load(); + await model?.resolve(); model?.textEditorModel?.setValue('foo'); assert.strictEqual(accessor.workingCopyService.dirtyCount, 1); - const event = new BeforeShutdownEventImpl(); - accessor.lifecycleService.fireWillShutdown(event); + const event = new TestBeforeShutdownEvent(); + accessor.lifecycleService.fireBeforeShutdown(event); const veto = await event.value; assert.ok(!veto); - assert.ok(accessor.backupFileService.discardedBackups.length > 0); + assert.ok(accessor.workingCopyBackupService.discardedBackups.length > 0); await cleanup(); }); @@ -285,11 +280,11 @@ flakySuite('BackupTracker (native)', function () { accessor.fileDialogService.setConfirmResult(ConfirmResult.SAVE); accessor.filesConfigurationService.onFilesConfigurationChange({ files: { hotExit: 'off' } }); - await model?.load(); + await model?.resolve(); model?.textEditorModel?.setValue('foo'); assert.strictEqual(accessor.workingCopyService.dirtyCount, 1); - const event = new BeforeShutdownEventImpl(); - accessor.lifecycleService.fireWillShutdown(event); + const event = new TestBeforeShutdownEvent(); + accessor.lifecycleService.fireBeforeShutdown(event); const veto = await event.value; assert.ok(!veto); @@ -298,6 +293,36 @@ flakySuite('BackupTracker (native)', function () { await cleanup(); }); + test('onWillShutdown - veto if backup fails', async function () { + const { accessor, cleanup } = await createTracker(); + + class TestBackupWorkingCopy extends TestWorkingCopy { + + constructor(resource: URI) { + super(resource); + + accessor.workingCopyService.registerWorkingCopy(this); + } + + override async backup(token: CancellationToken): Promise { + throw new Error('unable to backup'); + } + } + + const resource = toResource.call(this, '/path/custom.txt'); + const customWorkingCopy = new TestBackupWorkingCopy(resource); + customWorkingCopy.setDirty(true); + + const event = new TestBeforeShutdownEvent(); + event.reason = ShutdownReason.QUIT; + accessor.lifecycleService.fireBeforeShutdown(event); + + const veto = await event.value; + assert.ok(veto); + + await cleanup(); + }); + suite('Hot Exit', () => { suite('"onExit" setting', () => { test('should hot exit on non-Mac (reason: CLOSE, windows: single, workspace)', function () { @@ -425,16 +450,16 @@ flakySuite('BackupTracker (native)', function () { // Set cancel to force a veto if hot exit does not trigger accessor.fileDialogService.setConfirmResult(ConfirmResult.CANCEL); - await model?.load(); + await model?.resolve(); model?.textEditorModel?.setValue('foo'); assert.strictEqual(accessor.workingCopyService.dirtyCount, 1); - const event = new BeforeShutdownEventImpl(); + const event = new TestBeforeShutdownEvent(); event.reason = shutdownReason; - accessor.lifecycleService.fireWillShutdown(event); + accessor.lifecycleService.fireBeforeShutdown(event); const veto = await event.value; - assert.strictEqual(accessor.backupFileService.discardedBackups.length, 0); // When hot exit is set, backups should never be cleaned since the confirm result is cancel + assert.strictEqual(accessor.workingCopyBackupService.discardedBackups.length, 0); // When hot exit is set, backups should never be cleaned since the confirm result is cancel assert.strictEqual(veto, shouldVeto); await cleanup(); diff --git a/src/vs/workbench/services/workspaces/browser/abstractWorkspaceEditingService.ts b/src/vs/workbench/services/workspaces/browser/abstractWorkspaceEditingService.ts index 64cd61a6..36495a67 100644 --- a/src/vs/workbench/services/workspaces/browser/abstractWorkspaceEditingService.ts +++ b/src/vs/workbench/services/workspaces/browser/abstractWorkspaceEditingService.ts @@ -14,7 +14,7 @@ import { ConfigurationScope, IConfigurationRegistry, Extensions as Configuration import { Registry } from 'vs/platform/registry/common/platform'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { distinct } from 'vs/base/common/arrays'; -import { isEqual, isEqualAuthority } from 'vs/base/common/resources'; +import { dirname, isEqual, isEqualAuthority } from 'vs/base/common/resources'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { IFileService } from 'vs/platform/files/common/files'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -26,6 +26,7 @@ import { IHostService } from 'vs/workbench/services/host/browser/host'; import { Schemas } from 'vs/base/common/network'; import { SaveReason } from 'vs/workbench/common/editor'; import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; +import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust'; const UNTITLED_WORKSPACE_FILENAME = `workspace.${WORKSPACE_EXTENSION}`; @@ -46,7 +47,8 @@ export abstract class AbstractWorkspaceEditingService implements IWorkspaceEditi @IFileDialogService private readonly fileDialogService: IFileDialogService, @IDialogService protected readonly dialogService: IDialogService, @IHostService protected readonly hostService: IHostService, - @IUriIdentityService protected readonly uriIdentityService: IUriIdentityService + @IUriIdentityService protected readonly uriIdentityService: IUriIdentityService, + @IWorkspaceTrustManagementService private readonly workspaceTrustManagementService: IWorkspaceTrustManagementService ) { } async pickNewWorkspacePath(): Promise { @@ -254,6 +256,9 @@ export abstract class AbstractWorkspaceEditingService implements IWorkspaceEditi const raw = await this.fileService.readFile(configPathURI); const newRawWorkspaceContents = rewriteWorkspaceFileForNewLocation(raw.value.toString(), configPathURI, isFromUntitledWorkspace, targetConfigPathURI, this.uriIdentityService.extUri); await this.textFileService.create([{ resource: targetConfigPathURI, value: newRawWorkspaceContents, options: { overwrite: true } }]); + + // Set trust for the workspace file + this.trustWorkspaceConfiguration(targetConfigPathURI); } protected async saveWorkspace(workspace: IWorkspaceIdentifier): Promise { @@ -354,6 +359,12 @@ export abstract class AbstractWorkspaceEditingService implements IWorkspaceEditi return this.jsonEditingService.write(toWorkspace.configPath, [{ path: ['settings'], value: targetWorkspaceConfiguration }], true); } + private trustWorkspaceConfiguration(configPathURI: URI): void { + if (this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY && this.workspaceTrustManagementService.isWorkpaceTrusted()) { + this.workspaceTrustManagementService.setFoldersTrust([dirname(configPathURI)], true); + } + } + protected getCurrentWorkspaceIdentifier(): IWorkspaceIdentifier | undefined { const workspace = this.contextService.getWorkspace(); if (workspace?.configuration) { diff --git a/src/vs/workbench/services/workspaces/browser/workspaceEditingService.ts b/src/vs/workbench/services/workspaces/browser/workspaceEditingService.ts index 6af823c7..e26c67e9 100644 --- a/src/vs/workbench/services/workspaces/browser/workspaceEditingService.ts +++ b/src/vs/workbench/services/workspaces/browser/workspaceEditingService.ts @@ -20,11 +20,10 @@ import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/commo import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { URI } from 'vs/base/common/uri'; import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; +import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust'; export class BrowserWorkspaceEditingService extends AbstractWorkspaceEditingService { - declare readonly _serviceBrand: undefined; - constructor( @IJSONEditingService jsonEditingService: IJSONEditingService, @IWorkspaceContextService contextService: WorkspaceService, @@ -38,9 +37,10 @@ export class BrowserWorkspaceEditingService extends AbstractWorkspaceEditingServ @IFileDialogService fileDialogService: IFileDialogService, @IDialogService dialogService: IDialogService, @IHostService hostService: IHostService, - @IUriIdentityService uriIdentityService: IUriIdentityService + @IUriIdentityService uriIdentityService: IUriIdentityService, + @IWorkspaceTrustManagementService workspaceTrustManagementService: IWorkspaceTrustManagementService ) { - super(jsonEditingService, contextService, configurationService, notificationService, commandService, fileService, textFileService, workspacesService, environmentService, fileDialogService, dialogService, hostService, uriIdentityService); + super(jsonEditingService, contextService, configurationService, notificationService, commandService, fileService, textFileService, workspacesService, environmentService, fileDialogService, dialogService, hostService, uriIdentityService, workspaceTrustManagementService); } async enterWorkspace(path: URI): Promise { diff --git a/src/vs/workbench/services/workspaces/browser/workspaceTrustEditorInput.ts b/src/vs/workbench/services/workspaces/browser/workspaceTrustEditorInput.ts index 78181c57..b24e0bb7 100644 --- a/src/vs/workbench/services/workspaces/browser/workspaceTrustEditorInput.ts +++ b/src/vs/workbench/services/workspaces/browser/workspaceTrustEditorInput.ts @@ -6,37 +6,25 @@ import { Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; -import { IWorkspaceTrustService } from 'vs/platform/workspace/common/workspaceTrust'; import { EditorInput } from 'vs/workbench/common/editor'; -import { WorkspaceTrustEditorModel, WorkspaceTrustService } from 'vs/workbench/services/workspaces/common/workspaceTrust'; export class WorkspaceTrustEditorInput extends EditorInput { static readonly ID: string = 'workbench.input.workspaceTrust'; + override get typeId(): string { + return WorkspaceTrustEditorInput.ID; + } + readonly resource: URI = URI.from({ scheme: Schemas.vscodeWorkspaceTrust, path: `workspaceTrustEditor` }); - constructor( - @IWorkspaceTrustService private readonly workspaceTrustService: WorkspaceTrustService - ) { - super(); - } - - getTypeId(): string { - return WorkspaceTrustEditorInput.ID; - } - - matches(otherInput: unknown): boolean { + override matches(otherInput: unknown): boolean { return otherInput instanceof WorkspaceTrustEditorInput; } - getName(): string { + override getName(): string { return localize('workspaceTrustEditorInputName', "Workspace Trust"); } - - async resolve(): Promise { - return this.workspaceTrustService.workspaceTrustEditorModel; - } } diff --git a/src/vs/workbench/services/workspaces/common/workspaceTrust.ts b/src/vs/workbench/services/workspaces/common/workspaceTrust.ts index fba1e83e..ac0463e5 100644 --- a/src/vs/workbench/services/workspaces/common/workspaceTrust.ts +++ b/src/vs/workbench/services/workspaces/common/workspaceTrust.ts @@ -3,66 +3,96 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Emitter, Event } from 'vs/base/common/event'; +import { Emitter } from 'vs/base/common/event'; +import { splitName } from 'vs/base/common/labels'; import { Disposable } from 'vs/base/common/lifecycle'; +import { Schemas } from 'vs/base/common/network'; +import { isWeb } from 'vs/base/common/platform'; +import { dirname } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; +import { localize } from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; -import { IWorkspace, IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { IWorkspaceTrustModel, WorkspaceTrustRequestOptions, IWorkspaceTrustRequestModel, IWorkspaceTrustService, IWorkspaceTrustStateInfo, WorkspaceTrustState, WorkspaceTrustStateChangeEvent, IWorkspaceTrustFolderInfo } from 'vs/platform/workspace/common/workspaceTrust'; -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'; +import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; +import { WorkspaceTrustRequestOptions, IWorkspaceTrustManagementService, IWorkspaceTrustInfo, IWorkspaceTrustUriInfo, IWorkspaceTrustRequestService } from 'vs/platform/workspace/common/workspaceTrust'; +import { isSingleFolderWorkspaceIdentifier, isUntitledWorkspace, toWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; +import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; -export const WORKSPACE_TRUST_ENABLED = 'workspace.trustEnabled'; +export const WORKSPACE_TRUST_ENABLED = 'security.workspace.trust.enabled'; +export const WORKSPACE_TRUST_STARTUP_PROMPT = 'security.workspace.trust.startupPrompt'; +export const WORKSPACE_TRUST_EXTENSION_SUPPORT = 'extensions.supportUntrustedWorkspaces'; export const WORKSPACE_TRUST_STORAGE_KEY = 'content.trust.model.key'; export const WorkspaceTrustContext = { PendingRequest: new RawContextKey('workspaceTrustPendingRequest', false), - TrustState: new RawContextKey('workspaceTrustState', WorkspaceTrustState.Unknown) + IsTrusted: new RawContextKey('isWorkspaceTrusted', false, localize('workspaceTrustCtx', "Whether the current workspace has been trusted by the user.")) }; -export class WorkspaceTrustEditorModel extends EditorModel { - constructor( - readonly dataModel: IWorkspaceTrustModel, - private readonly workspaceTrustService: WorkspaceTrustService - ) { - super(); +export function isWorkspaceTrustEnabled(configurationService: IConfigurationService): boolean { + if (isWeb) { + return false; } - get currentWorkspaceTrustState(): WorkspaceTrustState { - return this.workspaceTrustService.getWorkspaceTrustState(); - } + return configurationService.inspect(WORKSPACE_TRUST_ENABLED).userValue ?? false; } -export class WorkspaceTrustModel extends Disposable implements IWorkspaceTrustModel { - private storageKey = WORKSPACE_TRUST_STORAGE_KEY; - private trustStateInfo: IWorkspaceTrustStateInfo; +export class WorkspaceTrustManagementService extends Disposable implements IWorkspaceTrustManagementService { - private readonly _onDidChangeTrustState = this._register(new Emitter()); - readonly onDidChangeTrustState = this._onDidChangeTrustState.event; + _serviceBrand: undefined; + + private readonly storageKey = WORKSPACE_TRUST_STORAGE_KEY; + + private readonly _onDidChangeTrust = this._register(new Emitter()); + readonly onDidChangeTrust = this._onDidChangeTrust.event; + + private readonly _onDidChangeTrustedFolders = this._register(new Emitter()); + readonly onDidChangeTrustedFolders = this._onDidChangeTrustedFolders.event; + + private _isWorkspaceTrusted: boolean = false; + private _trustStateInfo: IWorkspaceTrustInfo; constructor( - private readonly storageService: IStorageService + @IConfigurationService readonly configurationService: IConfigurationService, + @IEnvironmentService private readonly environmentService: IEnvironmentService, + @IStorageService private readonly storageService: IStorageService, + @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, + @IWorkspaceContextService private readonly workspaceService: IWorkspaceContextService ) { super(); - this.trustStateInfo = this.loadTrustInfo(); + this._trustStateInfo = this.loadTrustInfo(); + this._isWorkspaceTrusted = this.calculateWorkspaceTrust(); + + this.registerListeners(); + } + + private set currentTrustState(trusted: boolean) { + if (this._isWorkspaceTrusted === trusted) { return; } + this._isWorkspaceTrusted = trusted; + + this._onDidChangeTrust.fire(trusted); + } + + private registerListeners(): void { + this._register(this.workspaceService.onDidChangeWorkspaceFolders(() => this.currentTrustState = this.calculateWorkspaceTrust())); + this._register(this.workspaceService.onDidChangeWorkbenchState(() => this.currentTrustState = this.calculateWorkspaceTrust())); this._register(this.storageService.onDidChangeValue(changeEvent => { if (changeEvent.key === this.storageKey) { - this.onDidStorageChange(); + this._trustStateInfo = this.loadTrustInfo(); + this.currentTrustState = this.calculateWorkspaceTrust(); + + this._onDidChangeTrustedFolders.fire(); } })); } - private loadTrustInfo(): IWorkspaceTrustStateInfo { + private loadTrustInfo(): IWorkspaceTrustInfo { const infoAsString = this.storageService.get(this.storageKey, StorageScope.GLOBAL); - let result: IWorkspaceTrustStateInfo | undefined; + let result: IWorkspaceTrustInfo | undefined; try { if (infoAsString) { result = JSON.parse(infoAsString); @@ -71,83 +101,103 @@ export class WorkspaceTrustModel extends Disposable implements IWorkspaceTrustMo if (!result) { result = { - localFolders: [], - //trustedRemoteItems: [] + uriTrustInfo: [] }; } - if (!result.localFolders) { - result.localFolders = []; + if (!result.uriTrustInfo) { + result.uriTrustInfo = []; } - // if (!result.trustedRemoteItems) { - // result.trustedRemoteItems = []; - // } + result.uriTrustInfo = result.uriTrustInfo.map(info => { return { uri: URI.revive(info.uri), trusted: info.trusted }; }); + result.uriTrustInfo = result.uriTrustInfo.filter(info => info.trusted); return result; } private saveTrustInfo(): void { - this.storageService.store(this.storageKey, JSON.stringify(this.trustStateInfo), StorageScope.GLOBAL, StorageTarget.MACHINE); + this.storageService.store(this.storageKey, JSON.stringify(this._trustStateInfo), StorageScope.GLOBAL, StorageTarget.MACHINE); } - private onDidStorageChange(): void { - this.trustStateInfo = this.loadTrustInfo(); - - this._onDidChangeTrustState.fire(); - } - - setTrustedFolders(folders: URI[]): void { - this.trustStateInfo.localFolders = this.trustStateInfo.localFolders.filter(folder => folder.trustState !== WorkspaceTrustState.Trusted); - for (const folder of folders) { - this.trustStateInfo.localFolders.push({ - trustState: WorkspaceTrustState.Trusted, - uri: folder.fsPath - }); + private calculateWorkspaceTrust(): boolean { + if (!isWorkspaceTrustEnabled(this.configurationService)) { + return true; } - this.saveTrustInfo(); - } - - setUntrustedFolders(folders: URI[]): void { - this.trustStateInfo.localFolders = this.trustStateInfo.localFolders.filter(folder => folder.trustState !== WorkspaceTrustState.Untrusted); - for (const folder of folders) { - this.trustStateInfo.localFolders.push({ - trustState: WorkspaceTrustState.Untrusted, - uri: folder.fsPath - }); + if (this.environmentService.extensionTestsLocationURI) { + return true; // trust running tests with vscode-test } - this.saveTrustInfo(); + if (this.workspaceService.getWorkbenchState() === WorkbenchState.EMPTY) { + return true; + } + + const workspaceFolders = this.getWorkspaceFolders(); + const trusted = this.getFoldersTrust(workspaceFolders); + + return trusted; } - setFolderTrustState(folder: URI, trustState: WorkspaceTrustState): void { - let changed = false; + private getFoldersTrust(folders: URI[]): boolean { + let state = true; + for (const folder of folders) { + const { trusted } = this.getFolderTrustInfo(folder); - const folderPath = folder.fsPath; - - if (trustState === WorkspaceTrustState.Unknown) { - const before = this.trustStateInfo.localFolders.length; - this.trustStateInfo.localFolders = this.trustStateInfo.localFolders.filter(info => isEqual(URI.file(info.uri).fsPath, folderPath)); - - if (this.trustStateInfo.localFolders.length !== before) { - changed = true; + if (!trusted) { + state = trusted; + return state; } - } else { - let found = false; - for (const trustInfo of this.trustStateInfo.localFolders) { - if (isEqual(URI.file(trustInfo.uri).fsPath, folderPath)) { - found = true; - if (trustInfo.trustState !== trustState) { - trustInfo.trustState = trustState; - changed = true; - } + } + + return state; + } + + private getWorkspaceFolders(): URI[] { + const folderURIs = this.workspaceService.getWorkspace().folders.map(f => f.uri); + const workspaceConfiguration = this.workspaceService.getWorkspace().configuration; + if (workspaceConfiguration && !isUntitledWorkspace(workspaceConfiguration, this.environmentService)) { + folderURIs.push(dirname(workspaceConfiguration)); + } + + return folderURIs; + } + + public getFolderTrustInfo(folder: URI): IWorkspaceTrustUriInfo { + let resultState = false; + let maxLength = -1; + + let resultUri = folder; + + for (const trustInfo of this._trustStateInfo.uriTrustInfo) { + if (this.uriIdentityService.extUri.isEqualOrParent(folder, trustInfo.uri)) { + const fsPath = trustInfo.uri.fsPath; + if (fsPath.length > maxLength) { + maxLength = fsPath.length; + resultState = trustInfo.trusted; + resultUri = trustInfo.uri; } } + } - if (!found) { - this.trustStateInfo.localFolders.push({ uri: folderPath, trustState }); - changed = true; + return { trusted: resultState, uri: resultUri }; + } + + setFoldersTrust(folders: URI[], trusted: boolean): void { + let changed = false; + + for (const folder of folders) { + if (trusted) { + const foundItem = this._trustStateInfo.uriTrustInfo.find(trustInfo => this.uriIdentityService.extUri.isEqual(trustInfo.uri, folder)); + if (!foundItem) { + this._trustStateInfo.uriTrustInfo.push({ uri: folder, trusted: true }); + changed = true; + } + } else { + const previousLength = this._trustStateInfo.uriTrustInfo.length; + this._trustStateInfo.uriTrustInfo = this._trustStateInfo.uriTrustInfo.filter(trustInfo => !this.uriIdentityService.extUri.isEqual(trustInfo.uri, folder)); + if (previousLength !== this._trustStateInfo.uriTrustInfo.length) { + changed = true; + } } } @@ -156,304 +206,173 @@ export class WorkspaceTrustModel extends Disposable implements IWorkspaceTrustMo } } - getFolderTrustStateInfo(folder: URI): IWorkspaceTrustFolderInfo { - let resultState = WorkspaceTrustState.Unknown; - let maxLength = -1; - - const folderPath = folder.fsPath; - let resultFolder = folderPath; - - for (const trustInfo of this.trustStateInfo.localFolders) { - const trustInfoPath = URI.file(trustInfo.uri).fsPath; - - if (isEqualOrParent(folderPath, trustInfoPath)) { - if (trustInfoPath.length > maxLength) { - maxLength = trustInfoPath.length; - resultState = trustInfo.trustState; - resultFolder = trustInfoPath; - } - } - } - - return { trustState: resultState, uri: resultFolder }; + canSetWorkspaceTrust(): boolean { + return this.workspaceService.getWorkbenchState() !== WorkbenchState.EMPTY; } - getTrustStateInfo(): IWorkspaceTrustStateInfo { - return this.trustStateInfo; + canSetParentFolderTrust(): boolean { + const workspaceIdentifier = toWorkspaceIdentifier(this.workspaceService.getWorkspace()); + return isSingleFolderWorkspaceIdentifier(workspaceIdentifier) && workspaceIdentifier.uri.scheme === Schemas.file; + } + + isWorkpaceTrusted(): boolean { + return this._isWorkspaceTrusted; + } + + setParentFolderTrust(trusted: boolean): void { + const workspaceIdentifier = toWorkspaceIdentifier(this.workspaceService.getWorkspace()); + if (isSingleFolderWorkspaceIdentifier(workspaceIdentifier) && workspaceIdentifier.uri.scheme === Schemas.file) { + const { parentPath } = splitName(workspaceIdentifier.uri.fsPath); + + this.setFoldersTrust([URI.file(parentPath)], trusted); + } + } + + setWorkspaceTrust(trusted: boolean): void { + const workspaceFolders = this.getWorkspaceFolders(); + this.setFoldersTrust(workspaceFolders, trusted); + } + + getTrustedFolders(): URI[] { + return this._trustStateInfo.uriTrustInfo.map(info => info.uri); + } + + setTrustedFolders(folders: URI[]): void { + this._trustStateInfo.uriTrustInfo = []; + for (const folder of folders) { + this._trustStateInfo.uriTrustInfo.push({ + trusted: true, + uri: folder + }); + } + + this.saveTrustInfo(); } } -export class WorkspaceTrustRequestModel extends Disposable implements IWorkspaceTrustRequestModel { - trustRequestOptions: WorkspaceTrustRequestOptions | undefined; - - private readonly _onDidInitiateRequest = this._register(new Emitter()); - readonly onDidInitiateRequest: Event = this._onDidInitiateRequest.event; - - 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(options: WorkspaceTrustRequestOptions): void { - if (this.trustRequestOptions && (!options.modal || this.trustRequestOptions.modal)) { - return; - } - - this.trustRequestOptions = options; - this._onDidInitiateRequest.fire(); - } - - completeRequest(trustState?: WorkspaceTrustState): void { - this.trustRequestOptions = undefined; - this._onDidCompleteRequest.fire(trustState); - } - - cancelRequest(): void { - this.trustRequestOptions = undefined; - this._onDidCancelRequest.fire(); - } -} - -export class WorkspaceTrustService extends Disposable implements IWorkspaceTrustService { - +export class WorkspaceTrustRequestService extends Disposable implements IWorkspaceTrustRequestService { _serviceBrand: undefined; - private readonly dataModel: IWorkspaceTrustModel; - readonly requestModel: IWorkspaceTrustRequestModel; - private editorModel?: WorkspaceTrustEditorModel; - private readonly _onDidChangeTrustState = this._register(new Emitter()); - readonly onDidChangeTrustState = this._onDidChangeTrustState.event; - - private _currentTrustState: WorkspaceTrustState = WorkspaceTrustState.Unknown; - 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; + private _trusted!: boolean; + private _trustRequestPromise?: Promise; + private _trustRequestResolver?: (trusted: boolean) => void; + private _modalTrustRequestPromise?: Promise; + private _modalTrustRequestResolver?: (trusted: boolean | undefined) => void; + private readonly _ctxWorkspaceTrustState: IContextKey; private readonly _ctxWorkspaceTrustPendingRequest: IContextKey; + private readonly _onDidInitiateWorkspaceTrustRequest = this._register(new Emitter()); + readonly onDidInitiateWorkspaceTrustRequest = this._onDidInitiateWorkspaceTrustRequest.event; + + private readonly _onDidCompleteWorkspaceTrustRequest = this._register(new Emitter()); + readonly onDidCompleteWorkspaceTrustRequest = this._onDidCompleteWorkspaceTrustRequest.event; + constructor( - @IConfigurationService readonly configurationService: IConfigurationService, - @IContextKeyService readonly contextKeyService: IContextKeyService, - @IStorageService private readonly storageService: IStorageService, - @IWorkspaceContextService private readonly workspaceService: IWorkspaceContextService, - @ITelemetryService private readonly telemetryService: ITelemetryService, + @IContextKeyService contextKeyService: IContextKeyService, + @IWorkspaceTrustManagementService private readonly workspaceTrustManagementService: IWorkspaceTrustManagementService, ) { super(); - this.dataModel = this._register(new WorkspaceTrustModel(this.storageService)); - this.requestModel = this._register(new WorkspaceTrustRequestModel()); + this._register(this.workspaceTrustManagementService.onDidChangeTrust(trusted => this.onTrustStateChanged(trusted))); - this._workspace = this.workspaceService.getWorkspace(); - this._currentTrustState = this.calculateWorkspaceTrustState(); - - 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._ctxWorkspaceTrustState = WorkspaceTrustContext.IsTrusted.bindTo(contextKeyService); this._ctxWorkspaceTrustPendingRequest = WorkspaceTrustContext.PendingRequest.bindTo(contextKeyService); - this._ctxWorkspaceTrustState.set(this.currentTrustState); + + this.trusted = this.workspaceTrustManagementService.isWorkpaceTrusted(); } - private get currentTrustState(): WorkspaceTrustState { - return this._currentTrustState; + private get trusted(): boolean { + return this._trusted; } - private set currentTrustState(trustState: WorkspaceTrustState) { - if (this._currentTrustState === trustState) { return; } - const previousState = this._currentTrustState; - this._currentTrustState = trustState; - - this._onDidChangeTrustState.fire({ previousTrustState: previousState, currentTrustState: this._currentTrustState }); + private set trusted(trusted: boolean) { + this._trusted = trusted; + this._ctxWorkspaceTrustState.set(trusted); } - get workspaceTrustEditorModel(): WorkspaceTrustEditorModel { - if (this.editorModel === undefined) { - this.editorModel = this._register(new WorkspaceTrustEditorModel(this.dataModel, this)); + private onTrustStateChanged(trusted: boolean): void { + // Resolve any pending soft requests for workspace trust + if (this._trustRequestResolver) { + this._trustRequestResolver(trusted); + + this._trustRequestResolver = undefined; + this._trustRequestPromise = undefined; } - return this.editorModel; + // Update context if there are no pending requests + if (!this._modalTrustRequestPromise && !this._trustRequestPromise) { + this._ctxWorkspaceTrustPendingRequest.set(false); + } + + this.trusted = trusted; } - private logInitialWorkspaceTrustInfo(): void { - if (!this.isWorkspaceTrustEnabled()) { - return; - } - - type WorkspaceTrustInfoEventClassification = { - trustedFoldersCount: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; - untrustedFoldersCount: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; - }; - - type WorkspaceTrustInfoEvent = { - trustedFoldersCount: number, - untrustedFoldersCount: number - }; - - this.telemetryService.publicLog2('workspaceTrustFolderCounts', { - trustedFoldersCount: this.dataModel.getTrustStateInfo().localFolders.filter(item => item.trustState === WorkspaceTrustState.Trusted).length, - untrustedFoldersCount: this.dataModel.getTrustStateInfo().localFolders.filter(item => item.trustState === WorkspaceTrustState.Untrusted).length - }); - } - - private logWorkspaceTrustFolderInfo(workspaceFolder: string, trustedFolder: string): void { - if (!this.isWorkspaceTrustEnabled()) { - return; - } - - type WorkspaceTrustFolderInfoEventClassification = { - trustedFolderDepth: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; - workspaceFolderDepth: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; - delta: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; - }; - - type WorkspaceTrustFolderInfoEvent = { - trustedFolderDepth: number, - workspaceFolderDepth: number, - delta: number - }; - - const getDepth = (folder: string): number => { - let resolvedPath = resolve(folder); - - let depth = 0; - while (dirname(resolvedPath) !== resolvedPath && depth < 100) { - resolvedPath = dirname(resolvedPath); - depth++; - } - - return depth; - }; - - const workspaceFolderDepth = getDepth(workspaceFolder); - const trustedFolderDepth = getDepth(trustedFolder); - const delta = workspaceFolderDepth - trustedFolderDepth; - - this.telemetryService.publicLog2('workspaceFolderDepthBelowTrustedFolder', { workspaceFolderDepth, trustedFolderDepth, delta }); - } - - private calculateWorkspaceTrustState(): WorkspaceTrustState { - if (!this.isWorkspaceTrustEnabled()) { - return WorkspaceTrustState.Trusted; - } - - if (this.workspaceService.getWorkbenchState() === WorkbenchState.EMPTY) { - return WorkspaceTrustState.Trusted; - } - - let state = undefined; - for (const folder of this._workspace.folders) { - const { trustState, uri } = this.dataModel.getFolderTrustStateInfo(folder.uri); - - switch (trustState) { - case WorkspaceTrustState.Untrusted: - return WorkspaceTrustState.Untrusted; - case WorkspaceTrustState.Unknown: - state = trustState; - break; - case WorkspaceTrustState.Trusted: - this.logWorkspaceTrustFolderInfo(folder.uri.fsPath, uri); - if (state === undefined) { - state = trustState; - } - break; - } - } - - return state ?? WorkspaceTrustState.Unknown; - } - - private onTrustRequestCompleted(trustState?: WorkspaceTrustState): void { + cancelRequest(): void { if (this._modalTrustRequestResolver) { - this._modalTrustRequestResolver(trustState === undefined ? this.currentTrustState : trustState); + this._modalTrustRequestResolver(undefined); + + this._modalTrustRequestResolver = undefined; + this._modalTrustRequestPromise = undefined; } - if (this._inFlightResolver) { - this._inFlightResolver(trustState === undefined ? this.currentTrustState : trustState); + } + + completeRequest(trusted?: boolean): void { + if (this._modalTrustRequestResolver) { + this._modalTrustRequestResolver(trusted ?? this.trusted); + + this._modalTrustRequestResolver = undefined; + this._modalTrustRequestPromise = undefined; + } + if (this._trustRequestResolver) { + this._trustRequestResolver(trusted ?? this.trusted); + + this._trustRequestResolver = undefined; + this._trustRequestPromise = undefined; } - this._inFlightResolver = undefined; - this._trustRequestPromise = undefined; - - this._modalTrustRequestResolver = undefined; - this._modalTrustRequestRejecter = undefined; - this._modalTrustRequestPromise = undefined; - - if (trustState === undefined) { + if (trusted === undefined) { return; } - this._workspace.folders.forEach(folder => { - this.dataModel.setFolderTrustState(folder.uri, trustState); - }); - - this._ctxWorkspaceTrustPendingRequest.set(false); - this._ctxWorkspaceTrustState.set(trustState); + this.workspaceTrustManagementService.setWorkspaceTrust(trusted); + this._onDidCompleteWorkspaceTrustRequest.fire(trusted); } - private onTrustRequestCancelled(): void { - if (this._modalTrustRequestRejecter) { - this._modalTrustRequestRejecter(canceled()); - } - - this._modalTrustRequestResolver = undefined; - this._modalTrustRequestRejecter = undefined; - this._modalTrustRequestPromise = undefined; - } - - getWorkspaceTrustState(): WorkspaceTrustState { - return this.currentTrustState; - } - - isWorkspaceTrustEnabled(): boolean { - return this.configurationService.getValue(WORKSPACE_TRUST_ENABLED) ?? false; - } - - async requireWorkspaceTrust(options: WorkspaceTrustRequestOptions = { modal: true }): Promise { + async requestWorkspaceTrust(options: WorkspaceTrustRequestOptions = { modal: false }): Promise { // Trusted workspace - if (this.currentTrustState === WorkspaceTrustState.Trusted) { - return this.currentTrustState; - } - // Untrusted workspace - soft request - if (this.currentTrustState === WorkspaceTrustState.Untrusted && !options.modal) { - return this.currentTrustState; + if (this.trusted) { + return this.trusted; } if (options.modal) { // Modal request if (!this._modalTrustRequestPromise) { // Create promise - this._modalTrustRequestPromise = new Promise((resolve, reject) => { + this._modalTrustRequestPromise = new Promise(resolve => { this._modalTrustRequestResolver = resolve; - this._modalTrustRequestRejecter = reject; }); } else { - // Return existing promises + // Return existing promise return this._modalTrustRequestPromise; } } else { // Soft request if (!this._trustRequestPromise) { + // Create promise this._trustRequestPromise = new Promise(resolve => { - this._inFlightResolver = resolve; + this._trustRequestResolver = resolve; }); } else { + // Return existing promise return this._trustRequestPromise; } } - this.requestModel.initiateRequest(options); this._ctxWorkspaceTrustPendingRequest.set(true); + this._onDidInitiateWorkspaceTrustRequest.fire(options); return options.modal ? this._modalTrustRequestPromise! : this._trustRequestPromise!; } } -registerSingleton(IWorkspaceTrustService, WorkspaceTrustService); +registerSingleton(IWorkspaceTrustRequestService, WorkspaceTrustRequestService); diff --git a/src/vs/workbench/services/workspaces/electron-sandbox/workspaceEditingService.ts b/src/vs/workbench/services/workspaces/electron-sandbox/workspaceEditingService.ts index 486e9e29..32ce1d95 100644 --- a/src/vs/workbench/services/workspaces/electron-sandbox/workspaceEditingService.ts +++ b/src/vs/workbench/services/workspaces/electron-sandbox/workspaceEditingService.ts @@ -12,7 +12,7 @@ import { IWorkspacesService, isUntitledWorkspace, IWorkspaceIdentifier, hasWorks import { WorkspaceService } from 'vs/workbench/services/configuration/browser/configurationService'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; +import { IWorkingCopyBackupService } from 'vs/workbench/services/workingCopy/common/workingCopyBackup'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { basename } from 'vs/base/common/resources'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; @@ -29,13 +29,12 @@ import { AbstractWorkspaceEditingService } from 'vs/workbench/services/workspace import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; import { isMacintosh } from 'vs/base/common/platform'; import { mnemonicButtonLabel } from 'vs/base/common/labels'; -import { BackupFileService } from 'vs/workbench/services/backup/common/backupFileService'; +import { WorkingCopyBackupService } from 'vs/workbench/services/workingCopy/common/workingCopyBackupService'; import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; +import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust'; export class NativeWorkspaceEditingService extends AbstractWorkspaceEditingService { - declare readonly _serviceBrand: undefined; - constructor( @IJSONEditingService jsonEditingService: IJSONEditingService, @IWorkspaceContextService contextService: WorkspaceService, @@ -43,21 +42,22 @@ export class NativeWorkspaceEditingService extends AbstractWorkspaceEditingServi @IConfigurationService configurationService: IConfigurationService, @IStorageService private storageService: IStorageService, @IExtensionService private extensionService: IExtensionService, - @IBackupFileService private backupFileService: IBackupFileService, + @IWorkingCopyBackupService private workingCopyBackupService: IWorkingCopyBackupService, @INotificationService notificationService: INotificationService, @ICommandService commandService: ICommandService, @IFileService fileService: IFileService, @ITextFileService textFileService: ITextFileService, @IWorkspacesService workspacesService: IWorkspacesService, - @INativeWorkbenchEnvironmentService protected environmentService: INativeWorkbenchEnvironmentService, + @INativeWorkbenchEnvironmentService environmentService: INativeWorkbenchEnvironmentService, @IFileDialogService fileDialogService: IFileDialogService, - @IDialogService protected dialogService: IDialogService, + @IDialogService dialogService: IDialogService, @ILifecycleService private readonly lifecycleService: ILifecycleService, @ILabelService private readonly labelService: ILabelService, @IHostService hostService: IHostService, - @IUriIdentityService uriIdentityService: IUriIdentityService + @IUriIdentityService uriIdentityService: IUriIdentityService, + @IWorkspaceTrustManagementService workspaceTrustManagementService: IWorkspaceTrustManagementService ) { - super(jsonEditingService, contextService, configurationService, notificationService, commandService, fileService, textFileService, workspacesService, environmentService, fileDialogService, dialogService, hostService, uriIdentityService); + super(jsonEditingService, contextService, configurationService, notificationService, commandService, fileService, textFileService, workspacesService, environmentService, fileDialogService, dialogService, hostService, uriIdentityService, workspaceTrustManagementService); this.registerListeners(); } @@ -139,7 +139,7 @@ export class NativeWorkspaceEditingService extends AbstractWorkspaceEditingServi } } - async isValidTargetWorkspacePath(path: URI): Promise { + override async isValidTargetWorkspacePath(path: URI): Promise { const windows = await this.nativeHostService.getWindows(); // Prevent overwriting a workspace that is currently opened in another window @@ -167,9 +167,9 @@ export class NativeWorkspaceEditingService extends AbstractWorkspaceEditingServi await this.migrateStorage(result.workspace); // Reinitialize backup service - if (this.backupFileService instanceof BackupFileService) { + if (this.workingCopyBackupService instanceof WorkingCopyBackupService) { const newBackupWorkspaceHome = result.backupPath ? URI.file(result.backupPath).with({ scheme: this.environmentService.userRoamingDataHome.scheme }) : undefined; - this.backupFileService.reinitialize(newBackupWorkspaceHome); + this.workingCopyBackupService.reinitialize(newBackupWorkspaceHome); } } diff --git a/src/vs/workbench/services/workspaces/test/browser/workspaces.test.ts b/src/vs/workbench/services/workspaces/test/browser/workspaces.test.ts index 5ea5db35..dc462709 100644 --- a/src/vs/workbench/services/workspaces/test/browser/workspaces.test.ts +++ b/src/vs/workbench/services/workspaces/test/browser/workspaces.test.ts @@ -5,11 +5,7 @@ import * as assert from 'assert'; import { URI } from 'vs/base/common/uri'; -import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; -import { IWorkspaceTrustService, workspaceTrustStateToString } from 'vs/platform/workspace/common/workspaceTrust'; import { getWorkspaceIdentifier, getSingleFolderWorkspaceIdentifier } from 'vs/workbench/services/workspaces/browser/workspaces'; -import { WorkspaceTrustService } from 'vs/workbench/services/workspaces/common/workspaceTrust'; -import { workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; suite('Workspaces', () => { test('workspace identifiers are stable', function () { @@ -22,18 +18,18 @@ suite('Workspaces', () => { }); }); -suite('Workspace Trust', () => { - let workspaceTrustService: IWorkspaceTrustService; - setup(() => { - const instantiationService: TestInstantiationService = workbenchInstantiationService(); - workspaceTrustService = instantiationService.createInstance(WorkspaceTrustService); - }); +// suite('Workspace Trust', () => { +// let workspaceTrustService: IWorkspaceTrustService; +// setup(() => { +// const instantiationService: TestInstantiationService = workbenchInstantiationService(); +// workspaceTrustService = instantiationService.createInstance(WorkspaceTrustService); +// }); - teardown(() => { +// teardown(() => { - }); +// }); - test('Sample Test', function () { - assert.strictEqual(workspaceTrustStateToString(workspaceTrustService.getWorkspaceTrustState()), 'Trusted'); - }); -}); +// test('Sample Test', function () { +// assert.strictEqual(workspaceTrustStateToString(workspaceTrustService.getWorkspaceTrustState()), 'Trusted'); +// }); +// }); diff --git a/src/vs/workbench/services/workspaces/test/common/testWorkspaceTrustService.ts b/src/vs/workbench/services/workspaces/test/common/testWorkspaceTrustService.ts index 2953fcb2..d3d696eb 100644 --- a/src/vs/workbench/services/workspaces/test/common/testWorkspaceTrustService.ts +++ b/src/vs/workbench/services/workspaces/test/common/testWorkspaceTrustService.ts @@ -3,26 +3,86 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Event } from 'vs/base/common/event'; -import { WorkspaceTrustRequestOptions, IWorkspaceTrustRequestModel, IWorkspaceTrustService, WorkspaceTrustChangeEvent, WorkspaceTrustState } from 'vs/platform/workspace/common/workspaceTrust'; -import { WorkspaceTrustRequestModel } from 'vs/workbench/services/workspaces/common/workspaceTrust'; +import { Emitter } from 'vs/base/common/event'; +import { URI } from 'vs/base/common/uri'; +import { IWorkspaceTrustManagementService, IWorkspaceTrustRequestService, IWorkspaceTrustUriInfo, WorkspaceTrustRequestOptions } from 'vs/platform/workspace/common/workspaceTrust'; -export class TestWorkspaceTrustService implements IWorkspaceTrustService { + +export class TestWorkspaceTrustManagementService implements IWorkspaceTrustManagementService { _serviceBrand: undefined; - requestModel: IWorkspaceTrustRequestModel = new WorkspaceTrustRequestModel(); + private _onDidChangeTrust = new Emitter(); + onDidChangeTrust = this._onDidChangeTrust.event; - onDidChangeTrustState: WorkspaceTrustChangeEvent = Event.None; + private _onDidChangeTrustedFolders = new Emitter(); + onDidChangeTrustedFolders = this._onDidChangeTrustedFolders.event; - getWorkspaceTrustState(): WorkspaceTrustState { - return WorkspaceTrustState.Trusted; + private trusted: boolean; + + constructor(trusted: boolean = true) { + this.trusted = trusted; } - isWorkspaceTrustEnabled(): boolean { - return true; + getTrustedFolders(): URI[] { + throw new Error('Method not implemented.'); } - requireWorkspaceTrust(options?: WorkspaceTrustRequestOptions): Promise { - return Promise.resolve(WorkspaceTrustState.Trusted); + setParentFolderTrust(trusted: boolean): void { + throw new Error('Method not implemented.'); + } + + getFolderTrustInfo(folder: URI): IWorkspaceTrustUriInfo { + throw new Error('Method not implemented.'); + } + + setTrustedFolders(folders: URI[]): void { + throw new Error('Method not implemented.'); + } + + setFoldersTrust(folders: URI[], trusted: boolean): void { + throw new Error('Method not implemented.'); + } + + canSetParentFolderTrust(): boolean { + throw new Error('Method not implemented.'); + } + + canSetWorkspaceTrust(): boolean { + throw new Error('Method not implemented.'); + } + + isWorkpaceTrusted(): boolean { + return this.trusted; + } + + setWorkspaceTrust(trusted: boolean): void { + if (this.trusted !== trusted) { + this.trusted = trusted; + this._onDidChangeTrust.fire(this.trusted); + } + } +} + +export class TestWorkspaceTrustRequestService implements IWorkspaceTrustRequestService { + _serviceBrand: any; + + private readonly _onDidInitiateWorkspaceTrustRequest = new Emitter(); + readonly onDidInitiateWorkspaceTrustRequest = this._onDidInitiateWorkspaceTrustRequest.event; + + private readonly _onDidCompleteWorkspaceTrustRequest = new Emitter(); + readonly onDidCompleteWorkspaceTrustRequest = this._onDidCompleteWorkspaceTrustRequest.event; + + constructor(private readonly _trusted: boolean) { } + + cancelRequest(): void { + throw new Error('Method not implemented.'); + } + + completeRequest(trusted?: boolean): void { + throw new Error('Method not implemented.'); + } + + async requestWorkspaceTrust(options?: WorkspaceTrustRequestOptions): Promise { + return this._trusted; } } diff --git a/src/vs/workbench/test/browser/api/extHost.api.impl.test.ts b/src/vs/workbench/test/browser/api/extHost.api.impl.test.ts index 4018e8cf..2709cfda 100644 --- a/src/vs/workbench/test/browser/api/extHost.api.impl.test.ts +++ b/src/vs/workbench/test/browser/api/extHost.api.impl.test.ts @@ -11,11 +11,11 @@ import { isWindows } from 'vs/base/common/platform'; suite('ExtHost API', function () { test('issue #51387: originalFSPath', function () { if (isWindows) { - assert.equal(originalFSPath(URI.file('C:\\test')).charAt(0), 'C'); - assert.equal(originalFSPath(URI.file('c:\\test')).charAt(0), 'c'); + assert.strictEqual(originalFSPath(URI.file('C:\\test')).charAt(0), 'C'); + assert.strictEqual(originalFSPath(URI.file('c:\\test')).charAt(0), 'c'); - assert.equal(originalFSPath(URI.revive(JSON.parse(JSON.stringify(URI.file('C:\\test'))))).charAt(0), 'C'); - assert.equal(originalFSPath(URI.revive(JSON.parse(JSON.stringify(URI.file('c:\\test'))))).charAt(0), 'c'); + assert.strictEqual(originalFSPath(URI.revive(JSON.parse(JSON.stringify(URI.file('C:\\test'))))).charAt(0), 'C'); + assert.strictEqual(originalFSPath(URI.revive(JSON.parse(JSON.stringify(URI.file('c:\\test'))))).charAt(0), 'c'); } }); }); diff --git a/src/vs/workbench/test/browser/api/extHostApiCommands.test.ts b/src/vs/workbench/test/browser/api/extHostApiCommands.test.ts index ee9db602..893e98fe 100644 --- a/src/vs/workbench/test/browser/api/extHostApiCommands.test.ts +++ b/src/vs/workbench/test/browser/api/extHostApiCommands.test.ts @@ -90,14 +90,14 @@ suite('ExtHostLanguageFeatureCommands', function () { rpcProtocol = new TestRPCProtocol(); const services = new ServiceCollection(); services.set(IExtensionService, new class extends mock() { - async activateByEvent() { + override async activateByEvent() { } }); services.set(ICommandService, new SyncDescriptor(class extends mock() { - executeCommand(id: string, ...args: any): any { + override executeCommand(id: string, ...args: any): any { const command = CommandsRegistry.getCommands().get(id); if (!command) { return Promise.reject(new Error(id + ' NOT known')); @@ -108,17 +108,17 @@ suite('ExtHostLanguageFeatureCommands', function () { })); services.set(IMarkerService, new MarkerService()); services.set(IModelService, new class extends mock() { - getModel() { return model; } + override getModel() { return model; } }); services.set(ITextModelService, new class extends mock() { - async createModelReference() { + override async createModelReference() { return new ImmortalReference(new class extends mock() { - textEditorModel = model; + override textEditorModel = model; }); } }); services.set(IEditorWorkerService, new class extends mock() { - async computeMoreMinimalEdits(_uri: any, edits: any) { + override async computeMoreMinimalEdits(_uri: any, edits: any) { return edits || undefined; } }); @@ -310,6 +310,42 @@ suite('ExtHostLanguageFeatureCommands', function () { }); }); + + test('Definition, back and forth (sorting & de-deduping)', function () { + + disposables.push(extHost.registerDefinitionProvider(nullExtensionDescription, defaultSelector, { + provideDefinition(doc: any): any { + return new types.Location(URI.parse('file:///b'), new types.Range(1, 0, 0, 0)); + } + })); + disposables.push(extHost.registerDefinitionProvider(nullExtensionDescription, defaultSelector, { + provideDefinition(doc: any): any { + // duplicate result will get removed + return new types.Location(URI.parse('file:///b'), new types.Range(1, 0, 0, 0)); + } + })); + disposables.push(extHost.registerDefinitionProvider(nullExtensionDescription, defaultSelector, { + provideDefinition(doc: any): any { + return [ + new types.Location(URI.parse('file:///a'), new types.Range(2, 0, 0, 0)), + new types.Location(URI.parse('file:///c'), new types.Range(3, 0, 0, 0)), + new types.Location(URI.parse('file:///d'), new types.Range(4, 0, 0, 0)), + ]; + } + })); + + return rpcProtocol.sync().then(() => { + return commands.executeCommand('vscode.executeDefinitionProvider', model.uri, new types.Position(0, 0)).then(values => { + assert.strictEqual(values.length, 4); + + assert.strictEqual(values[0].uri.path, '/a'); + assert.strictEqual(values[1].uri.path, '/b'); + assert.strictEqual(values[2].uri.path, '/c'); + assert.strictEqual(values[3].uri.path, '/d'); + }); + }); + }); + test('Definition Link', () => { disposables.push(extHost.registerDefinitionProvider(nullExtensionDescription, defaultSelector, { provideDefinition(doc: any): (vscode.Location | vscode.LocationLink)[] { diff --git a/src/vs/workbench/test/browser/api/extHostBulkEdits.test.ts b/src/vs/workbench/test/browser/api/extHostBulkEdits.test.ts index 88a25eea..a77e9d63 100644 --- a/src/vs/workbench/test/browser/api/extHostBulkEdits.test.ts +++ b/src/vs/workbench/test/browser/api/extHostBulkEdits.test.ts @@ -24,7 +24,7 @@ suite('ExtHostBulkEdits.applyWorkspaceEdit', () => { let rpcProtocol = new TestRPCProtocol(); rpcProtocol.set(MainContext.MainThreadBulkEdits, new class extends mock() { - $tryApplyWorkspaceEdit(_workspaceResourceEdits: IWorkspaceEditDto): Promise { + override $tryApplyWorkspaceEdit(_workspaceResourceEdits: IWorkspaceEditDto): Promise { workspaceResourceEdits = _workspaceResourceEdits; return Promise.resolve(true); } diff --git a/src/vs/workbench/test/browser/api/extHostCommands.test.ts b/src/vs/workbench/test/browser/api/extHostCommands.test.ts index 2ecab2e8..c9c02395 100644 --- a/src/vs/workbench/test/browser/api/extHostCommands.test.ts +++ b/src/vs/workbench/test/browser/api/extHostCommands.test.ts @@ -18,10 +18,10 @@ suite('ExtHostCommands', function () { let lastUnregister: string; const shape = new class extends mock() { - $registerCommand(id: string): void { + override $registerCommand(id: string): void { // } - $unregisterCommand(id: string): void { + override $unregisterCommand(id: string): void { lastUnregister = id; } }; @@ -41,10 +41,10 @@ suite('ExtHostCommands', function () { let unregisterCounter = 0; const shape = new class extends mock() { - $registerCommand(id: string): void { + override $registerCommand(id: string): void { // } - $unregisterCommand(id: string): void { + override $unregisterCommand(id: string): void { unregisterCounter += 1; } }; @@ -65,10 +65,10 @@ suite('ExtHostCommands', function () { let count = 0; const shape = new class extends mock() { - $registerCommand(id: string): void { + override $registerCommand(id: string): void { // } - async $executeCommand(id: string, args: any[], retry: boolean): Promise { + override async $executeCommand(id: string, args: any[], retry: boolean): Promise { count++; assert.strictEqual(retry, count === 1); if (count === 1) { diff --git a/src/vs/workbench/test/browser/api/extHostConfiguration.test.ts b/src/vs/workbench/test/browser/api/extHostConfiguration.test.ts index 6c27e95c..ed9d51dd 100644 --- a/src/vs/workbench/test/browser/api/extHostConfiguration.test.ts +++ b/src/vs/workbench/test/browser/api/extHostConfiguration.test.ts @@ -18,20 +18,19 @@ import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitData import { IExtHostFileSystemInfo } from 'vs/workbench/api/common/extHostFileSystemInfo'; import { FileSystemProviderCapabilities } from 'vs/platform/files/common/files'; import { isLinux } from 'vs/base/common/platform'; -import { WorkspaceTrustState } from 'vs/platform/workspace/common/workspaceTrust'; suite('ExtHostConfiguration', function () { class RecordingShape extends mock() { lastArgs!: [ConfigurationTarget, string, any]; - $updateConfigurationOption(target: ConfigurationTarget, key: string, value: any): Promise { + override $updateConfigurationOption(target: ConfigurationTarget, key: string, value: any): Promise { this.lastArgs = [target, key, value]; return Promise.resolve(undefined); } } function createExtHostWorkspace(): ExtHostWorkspace { - return new ExtHostWorkspace(new TestRPCProtocol(), new class extends mock() { }, new class extends mock() { getCapabilities() { return isLinux ? FileSystemProviderCapabilities.PathCaseSensitive : undefined; } }, new NullLogService()); + return new ExtHostWorkspace(new TestRPCProtocol(), new class extends mock() { }, new class extends mock() { override getCapabilities() { return isLinux ? FileSystemProviderCapabilities.PathCaseSensitive : undefined; } }, new NullLogService()); } function createExtHostConfiguration(contents: any = Object.create(null), shape?: MainThreadConfigurationShape) { @@ -319,7 +318,7 @@ suite('ExtHostConfiguration', function () { 'id': 'foo', 'folders': [aWorkspaceFolder(URI.file('foo'), 0)], 'name': 'foo' - }, WorkspaceTrustState.Trusted); + }, true); const testObject = new ExtHostConfigProvider( new class extends mock() { }, extHostWorkspace, @@ -395,7 +394,7 @@ suite('ExtHostConfiguration', function () { 'id': 'foo', 'folders': [aWorkspaceFolder(firstRoot, 0), aWorkspaceFolder(secondRoot, 1)], 'name': 'foo' - }, WorkspaceTrustState.Trusted); + }, true); const testObject = new ExtHostConfigProvider( new class extends mock() { }, extHostWorkspace, @@ -498,7 +497,7 @@ suite('ExtHostConfiguration', function () { 'id': 'foo', 'folders': [aWorkspaceFolder(firstRoot, 0), aWorkspaceFolder(secondRoot, 1)], 'name': 'foo' - }, WorkspaceTrustState.Trusted); + }, true); const testObject = new ExtHostConfigProvider( new class extends mock() { }, extHostWorkspace, @@ -657,7 +656,7 @@ suite('ExtHostConfiguration', function () { test('update/error-state not OK', function () { const shape = new class extends mock() { - $updateConfigurationOption(target: ConfigurationTarget, key: string, value: any): Promise { + override $updateConfigurationOption(target: ConfigurationTarget, key: string, value: any): Promise { return Promise.reject(new Error('Unknown Key')); // something !== OK } }; @@ -676,7 +675,7 @@ suite('ExtHostConfiguration', function () { 'id': 'foo', 'folders': [workspaceFolder], 'name': 'foo' - }, WorkspaceTrustState.Trusted); + }, true); const testObject = new ExtHostConfigProvider( new class extends mock() { }, extHostWorkspace, @@ -732,7 +731,7 @@ suite('ExtHostConfiguration', function () { function toConfigurationModel(obj: any): ConfigurationModel { const parser = new ConfigurationModelParser('test'); - parser.parseContent(JSON.stringify(obj)); + parser.parse(JSON.stringify(obj)); return parser.configurationModel; } diff --git a/src/vs/workbench/test/browser/api/extHostDecorations.test.ts b/src/vs/workbench/test/browser/api/extHostDecorations.test.ts index 0c926b50..4dc01fc5 100644 --- a/src/vs/workbench/test/browser/api/extHostDecorations.test.ts +++ b/src/vs/workbench/test/browser/api/extHostDecorations.test.ts @@ -25,14 +25,14 @@ suite('ExtHostDecorations', function () { providers.clear(); mainThreadShape = new class extends mock() { - $registerDecorationProvider(handle: number) { + override $registerDecorationProvider(handle: number) { providers.add(handle); } }; extHostDecorations = new ExtHostDecorations( new class extends mock() { - getProxy(): any { + override getProxy(): any { return mainThreadShape; } }, diff --git a/src/vs/workbench/test/browser/api/extHostDiagnostics.test.ts b/src/vs/workbench/test/browser/api/extHostDiagnostics.test.ts index 68fd8bca..241e6413 100644 --- a/src/vs/workbench/test/browser/api/extHostDiagnostics.test.ts +++ b/src/vs/workbench/test/browser/api/extHostDiagnostics.test.ts @@ -18,10 +18,10 @@ import { nullExtensionDescription } from 'vs/workbench/services/extensions/commo suite('ExtHostDiagnostics', () => { class DiagnosticsShape extends mock() { - $changeMany(owner: string, entries: [UriComponents, IMarkerData[]][]): void { + override $changeMany(owner: string, entries: [UriComponents, IMarkerData[]][]): void { // } - $clear(owner: string): void { + override $clear(owner: string): void { // } } @@ -164,7 +164,7 @@ suite('ExtHostDiagnostics', () => { let lastEntries!: [UriComponents, IMarkerData[]][]; let collection = new DiagnosticCollection('test', 'test', 100, new class extends DiagnosticsShape { - $changeMany(owner: string, entries: [UriComponents, IMarkerData[]][]): void { + override $changeMany(owner: string, entries: [UriComponents, IMarkerData[]][]): void { lastEntries = entries; return super.$changeMany(owner, entries); } @@ -198,7 +198,7 @@ suite('ExtHostDiagnostics', () => { const emitter = new Emitter(); emitter.event(_ => eventCount += 1); const collection = new DiagnosticCollection('test', 'test', 100, new class extends DiagnosticsShape { - $changeMany() { + override $changeMany() { changeCount += 1; } }, emitter); @@ -263,7 +263,7 @@ suite('ExtHostDiagnostics', () => { let lastEntries!: [UriComponents, IMarkerData[]][]; let collection = new DiagnosticCollection('test', 'test', 250, new class extends DiagnosticsShape { - $changeMany(owner: string, entries: [UriComponents, IMarkerData[]][]): void { + override $changeMany(owner: string, entries: [UriComponents, IMarkerData[]][]): void { lastEntries = entries; return super.$changeMany(owner, entries); } @@ -350,7 +350,7 @@ suite('ExtHostDiagnostics', () => { test('diagnostics with related information', function (done) { let collection = new DiagnosticCollection('ddd', 'test', 100, new class extends DiagnosticsShape { - $changeMany(owner: string, entries: [UriComponents, IMarkerData[]][]) { + override $changeMany(owner: string, entries: [UriComponents, IMarkerData[]][]) { let [[, data]] = entries; assert.strictEqual(entries.length, 1); @@ -408,7 +408,7 @@ suite('ExtHostDiagnostics', () => { test('Error updating diagnostics from extension #60394', function () { let callCount = 0; let collection = new DiagnosticCollection('ddd', 'test', 100, new class extends DiagnosticsShape { - $changeMany(owner: string, entries: [UriComponents, IMarkerData[]][]) { + override $changeMany(owner: string, entries: [UriComponents, IMarkerData[]][]) { callCount += 1; } }, new Emitter()); diff --git a/src/vs/workbench/test/browser/api/extHostDocumentData.test.ts b/src/vs/workbench/test/browser/api/extHostDocumentData.test.ts index 9de6d423..c1a5af90 100644 --- a/src/vs/workbench/test/browser/api/extHostDocumentData.test.ts +++ b/src/vs/workbench/test/browser/api/extHostDocumentData.test.ts @@ -50,7 +50,7 @@ suite('ExtHostDocumentData', () => { test('save, when disposed', function () { let saved: URI; let data = new ExtHostDocumentData(new class extends mock() { - $trySaveDocument(uri: URI) { + override $trySaveDocument(uri: URI) { assert.ok(!saved); saved = uri; return Promise.resolve(true); @@ -365,16 +365,16 @@ suite('ExtHostDocumentData updates line mapping', () => { let line = 0, character = 0, previousIsCarriageReturn = false; for (let offset = 0; offset <= allText.length; offset++) { // The position coordinate system cannot express the position between \r and \n - const position = new Position(line, character + (previousIsCarriageReturn ? -1 : 0)); + const position: Position = new Position(line, character + (previousIsCarriageReturn ? -1 : 0)); if (direction === AssertDocumentLineMappingDirection.OffsetToPosition) { let actualPosition = doc.document.positionAt(offset); - assert.equal(positionToStr(actualPosition), positionToStr(position), 'positionAt mismatch for offset ' + offset); + assert.strictEqual(positionToStr(actualPosition), positionToStr(position), 'positionAt mismatch for offset ' + offset); } else { // The position coordinate system cannot express the position between \r and \n - let expectedOffset = offset + (previousIsCarriageReturn ? -1 : 0); + let expectedOffset: number = offset + (previousIsCarriageReturn ? -1 : 0); let actualOffset = doc.document.offsetAt(position); - assert.equal(actualOffset, expectedOffset, 'offsetAt mismatch for position ' + positionToStr(position)); + assert.strictEqual(actualOffset, expectedOffset, 'offsetAt mismatch for position ' + positionToStr(position)); } if (allText.charAt(offset) === '\n') { diff --git a/src/vs/workbench/test/browser/api/extHostDocumentSaveParticipant.test.ts b/src/vs/workbench/test/browser/api/extHostDocumentSaveParticipant.test.ts index a89f12a7..b41dc067 100644 --- a/src/vs/workbench/test/browser/api/extHostDocumentSaveParticipant.test.ts +++ b/src/vs/workbench/test/browser/api/extHostDocumentSaveParticipant.test.ts @@ -379,7 +379,7 @@ suite('ExtHostDocumentSaveParticipant', () => { test('Log failing listener', function () { let didLogSomething = false; let participant = new ExtHostDocumentSaveParticipant(new class extends NullLogService { - error(message: string | Error, ...args: any[]): void { + override error(message: string | Error, ...args: any[]): void { didLogSomething = true; } }, documents, mainThreadBulkEdits); diff --git a/src/vs/workbench/test/browser/api/extHostLanguageFeatures.test.ts b/src/vs/workbench/test/browser/api/extHostLanguageFeatures.test.ts index 9e29d3ac..2338d947 100644 --- a/src/vs/workbench/test/browser/api/extHostLanguageFeatures.test.ts +++ b/src/vs/workbench/test/browser/api/extHostLanguageFeatures.test.ts @@ -268,7 +268,7 @@ suite('ExtHostLanguageFeatures', function () { assert.strictEqual(value.length, 2); }); - test('Definition, registration order ignored (results are sorted)', async () => { + test('Definition, registration order', async () => { disposables.push(extHost.registerDefinitionProvider(defaultExtension, defaultSelector, new class implements vscode.DefinitionProvider { provideDefinition(): any { @@ -286,8 +286,8 @@ suite('ExtHostLanguageFeatures', function () { const value = await getDefinitionsAtPosition(model, new EditorPosition(1, 1), CancellationToken.None); assert.strictEqual(value.length, 2); // let [first, second] = value; - assert.strictEqual(value[0].uri.authority, 'first'); - assert.strictEqual(value[1].uri.authority, 'second'); + assert.strictEqual(value[0].uri.authority, 'second'); + assert.strictEqual(value[1].uri.authority, 'first'); }); test('Definition, evil provider', async () => { @@ -521,7 +521,7 @@ suite('ExtHostLanguageFeatures', function () { // --- references - test('References, registration order ignored (results are sorted)', async () => { + test('References, registration order', async () => { disposables.push(extHost.registerReferenceProvider(defaultExtension, defaultSelector, new class implements vscode.ReferenceProvider { provideReferences(): any { @@ -539,8 +539,8 @@ suite('ExtHostLanguageFeatures', function () { let value = await getReferencesAtPosition(model, new EditorPosition(1, 2), false, CancellationToken.None); assert.strictEqual(value.length, 2); let [first, second] = value; - assert.strictEqual(first.uri.path, '/first'); - assert.strictEqual(second.uri.path, '/second'); + assert.strictEqual(first.uri.path, '/second'); + assert.strictEqual(second.uri.path, '/first'); }); test('References, data conversion', async () => { @@ -966,7 +966,7 @@ suite('ExtHostLanguageFeatures', function () { // --- format const NullWorkerService = new class extends mock() { - computeMoreMinimalEdits(resource: URI, edits: modes.TextEdit[] | null | undefined): Promise { + override computeMoreMinimalEdits(resource: URI, edits: modes.TextEdit[] | null | undefined): Promise { return Promise.resolve(withNullAsUndefined(edits)); } }; diff --git a/src/vs/workbench/test/browser/api/extHostMessagerService.test.ts b/src/vs/workbench/test/browser/api/extHostMessagerService.test.ts index 0c6ce4dc..d0965fec 100644 --- a/src/vs/workbench/test/browser/api/extHostMessagerService.test.ts +++ b/src/vs/workbench/test/browser/api/extHostMessagerService.test.ts @@ -117,7 +117,7 @@ suite('ExtHostMessageService', function () { suite('modal', () => { test('calls dialog service', async () => { const service = new MainThreadMessageService(null!, emptyNotificationService, emptyCommandService, new class extends mock() { - show(severity: Severity, message: string, buttons: string[]) { + override show(severity: Severity, message: string, buttons: string[]) { assert.strictEqual(severity, 1); assert.strictEqual(message, 'h'); assert.strictEqual(buttons.length, 2); @@ -132,7 +132,7 @@ suite('ExtHostMessageService', function () { test('returns undefined when cancelled', async () => { const service = new MainThreadMessageService(null!, emptyNotificationService, emptyCommandService, new class extends mock() { - show() { + override show() { return Promise.resolve({ choice: 1 }); } } as IDialogService); @@ -143,7 +143,7 @@ suite('ExtHostMessageService', function () { test('hides Cancel button when not needed', async () => { const service = new MainThreadMessageService(null!, emptyNotificationService, emptyCommandService, new class extends mock() { - show(severity: Severity, message: string, buttons: string[]) { + override show(severity: Severity, message: string, buttons: string[]) { assert.strictEqual(buttons.length, 1); return Promise.resolve({ choice: 0 }); } diff --git a/src/vs/workbench/test/browser/api/extHostNotebook.test.ts b/src/vs/workbench/test/browser/api/extHostNotebook.test.ts index 8477d5ae..787927f5 100644 --- a/src/vs/workbench/test/browser/api/extHostNotebook.test.ts +++ b/src/vs/workbench/test/browser/api/extHostNotebook.test.ts @@ -41,20 +41,20 @@ suite('NotebookCell#Document', function () { setup(async function () { rpcProtocol = new TestRPCProtocol(); rpcProtocol.set(MainContext.MainThreadCommands, new class extends mock() { - $registerCommand() { } + override $registerCommand() { } }); rpcProtocol.set(MainContext.MainThreadNotebook, new class extends mock() { - async $registerNotebookProvider() { } - async $unregisterNotebookProvider() { } + override async $registerNotebookProvider() { } + override async $unregisterNotebookProvider() { } }); extHostDocumentsAndEditors = new ExtHostDocumentsAndEditors(rpcProtocol, new NullLogService()); extHostDocuments = new ExtHostDocuments(rpcProtocol, extHostDocumentsAndEditors); const extHostStoragePaths = new class extends mock() { - workspaceValue() { + override workspaceValue() { return URI.from({ scheme: 'test', path: generateUuid() }); } }; - extHostNotebooks = new ExtHostNotebookController(rpcProtocol, new ExtHostCommands(rpcProtocol, new NullLogService()), extHostDocumentsAndEditors, extHostDocuments, { isExtensionDevelopmentDebug: false, webviewCspSource: '', webviewResourceRoot: '' }, new NullLogService(), extHostStoragePaths); + extHostNotebooks = new ExtHostNotebookController(rpcProtocol, new ExtHostCommands(rpcProtocol, new NullLogService()), extHostDocumentsAndEditors, extHostDocuments, new NullLogService(), extHostStoragePaths); let reg = extHostNotebooks.registerNotebookContentProvider(nullExtensionDescription, 'test', new class extends mock() { // async openNotebook() { } }); @@ -100,26 +100,26 @@ suite('NotebookCell#Document', function () { test('cell document is vscode.TextDocument', async function () { - assert.strictEqual(notebook.notebookDocument.cells.length, 2); + assert.strictEqual(notebook.apiNotebook.cellCount, 2); - const [c1, c2] = notebook.notebookDocument.cells; + const [c1, c2] = notebook.apiNotebook.getCells(); const d1 = extHostDocuments.getDocument(c1.document.uri); assert.ok(d1); assert.strictEqual(d1.languageId, c1.document.languageId); assert.strictEqual(d1.version, 1); - assert.ok(d1.notebook === notebook.notebookDocument); + assert.ok(d1.notebook === notebook.apiNotebook); const d2 = extHostDocuments.getDocument(c2.document.uri); assert.ok(d2); assert.strictEqual(d2.languageId, c2.document.languageId); assert.strictEqual(d2.version, 1); - assert.ok(d2.notebook === notebook.notebookDocument); + assert.ok(d2.notebook === notebook.apiNotebook); }); test('cell document goes when notebook closes', async function () { const cellUris: string[] = []; - for (let cell of notebook.notebookDocument.cells) { + for (let cell of notebook.apiNotebook.getCells()) { assert.ok(extHostDocuments.getDocument(cell.document.uri)); cellUris.push(cell.document.uri.toString()); } @@ -163,7 +163,7 @@ suite('NotebookCell#Document', function () { }); extHostNotebooks.$acceptModelChanged(notebookUri, { - versionId: notebook.notebookDocument.version + 1, + versionId: notebook.apiNotebook.version + 1, rawEvents: [ { kind: NotebookCellsChangeType.ModelChange, @@ -196,7 +196,7 @@ suite('NotebookCell#Document', function () { const docs: vscode.TextDocument[] = []; const addData: IModelAddedData[] = []; - for (let cell of notebook.notebookDocument.cells) { + for (let cell of notebook.apiNotebook.getCells()) { const doc = extHostDocuments.getDocument(cell.document.uri); assert.ok(doc); assert.strictEqual(extHostDocuments.getDocument(cell.document.uri).isClosed, false); @@ -218,14 +218,14 @@ suite('NotebookCell#Document', function () { extHostDocumentsAndEditors.$acceptDocumentsAndEditorsDelta({ removedDocuments: docs.map(d => d.uri) }); // notebook is still open -> cell documents stay open - for (let cell of notebook.notebookDocument.cells) { + for (let cell of notebook.apiNotebook.getCells()) { assert.ok(extHostDocuments.getDocument(cell.document.uri)); assert.strictEqual(extHostDocuments.getDocument(cell.document.uri).isClosed, false); } // close notebook -> docs are closed extHostNotebooks.$acceptDocumentAndEditorsDelta({ removedDocuments: [notebook.uri] }); - for (let cell of notebook.notebookDocument.cells) { + for (let cell of notebook.apiNotebook.getCells()) { assert.throws(() => extHostDocuments.getDocument(cell.document.uri)); } for (let doc of docs) { @@ -235,8 +235,8 @@ suite('NotebookCell#Document', function () { test('cell document goes when cell is removed', async function () { - assert.strictEqual(notebook.notebookDocument.cells.length, 2); - const [cell1, cell2] = notebook.notebookDocument.cells; + assert.strictEqual(notebook.apiNotebook.cellCount, 2); + const [cell1, cell2] = notebook.apiNotebook.getCells(); extHostNotebooks.$acceptModelChanged(notebook.uri, { versionId: 2, @@ -248,7 +248,7 @@ suite('NotebookCell#Document', function () { ] }, false); - assert.strictEqual(notebook.notebookDocument.cells.length, 1); + assert.strictEqual(notebook.apiNotebook.cellCount, 1); assert.strictEqual(cell1.document.isClosed, true); // ref still alive! assert.strictEqual(cell2.document.isClosed, false); @@ -256,32 +256,32 @@ suite('NotebookCell#Document', function () { }); test('cell document knows notebook', function () { - for (let cells of notebook.notebookDocument.cells) { - assert.strictEqual(cells.document.notebook === notebook.notebookDocument, true); + for (let cells of notebook.apiNotebook.getCells()) { + assert.strictEqual(cells.document.notebook === notebook.apiNotebook, true); } }); test('cell#index', function () { - assert.strictEqual(notebook.notebookDocument.cells.length, 2); - const [first, second] = notebook.notebookDocument.cells; + assert.strictEqual(notebook.apiNotebook.cellCount, 2); + const [first, second] = notebook.apiNotebook.getCells(); assert.strictEqual(first.index, 0); assert.strictEqual(second.index, 1); // remove first cell extHostNotebooks.$acceptModelChanged(notebook.uri, { - versionId: notebook.notebookDocument.version + 1, + versionId: notebook.apiNotebook.version + 1, rawEvents: [{ kind: NotebookCellsChangeType.ModelChange, changes: [[0, 1, []]] }] }, false); - assert.strictEqual(notebook.notebookDocument.cells.length, 1); + assert.strictEqual(notebook.apiNotebook.cellCount, 1); assert.strictEqual(second.index, 0); extHostNotebooks.$acceptModelChanged(notebookUri, { - versionId: notebook.notebookDocument.version + 1, + versionId: notebook.apiNotebook.version + 1, rawEvents: [{ kind: NotebookCellsChangeType.ModelChange, changes: [[0, 0, [{ @@ -304,7 +304,7 @@ suite('NotebookCell#Document', function () { }] }, false); - assert.strictEqual(notebook.notebookDocument.cells.length, 3); + assert.strictEqual(notebook.apiNotebook.cellCount, 3); assert.strictEqual(second.index, 2); }); @@ -313,7 +313,7 @@ suite('NotebookCell#Document', function () { const p = Event.toPromise(extHostNotebooks.onDidChangeNotebookCells); // DON'T call this, make sure the cell-documents have not been created yet - // assert.strictEqual(notebook.notebookDocument.cells.length, 2); + // assert.strictEqual(notebook.notebookDocument.cellCount, 2); extHostNotebooks.$acceptModelChanged(notebook.uri, { versionId: 100, @@ -339,11 +339,11 @@ suite('NotebookCell#Document', function () { }] }, false); - assert.strictEqual(notebook.notebookDocument.cells.length, 2); + assert.strictEqual(notebook.apiNotebook.cellCount, 2); const event = await p; - assert.strictEqual(event.document === notebook.notebookDocument, true); + assert.strictEqual(event.document === notebook.apiNotebook, true); assert.strictEqual(event.changes.length, 1); assert.strictEqual(event.changes[0].deletedCount, 2); assert.strictEqual(event.changes[0].deletedItems[0].document.isClosed, true); @@ -391,7 +391,7 @@ suite('NotebookCell#Document', function () { test('change cell language triggers onDidChange events', async function () { - const [first] = notebook.notebookDocument.cells; + const first = notebook.apiNotebook.cellAt(0); assert.strictEqual(first.document.languageId, 'markdown'); diff --git a/src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts b/src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts index fea5c7e6..2eca9f59 100644 --- a/src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts +++ b/src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts @@ -38,20 +38,20 @@ suite('NotebookConcatDocument', function () { rpcProtocol = new TestRPCProtocol(); rpcProtocol.set(MainContext.MainThreadCommands, new class extends mock() { - $registerCommand() { } + override $registerCommand() { } }); rpcProtocol.set(MainContext.MainThreadNotebook, new class extends mock() { - async $registerNotebookProvider() { } - async $unregisterNotebookProvider() { } + override async $registerNotebookProvider() { } + override async $unregisterNotebookProvider() { } }); extHostDocumentsAndEditors = new ExtHostDocumentsAndEditors(rpcProtocol, new NullLogService()); extHostDocuments = new ExtHostDocuments(rpcProtocol, extHostDocumentsAndEditors); const extHostStoragePaths = new class extends mock() { - workspaceValue() { + override workspaceValue() { return URI.from({ scheme: 'test', path: generateUuid() }); } }; - extHostNotebooks = new ExtHostNotebookController(rpcProtocol, new ExtHostCommands(rpcProtocol, new NullLogService()), extHostDocumentsAndEditors, extHostDocuments, { isExtensionDevelopmentDebug: false, webviewCspSource: '', webviewResourceRoot: '' }, new NullLogService(), extHostStoragePaths); + extHostNotebooks = new ExtHostNotebookController(rpcProtocol, new ExtHostCommands(rpcProtocol, new NullLogService()), extHostDocumentsAndEditors, extHostDocuments, new NullLogService(), extHostStoragePaths); let reg = extHostNotebooks.registerNotebookContentProvider(nullExtensionDescription, 'test', new class extends mock() { // async openNotebook() { } }); @@ -70,14 +70,12 @@ suite('NotebookConcatDocument', function () { }], versionId: 0 }], - addedEditors: [ - { - documentUri: notebookUri, - id: '_notebook_editor_0', - selections: [{ start: 0, end: 1 }], - visibleRanges: [] - } - ] + addedEditors: [{ + documentUri: notebookUri, + id: '_notebook_editor_0', + selections: [{ start: 0, end: 1 }], + visibleRanges: [] + }] }); extHostNotebooks.$acceptDocumentAndEditorsDelta({ newActiveEditor: '_notebook_editor_0' }); @@ -89,7 +87,7 @@ suite('NotebookConcatDocument', function () { }); test('empty', function () { - let doc = new ExtHostNotebookConcatDocument(extHostNotebooks, extHostDocuments, notebook.notebookDocument, undefined); + let doc = new ExtHostNotebookConcatDocument(extHostNotebooks, extHostDocuments, notebook.apiNotebook, undefined); assert.strictEqual(doc.getText(), ''); assert.strictEqual(doc.version, 0); @@ -125,7 +123,7 @@ suite('NotebookConcatDocument', function () { const cellUri2 = CellUri.generate(notebook.uri, 2); extHostNotebooks.$acceptModelChanged(notebookUri, { - versionId: notebook.notebookDocument.version + 1, + versionId: notebook.apiNotebook.version + 1, rawEvents: [{ kind: NotebookCellsChangeType.ModelChange, changes: [[0, 0, [{ @@ -150,9 +148,9 @@ suite('NotebookConcatDocument', function () { }, false); - assert.strictEqual(notebook.notebookDocument.cells.length, 1 + 2); // markdown and code + assert.strictEqual(notebook.apiNotebook.cellCount, 1 + 2); // markdown and code - let doc = new ExtHostNotebookConcatDocument(extHostNotebooks, extHostDocuments, notebook.notebookDocument, undefined); + let doc = new ExtHostNotebookConcatDocument(extHostNotebooks, extHostDocuments, notebook.apiNotebook, undefined); assert.strictEqual(doc.contains(cellUri1), true); assert.strictEqual(doc.contains(cellUri2), true); @@ -162,7 +160,7 @@ suite('NotebookConcatDocument', function () { test('location, position mapping', function () { extHostNotebooks.$acceptModelChanged(notebookUri, { - versionId: notebook.notebookDocument.version + 1, + versionId: notebook.apiNotebook.version + 1, rawEvents: [ { kind: NotebookCellsChangeType.ModelChange, @@ -188,26 +186,26 @@ suite('NotebookConcatDocument', function () { }, false); - assert.strictEqual(notebook.notebookDocument.cells.length, 1 + 2); // markdown and code + assert.strictEqual(notebook.apiNotebook.cellCount, 1 + 2); // markdown and code - let doc = new ExtHostNotebookConcatDocument(extHostNotebooks, extHostDocuments, notebook.notebookDocument, undefined); + let doc = new ExtHostNotebookConcatDocument(extHostNotebooks, extHostDocuments, notebook.apiNotebook, undefined); assertLines(doc, 'Hello', 'World', 'Hello World!', 'Hallo', 'Welt', 'Hallo Welt!'); - assertLocation(doc, new Position(0, 0), new Location(notebook.notebookDocument.cells[0].document.uri, new Position(0, 0))); - assertLocation(doc, new Position(4, 0), new Location(notebook.notebookDocument.cells[1].document.uri, new Position(1, 0))); - assertLocation(doc, new Position(4, 3), new Location(notebook.notebookDocument.cells[1].document.uri, new Position(1, 3))); - assertLocation(doc, new Position(5, 11), new Location(notebook.notebookDocument.cells[1].document.uri, new Position(2, 11))); - assertLocation(doc, new Position(5, 12), new Location(notebook.notebookDocument.cells[1].document.uri, new Position(2, 11)), false); // don't check identity because position will be clamped + assertLocation(doc, new Position(0, 0), new Location(notebook.apiNotebook.cellAt(0).document.uri, new Position(0, 0))); + assertLocation(doc, new Position(4, 0), new Location(notebook.apiNotebook.cellAt(1).document.uri, new Position(1, 0))); + assertLocation(doc, new Position(4, 3), new Location(notebook.apiNotebook.cellAt(1).document.uri, new Position(1, 3))); + assertLocation(doc, new Position(5, 11), new Location(notebook.apiNotebook.cellAt(1).document.uri, new Position(2, 11))); + assertLocation(doc, new Position(5, 12), new Location(notebook.apiNotebook.cellAt(1).document.uri, new Position(2, 11)), false); // don't check identity because position will be clamped }); test('location, position mapping, cell changes', function () { - let doc = new ExtHostNotebookConcatDocument(extHostNotebooks, extHostDocuments, notebook.notebookDocument, undefined); + let doc = new ExtHostNotebookConcatDocument(extHostNotebooks, extHostDocuments, notebook.apiNotebook, undefined); // UPDATE 1 extHostNotebooks.$acceptModelChanged(notebookUri, { - versionId: notebook.notebookDocument.version + 1, + versionId: notebook.apiNotebook.version + 1, rawEvents: [ { kind: NotebookCellsChangeType.ModelChange, @@ -223,18 +221,18 @@ suite('NotebookConcatDocument', function () { } ] }, false); - assert.strictEqual(notebook.notebookDocument.cells.length, 1 + 1); + assert.strictEqual(notebook.apiNotebook.cellCount, 1 + 1); assert.strictEqual(doc.version, 1); assertLines(doc, 'Hello', 'World', 'Hello World!'); - assertLocation(doc, new Position(0, 0), new Location(notebook.notebookDocument.cells[0].document.uri, new Position(0, 0))); - assertLocation(doc, new Position(2, 2), new Location(notebook.notebookDocument.cells[0].document.uri, new Position(2, 2))); - assertLocation(doc, new Position(4, 0), new Location(notebook.notebookDocument.cells[0].document.uri, new Position(2, 12)), false); // clamped + assertLocation(doc, new Position(0, 0), new Location(notebook.apiNotebook.cellAt(0).document.uri, new Position(0, 0))); + assertLocation(doc, new Position(2, 2), new Location(notebook.apiNotebook.cellAt(0).document.uri, new Position(2, 2))); + assertLocation(doc, new Position(4, 0), new Location(notebook.apiNotebook.cellAt(0).document.uri, new Position(2, 12)), false); // clamped // UPDATE 2 extHostNotebooks.$acceptModelChanged(notebookUri, { - versionId: notebook.notebookDocument.version + 1, + versionId: notebook.apiNotebook.version + 1, rawEvents: [ { kind: NotebookCellsChangeType.ModelChange, @@ -251,18 +249,18 @@ suite('NotebookConcatDocument', function () { ] }, false); - assert.strictEqual(notebook.notebookDocument.cells.length, 1 + 2); + assert.strictEqual(notebook.apiNotebook.cellCount, 1 + 2); assert.strictEqual(doc.version, 2); assertLines(doc, 'Hello', 'World', 'Hello World!', 'Hallo', 'Welt', 'Hallo Welt!'); - assertLocation(doc, new Position(0, 0), new Location(notebook.notebookDocument.cells[0].document.uri, new Position(0, 0))); - assertLocation(doc, new Position(4, 0), new Location(notebook.notebookDocument.cells[1].document.uri, new Position(1, 0))); - assertLocation(doc, new Position(4, 3), new Location(notebook.notebookDocument.cells[1].document.uri, new Position(1, 3))); - assertLocation(doc, new Position(5, 11), new Location(notebook.notebookDocument.cells[1].document.uri, new Position(2, 11))); - assertLocation(doc, new Position(5, 12), new Location(notebook.notebookDocument.cells[1].document.uri, new Position(2, 11)), false); // don't check identity because position will be clamped + assertLocation(doc, new Position(0, 0), new Location(notebook.apiNotebook.cellAt(0).document.uri, new Position(0, 0))); + assertLocation(doc, new Position(4, 0), new Location(notebook.apiNotebook.cellAt(1).document.uri, new Position(1, 0))); + assertLocation(doc, new Position(4, 3), new Location(notebook.apiNotebook.cellAt(1).document.uri, new Position(1, 3))); + assertLocation(doc, new Position(5, 11), new Location(notebook.apiNotebook.cellAt(1).document.uri, new Position(2, 11))); + assertLocation(doc, new Position(5, 12), new Location(notebook.apiNotebook.cellAt(1).document.uri, new Position(2, 11)), false); // don't check identity because position will be clamped // UPDATE 3 (remove cell #2 again) extHostNotebooks.$acceptModelChanged(notebookUri, { - versionId: notebook.notebookDocument.version + 1, + versionId: notebook.apiNotebook.version + 1, rawEvents: [ { kind: NotebookCellsChangeType.ModelChange, @@ -270,21 +268,21 @@ suite('NotebookConcatDocument', function () { } ] }, false); - assert.strictEqual(notebook.notebookDocument.cells.length, 1 + 1); + assert.strictEqual(notebook.apiNotebook.cellCount, 1 + 1); assert.strictEqual(doc.version, 3); assertLines(doc, 'Hello', 'World', 'Hello World!'); - assertLocation(doc, new Position(0, 0), new Location(notebook.notebookDocument.cells[0].document.uri, new Position(0, 0))); - assertLocation(doc, new Position(2, 2), new Location(notebook.notebookDocument.cells[0].document.uri, new Position(2, 2))); - assertLocation(doc, new Position(4, 0), new Location(notebook.notebookDocument.cells[0].document.uri, new Position(2, 12)), false); // clamped + assertLocation(doc, new Position(0, 0), new Location(notebook.apiNotebook.cellAt(0).document.uri, new Position(0, 0))); + assertLocation(doc, new Position(2, 2), new Location(notebook.apiNotebook.cellAt(0).document.uri, new Position(2, 2))); + assertLocation(doc, new Position(4, 0), new Location(notebook.apiNotebook.cellAt(0).document.uri, new Position(2, 12)), false); // clamped }); test('location, position mapping, cell-document changes', function () { - let doc = new ExtHostNotebookConcatDocument(extHostNotebooks, extHostDocuments, notebook.notebookDocument, undefined); + let doc = new ExtHostNotebookConcatDocument(extHostNotebooks, extHostDocuments, notebook.apiNotebook, undefined); // UPDATE 1 extHostNotebooks.$acceptModelChanged(notebookUri, { - versionId: notebook.notebookDocument.version + 1, + versionId: notebook.apiNotebook.version + 1, rawEvents: [ { @@ -309,21 +307,21 @@ suite('NotebookConcatDocument', function () { } ] }, false); - assert.strictEqual(notebook.notebookDocument.cells.length, 1 + 2); + assert.strictEqual(notebook.apiNotebook.cellCount, 1 + 2); assert.strictEqual(doc.version, 1); assertLines(doc, 'Hello', 'World', 'Hello World!', 'Hallo', 'Welt', 'Hallo Welt!'); - assertLocation(doc, new Position(0, 0), new Location(notebook.notebookDocument.cells[0].document.uri, new Position(0, 0))); - assertLocation(doc, new Position(2, 2), new Location(notebook.notebookDocument.cells[0].document.uri, new Position(2, 2))); - assertLocation(doc, new Position(2, 12), new Location(notebook.notebookDocument.cells[0].document.uri, new Position(2, 12))); - assertLocation(doc, new Position(4, 0), new Location(notebook.notebookDocument.cells[1].document.uri, new Position(1, 0))); - assertLocation(doc, new Position(4, 3), new Location(notebook.notebookDocument.cells[1].document.uri, new Position(1, 3))); + assertLocation(doc, new Position(0, 0), new Location(notebook.apiNotebook.cellAt(0).document.uri, new Position(0, 0))); + assertLocation(doc, new Position(2, 2), new Location(notebook.apiNotebook.cellAt(0).document.uri, new Position(2, 2))); + assertLocation(doc, new Position(2, 12), new Location(notebook.apiNotebook.cellAt(0).document.uri, new Position(2, 12))); + assertLocation(doc, new Position(4, 0), new Location(notebook.apiNotebook.cellAt(1).document.uri, new Position(1, 0))); + assertLocation(doc, new Position(4, 3), new Location(notebook.apiNotebook.cellAt(1).document.uri, new Position(1, 3))); // offset math let cell1End = doc.offsetAt(new Position(2, 12)); assert.strictEqual(doc.positionAt(cell1End).isEqual(new Position(2, 12)), true); - extHostDocuments.$acceptModelChanged(notebook.notebookDocument.cells[0].document.uri, { + extHostDocuments.$acceptModelChanged(notebook.apiNotebook.cellAt(0).document.uri, { versionId: 0, eol: '\n', changes: [{ @@ -334,7 +332,7 @@ suite('NotebookConcatDocument', function () { }] }, false); assertLines(doc, 'Hello', 'World', 'Hi World!', 'Hallo', 'Welt', 'Hallo Welt!'); - assertLocation(doc, new Position(2, 12), new Location(notebook.notebookDocument.cells[0].document.uri, new Position(2, 9)), false); + assertLocation(doc, new Position(2, 12), new Location(notebook.apiNotebook.cellAt(0).document.uri, new Position(2, 9)), false); assert.strictEqual(doc.positionAt(cell1End).isEqual(new Position(3, 2)), true); @@ -343,7 +341,7 @@ suite('NotebookConcatDocument', function () { test('selector', function () { extHostNotebooks.$acceptModelChanged(notebookUri, { - versionId: notebook.notebookDocument.version + 1, + versionId: notebook.apiNotebook.version + 1, rawEvents: [ { kind: NotebookCellsChangeType.ModelChange, @@ -368,16 +366,16 @@ suite('NotebookConcatDocument', function () { ] }, false); - const mixedDoc = new ExtHostNotebookConcatDocument(extHostNotebooks, extHostDocuments, notebook.notebookDocument, undefined); - const fooLangDoc = new ExtHostNotebookConcatDocument(extHostNotebooks, extHostDocuments, notebook.notebookDocument, 'fooLang'); - const barLangDoc = new ExtHostNotebookConcatDocument(extHostNotebooks, extHostDocuments, notebook.notebookDocument, 'barLang'); + const mixedDoc = new ExtHostNotebookConcatDocument(extHostNotebooks, extHostDocuments, notebook.apiNotebook, undefined); + const fooLangDoc = new ExtHostNotebookConcatDocument(extHostNotebooks, extHostDocuments, notebook.apiNotebook, 'fooLang'); + const barLangDoc = new ExtHostNotebookConcatDocument(extHostNotebooks, extHostDocuments, notebook.apiNotebook, 'barLang'); assertLines(mixedDoc, 'fooLang-document', 'barLang-document'); assertLines(fooLangDoc, 'fooLang-document'); assertLines(barLangDoc, 'barLang-document'); extHostNotebooks.$acceptModelChanged(notebookUri, { - versionId: notebook.notebookDocument.version + 1, + versionId: notebook.apiNotebook.version + 1, rawEvents: [ { kind: NotebookCellsChangeType.ModelChange, @@ -415,7 +413,7 @@ suite('NotebookConcatDocument', function () { test('offsetAt(position) <-> positionAt(offset)', function () { extHostNotebooks.$acceptModelChanged(notebookUri, { - versionId: notebook.notebookDocument.version + 1, + versionId: notebook.apiNotebook.version + 1, rawEvents: [ { kind: NotebookCellsChangeType.ModelChange, @@ -440,9 +438,9 @@ suite('NotebookConcatDocument', function () { ] }, false); - assert.strictEqual(notebook.notebookDocument.cells.length, 1 + 2); // markdown and code + assert.strictEqual(notebook.apiNotebook.cellCount, 1 + 2); // markdown and code - let doc = new ExtHostNotebookConcatDocument(extHostNotebooks, extHostDocuments, notebook.notebookDocument, undefined); + let doc = new ExtHostNotebookConcatDocument(extHostNotebooks, extHostDocuments, notebook.apiNotebook, undefined); assertLines(doc, 'Hello', 'World', 'Hello World!', 'Hallo', 'Welt', 'Hallo Welt!'); assertOffsetAtPosition(doc, 0, { line: 0, character: 0 }); @@ -472,7 +470,7 @@ suite('NotebookConcatDocument', function () { test('locationAt(position) <-> positionAt(location)', function () { extHostNotebooks.$acceptModelChanged(notebookUri, { - versionId: notebook.notebookDocument.version + 1, + versionId: notebook.apiNotebook.version + 1, rawEvents: [ { kind: NotebookCellsChangeType.ModelChange, @@ -497,23 +495,23 @@ suite('NotebookConcatDocument', function () { ] }, false); - assert.strictEqual(notebook.notebookDocument.cells.length, 1 + 2); // markdown and code + assert.strictEqual(notebook.apiNotebook.cellCount, 1 + 2); // markdown and code - let doc = new ExtHostNotebookConcatDocument(extHostNotebooks, extHostDocuments, notebook.notebookDocument, undefined); + let doc = new ExtHostNotebookConcatDocument(extHostNotebooks, extHostDocuments, notebook.apiNotebook, undefined); assertLines(doc, 'Hello', 'World', 'Hello World!', 'Hallo', 'Welt', 'Hallo Welt!'); - assertLocationAtPosition(doc, { line: 0, character: 0 }, { uri: notebook.notebookDocument.cells[0].document.uri, line: 0, character: 0 }); - assertLocationAtPosition(doc, { line: 2, character: 0 }, { uri: notebook.notebookDocument.cells[0].document.uri, line: 2, character: 0 }); - assertLocationAtPosition(doc, { line: 2, character: 12 }, { uri: notebook.notebookDocument.cells[0].document.uri, line: 2, character: 12 }); - assertLocationAtPosition(doc, { line: 3, character: 0 }, { uri: notebook.notebookDocument.cells[1].document.uri, line: 0, character: 0 }); - assertLocationAtPosition(doc, { line: 5, character: 0 }, { uri: notebook.notebookDocument.cells[1].document.uri, line: 2, character: 0 }); - assertLocationAtPosition(doc, { line: 5, character: 11 }, { uri: notebook.notebookDocument.cells[1].document.uri, line: 2, character: 11 }); + assertLocationAtPosition(doc, { line: 0, character: 0 }, { uri: notebook.apiNotebook.cellAt(0).document.uri, line: 0, character: 0 }); + assertLocationAtPosition(doc, { line: 2, character: 0 }, { uri: notebook.apiNotebook.cellAt(0).document.uri, line: 2, character: 0 }); + assertLocationAtPosition(doc, { line: 2, character: 12 }, { uri: notebook.apiNotebook.cellAt(0).document.uri, line: 2, character: 12 }); + assertLocationAtPosition(doc, { line: 3, character: 0 }, { uri: notebook.apiNotebook.cellAt(1).document.uri, line: 0, character: 0 }); + assertLocationAtPosition(doc, { line: 5, character: 0 }, { uri: notebook.apiNotebook.cellAt(1).document.uri, line: 2, character: 0 }); + assertLocationAtPosition(doc, { line: 5, character: 11 }, { uri: notebook.apiNotebook.cellAt(1).document.uri, line: 2, character: 11 }); }); test('getText(range)', function () { extHostNotebooks.$acceptModelChanged(notebookUri, { - versionId: notebook.notebookDocument.version + 1, + versionId: notebook.apiNotebook.version + 1, rawEvents: [ { kind: NotebookCellsChangeType.ModelChange, @@ -538,9 +536,9 @@ suite('NotebookConcatDocument', function () { ] }, false); - assert.strictEqual(notebook.notebookDocument.cells.length, 1 + 2); // markdown and code + assert.strictEqual(notebook.apiNotebook.cellCount, 1 + 2); // markdown and code - let doc = new ExtHostNotebookConcatDocument(extHostNotebooks, extHostDocuments, notebook.notebookDocument, undefined); + let doc = new ExtHostNotebookConcatDocument(extHostNotebooks, extHostDocuments, notebook.apiNotebook, undefined); assertLines(doc, 'Hello', 'World', 'Hello World!', 'Hallo', 'Welt', 'Hallo Welt!'); assert.strictEqual(doc.getText(new Range(0, 0, 0, 0)), ''); @@ -551,7 +549,7 @@ suite('NotebookConcatDocument', function () { test('validateRange/Position', function () { extHostNotebooks.$acceptModelChanged(notebookUri, { - versionId: notebook.notebookDocument.version + 1, + versionId: notebook.apiNotebook.version + 1, rawEvents: [ { kind: NotebookCellsChangeType.ModelChange, @@ -576,9 +574,9 @@ suite('NotebookConcatDocument', function () { ] }, false); - assert.strictEqual(notebook.notebookDocument.cells.length, 1 + 2); // markdown and code + assert.strictEqual(notebook.apiNotebook.cellCount, 1 + 2); // markdown and code - let doc = new ExtHostNotebookConcatDocument(extHostNotebooks, extHostDocuments, notebook.notebookDocument, undefined); + let doc = new ExtHostNotebookConcatDocument(extHostNotebooks, extHostDocuments, notebook.apiNotebook, undefined); assertLines(doc, 'Hello', 'World', 'Hello World!', 'Hallo', 'Welt', 'Hallo Welt!'); diff --git a/src/vs/workbench/test/browser/api/extHostNotebookKernel.test.ts b/src/vs/workbench/test/browser/api/extHostNotebookKernel.test.ts deleted file mode 100644 index 24afbcca..00000000 --- a/src/vs/workbench/test/browser/api/extHostNotebookKernel.test.ts +++ /dev/null @@ -1,109 +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 { TestRPCProtocol } from 'vs/workbench/test/browser/api/testRPCProtocol'; -import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; -import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; -import { NullLogService } from 'vs/platform/log/common/log'; -import { ExtHostNotebookController } from 'vs/workbench/api/common/extHostNotebook'; -import { ExtHostNotebookDocument } from 'vs/workbench/api/common/extHostNotebookDocument'; -import { URI } from 'vs/base/common/uri'; -import { CellKind, CellUri } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; -import { nullExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; -import * as vscode from 'vscode'; -import { mock } from 'vs/workbench/test/common/workbenchTestServices'; -import { MainContext, MainThreadCommandsShape, MainThreadNotebookShape } from 'vs/workbench/api/common/extHost.protocol'; -import { DisposableStore } from 'vs/base/common/lifecycle'; -import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths'; -import { generateUuid } from 'vs/base/common/uuid'; -import { CancellationToken } from 'vs/base/common/cancellation'; - -suite('NotebookKernel', function () { - - let rpcProtocol: TestRPCProtocol; - let notebook: ExtHostNotebookDocument; - let extHostDocumentsAndEditors: ExtHostDocumentsAndEditors; - let extHostDocuments: ExtHostDocuments; - let extHostNotebooks: ExtHostNotebookController; - const notebookUri = URI.parse('test:///notebook.file'); - const disposables = new DisposableStore(); - - setup(async function () { - disposables.clear(); - - rpcProtocol = new TestRPCProtocol(); - rpcProtocol.set(MainContext.MainThreadCommands, new class extends mock() { - $registerCommand() { } - }); - rpcProtocol.set(MainContext.MainThreadNotebook, new class extends mock() { - async $registerNotebookProvider() { } - async $unregisterNotebookProvider() { } - async $registerNotebookKernelProvider() { } - async $unregisterNotebookKernelProvider() { } - }); - extHostDocumentsAndEditors = new ExtHostDocumentsAndEditors(rpcProtocol, new NullLogService()); - extHostDocuments = new ExtHostDocuments(rpcProtocol, extHostDocumentsAndEditors); - const extHostStoragePaths = new class extends mock() { - workspaceValue() { - return URI.from({ scheme: 'test', path: generateUuid() }); - } - }; - extHostNotebooks = new ExtHostNotebookController(rpcProtocol, new ExtHostCommands(rpcProtocol, new NullLogService()), extHostDocumentsAndEditors, extHostDocuments, { isExtensionDevelopmentDebug: false, webviewCspSource: '', webviewResourceRoot: '' }, new NullLogService(), extHostStoragePaths); - let reg = extHostNotebooks.registerNotebookContentProvider(nullExtensionDescription, 'test', new class extends mock() { - // async openNotebook() { } - }); - - const kernels = [new class extends mock() { - id = 'first'; - }, new class extends mock() { - id = 'second'; - }]; - - let kernelReg = extHostNotebooks.registerNotebookKernelProvider(nullExtensionDescription, { viewType: 'test' }, new class extends mock() { - async provideKernels() { return kernels; } - }); - - // init - extHostNotebooks.$acceptDocumentAndEditorsDelta({ - addedDocuments: [{ - uri: notebookUri, - viewType: 'test', - cells: [{ - handle: 0, - uri: CellUri.generate(notebookUri, 0), - source: ['console.log'], - eol: '\n', - language: 'javascript', - cellKind: CellKind.Code, - outputs: [], - }], - versionId: 0 - }], - addedEditors: [ - { - documentUri: notebookUri, - id: '_notebook_editor_0', - selections: [{ start: 0, end: 1 }], - visibleRanges: [] - } - ] - }); - extHostNotebooks.$acceptDocumentAndEditorsDelta({ newActiveEditor: '_notebook_editor_0' }); - - notebook = extHostNotebooks.notebookDocuments[0]!; - - disposables.add(reg); - disposables.add(kernelReg); - disposables.add(notebook); - disposables.add(extHostDocuments); - }); - - test('provide kernels', async function () { - const dto = await extHostNotebooks.$provideNotebookKernels(0, notebook.uri, CancellationToken.None); - assert.deepStrictEqual(dto.map(kernel => kernel.id), ['first', 'second']); - }); -}); diff --git a/src/vs/workbench/test/browser/api/extHostNotebookKernel2.test.ts b/src/vs/workbench/test/browser/api/extHostNotebookKernel2.test.ts new file mode 100644 index 00000000..1af0ca88 --- /dev/null +++ b/src/vs/workbench/test/browser/api/extHostNotebookKernel2.test.ts @@ -0,0 +1,96 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { TestRPCProtocol } from 'vs/workbench/test/browser/api/testRPCProtocol'; +import { ExtHostNotebookController } from 'vs/workbench/api/common/extHostNotebook'; +import { nullExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; +import { mock } from 'vs/workbench/test/common/workbenchTestServices'; +import { INotebookKernelDto2, MainContext, MainThreadCommandsShape, MainThreadNotebookKernelsShape } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostNotebookKernels } from 'vs/workbench/api/common/extHostNotebookKernels'; +import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; + +suite('NotebookKernel', function () { + + let rpcProtocol: TestRPCProtocol; + let extHostNotebookKernels: ExtHostNotebookKernels; + + const kernelData = new Map(); + + setup(async function () { + + kernelData.clear(); + + rpcProtocol = new TestRPCProtocol(); + rpcProtocol.set(MainContext.MainThreadCommands, new class extends mock() { + override $registerCommand() { } + }); + rpcProtocol.set(MainContext.MainThreadNotebookKernels, new class extends mock() { + override async $addKernel(handle: number, data: INotebookKernelDto2): Promise { + kernelData.set(handle, data); + } + override $removeKernel(handle: number) { + kernelData.delete(handle); + } + override $updateKernel(handle: number, data: Partial) { + assert.strictEqual(kernelData.has(handle), true); + kernelData.set(handle, { ...kernelData.get(handle)!, ...data, }); + } + }); + + extHostNotebookKernels = new ExtHostNotebookKernels( + rpcProtocol, + new class extends mock() { }, + new class extends mock() { } + ); + }); + + test('create/dispose kernel', async function () { + + const kernel = extHostNotebookKernels.createNotebookController(nullExtensionDescription, 'foo', '*', 'Foo'); + + assert.throws(() => (kernel).id = 'dd'); + assert.throws(() => (kernel).viewType = 'dd'); + + assert.ok(kernel); + assert.strictEqual(kernel.id, 'foo'); + assert.strictEqual(kernel.label, 'Foo'); + assert.strictEqual(kernel.viewType, '*'); + + await rpcProtocol.sync(); + assert.strictEqual(kernelData.size, 1); + + let [first] = kernelData.values(); + assert.strictEqual(first.id, 'nullExtensionDescription/foo'); + assert.strictEqual(ExtensionIdentifier.equals(first.extensionId, nullExtensionDescription.identifier), true); + assert.strictEqual(first.label, 'Foo'); + assert.strictEqual(first.viewType, '*'); + + kernel.dispose(); + await rpcProtocol.sync(); + assert.strictEqual(kernelData.size, 0); + }); + + test('update kernel', async function () { + + const kernel = extHostNotebookKernels.createNotebookController(nullExtensionDescription, 'foo', '*', 'Foo'); + + await rpcProtocol.sync(); + assert.ok(kernel); + + let [first] = kernelData.values(); + assert.strictEqual(first.id, 'nullExtensionDescription/foo'); + assert.strictEqual(first.label, 'Foo'); + + kernel.label = 'Far'; + assert.strictEqual(kernel.label, 'Far'); + + await rpcProtocol.sync(); + [first] = kernelData.values(); + assert.strictEqual(first.id, 'nullExtensionDescription/foo'); + assert.strictEqual(first.label, 'Far'); + }); +}); diff --git a/src/vs/workbench/test/browser/api/extHostTesting.test.ts b/src/vs/workbench/test/browser/api/extHostTesting.test.ts index cb245d0f..62340f17 100644 --- a/src/vs/workbench/test/browser/api/extHostTesting.test.ts +++ b/src/vs/workbench/test/browser/api/extHostTesting.test.ts @@ -4,13 +4,16 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { URI } from 'vs/base/common/uri'; +import { createDefaultDocumentTestRoot, TestItemFilteredWrapper } from 'vs/workbench/api/common/extHostTesting'; import * as convert from 'vs/workbench/api/common/extHostTypeConverters'; import { TestDiffOpType, TestItemExpandState } from 'vs/workbench/contrib/testing/common/testCollection'; -import { stubTest, testStubs } from 'vs/workbench/contrib/testing/common/testStubs'; +import { stubTest, TestItemImpl, testStubs } from 'vs/workbench/contrib/testing/common/testStubs'; import { TestOwnedTestCollection, TestSingleUseCollection } from 'vs/workbench/contrib/testing/test/common/ownedTestCollection'; -import { TestItem } from 'vscode'; +import { TestItem, TextDocument } from 'vscode'; -const simplify = (item: TestItem) => ({ +const simplify = (item: TestItem) => ({ id: item.id, label: item.label, uri: item.uri, @@ -19,13 +22,21 @@ const simplify = (item: TestItem) => ({ debuggable: item.debuggable, }); -const assertTreesEqual = (a: TestItem, b: TestItem) => { +const assertTreesEqual = (a: TestItem | undefined, b: TestItem | undefined) => { + if (!a) { + throw new assert.AssertionError({ message: 'Expected a to be defined', actual: a }); + } + + if (!b) { + throw new assert.AssertionError({ message: 'Expected b to be defined', actual: b }); + } + assert.deepStrictEqual(simplify(a), simplify(b)); - const aChildren = [...a.children].slice().sort(); - const bChildren = [...b.children].slice().sort(); + const aChildren = [...a.children.keys()].slice().sort(); + const bChildren = [...b.children.keys()].slice().sort(); assert.strictEqual(aChildren.length, bChildren.length, `expected ${a.label}.children.length == ${b.label}.children.length`); - aChildren.forEach((_, i) => assertTreesEqual(aChildren[i], bChildren[i])); + aChildren.forEach(key => assertTreesEqual(a.children.get(key), b.children.get(key))); }; // const assertTreeListEqual = (a: ReadonlyArray, b: ReadonlyArray) => { @@ -67,15 +78,15 @@ suite('ExtHost Testing', () => { assert.deepStrictEqual(single.collectDiff(), [ [ TestDiffOpType.Add, - { src: { tree: 0, provider: 'pid' }, parent: null, expand: TestItemExpandState.BusyExpanding, item: { ...convert.TestItem.from(stubTest('root')), expandable: true } } + { src: { tree: 0, controller: 'pid' }, parent: null, expand: TestItemExpandState.BusyExpanding, item: { ...convert.TestItem.from(stubTest('root')) } } ], [ TestDiffOpType.Add, - { src: { tree: 0, provider: 'pid' }, parent: 'id-root', expand: TestItemExpandState.Expandable, item: { ...convert.TestItem.from(stubTest('a')), expandable: true } } + { src: { tree: 0, controller: 'pid' }, parent: 'id-root', expand: TestItemExpandState.Expandable, item: { ...convert.TestItem.from(stubTest('a')) } } ], [ TestDiffOpType.Add, - { src: { tree: 0, provider: 'pid' }, parent: 'id-root', expand: TestItemExpandState.NotExpandable, item: convert.TestItem.from(stubTest('b')) } + { src: { tree: 0, controller: 'pid' }, parent: 'id-root', expand: TestItemExpandState.NotExpandable, item: convert.TestItem.from(stubTest('b')) } ], [ TestDiffOpType.Update, @@ -87,11 +98,11 @@ suite('ExtHost Testing', () => { ], [ TestDiffOpType.Add, - { src: { tree: 0, provider: 'pid' }, parent: 'id-a', expand: TestItemExpandState.NotExpandable, item: convert.TestItem.from(stubTest('aa')) } + { src: { tree: 0, controller: 'pid' }, parent: 'id-a', expand: TestItemExpandState.NotExpandable, item: convert.TestItem.from(stubTest('aa')) } ], [ TestDiffOpType.Add, - { src: { tree: 0, provider: 'pid' }, parent: 'id-a', expand: TestItemExpandState.NotExpandable, item: convert.TestItem.from(stubTest('ab')) } + { src: { tree: 0, controller: 'pid' }, parent: 'id-a', expand: TestItemExpandState.NotExpandable, item: convert.TestItem.from(stubTest('ab')) } ], [ TestDiffOpType.Update, @@ -126,7 +137,7 @@ suite('ExtHost Testing', () => { single.addRoot(tests, 'pid'); single.expand('id-root', Infinity); single.collectDiff(); - tests.children.delete('id-a'); + tests.children.get('id-a')!.dispose(); assert.deepStrictEqual(single.collectDiff(), [ [TestDiffOpType.Remove, 'id-a'], @@ -141,11 +152,11 @@ suite('ExtHost Testing', () => { single.expand('id-root', Infinity); single.collectDiff(); const child = stubTest('ac'); - tests.children.get('id-a')!.children!.add(child); + tests.children.get('id-a')!.addChild(child); assert.deepStrictEqual(single.collectDiff(), [ [TestDiffOpType.Add, { - src: { tree: 0, provider: 'pid' }, + src: { tree: 0, controller: 'pid' }, parent: 'id-a', expand: TestItemExpandState.NotExpandable, item: convert.TestItem.from(child), @@ -160,284 +171,256 @@ suite('ExtHost Testing', () => { }); - // todo@connor4312: re-renable when we figure out what observing looks like we async children - // suite('MirroredTestCollection', () => { - // let m: TestMirroredCollection; - // setup(() => m = new TestMirroredCollection()); + suite('MirroredTestCollection', () => { + // todo@connor4312: re-renable when we figure out what observing looks like we async children + // let m: TestMirroredCollection; + // setup(() => m = new TestMirroredCollection()); - // test('mirrors creation of the root', () => { - // const tests = testStubs.nested(); - // single.addRoot(tests, 'pid'); - // single.expand('id-root', Infinity); - // m.apply(single.collectDiff()); - // assertTreesEqual(m.rootTestItems[0], owned.getTestById('id-root')![1].actual); - // assert.strictEqual(m.length, single.itemToInternal.size); - // }); + // test('mirrors creation of the root', () => { + // const tests = testStubs.nested(); + // single.addRoot(tests, 'pid'); + // single.expand('id-root', Infinity); + // m.apply(single.collectDiff()); + // assertTreesEqual(m.rootTestItems[0], owned.getTestById('id-root')![1].actual); + // assert.strictEqual(m.length, single.itemToInternal.size); + // }); - // test('mirrors node deletion', () => { - // const tests = testStubs.nested(); - // single.addRoot(tests, 'pid'); - // m.apply(single.collectDiff()); - // single.expand('id-root', Infinity); - // tests.children!.splice(0, 1); - // single.onItemChange(tests, 'pid'); - // single.expand('id-root', Infinity); - // m.apply(single.collectDiff()); + // test('mirrors node deletion', () => { + // const tests = testStubs.nested(); + // single.addRoot(tests, 'pid'); + // m.apply(single.collectDiff()); + // single.expand('id-root', Infinity); + // tests.children!.splice(0, 1); + // single.onItemChange(tests, 'pid'); + // single.expand('id-root', Infinity); + // m.apply(single.collectDiff()); - // assertTreesEqual(m.rootTestItems[0], owned.getTestById('id-root')![1].actual); - // assert.strictEqual(m.length, single.itemToInternal.size); - // }); + // assertTreesEqual(m.rootTestItems[0], owned.getTestById('id-root')![1].actual); + // assert.strictEqual(m.length, single.itemToInternal.size); + // }); - // test('mirrors node addition', () => { - // const tests = testStubs.nested(); - // single.addRoot(tests, 'pid'); - // m.apply(single.collectDiff()); - // tests.children![0].children!.push(stubTest('ac')); - // single.onItemChange(tests, 'pid'); - // m.apply(single.collectDiff()); + // test('mirrors node addition', () => { + // const tests = testStubs.nested(); + // single.addRoot(tests, 'pid'); + // m.apply(single.collectDiff()); + // tests.children![0].children!.push(stubTest('ac')); + // single.onItemChange(tests, 'pid'); + // m.apply(single.collectDiff()); - // assertTreesEqual(m.rootTestItems[0], owned.getTestById('id-root')![1].actual); - // assert.strictEqual(m.length, single.itemToInternal.size); - // }); + // assertTreesEqual(m.rootTestItems[0], owned.getTestById('id-root')![1].actual); + // assert.strictEqual(m.length, single.itemToInternal.size); + // }); - // test('mirrors node update', () => { - // const tests = testStubs.nested(); - // single.addRoot(tests, 'pid'); - // m.apply(single.collectDiff()); - // tests.children![0].description = 'Hello world'; /* item a */ - // single.onItemChange(tests, 'pid'); - // m.apply(single.collectDiff()); + // test('mirrors node update', () => { + // const tests = testStubs.nested(); + // single.addRoot(tests, 'pid'); + // m.apply(single.collectDiff()); + // tests.children![0].description = 'Hello world'; /* item a */ + // single.onItemChange(tests, 'pid'); + // m.apply(single.collectDiff()); - // assertTreesEqual(m.rootTestItems[0], owned.getTestById('id-root')![1].actual); - // }); + // assertTreesEqual(m.rootTestItems[0], owned.getTestById('id-root')![1].actual); + // }); - // suite('MirroredChangeCollector', () => { - // let tests = testStubs.nested(); - // setup(() => { - // tests = testStubs.nested(); - // single.addRoot(tests, 'pid'); - // m.apply(single.collectDiff()); - // }); + // suite('MirroredChangeCollector', () => { + // let tests = testStubs.nested(); + // setup(() => { + // tests = testStubs.nested(); + // single.addRoot(tests, 'pid'); + // m.apply(single.collectDiff()); + // }); - // test('creates change for root', () => { - // assertTreeListEqual(m.changeEvent.added, [ - // tests, - // tests.children[0], - // tests.children![0].children![0], - // tests.children![0].children![1], - // tests.children[1], - // ]); - // assertTreeListEqual(m.changeEvent.removed, []); - // assertTreeListEqual(m.changeEvent.updated, []); - // }); + // test('creates change for root', () => { + // assertTreeListEqual(m.changeEvent.added, [ + // tests, + // tests.children[0], + // tests.children![0].children![0], + // tests.children![0].children![1], + // tests.children[1], + // ]); + // assertTreeListEqual(m.changeEvent.removed, []); + // assertTreeListEqual(m.changeEvent.updated, []); + // }); - // test('creates change for delete', () => { - // const rm = tests.children.shift()!; - // single.onItemChange(tests, 'pid'); - // m.apply(single.collectDiff()); + // test('creates change for delete', () => { + // const rm = tests.children.shift()!; + // single.onItemChange(tests, 'pid'); + // m.apply(single.collectDiff()); - // assertTreeListEqual(m.changeEvent.added, []); - // assertTreeListEqual(m.changeEvent.removed, [ - // { ...rm }, - // { ...rm.children![0] }, - // { ...rm.children![1] }, - // ]); - // assertTreeListEqual(m.changeEvent.updated, []); - // }); + // assertTreeListEqual(m.changeEvent.added, []); + // assertTreeListEqual(m.changeEvent.removed, [ + // { ...rm }, + // { ...rm.children![0] }, + // { ...rm.children![1] }, + // ]); + // assertTreeListEqual(m.changeEvent.updated, []); + // }); - // test('creates change for update', () => { - // tests.children[0].label = 'updated!'; - // single.onItemChange(tests, 'pid'); - // m.apply(single.collectDiff()); + // test('creates change for update', () => { + // tests.children[0].label = 'updated!'; + // single.onItemChange(tests, 'pid'); + // m.apply(single.collectDiff()); - // assertTreeListEqual(m.changeEvent.added, []); - // assertTreeListEqual(m.changeEvent.removed, []); - // assertTreeListEqual(m.changeEvent.updated, [tests.children[0]]); - // }); + // assertTreeListEqual(m.changeEvent.added, []); + // assertTreeListEqual(m.changeEvent.removed, []); + // assertTreeListEqual(m.changeEvent.updated, [tests.children[0]]); + // }); - // test('is a no-op if a node is added and removed', () => { - // const nested = testStubs.nested('id2-'); - // tests.children.push(nested); - // single.onItemChange(tests, 'pid'); - // tests.children.pop(); - // single.onItemChange(tests, 'pid'); - // const previousEvent = m.changeEvent; - // m.apply(single.collectDiff()); - // assert.strictEqual(m.changeEvent, previousEvent); - // }); + // test('is a no-op if a node is added and removed', () => { + // const nested = testStubs.nested('id2-'); + // tests.children.push(nested); + // single.onItemChange(tests, 'pid'); + // tests.children.pop(); + // single.onItemChange(tests, 'pid'); + // const previousEvent = m.changeEvent; + // m.apply(single.collectDiff()); + // assert.strictEqual(m.changeEvent, previousEvent); + // }); - // test('is a single-op if a node is added and changed', () => { - // const child = stubTest('c'); - // tests.children.push(child); - // single.onItemChange(tests, 'pid'); - // child.label = 'd'; - // single.onItemChange(tests, 'pid'); - // m.apply(single.collectDiff()); + // test('is a single-op if a node is added and changed', () => { + // const child = stubTest('c'); + // tests.children.push(child); + // single.onItemChange(tests, 'pid'); + // child.label = 'd'; + // single.onItemChange(tests, 'pid'); + // m.apply(single.collectDiff()); - // assertTreeListEqual(m.changeEvent.added, [child]); - // assertTreeListEqual(m.changeEvent.removed, []); - // assertTreeListEqual(m.changeEvent.updated, []); - // }); + // assertTreeListEqual(m.changeEvent.added, [child]); + // assertTreeListEqual(m.changeEvent.removed, []); + // assertTreeListEqual(m.changeEvent.updated, []); + // }); - // test('gets the common ancestor (1)', () => { - // tests.children![0].children![0].label = 'za'; - // tests.children![0].children![1].label = 'zb'; - // single.onItemChange(tests, 'pid'); - // m.apply(single.collectDiff()); + // test('gets the common ancestor (1)', () => { + // tests.children![0].children![0].label = 'za'; + // tests.children![0].children![1].label = 'zb'; + // single.onItemChange(tests, 'pid'); + // m.apply(single.collectDiff()); - // }); + // }); - // test('gets the common ancestor (2)', () => { - // tests.children![0].children![0].label = 'za'; - // tests.children![1].label = 'ab'; - // single.onItemChange(tests, 'pid'); - // m.apply(single.collectDiff()); - // }); - // }); + // test('gets the common ancestor (2)', () => { + // tests.children![0].children![0].label = 'za'; + // tests.children![1].label = 'ab'; + // single.onItemChange(tests, 'pid'); + // m.apply(single.collectDiff()); + // }); + // }); - // suite('TestItemFilteredWrapper', () => { - // const stubTestWithLocation = (label: string, location: Location, children: StubTestItem[] = []) => { - // const t = stubTest(label, undefined, children); - // t.location = location as any; - // return t; - // }; + suite('TestItemFilteredWrapper', () => { + const textDocumentFilter = { + uri: URI.parse('file:///foo.ts'), + } as TextDocument; - // const location1: Location = { - // range: new Range(0, 0, 0, 0), - // uri: URI.parse('file:///foo.ts') - // }; + let testsWithLocation: TestItemImpl; + setup(async () => { + testsWithLocation = + stubTest('root', undefined, [ + stubTest('a', undefined, [ + stubTest('aa', undefined, undefined, URI.parse('file:///foo.ts')), + stubTest('ab', undefined, undefined, URI.parse('file:///foo.ts')) + ], URI.parse('file:///foo.ts')), + stubTest('b', undefined, [ + stubTest('ba', undefined, undefined, URI.parse('file:///bar.ts')), + stubTest('bb', undefined, undefined, URI.parse('file:///bar.ts')) + ], URI.parse('file:///bar.ts')), + stubTest('c', undefined, undefined, URI.parse('file:///baz.ts')), + ]); - // const location2: Location = { - // range: new Range(0, 0, 0, 0), - // uri: URI.parse('file:///bar.ts') - // }; + // todo: this is not used, don't think it's needed anymore + await createDefaultDocumentTestRoot( + { + createWorkspaceTestRoot: () => testsWithLocation as TestItem, + runTests() { + throw new Error('no implemented'); + } + }, + textDocumentFilter, + undefined, + CancellationToken.None + ); + }); - // const location3: Location = { - // range: new Range(0, 0, 0, 0), - // uri: URI.parse('file:///baz.ts') - // }; + teardown(() => { + TestItemFilteredWrapper.removeFilter(textDocumentFilter); + }); - // const textDocumentFilter = { - // uri: location1.uri - // } as TextDocument; + test('gets all actual properties', () => { + const testItem = stubTest('test1'); + const wrapper = TestItemFilteredWrapper.getWrapperForTestItem(testItem, textDocumentFilter); - // let testsWithLocation: StubTestItem; - // let hierarchy: TestHierarchy; - // setup(async () => { - // testsWithLocation = - // stubTest('root', undefined, [ - // stubTestWithLocation('a', location1, [stubTestWithLocation('aa', location1), stubTestWithLocation('ab', location1)]), - // stubTestWithLocation('b', location2, [stubTestWithLocation('ba', location2), stubTestWithLocation('bb', location2)]), - // stubTestWithLocation('b', location3), - // ]); + assert.strictEqual(testItem.debuggable, wrapper.debuggable); + assert.strictEqual(testItem.description, wrapper.description); + assert.strictEqual(testItem.label, wrapper.label); + assert.strictEqual(testItem.uri, wrapper.uri); + assert.strictEqual(testItem.runnable, wrapper.runnable); + }); - // hierarchy = (await createDefaultDocumentTestHierarchy( - // { - // provideWorkspaceTestHierarchy: () => ({ - // getChildren.getChildren, - // getParent.getParent, - // onDidChangeTest: new Emitter().event, - // root: testsWithLocation - // }), - // runTests() { - // throw new Error('no implemented'); - // } - // }, - // textDocumentFilter, - // undefined, - // CancellationToken.None - // ))!; - // }); + test('gets no children if nothing matches Uri filter', () => { + const tests = testStubs.nested(); + const wrapper = TestItemFilteredWrapper.getWrapperForTestItem(tests, textDocumentFilter); + wrapper.resolveHandler?.(CancellationToken.None); + assert.strictEqual(wrapper.children.size, 0); + }); - // teardown(() => { - // TestItemFilteredWrapper.removeFilter(textDocumentFilter); - // }); + test('filter is applied to children', () => { + const wrapper = TestItemFilteredWrapper.getWrapperForTestItem(testsWithLocation, textDocumentFilter); + assert.strictEqual(wrapper.label, 'root'); + wrapper.resolveHandler?.(CancellationToken.None); - // test('gets all actual properties', () => { - // const testItem: TestItem = stubTest('test1'); - // const wrapper: TestItemFilteredWrapper = TestItemFilteredWrapper.getWrapperForTestItem(testItem, textDocumentFilter); + const children = [...wrapper.children.values()]; + assert.strictEqual(children.length, 1); + assert.strictEqual(children[0] instanceof TestItemFilteredWrapper, true); + assert.strictEqual(children[0].label, 'a'); + }); - // assert.strictEqual(testItem.debuggable, wrapper.debuggable); - // assert.strictEqual(testItem.description, wrapper.description); - // assert.strictEqual(testItem.label, wrapper.label); - // assert.strictEqual(testItem.location, wrapper.location); - // assert.strictEqual(testItem.runnable, wrapper.runnable); - // }); + test('can get if node has matching filter', () => { + const rootWrapper = TestItemFilteredWrapper.getWrapperForTestItem(testsWithLocation, textDocumentFilter); + rootWrapper.resolveHandler?.(CancellationToken.None); - // test('gets no children if nothing matches Uri filter', () => { - // let tests: TestItem = testStubs.nested(); - // const wrapper = TestItemFilteredWrapper.getWrapperForTestItem(tests, textDocumentFilter); - // const children = hierarchy.getChildren(wrapper, CancellationToken.None) as TestItemFilteredWrapper[]; - // assert.strictEqual(children.length, 0); - // }); + const invisible = testsWithLocation.children.get('id-b')!; + const invisibleWrapper = TestItemFilteredWrapper.getWrapperForTestItem(invisible, textDocumentFilter); + const visible = testsWithLocation.children.get('id-a')!; + const visibleWrapper = TestItemFilteredWrapper.getWrapperForTestItem(visible, textDocumentFilter); - // test('filter is applied to children', () => { - // const wrapper = TestItemFilteredWrapper.getWrapperForTestItem(testsWithLocation, textDocumentFilter); - // assert.strictEqual(wrapper.label, 'root'); - // const children = hierarchy.getChildren(wrapper, CancellationToken.None) as TestItemFilteredWrapper[]; - // assert.strictEqual(children.length, 1); - // assert.strictEqual(children[0] instanceof TestItemFilteredWrapper, true); - // assert.strictEqual(children[0].label, 'a'); - // }); + // The root is always visible + assert.strictEqual(rootWrapper.hasNodeMatchingFilter, true); + assert.strictEqual(invisibleWrapper.hasNodeMatchingFilter, false); + assert.strictEqual(visibleWrapper.hasNodeMatchingFilter, true); + }); - // test('can get if node has matching filter', () => { - // const rootWrapper = TestItemFilteredWrapper.getWrapperForTestItem(testsWithLocation, textDocumentFilter); + test('can reset cached value of hasNodeMatchingFilter', () => { + const wrapper = TestItemFilteredWrapper.getWrapperForTestItem(testsWithLocation, textDocumentFilter); + wrapper.resolveHandler?.(CancellationToken.None); - // const invisible = testsWithLocation.children![1]; - // const invisibleWrapper = TestItemFilteredWrapper.getWrapperForTestItem(invisible, textDocumentFilter); - // const visible = testsWithLocation.children![0]; - // const visibleWrapper = TestItemFilteredWrapper.getWrapperForTestItem(visible, textDocumentFilter); + const invisible = testsWithLocation.children.get('id-b')!; + const invisibleWrapper = TestItemFilteredWrapper.getWrapperForTestItem(invisible, textDocumentFilter); - // // The root is always visible - // assert.strictEqual(rootWrapper.hasNodeMatchingFilter, true); - // assert.strictEqual(invisibleWrapper.hasNodeMatchingFilter, false); - // assert.strictEqual(visibleWrapper.hasNodeMatchingFilter, true); - // }); + assert.strictEqual(wrapper.children.get('id-b'), undefined); + assert.strictEqual(invisibleWrapper.hasNodeMatchingFilter, false); - // test('can get visible parent', () => { - // const rootWrapper = TestItemFilteredWrapper.getWrapperForTestItem(testsWithLocation, textDocumentFilter); + invisible.addChild(stubTest('bc', undefined, undefined, URI.parse('file:///foo.ts'))); + assert.strictEqual(invisibleWrapper.hasNodeMatchingFilter, true); + assert.strictEqual(invisibleWrapper.children.size, 1); + assert.strictEqual(wrapper.children.get('id-b'), invisibleWrapper); + }); - // const invisible = testsWithLocation.children![1]; - // const invisibleWrapper = TestItemFilteredWrapper.getWrapperForTestItem(invisible, textDocumentFilter); - // const visible = testsWithLocation.children![0]; - // const visibleWrapper = TestItemFilteredWrapper.getWrapperForTestItem(visible, textDocumentFilter); + // test('can reset cached value of hasNodeMatchingFilter of parents up to visible parent', () => { + // const rootWrapper = TestItemFilteredWrapper.getWrapperForTestItem(testsWithLocation, textDocumentFilter); - // // The root is always visible - // assert.strictEqual(rootWrapper.visibleParent, rootWrapper); - // assert.strictEqual(invisibleWrapper.visibleParent, rootWrapper); - // assert.strictEqual(visibleWrapper.visibleParent, visibleWrapper); - // }); + // const invisibleParent = testsWithLocation.children.get('id-b')!; + // const invisibleParentWrapper = TestItemFilteredWrapper.getWrapperForTestItem(invisibleParent, textDocumentFilter); + // const invisible = invisibleParent.children.get('id-bb')!; + // const invisibleWrapper = TestItemFilteredWrapper.getWrapperForTestItem(invisible, textDocumentFilter); - // test('can reset cached value of hasNodeMatchingFilter', () => { - // TestItemFilteredWrapper.getWrapperForTestItem(testsWithLocation, textDocumentFilter); + // assert.strictEqual(invisibleParentWrapper.hasNodeMatchingFilter, false); + // invisible.location = location1 as any; + // assert.strictEqual(invisibleParentWrapper.hasNodeMatchingFilter, false); + // invisibleWrapper.reset(); + // assert.strictEqual(invisibleParentWrapper.hasNodeMatchingFilter, true); - // const invisible = testsWithLocation.children![1]; - // const invisibleWrapper = TestItemFilteredWrapper.getWrapperForTestItem(invisible, textDocumentFilter); - - // assert.strictEqual(invisibleWrapper.hasNodeMatchingFilter, false); - // invisible.location = location1 as any; - // assert.strictEqual(invisibleWrapper.hasNodeMatchingFilter, false); - // invisibleWrapper.reset(); - // assert.strictEqual(invisibleWrapper.hasNodeMatchingFilter, true); - // }); - - // test('can reset cached value of hasNodeMatchingFilter of parents up to visible parent', () => { - // const rootWrapper = TestItemFilteredWrapper.getWrapperForTestItem(testsWithLocation, textDocumentFilter); - - // const invisibleParent = testsWithLocation.children![1]; - // const invisibleParentWrapper = TestItemFilteredWrapper.getWrapperForTestItem(invisibleParent, textDocumentFilter); - // const invisible = invisibleParent.children![1]; - // const invisibleWrapper = TestItemFilteredWrapper.getWrapperForTestItem(invisible, textDocumentFilter); - - // assert.strictEqual(invisibleParentWrapper.hasNodeMatchingFilter, false); - // invisible.location = location1 as any; - // assert.strictEqual(invisibleParentWrapper.hasNodeMatchingFilter, false); - // invisibleWrapper.reset(); - // assert.strictEqual(invisibleParentWrapper.hasNodeMatchingFilter, true); - - // // the root should be undefined due to the reset. - // assert.strictEqual((rootWrapper as any).matchesFilter, undefined); - // }); - // }); - // }); + // // the root should be undefined due to the reset. + // assert.strictEqual((rootWrapper as any).matchesFilter, undefined); + // }); + }); + }); }); diff --git a/src/vs/workbench/test/browser/api/extHostTextEditor.test.ts b/src/vs/workbench/test/browser/api/extHostTextEditor.test.ts index ca9a7f79..9d69ae57 100644 --- a/src/vs/workbench/test/browser/api/extHostTextEditor.test.ts +++ b/src/vs/workbench/test/browser/api/extHostTextEditor.test.ts @@ -44,7 +44,7 @@ suite('ExtHostTextEditor', () => { let applyCount = 0; let editor = new ExtHostTextEditor('edt1', new class extends mock() { - $tryApplyEdits(): Promise { + override $tryApplyEdits(): Promise { applyCount += 1; return Promise.resolve(true); } diff --git a/src/vs/workbench/test/browser/api/extHostTreeViews.test.ts b/src/vs/workbench/test/browser/api/extHostTreeViews.test.ts index 134e347d..5182f39f 100644 --- a/src/vs/workbench/test/browser/api/extHostTreeViews.test.ts +++ b/src/vs/workbench/test/browser/api/extHostTreeViews.test.ts @@ -26,16 +26,16 @@ suite('ExtHostTreeView', function () { onRefresh = new Emitter<{ [treeItemHandle: string]: ITreeItem }>(); - async $registerTreeViewDataProvider(treeViewId: string): Promise { + override async $registerTreeViewDataProvider(treeViewId: string): Promise { } - $refresh(viewId: string, itemsToRefresh: { [treeItemHandle: string]: ITreeItem }): Promise { + override $refresh(viewId: string, itemsToRefresh: { [treeItemHandle: string]: ITreeItem }): Promise { return Promise.resolve(null).then(() => { this.onRefresh.fire(itemsToRefresh); }); } - $reveal(): Promise { + override $reveal(): Promise { return Promise.resolve(); } diff --git a/src/vs/workbench/test/browser/api/extHostTypes.test.ts b/src/vs/workbench/test/browser/api/extHostTypes.test.ts index 4a413eb8..788aec8b 100644 --- a/src/vs/workbench/test/browser/api/extHostTypes.test.ts +++ b/src/vs/workbench/test/browser/api/extHostTypes.test.ts @@ -651,10 +651,6 @@ suite('ExtHostTypes', function () { test('NotebookMetadata - defaults', function () { const obj = new types.NotebookDocumentMetadata(); - assert.strictEqual(obj.cellEditable, notebookDocumentMetadataDefaults.cellEditable); - assert.strictEqual(obj.cellHasExecutionOrder, notebookDocumentMetadataDefaults.cellHasExecutionOrder); - assert.deepStrictEqual(obj.custom, notebookDocumentMetadataDefaults.custom); - assert.strictEqual(obj.editable, notebookDocumentMetadataDefaults.editable); assert.strictEqual(obj.trusted, notebookDocumentMetadataDefaults.trusted); }); @@ -668,18 +664,42 @@ suite('ExtHostTypes', function () { assert.strictEqual(newObj.trusted, false); }); - test('NotebookCellMetadata - with', function () { - const obj = new types.NotebookCellMetadata(true, false, true); - - const newObj = obj.with({ statusMessage: 'hello' }); + test('NotebookMetadata - with custom', function () { + const obj = new types.NotebookDocumentMetadata(); + const newObj = obj.with({ trusted: false, mycustom: { display: 'hello' } }); assert.ok(obj !== newObj); - assert.strictEqual(obj.statusMessage, undefined); - assert.strictEqual(obj.editable, true); + const sameObj = newObj.with({ trusted: false }); + assert.ok(newObj === sameObj); + assert.strictEqual(obj.trusted, true); + assert.strictEqual(newObj.trusted, false); + assert.deepStrictEqual(newObj.mycustom, { display: 'hello' }); + }); + + test('NotebookCellMetadata - with', function () { + const obj = new types.NotebookCellMetadata(true, true); + + const newObj = obj.with({ inputCollapsed: false }); + assert.ok(obj !== newObj); + assert.strictEqual(obj.inputCollapsed, true); assert.strictEqual(obj.custom, undefined); - assert.strictEqual(newObj.statusMessage, 'hello'); - assert.strictEqual(newObj.editable, true); + assert.strictEqual(newObj.inputCollapsed, false); assert.strictEqual(newObj.custom, undefined); + }); + test('NotebookCellMetadata - with custom', function () { + const obj = new types.NotebookCellMetadata(true, true); + const newObj = obj.with({ inputCollapsed: false, custom: { display: 'hello' } }); + assert.ok(obj !== newObj); + const sameObj = newObj.with({ inputCollapsed: false }); + assert.ok(newObj === sameObj); + assert.strictEqual(obj.inputCollapsed, true); + assert.strictEqual(newObj.inputCollapsed, false); + assert.deepStrictEqual(newObj.custom, { display: 'hello' }); + + const newCustom = newObj.with({ anotherCustom: { display: 'hello2' } }); + assert.strictEqual(newCustom.inputCollapsed, false); + assert.deepStrictEqual(newCustom.mycustom, undefined); + assert.deepStrictEqual(newCustom.anotherCustom, { display: 'hello2' }); }); }); diff --git a/src/vs/workbench/test/browser/api/extHostWorkspace.test.ts b/src/vs/workbench/test/browser/api/extHostWorkspace.test.ts index 709acc15..86fa0e31 100644 --- a/src/vs/workbench/test/browser/api/extHostWorkspace.test.ts +++ b/src/vs/workbench/test/browser/api/extHostWorkspace.test.ts @@ -23,16 +23,15 @@ import { IPatternInfo } from 'vs/workbench/services/search/common/search'; import { isLinux, isWindows } from 'vs/base/common/platform'; import { IExtHostFileSystemInfo } from 'vs/workbench/api/common/extHostFileSystemInfo'; import { FileSystemProviderCapabilities } from 'vs/platform/files/common/files'; -import { WorkspaceTrustState } from 'vs/platform/workspace/common/workspaceTrust'; function createExtHostWorkspace(mainContext: IMainContext, data: IWorkspaceData, logService: ILogService): ExtHostWorkspace { const result = new ExtHostWorkspace( new ExtHostRpcService(mainContext), new class extends mock() { workspace = data; }, - new class extends mock() { getCapabilities() { return isLinux ? FileSystemProviderCapabilities.PathCaseSensitive : undefined; } }, + new class extends mock() { override getCapabilities() { return isLinux ? FileSystemProviderCapabilities.PathCaseSensitive : undefined; } }, logService, ); - result.$initializeWorkspace(data, WorkspaceTrustState.Trusted); + result.$initializeWorkspace(data, true); return result; } @@ -271,7 +270,7 @@ suite('ExtHostWorkspace', function () { ws.$acceptWorkspaceData({ id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.parse('foo:bar3'), 0)] }); ws.$acceptWorkspaceData({ id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.parse('foo:bar3'), 0), aWorkspaceFolderData(URI.parse('foo:bar'), 1)] }); - assert.notEqual(firstFolder, ws.workspace!.folders[0]); + assert.notStrictEqual(firstFolder, ws.workspace!.folders[0]); }); test('updateWorkspaceFolders - invalid arguments', function () { @@ -590,7 +589,7 @@ suite('ExtHostWorkspace', function () { let mainThreadCalled = false; rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock() { - $startFileSearch(includePattern: string, _includeFolder: UriComponents | null, excludePatternOrDisregardExcludes: string | false, maxResults: number, token: CancellationToken): Promise { + override $startFileSearch(includePattern: string, _includeFolder: UriComponents | null, excludePatternOrDisregardExcludes: string | false, maxResults: number, token: CancellationToken): Promise { mainThreadCalled = true; assert.strictEqual(includePattern, 'foo'); assert.strictEqual(_includeFolder, null); @@ -612,7 +611,7 @@ suite('ExtHostWorkspace', function () { let mainThreadCalled = false; rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock() { - $startFileSearch(includePattern: string, _includeFolder: UriComponents | null, excludePatternOrDisregardExcludes: string | false, maxResults: number, token: CancellationToken): Promise { + override $startFileSearch(includePattern: string, _includeFolder: UriComponents | null, excludePatternOrDisregardExcludes: string | false, maxResults: number, token: CancellationToken): Promise { mainThreadCalled = true; assert.strictEqual(includePattern, 'glob/**'); assert.deepStrictEqual(_includeFolder ? URI.from(_includeFolder).toJSON() : null, URI.file('/other/folder').toJSON()); @@ -641,7 +640,7 @@ suite('ExtHostWorkspace', function () { let mainThreadCalled = false; rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock() { - $startFileSearch(includePattern: string, _includeFolder: UriComponents | null, excludePatternOrDisregardExcludes: string | false, maxResults: number, token: CancellationToken): Promise { + override $startFileSearch(includePattern: string, _includeFolder: UriComponents | null, excludePatternOrDisregardExcludes: string | false, maxResults: number, token: CancellationToken): Promise { mainThreadCalled = true; assert.strictEqual(includePattern, 'glob/**'); assert.deepStrictEqual(_includeFolder, URI.file('/other/folder').toJSON()); @@ -662,7 +661,7 @@ suite('ExtHostWorkspace', function () { let mainThreadCalled = false; rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock() { - $startFileSearch(includePattern: string, _includeFolder: UriComponents | null, excludePatternOrDisregardExcludes: string | false, maxResults: number, token: CancellationToken): Promise { + override $startFileSearch(includePattern: string, _includeFolder: UriComponents | null, excludePatternOrDisregardExcludes: string | false, maxResults: number, token: CancellationToken): Promise { mainThreadCalled = true; return Promise.resolve(null); } @@ -682,7 +681,7 @@ suite('ExtHostWorkspace', function () { let mainThreadCalled = false; rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock() { - $startFileSearch(includePattern: string, _includeFolder: UriComponents | null, excludePatternOrDisregardExcludes: string | false, maxResults: number, token: CancellationToken): Promise { + override $startFileSearch(includePattern: string, _includeFolder: UriComponents | null, excludePatternOrDisregardExcludes: string | false, maxResults: number, token: CancellationToken): Promise { mainThreadCalled = true; assert(excludePatternOrDisregardExcludes, 'glob/**'); // Note that the base portion is ignored, see #52651 return Promise.resolve(null); @@ -701,7 +700,7 @@ suite('ExtHostWorkspace', function () { let mainThreadCalled = false; rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock() { - async $startTextSearch(query: IPatternInfo, folder: UriComponents | null, options: ITextQueryBuilderOptions, requestId: number, token: CancellationToken): Promise { + override async $startTextSearch(query: IPatternInfo, folder: UriComponents | null, options: ITextQueryBuilderOptions, requestId: number, token: CancellationToken): Promise { mainThreadCalled = true; assert.strictEqual(query.pattern, 'foo'); assert.strictEqual(folder, null); @@ -722,7 +721,7 @@ suite('ExtHostWorkspace', function () { let mainThreadCalled = false; rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock() { - async $startTextSearch(query: IPatternInfo, folder: UriComponents | null, options: ITextQueryBuilderOptions, requestId: number, token: CancellationToken): Promise { + override async $startTextSearch(query: IPatternInfo, folder: UriComponents | null, options: ITextQueryBuilderOptions, requestId: number, token: CancellationToken): Promise { mainThreadCalled = true; assert.strictEqual(query.pattern, 'foo'); assert.strictEqual(folder, null); @@ -743,7 +742,7 @@ suite('ExtHostWorkspace', function () { let mainThreadCalled = false; rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock() { - async $startTextSearch(query: IPatternInfo, folder: UriComponents | null, options: ITextQueryBuilderOptions, requestId: number, token: CancellationToken): Promise { + override async $startTextSearch(query: IPatternInfo, folder: UriComponents | null, options: ITextQueryBuilderOptions, requestId: number, token: CancellationToken): Promise { mainThreadCalled = true; assert.strictEqual(query.pattern, 'foo'); assert.deepStrictEqual(folder, URI.file('/other/folder').toJSON()); @@ -764,7 +763,7 @@ suite('ExtHostWorkspace', function () { let mainThreadCalled = false; rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock() { - async $startTextSearch(query: IPatternInfo, folder: UriComponents | null, options: ITextQueryBuilderOptions, requestId: number, token: CancellationToken): Promise { + override async $startTextSearch(query: IPatternInfo, folder: UriComponents | null, options: ITextQueryBuilderOptions, requestId: number, token: CancellationToken): Promise { mainThreadCalled = true; return null; } @@ -782,7 +781,7 @@ suite('ExtHostWorkspace', function () { let mainThreadCalled = false; rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock() { - async $startTextSearch(query: IPatternInfo, folder: UriComponents | null, options: ITextQueryBuilderOptions, requestId: number, token: CancellationToken): Promise { + override async $startTextSearch(query: IPatternInfo, folder: UriComponents | null, options: ITextQueryBuilderOptions, requestId: number, token: CancellationToken): Promise { mainThreadCalled = true; assert.strictEqual(query.pattern, 'foo'); assert.deepStrictEqual(folder, null); diff --git a/src/vs/workbench/test/browser/api/mainThreadCommands.test.ts b/src/vs/workbench/test/browser/api/mainThreadCommands.test.ts index 65dc8e42..3b2f85bd 100644 --- a/src/vs/workbench/test/browser/api/mainThreadCommands.test.ts +++ b/src/vs/workbench/test/browser/api/mainThreadCommands.test.ts @@ -51,13 +51,13 @@ suite('MainThreadCommands', function () { const commands = new MainThreadCommands( SingleProxyRPCProtocol(null), new class extends mock() { - executeCommand(id: string): Promise { + override executeCommand(id: string): Promise { runs.push(id); return Promise.resolve(undefined); } }, new class extends mock() { - activateByEvent(id: string) { + override activateByEvent(id: string) { activations.push(id); return Promise.resolve(); } diff --git a/src/vs/workbench/test/browser/api/mainThreadDiagnostics.test.ts b/src/vs/workbench/test/browser/api/mainThreadDiagnostics.test.ts index 6f28fcce..13d65109 100644 --- a/src/vs/workbench/test/browser/api/mainThreadDiagnostics.test.ts +++ b/src/vs/workbench/test/browser/api/mainThreadDiagnostics.test.ts @@ -38,7 +38,7 @@ suite('MainThreadDiagnostics', function () { }, markerService, new class extends mock() { - asCanonicalUri(uri: URI) { return uri; } + override asCanonicalUri(uri: URI) { return uri; } } ); diff --git a/src/vs/workbench/test/browser/api/mainThreadDocumentContentProviders.test.ts b/src/vs/workbench/test/browser/api/mainThreadDocumentContentProviders.test.ts index 44e7a8a7..526e1769 100644 --- a/src/vs/workbench/test/browser/api/mainThreadDocumentContentProviders.test.ts +++ b/src/vs/workbench/test/browser/api/mainThreadDocumentContentProviders.test.ts @@ -22,13 +22,13 @@ suite('MainThreadDocumentContentProviders', function () { let providers = new MainThreadDocumentContentProviders(new TestRPCProtocol(), null!, null!, new class extends mock() { - getModel(_uri: URI) { + override getModel(_uri: URI) { assert.strictEqual(uri.toString(), _uri.toString()); return model; } }, new class extends mock() { - computeMoreMinimalEdits(_uri: URI, data: TextEdit[] | undefined) { + override computeMoreMinimalEdits(_uri: URI, data: TextEdit[] | undefined) { assert.strictEqual(model.getValue(), '1'); return Promise.resolve(data); } diff --git a/src/vs/workbench/test/browser/api/mainThreadDocumentsAndEditors.test.ts b/src/vs/workbench/test/browser/api/mainThreadDocumentsAndEditors.test.ts index c282692f..35ce024f 100644 --- a/src/vs/workbench/test/browser/api/mainThreadDocumentsAndEditors.test.ts +++ b/src/vs/workbench/test/browser/api/mainThreadDocumentsAndEditors.test.ts @@ -57,8 +57,8 @@ suite('MainThreadDocumentsAndEditors', () => { modelService = new ModelServiceImpl(configService, new TestTextResourcePropertiesService(configService), new TestThemeService(), new NullLogService(), undoRedoService); codeEditorService = new TestCodeEditorService(); textFileService = new class extends mock() { - isDirty() { return false; } - files = { + override isDirty() { return false; } + override files = { onDidSave: Event.None, onDidRevert: Event.None, onDidChangeDirty: Event.None @@ -68,14 +68,14 @@ suite('MainThreadDocumentsAndEditors', () => { const editorGroupService = new TestEditorGroupsService(); const fileService = new class extends mock() { - onDidRunOperation = Event.None; - onDidChangeFileSystemProviderCapabilities = Event.None; - onDidChangeFileSystemProviderRegistrations = Event.None; + override onDidRunOperation = Event.None; + override onDidChangeFileSystemProviderCapabilities = Event.None; + override onDidChangeFileSystemProviderRegistrations = Event.None; }; new MainThreadDocumentsAndEditors( SingleProxyRPCProtocol(new class extends mock() { - $acceptDocumentsAndEditorsDelta(delta: IDocumentsAndEditorsDelta) { deltas.push(delta); } + override $acceptDocumentsAndEditorsDelta(delta: IDocumentsAndEditorsDelta) { deltas.push(delta); } }), modelService, textFileService, @@ -86,10 +86,9 @@ suite('MainThreadDocumentsAndEditors', () => { editorGroupService, null!, new class extends mock() implements IPanelService { - declare readonly _serviceBrand: undefined; - onDidPanelOpen = Event.None; - onDidPanelClose = Event.None; - getActivePanel() { + override onDidPanelOpen = Event.None; + override onDidPanelClose = Event.None; + override getActivePanel() { return undefined; } }, @@ -97,7 +96,7 @@ suite('MainThreadDocumentsAndEditors', () => { new TestWorkingCopyFileService(), new UriIdentityService(fileService), new class extends mock() { - readText() { + override readText() { return Promise.resolve('clipboard_contents'); } }, diff --git a/src/vs/workbench/test/browser/api/mainThreadEditors.test.ts b/src/vs/workbench/test/browser/api/mainThreadEditors.test.ts index bd8224ef..d11b803d 100644 --- a/src/vs/workbench/test/browser/api/mainThreadEditors.test.ts +++ b/src/vs/workbench/test/browser/api/mainThreadEditors.test.ts @@ -51,7 +51,6 @@ import { TestTextResourcePropertiesService, TestContextService } from 'vs/workbe import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; import { extUri } from 'vs/base/common/resources'; import { ITextSnapshot } from 'vs/editor/common/model'; -import { VSBuffer, VSBufferReadable } from 'vs/base/common/buffer'; import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; @@ -100,44 +99,44 @@ suite('MainThreadEditors', () => { services.set(ILifecycleService, new TestLifecycleService()); services.set(IEditorGroupsService, new TestEditorGroupsService()); services.set(ITextFileService, new class extends mock() { - isDirty() { return false; } - files = { + override isDirty() { return false; } + override files = { onDidSave: Event.None, onDidRevert: Event.None, onDidChangeDirty: Event.None }; - create(operations: { resource: URI }[]) { + override create(operations: { resource: URI }[]) { for (const o of operations) { createdResources.add(o.resource); } return Promise.resolve(Object.create(null)); } - async getEncodedReadable(resource: URI, value?: string | ITextSnapshot): Promise { + override async getEncodedReadable(resource: URI, value?: string | ITextSnapshot): Promise { return undefined; } }); services.set(IWorkingCopyFileService, new class extends mock() { - onDidRunWorkingCopyFileOperation = Event.None; - createFolder(operations: ICreateOperation[]): any { + override onDidRunWorkingCopyFileOperation = Event.None; + override createFolder(operations: ICreateOperation[]): any { this.create(operations); } - create(operations: ICreateFileOperation[]) { + override create(operations: ICreateFileOperation[]) { for (const operation of operations) { createdResources.add(operation.resource); } return Promise.resolve(Object.create(null)); } - move(operations: IMoveOperation[]) { + override move(operations: IMoveOperation[]) { const { source, target } = operations[0].file; movedResources.set(source, target); return Promise.resolve(Object.create(null)); } - copy(operations: ICopyOperation[]) { + override copy(operations: ICopyOperation[]) { const { source, target } = operations[0].file; copiedResources.set(source, target); return Promise.resolve(Object.create(null)); } - delete(operations: IDeleteOperation[]) { + override delete(operations: IDeleteOperation[]) { for (const operation of operations) { deletedResources.add(operation.resource); } @@ -145,9 +144,9 @@ suite('MainThreadEditors', () => { } }); services.set(ITextModelService, new class extends mock() { - createModelReference(resource: URI): Promise> { + override createModelReference(resource: URI): Promise> { const textEditorModel = new class extends mock() { - textEditorModel = modelService.getModel(resource)!; + override textEditorModel = modelService.getModel(resource)!; }; textEditorModel.isReadonly = () => false; return Promise.resolve(new ImmortalReference(textEditorModel)); @@ -157,26 +156,25 @@ suite('MainThreadEditors', () => { }); services.set(IPanelService, new class extends mock() implements IPanelService { - declare readonly _serviceBrand: undefined; - onDidPanelOpen = Event.None; - onDidPanelClose = Event.None; - getActivePanel() { + override onDidPanelOpen = Event.None; + override onDidPanelClose = Event.None; + override getActivePanel() { return undefined; } }); services.set(IUriIdentityService, new class extends mock() { - get extUri() { return extUri; } + override get extUri() { return extUri; } }); const instaService = new InstantiationService(services); const rpcProtocol = new TestRPCProtocol(); rpcProtocol.set(ExtHostContext.ExtHostDocuments, new class extends mock() { - $acceptModelChanged(): void { + override $acceptModelChanged(): void { } }); rpcProtocol.set(ExtHostContext.ExtHostDocumentsAndEditors, new class extends mock() { - $acceptDocumentsAndEditorsDelta(): void { + override $acceptDocumentsAndEditorsDelta(): void { } }); diff --git a/src/vs/workbench/test/browser/api/mainThreadTreeViews.test.ts b/src/vs/workbench/test/browser/api/mainThreadTreeViews.test.ts index 6b8365ec..502e756e 100644 --- a/src/vs/workbench/test/browser/api/mainThreadTreeViews.test.ts +++ b/src/vs/workbench/test/browser/api/mainThreadTreeViews.test.ts @@ -29,15 +29,15 @@ suite('MainThreadHostTreeView', function () { } class MockExtHostTreeViewsShape extends mock() { - async $getChildren(treeViewId: string, treeItemHandle?: string): Promise { + override async $getChildren(treeViewId: string, treeItemHandle?: string): Promise { return [{ handle: 'testItem1', collapsibleState: TreeItemCollapsibleState.Expanded, customProp: customValue }]; } - async $hasResolve(): Promise { + override async $hasResolve(): Promise { return false; } - $setVisible(): void { } + override $setVisible(): void { } } let container: ViewContainer; diff --git a/src/vs/workbench/test/browser/codeeditor.test.ts b/src/vs/workbench/test/browser/codeeditor.test.ts index 736e0957..35100e22 100644 --- a/src/vs/workbench/test/browser/codeeditor.test.ts +++ b/src/vs/workbench/test/browser/codeeditor.test.ts @@ -55,52 +55,52 @@ suite('Editor - Range decorations', () => { }); test('highlight range for the resource if it is an active editor', function () { - let range: IRange = { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 }; + const range: IRange = new Range(1, 1, 1, 1); testObject.highlightRange({ resource: model.uri, range }); - let actuals = rangeHighlightDecorations(model); + const actuals = rangeHighlightDecorations(model); - assert.deepEqual([range], actuals); + assert.deepStrictEqual(actuals, [range]); }); test('remove highlight range', function () { testObject.highlightRange({ resource: model.uri, range: { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 } }); testObject.removeHighlightRange(); - let actuals = rangeHighlightDecorations(model); + const actuals = rangeHighlightDecorations(model); - assert.deepEqual([], actuals); + assert.deepStrictEqual(actuals, []); }); test('highlight range for the resource removes previous highlight', function () { testObject.highlightRange({ resource: model.uri, range: { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 } }); - let range: IRange = { startLineNumber: 2, startColumn: 2, endLineNumber: 4, endColumn: 3 }; + const range: IRange = new Range(2, 2, 4, 3); testObject.highlightRange({ resource: model.uri, range }); - let actuals = rangeHighlightDecorations(model); + const actuals = rangeHighlightDecorations(model); - assert.deepEqual([range], actuals); + assert.deepStrictEqual(actuals, [range]); }); test('highlight range for a new resource removes highlight of previous resource', function () { testObject.highlightRange({ resource: model.uri, range: { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 } }); - let anotherModel = prepareActiveEditor('anotherModel'); - let range: IRange = { startLineNumber: 2, startColumn: 2, endLineNumber: 4, endColumn: 3 }; + const anotherModel = prepareActiveEditor('anotherModel'); + const range: IRange = new Range(2, 2, 4, 3); testObject.highlightRange({ resource: anotherModel.uri, range }); let actuals = rangeHighlightDecorations(model); - assert.deepEqual([], actuals); + assert.deepStrictEqual(actuals, []); actuals = rangeHighlightDecorations(anotherModel); - assert.deepEqual([range], actuals); + assert.deepStrictEqual(actuals, [range]); }); test('highlight is removed on model change', function () { testObject.highlightRange({ resource: model.uri, range: { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 } }); prepareActiveEditor('anotherModel'); - let actuals = rangeHighlightDecorations(model); - assert.deepEqual([], actuals); + const actuals = rangeHighlightDecorations(model); + assert.deepStrictEqual(actuals, []); }); test('highlight is removed on cursor position change', function () { @@ -109,27 +109,27 @@ suite('Editor - Range decorations', () => { position: new Position(2, 1) }); - let actuals = rangeHighlightDecorations(model); - assert.deepEqual([], actuals); + const actuals = rangeHighlightDecorations(model); + assert.deepStrictEqual(actuals, []); }); test('range is not highlight if not active editor', function () { - let model = aModel(URI.file('some model')); + const model = aModel(URI.file('some model')); testObject.highlightRange({ resource: model.uri, range: { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 } }); - let actuals = rangeHighlightDecorations(model); - assert.deepEqual([], actuals); + const actuals = rangeHighlightDecorations(model); + assert.deepStrictEqual(actuals, []); }); test('previous highlight is not removed if not active editor', function () { - let range: IRange = { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 }; + const range = new Range(1, 1, 1, 1); testObject.highlightRange({ resource: model.uri, range }); - let model1 = aModel(URI.file('some model')); + const model1 = aModel(URI.file('some model')); testObject.highlightRange({ resource: model1.uri, range: { startLineNumber: 2, startColumn: 1, endLineNumber: 2, endColumn: 1 } }); - let actuals = rangeHighlightDecorations(model); - assert.deepEqual([range], actuals); + const actuals = rangeHighlightDecorations(model); + assert.deepStrictEqual(actuals, [range]); }); function prepareActiveEditor(resource: string): TextModel { diff --git a/src/vs/workbench/test/browser/part.test.ts b/src/vs/workbench/test/browser/part.test.ts index 016bc8a6..60925fea 100644 --- a/src/vs/workbench/test/browser/part.test.ts +++ b/src/vs/workbench/test/browser/part.test.ts @@ -21,7 +21,7 @@ suite('Workbench parts', () => { minimumHeight: number = 50; maximumHeight: number = 50; - layout(width: number, height: number): void { + override layout(width: number, height: number): void { throw new Error('Method not implemented.'); } @@ -36,21 +36,21 @@ suite('Workbench parts', () => { super('myPart', { hasTitle: true }, new TestThemeService(), new TestStorageService(), new TestLayoutService()); } - createTitleArea(parent: HTMLElement): HTMLElement { + override createTitleArea(parent: HTMLElement): HTMLElement { assert.strictEqual(parent, this.expectedParent); return super.createTitleArea(parent)!; } - createContentArea(parent: HTMLElement): HTMLElement { + override createContentArea(parent: HTMLElement): HTMLElement { assert.strictEqual(parent, this.expectedParent); return super.createContentArea(parent)!; } - getMemento(scope: StorageScope, target: StorageTarget) { + override getMemento(scope: StorageScope, target: StorageTarget) { return super.getMemento(scope, target); } - saveState(): void { + override saveState(): void { return super.saveState(); } } @@ -61,7 +61,7 @@ suite('Workbench parts', () => { super('myPart2', { hasTitle: true }, new TestThemeService(), new TestStorageService(), new TestLayoutService()); } - createTitleArea(parent: HTMLElement): HTMLElement { + override createTitleArea(parent: HTMLElement): HTMLElement { const titleContainer = append(parent, $('div')); const titleLabel = append(titleContainer, $('span')); titleLabel.id = 'myPart.title'; @@ -70,7 +70,7 @@ suite('Workbench parts', () => { return titleContainer; } - createContentArea(parent: HTMLElement): HTMLElement { + override createContentArea(parent: HTMLElement): HTMLElement { const contentContainer = append(parent, $('div')); const contentSpan = append(contentContainer, $('span')); contentSpan.id = 'myPart.content'; @@ -86,11 +86,11 @@ suite('Workbench parts', () => { super('myPart2', { hasTitle: false }, new TestThemeService(), new TestStorageService(), new TestLayoutService()); } - createTitleArea(parent: HTMLElement): HTMLElement { + override createTitleArea(parent: HTMLElement): HTMLElement { return null!; } - createContentArea(parent: HTMLElement): HTMLElement { + override createContentArea(parent: HTMLElement): HTMLElement { const contentContainer = append(parent, $('div')); const contentSpan = append(contentContainer, $('span')); contentSpan.id = 'myPart.content'; diff --git a/src/vs/workbench/test/browser/parts/editor/breadcrumbModel.test.ts b/src/vs/workbench/test/browser/parts/editor/breadcrumbModel.test.ts index cd016718..8e20b9b7 100644 --- a/src/vs/workbench/test/browser/parts/editor/breadcrumbModel.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/breadcrumbModel.test.ts @@ -18,7 +18,7 @@ suite('Breadcrumb Model', function () { const workspaceService = new TestContextService(new Workspace('ffff', [new WorkspaceFolder({ uri: URI.parse('foo:/bar/baz/ws'), name: 'ws', index: 0 })])); const configService = new class extends TestConfigurationService { - getValue(...args: any[]) { + override getValue(...args: any[]) { if (args[0] === 'breadcrumbs.filePath') { return 'on'; } @@ -27,7 +27,7 @@ suite('Breadcrumb Model', function () { } return super.getValue(...args); } - updateValue() { + override updateValue() { return Promise.resolve(); } }; @@ -37,14 +37,14 @@ suite('Breadcrumb Model', function () { let model = new BreadcrumbsModel(URI.parse('foo:/bar/baz/ws/some/path/file.ts'), undefined, configService, workspaceService, new class extends mock() { }); let elements = model.getElements(); - assert.equal(elements.length, 3); + assert.strictEqual(elements.length, 3); let [one, two, three] = elements as FileElement[]; - assert.equal(one.kind, FileKind.FOLDER); - assert.equal(two.kind, FileKind.FOLDER); - assert.equal(three.kind, FileKind.FILE); - assert.equal(one.uri.toString(), 'foo:/bar/baz/ws/some'); - assert.equal(two.uri.toString(), 'foo:/bar/baz/ws/some/path'); - assert.equal(three.uri.toString(), 'foo:/bar/baz/ws/some/path/file.ts'); + assert.strictEqual(one.kind, FileKind.FOLDER); + assert.strictEqual(two.kind, FileKind.FOLDER); + assert.strictEqual(three.kind, FileKind.FILE); + assert.strictEqual(one.uri.toString(), 'foo:/bar/baz/ws/some'); + assert.strictEqual(two.uri.toString(), 'foo:/bar/baz/ws/some/path'); + assert.strictEqual(three.uri.toString(), 'foo:/bar/baz/ws/some/path/file.ts'); }); test('display uri matters for FileElement', function () { @@ -52,14 +52,14 @@ suite('Breadcrumb Model', function () { let model = new BreadcrumbsModel(URI.parse('foo:/bar/baz/ws/some/PATH/file.ts'), undefined, configService, workspaceService, new class extends mock() { }); let elements = model.getElements(); - assert.equal(elements.length, 3); + assert.strictEqual(elements.length, 3); let [one, two, three] = elements as FileElement[]; - assert.equal(one.kind, FileKind.FOLDER); - assert.equal(two.kind, FileKind.FOLDER); - assert.equal(three.kind, FileKind.FILE); - assert.equal(one.uri.toString(), 'foo:/bar/baz/ws/some'); - assert.equal(two.uri.toString(), 'foo:/bar/baz/ws/some/PATH'); - assert.equal(three.uri.toString(), 'foo:/bar/baz/ws/some/PATH/file.ts'); + assert.strictEqual(one.kind, FileKind.FOLDER); + assert.strictEqual(two.kind, FileKind.FOLDER); + assert.strictEqual(three.kind, FileKind.FILE); + assert.strictEqual(one.uri.toString(), 'foo:/bar/baz/ws/some'); + assert.strictEqual(two.uri.toString(), 'foo:/bar/baz/ws/some/PATH'); + assert.strictEqual(three.uri.toString(), 'foo:/bar/baz/ws/some/PATH/file.ts'); }); test('only uri, outside workspace', function () { @@ -67,11 +67,11 @@ suite('Breadcrumb Model', function () { let model = new BreadcrumbsModel(URI.parse('foo:/outside/file.ts'), undefined, configService, workspaceService, new class extends mock() { }); let elements = model.getElements(); - assert.equal(elements.length, 2); + assert.strictEqual(elements.length, 2); let [one, two] = elements as FileElement[]; - assert.equal(one.kind, FileKind.FOLDER); - assert.equal(two.kind, FileKind.FILE); - assert.equal(one.uri.toString(), 'foo:/outside'); - assert.equal(two.uri.toString(), 'foo:/outside/file.ts'); + assert.strictEqual(one.kind, FileKind.FOLDER); + assert.strictEqual(two.kind, FileKind.FILE); + assert.strictEqual(one.uri.toString(), 'foo:/outside'); + assert.strictEqual(two.uri.toString(), 'foo:/outside/file.ts'); }); }); diff --git a/src/vs/workbench/test/browser/parts/editor/editor.test.ts b/src/vs/workbench/test/browser/parts/editor/editor.test.ts index ef3c0a4e..f8ddebe1 100644 --- a/src/vs/workbench/test/browser/parts/editor/editor.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/editor.test.ts @@ -8,9 +8,16 @@ import { EditorResourceAccessor, SideBySideEditor, IEditorInputWithPreferredReso import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { URI } from 'vs/base/common/uri'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { workbenchInstantiationService, TestServiceAccessor, TestEditorInput } from 'vs/workbench/test/browser/workbenchTestServices'; +import { workbenchInstantiationService, TestServiceAccessor, TestEditorInput, registerTestDiffEditor, registerTestEditor, registerTestFileEditor, registerTestResourceEditor, TestFileEditorInput, createEditorPart } from 'vs/workbench/test/browser/workbenchTestServices'; import { Schemas } from 'vs/base/common/network'; import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { toResource } from 'vs/base/test/common/utils'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { whenEditorClosed } from 'vs/workbench/browser/editor'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { EditorService } from 'vs/workbench/services/editor/browser/editorService'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; suite('Workbench editor', () => { @@ -21,16 +28,40 @@ suite('Workbench editor', () => { } } + const disposables = new DisposableStore(); + + const TEST_EDITOR_ID = 'MyTestEditorForEditors'; + let instantiationService: IInstantiationService; let accessor: TestServiceAccessor; + async function createServices(): Promise { + const instantiationService = workbenchInstantiationService(); + + const part = await createEditorPart(instantiationService, disposables); + + instantiationService.stub(IEditorGroupsService, part); + + const editorService = instantiationService.createInstance(EditorService); + instantiationService.stub(IEditorService, editorService); + + return instantiationService.createInstance(TestServiceAccessor); + } + setup(() => { instantiationService = workbenchInstantiationService(); accessor = instantiationService.createInstance(TestServiceAccessor); + + disposables.add(registerTestFileEditor()); + disposables.add(registerTestDiffEditor()); + disposables.add(registerTestResourceEditor()); + disposables.add(registerTestEditor(TEST_EDITOR_ID, [new SyncDescriptor(TestFileEditorInput)])); }); teardown(() => { accessor.untitledTextEditorService.dispose(); + + disposables.clear(); }); test('EditorResourceAccessor', () => { @@ -123,4 +154,48 @@ suite('Workbench editor', () => { assert.strictEqual(EditorResourceAccessor.getCanonicalUri(fileWithPreferredResource)?.toString(), resource.toString()); assert.strictEqual(EditorResourceAccessor.getOriginalUri(fileWithPreferredResource)?.toString(), preferredResource.toString()); }); + + test('whenEditorClosed (single editor)', async function () { + return testWhenEditorClosed(false, false, toResource.call(this, '/path/index.txt')); + }); + + test('whenEditorClosed (multiple editor)', async function () { + return testWhenEditorClosed(false, false, toResource.call(this, '/path/index.txt'), toResource.call(this, '/test.html')); + }); + + test('whenEditorClosed (single editor, diff editor)', async function () { + return testWhenEditorClosed(true, false, toResource.call(this, '/path/index.txt')); + }); + + test('whenEditorClosed (multiple editor, diff editor)', async function () { + return testWhenEditorClosed(true, false, toResource.call(this, '/path/index.txt'), toResource.call(this, '/test.html')); + }); + + test('whenEditorClosed (single custom editor)', async function () { + return testWhenEditorClosed(false, true, toResource.call(this, '/path/index.txt')); + }); + + test('whenEditorClosed (multiple custom editor)', async function () { + return testWhenEditorClosed(false, true, toResource.call(this, '/path/index.txt'), toResource.call(this, '/test.html')); + }); + + async function testWhenEditorClosed(sideBySide: boolean, custom: boolean, ...resources: URI[]): Promise { + const accessor = await createServices(); + + for (const resource of resources) { + if (custom) { + await accessor.editorService.openEditor(new TestFileEditorInput(resource, 'testTypeId'), { pinned: true }); + } else if (sideBySide) { + await accessor.editorService.openEditor({ leftResource: resource, rightResource: resource, options: { pinned: true } }); + } else { + await accessor.editorService.openEditor({ resource, options: { pinned: true } }); + } + } + + const closedPromise = accessor.instantitionService.invokeFunction(accessor => whenEditorClosed(accessor, resources)); + + accessor.editorGroupService.activeGroup.closeAllEditors(); + + await closedPromise; + } }); diff --git a/src/vs/workbench/test/browser/parts/editor/editorGroups.test.ts b/src/vs/workbench/test/browser/parts/editor/editorGroupModel.test.ts similarity index 93% rename from src/vs/workbench/test/browser/parts/editor/editorGroups.test.ts rename to src/vs/workbench/test/browser/parts/editor/editorGroupModel.test.ts index 6f1c694c..db2aa54b 100644 --- a/src/vs/workbench/test/browser/parts/editor/editorGroups.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/editorGroupModel.test.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { EditorGroup, ISerializedEditorGroup, EditorCloseEvent } from 'vs/workbench/common/editor/editorGroup'; -import { Extensions as EditorExtensions, IEditorInputFactoryRegistry, EditorInput, IFileEditorInput, IEditorInputFactory, CloseDirection, EditorsOrder } from 'vs/workbench/common/editor'; +import { EditorGroupModel, ISerializedEditorGroupModel, EditorCloseEvent } from 'vs/workbench/common/editor/editorGroupModel'; +import { EditorExtensions, IEditorInputFactoryRegistry, EditorInput, IFileEditorInput, IEditorInputSerializer, CloseDirection, EditorsOrder } from 'vs/workbench/common/editor'; import { URI } from 'vs/base/common/uri'; import { TestLifecycleService, workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; @@ -23,7 +23,7 @@ import { IStorageService } from 'vs/platform/storage/common/storage'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { TestContextService, TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; -suite('Workbench editor groups', () => { +suite('Workbench editor group model', () => { function inst(): IInstantiationService { let inst = new TestInstantiationService(); @@ -39,17 +39,17 @@ suite('Workbench editor groups', () => { return inst; } - function createGroup(serialized?: ISerializedEditorGroup): EditorGroup { - return inst().createInstance(EditorGroup, serialized); + function createEditorGroupModel(serialized?: ISerializedEditorGroupModel): EditorGroupModel { + return inst().createInstance(EditorGroupModel, serialized); } - function closeAllEditors(group: EditorGroup): void { + function closeAllEditors(group: EditorGroupModel): void { for (const editor of group.getEditors(EditorsOrder.SEQUENTIAL)) { group.closeEditor(editor, false); } } - function closeEditors(group: EditorGroup, except: EditorInput, direction?: CloseDirection): void { + function closeEditors(group: EditorGroupModel, except: EditorInput, direction?: CloseDirection): void { const index = group.indexOf(except); if (index === -1) { return; // not found @@ -87,7 +87,7 @@ suite('Workbench editor groups', () => { disposed: EditorInput[]; } - function groupListener(group: EditorGroup): GroupEvents { + function groupListener(group: EditorGroupModel): GroupEvents { const groupEvents: GroupEvents = { opened: [], closed: [], @@ -106,7 +106,7 @@ suite('Workbench editor groups', () => { group.onDidChangeEditorPinned(e => group.isPinned(e) ? groupEvents.pinned.push(e) : groupEvents.unpinned.push(e)); group.onDidChangeEditorSticky(e => group.isSticky(e) ? groupEvents.sticky.push(e) : groupEvents.unsticky.push(e)); group.onDidMoveEditor(e => groupEvents.moved.push(e)); - group.onDidDisposeEditor(e => groupEvents.disposed.push(e)); + group.onWillDisposeEditor(e => groupEvents.disposed.push(e)); return groupEvents; } @@ -119,10 +119,10 @@ suite('Workbench editor groups', () => { constructor(public id: string) { super(); } - getTypeId() { return 'testEditorInputForGroups'; } - async resolve(): Promise { return null!; } + override get typeId() { return 'testEditorInputForGroups'; } + override async resolve(): Promise { return null!; } - matches(other: TestEditorInput): boolean { + override matches(other: TestEditorInput): boolean { return other && this.id === other.id && other instanceof TestEditorInput; } @@ -142,10 +142,10 @@ suite('Workbench editor groups', () => { constructor(public id: string) { super(); } - getTypeId() { return 'testEditorInputForGroups-nonSerializable'; } - async resolve(): Promise { return null; } + override get typeId() { return 'testEditorInputForGroups-nonSerializable'; } + override async resolve(): Promise { return null; } - matches(other: NonSerializableTestEditorInput): boolean { + override matches(other: NonSerializableTestEditorInput): boolean { return other && this.id === other.id && other instanceof NonSerializableTestEditorInput; } } @@ -157,12 +157,12 @@ suite('Workbench editor groups', () => { constructor(public id: string, public resource: URI) { super(); } - getTypeId() { return 'testFileEditorInputForGroups'; } - async resolve(): Promise { return null; } + override get typeId() { return 'testFileEditorInputForGroups'; } + override async resolve(): Promise { return null; } setPreferredName(name: string): void { } setPreferredDescription(description: string): void { } setPreferredResource(resource: URI): void { } - setEncoding(encoding: string) { } + async setEncoding(encoding: string) { } getEncoding() { return undefined; } setPreferredEncoding(encoding: string) { } setForceOpenAsBinary(): void { } @@ -170,7 +170,7 @@ suite('Workbench editor groups', () => { setPreferredMode(mode: string) { } isResolved(): boolean { return false; } - matches(other: TestFileEditorInput): boolean { + override matches(other: TestFileEditorInput): boolean { return other && this.id === other.id && other instanceof TestFileEditorInput; } } @@ -187,7 +187,7 @@ suite('Workbench editor groups', () => { id: string; } - class TestEditorInputFactory implements IEditorInputFactory { + class TestEditorInputSerializer implements IEditorInputSerializer { static disableSerialize = false; static disableDeserialize = false; @@ -197,7 +197,7 @@ suite('Workbench editor groups', () => { } serialize(editorInput: EditorInput): string | undefined { - if (TestEditorInputFactory.disableSerialize) { + if (TestEditorInputSerializer.disableSerialize) { return undefined; } @@ -210,7 +210,7 @@ suite('Workbench editor groups', () => { } deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): EditorInput | undefined { - if (TestEditorInputFactory.disableDeserialize) { + if (TestEditorInputSerializer.disableDeserialize) { return undefined; } @@ -223,10 +223,10 @@ suite('Workbench editor groups', () => { const disposables = new DisposableStore(); setup(() => { - TestEditorInputFactory.disableSerialize = false; - TestEditorInputFactory.disableDeserialize = false; + TestEditorInputSerializer.disableSerialize = false; + TestEditorInputSerializer.disableDeserialize = false; - disposables.add(Registry.as(EditorExtensions.EditorInputFactories).registerEditorInputFactory('testEditorInputForGroups', TestEditorInputFactory)); + disposables.add(Registry.as(EditorExtensions.EditorInputFactories).registerEditorInputSerializer('testEditorInputForGroups', TestEditorInputSerializer)); }); teardown(() => { @@ -236,7 +236,7 @@ suite('Workbench editor groups', () => { }); test('Clone Group', function () { - const group = createGroup(); + const group = createEditorGroupModel(); const input1 = input() as TestEditorInput; const input2 = input(); @@ -276,7 +276,7 @@ suite('Workbench editor groups', () => { }); test('contains()', function () { - const group = createGroup(); + const group = createEditorGroupModel(); const instantiationService = workbenchInstantiationService(); const input1 = input(); @@ -367,7 +367,7 @@ suite('Workbench editor groups', () => { test('group serialization', function () { inst().invokeFunction(accessor => Registry.as(EditorExtensions.EditorInputFactories).start(accessor)); - const group = createGroup(); + const group = createEditorGroupModel(); const input1 = input(); const input2 = input(); @@ -379,7 +379,7 @@ suite('Workbench editor groups', () => { group.openEditor(input2, { pinned: true, active: true }); group.openEditor(input3, { pinned: false, active: true }); - let deserialized = createGroup(group.serialize()); + let deserialized = createEditorGroupModel(group.serialize()); assert.strictEqual(group.id, deserialized.id); assert.strictEqual(deserialized.count, 3); assert.strictEqual(deserialized.getEditors(EditorsOrder.SEQUENTIAL).length, 3); @@ -390,19 +390,19 @@ suite('Workbench editor groups', () => { assert.strictEqual(deserialized.isActive(input3), true); // Case 2: inputs cannot be serialized - TestEditorInputFactory.disableSerialize = true; + TestEditorInputSerializer.disableSerialize = true; - deserialized = createGroup(group.serialize()); + deserialized = createEditorGroupModel(group.serialize()); assert.strictEqual(group.id, deserialized.id); assert.strictEqual(deserialized.count, 0); assert.strictEqual(deserialized.getEditors(EditorsOrder.SEQUENTIAL).length, 0); assert.strictEqual(deserialized.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE).length, 0); // Case 3: inputs cannot be deserialized - TestEditorInputFactory.disableSerialize = false; - TestEditorInputFactory.disableDeserialize = true; + TestEditorInputSerializer.disableSerialize = false; + TestEditorInputSerializer.disableDeserialize = true; - deserialized = createGroup(group.serialize()); + deserialized = createEditorGroupModel(group.serialize()); assert.strictEqual(group.id, deserialized.id); assert.strictEqual(deserialized.count, 0); assert.strictEqual(deserialized.getEditors(EditorsOrder.SEQUENTIAL).length, 0); @@ -411,7 +411,7 @@ suite('Workbench editor groups', () => { test('group serialization (sticky editor)', function () { inst().invokeFunction(accessor => Registry.as(EditorExtensions.EditorInputFactories).start(accessor)); - const group = createGroup(); + const group = createEditorGroupModel(); const input1 = input(); const input2 = input(); @@ -426,7 +426,7 @@ suite('Workbench editor groups', () => { group.stick(input2); assert.ok(group.isSticky(input2)); - let deserialized = createGroup(group.serialize()); + let deserialized = createEditorGroupModel(group.serialize()); assert.strictEqual(group.id, deserialized.id); assert.strictEqual(deserialized.count, 3); @@ -443,9 +443,9 @@ suite('Workbench editor groups', () => { assert.strictEqual(deserialized.isSticky(input3), false); // Case 2: inputs cannot be serialized - TestEditorInputFactory.disableSerialize = true; + TestEditorInputSerializer.disableSerialize = true; - deserialized = createGroup(group.serialize()); + deserialized = createEditorGroupModel(group.serialize()); assert.strictEqual(group.id, deserialized.id); assert.strictEqual(deserialized.count, 0); assert.strictEqual(deserialized.stickyCount, 0); @@ -453,10 +453,10 @@ suite('Workbench editor groups', () => { assert.strictEqual(deserialized.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE).length, 0); // Case 3: inputs cannot be deserialized - TestEditorInputFactory.disableSerialize = false; - TestEditorInputFactory.disableDeserialize = true; + TestEditorInputSerializer.disableSerialize = false; + TestEditorInputSerializer.disableDeserialize = true; - deserialized = createGroup(group.serialize()); + deserialized = createEditorGroupModel(group.serialize()); assert.strictEqual(group.id, deserialized.id); assert.strictEqual(deserialized.count, 0); assert.strictEqual(deserialized.stickyCount, 0); @@ -465,7 +465,7 @@ suite('Workbench editor groups', () => { }); test('One Editor', function () { - const group = createGroup(); + const group = createEditorGroupModel(); const events = groupListener(group); assert.strictEqual(group.count, 0); @@ -576,7 +576,7 @@ suite('Workbench editor groups', () => { }); test('Multiple Editors - Pinned and Active', function () { - const group = createGroup(); + const group = createEditorGroupModel(); const events = groupListener(group); const input1 = input('1'); @@ -650,7 +650,7 @@ suite('Workbench editor groups', () => { }); test('Multiple Editors - Preview editor moves to the side of the active one', function () { - const group = createGroup(); + const group = createEditorGroupModel(); const input1 = input(); const input2 = input(); @@ -679,7 +679,7 @@ suite('Workbench editor groups', () => { inst.stub(IConfigurationService, config); config.setUserConfiguration('workbench', { editor: { openPositioning: 'left' } }); - const group: EditorGroup = inst.createInstance(EditorGroup, undefined); + const group: EditorGroupModel = inst.createInstance(EditorGroupModel, undefined); const events = groupListener(group); @@ -703,7 +703,7 @@ suite('Workbench editor groups', () => { }); test('Multiple Editors - Pinned and Not Active', function () { - const group = createGroup(); + const group = createEditorGroupModel(); const input1 = input(); const input2 = input(); @@ -735,7 +735,7 @@ suite('Workbench editor groups', () => { }); test('Multiple Editors - Preview gets overwritten', function () { - const group = createGroup(); + const group = createEditorGroupModel(); const events = groupListener(group); const input1 = input(); @@ -768,7 +768,7 @@ suite('Workbench editor groups', () => { }); test('Multiple Editors - set active', function () { - const group = createGroup(); + const group = createEditorGroupModel(); const events = groupListener(group); const input1 = input(); @@ -803,7 +803,7 @@ suite('Workbench editor groups', () => { }); test('Multiple Editors - pin and unpin', function () { - const group = createGroup(); + const group = createEditorGroupModel(); const events = groupListener(group); const input1 = input(); @@ -852,7 +852,7 @@ suite('Workbench editor groups', () => { }); test('Multiple Editors - closing picks next from MRU list', function () { - const group = createGroup(); + const group = createEditorGroupModel(); const events = groupListener(group); const input1 = input(); @@ -911,7 +911,7 @@ suite('Workbench editor groups', () => { config.setUserConfiguration('workbench', { editor: { focusRecentEditorAfterClose: false } }); inst.stub(IConfigurationService, config); - const group = inst.createInstance(EditorGroup, undefined); + const group = inst.createInstance(EditorGroupModel, undefined); const events = groupListener(group); const input1 = input(); @@ -959,7 +959,7 @@ suite('Workbench editor groups', () => { }); test('Multiple Editors - move editor', function () { - const group = createGroup(); + const group = createEditorGroupModel(); const events = groupListener(group); const input1 = input(); @@ -1021,8 +1021,8 @@ suite('Workbench editor groups', () => { }); test('Multiple Editors - move editor across groups', function () { - const group1 = createGroup(); - const group2 = createGroup(); + const group1 = createEditorGroupModel(); + const group2 = createEditorGroupModel(); const g1_input1 = input(); const g1_input2 = input(); @@ -1043,8 +1043,8 @@ suite('Workbench editor groups', () => { }); test('Multiple Editors - move editor across groups (input already exists in group 1)', function () { - const group1 = createGroup(); - const group2 = createGroup(); + const group1 = createEditorGroupModel(); + const group2 = createEditorGroupModel(); const g1_input1 = input(); const g1_input2 = input(); @@ -1067,7 +1067,7 @@ suite('Workbench editor groups', () => { }); test('Multiple Editors - Pinned & Non Active', function () { - const group = createGroup(); + const group = createEditorGroupModel(); const input1 = input(); group.openEditor(input1); @@ -1098,7 +1098,7 @@ suite('Workbench editor groups', () => { }); test('Multiple Editors - Close Others, Close Left, Close Right', function () { - const group = createGroup(); + const group = createEditorGroupModel(); const input1 = input(); const input2 = input(); @@ -1153,7 +1153,7 @@ suite('Workbench editor groups', () => { }); test('Multiple Editors - real user example', function () { - const group = createGroup(); + const group = createEditorGroupModel(); // [] -> /index.html/ const indexHtml = input('index.html'); @@ -1287,7 +1287,7 @@ suite('Workbench editor groups', () => { inst.invokeFunction(accessor => Registry.as(EditorExtensions.EditorInputFactories).start(accessor)); - let group = createGroup(); + let group = createEditorGroupModel(); const input1 = input(); group.openEditor(input1); @@ -1298,7 +1298,7 @@ suite('Workbench editor groups', () => { assert.strictEqual(group.isActive(input1), true); // Create model again - should load from storage - group = inst.createInstance(EditorGroup, group.serialize()); + group = inst.createInstance(EditorGroupModel, group.serialize()); assert.strictEqual(group.count, 1); assert.strictEqual(group.activeEditor!.matches(input1), true); @@ -1321,7 +1321,7 @@ suite('Workbench editor groups', () => { inst.invokeFunction(accessor => Registry.as(EditorExtensions.EditorInputFactories).start(accessor)); - let group1 = createGroup(); + let group1 = createEditorGroupModel(); const g1_input1 = input(); const g1_input2 = input(); @@ -1331,7 +1331,7 @@ suite('Workbench editor groups', () => { group1.openEditor(g1_input2, { active: true, pinned: false }); group1.openEditor(g1_input3, { active: false, pinned: true }); - let group2 = createGroup(); + let group2 = createEditorGroupModel(); const g2_input1 = input(); const g2_input2 = input(); @@ -1357,8 +1357,8 @@ suite('Workbench editor groups', () => { assert.strictEqual(group2.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE)[2].matches(g2_input2), true); // Create model again - should load from storage - group1 = inst.createInstance(EditorGroup, group1.serialize()); - group2 = inst.createInstance(EditorGroup, group2.serialize()); + group1 = inst.createInstance(EditorGroupModel, group1.serialize()); + group2 = inst.createInstance(EditorGroupModel, group2.serialize()); assert.strictEqual(group1.count, 3); assert.strictEqual(group2.count, 3); @@ -1391,7 +1391,7 @@ suite('Workbench editor groups', () => { inst.invokeFunction(accessor => Registry.as(EditorExtensions.EditorInputFactories).start(accessor)); - let group = createGroup(); + let group = createEditorGroupModel(); const serializableInput1 = input(); const nonSerializableInput2 = input('3', true); @@ -1410,7 +1410,7 @@ suite('Workbench editor groups', () => { assert.strictEqual(group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE)[2].matches(serializableInput1), true); // Create model again - should load from storage - group = inst.createInstance(EditorGroup, group.serialize()); + group = inst.createInstance(EditorGroupModel, group.serialize()); assert.strictEqual(group.count, 2); assert.strictEqual(group.activeEditor!.matches(serializableInput2), true); @@ -1435,7 +1435,7 @@ suite('Workbench editor groups', () => { inst.invokeFunction(accessor => Registry.as(EditorExtensions.EditorInputFactories).start(accessor)); - let group = createGroup(); + let group = createEditorGroupModel(); const serializableInput1 = input(); const nonSerializableInput2 = input('3', true); @@ -1449,7 +1449,7 @@ suite('Workbench editor groups', () => { assert.strictEqual(group.stickyCount, 1); // Create model again - should load from storage - group = inst.createInstance(EditorGroup, group.serialize()); + group = inst.createInstance(EditorGroupModel, group.serialize()); assert.strictEqual(group.count, 2); assert.strictEqual(group.stickyCount, 0); @@ -1470,8 +1470,8 @@ suite('Workbench editor groups', () => { inst.invokeFunction(accessor => Registry.as(EditorExtensions.EditorInputFactories).start(accessor)); - let group1 = createGroup(); - let group2 = createGroup(); + let group1 = createEditorGroupModel(); + let group2 = createEditorGroupModel(); const serializableInput1 = input(); const serializableInput2 = input(); @@ -1483,8 +1483,8 @@ suite('Workbench editor groups', () => { group2.openEditor(nonSerializableInput); // Create model again - should load from storage - group1 = inst.createInstance(EditorGroup, group1.serialize()); - group2 = inst.createInstance(EditorGroup, group2.serialize()); + group1 = inst.createInstance(EditorGroupModel, group1.serialize()); + group2 = inst.createInstance(EditorGroupModel, group2.serialize()); assert.strictEqual(group1.count, 2); assert.strictEqual(group1.getEditors(EditorsOrder.SEQUENTIAL)[0].matches(serializableInput1), true); @@ -1492,8 +1492,8 @@ suite('Workbench editor groups', () => { }); test('Multiple Editors - Editor Dispose', function () { - const group1 = createGroup(); - const group2 = createGroup(); + const group1 = createEditorGroupModel(); + const group2 = createEditorGroupModel(); const group1Listener = groupListener(group1); const group2Listener = groupListener(group2); @@ -1523,7 +1523,7 @@ suite('Workbench editor groups', () => { }); test('Preview tab does not have a stable position (https://github.com/microsoft/vscode/issues/8245)', function () { - const group1 = createGroup(); + const group1 = createEditorGroupModel(); const input1 = input(); const input2 = input(); @@ -1538,8 +1538,8 @@ suite('Workbench editor groups', () => { }); test('Multiple Editors - Editor Emits Dirty and Label Changed', function () { - const group1 = createGroup(); - const group2 = createGroup(); + const group1 = createEditorGroupModel(); + const group2 = createEditorGroupModel(); const input1 = input(); const input2 = input(); @@ -1591,7 +1591,7 @@ suite('Workbench editor groups', () => { }); test('Sticky Editors', function () { - const group = createGroup(); + const group = createEditorGroupModel(); const input1 = input(); const input2 = input(); diff --git a/src/vs/workbench/test/browser/parts/editor/editorInput.test.ts b/src/vs/workbench/test/browser/parts/editor/editorInput.test.ts index 47baecf1..5070f701 100644 --- a/src/vs/workbench/test/browser/parts/editor/editorInput.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/editorInput.test.ts @@ -13,8 +13,8 @@ suite('Workbench editor input', () => { class MyEditorInput extends EditorInput { readonly resource = undefined; - getTypeId(): string { return ''; } - resolve(): any { return null; } + override get typeId(): string { return 'myEditorInput'; } + override resolve(): any { return null; } } test('EditorInput', () => { @@ -27,7 +27,7 @@ suite('Workbench editor input', () => { assert(!input.matches(null)); assert(input.getName()); - input.onDispose(() => { + input.onWillDispose(() => { assert(true); counter++; }); @@ -41,13 +41,13 @@ suite('Workbench editor input', () => { let counter = 0; let input = new MyEditorInput(); - input.onDispose(() => { + input.onWillDispose(() => { assert(true); counter++; }); let otherInput = new MyEditorInput(); - otherInput.onDispose(() => { + otherInput.onWillDispose(() => { assert(true); counter++; }); @@ -72,7 +72,7 @@ suite('Workbench editor input', () => { let otherInput = new MyEditorInput(); let diffInput = instantiationService.createInstance(DiffEditorInput, 'name', 'description', input, otherInput, undefined); - diffInput.onDispose(() => { + diffInput.onWillDispose(() => { counter++; assert(true); }); @@ -83,7 +83,7 @@ suite('Workbench editor input', () => { otherInput = new MyEditorInput(); let diffInput2 = instantiationService.createInstance(DiffEditorInput, 'name', 'description', input, otherInput, undefined); - diffInput2.onDispose(() => { + diffInput2.onWillDispose(() => { counter++; assert(true); }); diff --git a/src/vs/workbench/test/browser/parts/editor/editorModel.test.ts b/src/vs/workbench/test/browser/parts/editor/editorModel.test.ts index 6e9854ad..a478426e 100644 --- a/src/vs/workbench/test/browser/parts/editor/editorModel.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/editorModel.test.ts @@ -31,11 +31,11 @@ suite('Workbench editor model', () => { class MyEditorModel extends EditorModel { } class MyTextEditorModel extends BaseTextEditorModel { - createTextEditorModel(value: ITextBufferFactory, resource?: URI, preferredMode?: string) { + override createTextEditorModel(value: ITextBufferFactory, resource?: URI, preferredMode?: string) { return super.createTextEditorModel(value, resource, preferredMode); } - isReadonly(): boolean { + override isReadonly(): boolean { return false; } } @@ -67,28 +67,26 @@ suite('Workbench editor model', () => { const model = new MyEditorModel(); - model.onDispose(() => { + model.onWillDispose(() => { assert(true); counter++; }); - const resolvedModel = await model.load(); - assert(resolvedModel === model); - assert.strictEqual(resolvedModel.isDisposed(), false); + await model.resolve(); + assert.strictEqual(model.isDisposed(), false); assert.strictEqual(model.isResolved(), true); model.dispose(); assert.strictEqual(counter, 1); - assert.strictEqual(resolvedModel.isDisposed(), true); + assert.strictEqual(model.isDisposed(), true); }); test('BaseTextEditorModel', async () => { let modelService = stubModelService(instantiationService); const model = new MyTextEditorModel(modelService, modeService); - const resolvedModel = await model.load() as MyTextEditorModel; + await model.resolve(); - assert(resolvedModel === model); - resolvedModel.createTextEditorModel(createTextBufferFactory('foo'), null!, 'text/plain'); + model.createTextEditorModel(createTextBufferFactory('foo'), null!, 'text/plain'); assert.strictEqual(model.isResolved(), true); model.dispose(); }); diff --git a/src/vs/workbench/test/browser/parts/editor/editorPane.test.ts b/src/vs/workbench/test/browser/parts/editor/editorPane.test.ts index 625ac738..ec093fb0 100644 --- a/src/vs/workbench/test/browser/parts/editor/editorPane.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/editorPane.test.ts @@ -5,17 +5,17 @@ import * as assert from 'assert'; import { EditorPane, EditorMemento } from 'vs/workbench/browser/parts/editor/editorPane'; -import { EditorInput, EditorOptions, IEditorInputFactory, IEditorInputFactoryRegistry, Extensions as EditorExtensions } from 'vs/workbench/common/editor'; +import { EditorInput, EditorOptions, IEditorInputSerializer, IEditorInputFactoryRegistry, EditorExtensions } from 'vs/workbench/common/editor'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { Registry } from 'vs/platform/registry/common/platform'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; -import { workbenchInstantiationService, TestEditorGroupView, TestEditorGroupsService, registerTestResourceEditor } from 'vs/workbench/test/browser/workbenchTestServices'; +import { workbenchInstantiationService, TestEditorGroupView, TestEditorGroupsService, registerTestResourceEditor, TestEditorInput } from 'vs/workbench/test/browser/workbenchTestServices'; import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; import { URI } from 'vs/base/common/uri'; -import { IEditorRegistry, Extensions, EditorDescriptor } from 'vs/workbench/browser/editor'; +import { IEditorRegistry, EditorDescriptor } from 'vs/workbench/browser/editor'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IEditorModel } from 'vs/platform/editor/common/editor'; import { DisposableStore, dispose } from 'vs/base/common/lifecycle'; @@ -24,33 +24,33 @@ import { extUri } from 'vs/base/common/resources'; const NullThemeService = new TestThemeService(); -let EditorRegistry: IEditorRegistry = Registry.as(Extensions.Editors); +let EditorRegistry: IEditorRegistry = Registry.as(EditorExtensions.Editors); let EditorInputRegistry: IEditorInputFactoryRegistry = Registry.as(EditorExtensions.EditorInputFactories); -export class MyEditor extends EditorPane { +class TestEditor extends EditorPane { constructor(@ITelemetryService telemetryService: ITelemetryService) { - super('MyEditor', NullTelemetryService, NullThemeService, new TestStorageService()); + super('TestEditor', NullTelemetryService, NullThemeService, new TestStorageService()); } - getId(): string { return 'myEditor'; } + override getId(): string { return 'testEditor'; } layout(): void { } createEditor(): any { } } -export class MyOtherEditor extends EditorPane { +export class OtherTestEditor extends EditorPane { constructor(@ITelemetryService telemetryService: ITelemetryService) { - super('myOtherEditor', NullTelemetryService, NullThemeService, new TestStorageService()); + super('testOtherEditor', NullTelemetryService, NullThemeService, new TestStorageService()); } - getId(): string { return 'myOtherEditor'; } + override getId(): string { return 'testOtherEditor'; } layout(): void { } createEditor(): any { } } -class MyInputFactory implements IEditorInputFactory { +class TestInputSerializer implements IEditorInputSerializer { canSerialize(editorInput: EditorInput): boolean { return true; @@ -65,42 +65,42 @@ class MyInputFactory implements IEditorInputFactory { } } -class MyInput extends EditorInput { +class TestInput extends EditorInput { readonly resource = undefined; - getPreferredEditorId(ids: string[]) { + override getPreferredEditorId(ids: string[]) { return ids[1]; } - getTypeId(): string { - return ''; + override get typeId(): string { + return 'testInput'; } - resolve(): any { + override resolve(): any { return null; } } -class MyOtherInput extends EditorInput { +class OtherTestInput extends EditorInput { readonly resource = undefined; - getTypeId(): string { - return ''; + override get typeId(): string { + return 'otherTestInput'; } - resolve(): any { + override resolve(): any { return null; } } -class MyResourceEditorInput extends ResourceEditorInput { } +class TestResourceEditorInput extends ResourceEditorInput { } suite('Workbench EditorPane', () => { test('EditorPane API', async () => { - let e = new MyEditor(NullTelemetryService); - let input = new MyOtherInput(); + let e = new TestEditor(NullTelemetryService); + let input = new OtherTestInput(); let options = new EditorOptions(); assert(!e.isVisible()); @@ -112,7 +112,7 @@ suite('Workbench EditorPane', () => { e.setVisible(true, group); assert(e.isVisible()); assert.strictEqual(e.group, group); - input.onDispose(() => { + input.onWillDispose(() => { assert(false); }); e.dispose(); @@ -124,26 +124,26 @@ suite('Workbench EditorPane', () => { }); test('EditorDescriptor', () => { - let d = EditorDescriptor.create(MyEditor, 'id', 'name'); + let d = EditorDescriptor.create(TestEditor, 'id', 'name'); assert.strictEqual(d.getId(), 'id'); assert.strictEqual(d.getName(), 'name'); }); test('Editor Registration', function () { - let d1 = EditorDescriptor.create(MyEditor, 'id1', 'name'); - let d2 = EditorDescriptor.create(MyOtherEditor, 'id2', 'name'); + let d1 = EditorDescriptor.create(TestEditor, 'id1', 'name'); + let d2 = EditorDescriptor.create(OtherTestEditor, 'id2', 'name'); let oldEditorsCnt = EditorRegistry.getEditors().length; let oldInputCnt = (EditorRegistry).getEditorInputs().length; - const dispose1 = EditorRegistry.registerEditor(d1, [new SyncDescriptor(MyInput)]); - const dispose2 = EditorRegistry.registerEditor(d2, [new SyncDescriptor(MyInput), new SyncDescriptor(MyOtherInput)]); + const dispose1 = EditorRegistry.registerEditor(d1, [new SyncDescriptor(TestInput)]); + const dispose2 = EditorRegistry.registerEditor(d2, [new SyncDescriptor(TestInput), new SyncDescriptor(OtherTestInput)]); assert.strictEqual(EditorRegistry.getEditors().length, oldEditorsCnt + 2); assert.strictEqual((EditorRegistry).getEditorInputs().length, oldInputCnt + 3); - assert.strictEqual(EditorRegistry.getEditor(new MyInput()), d2); - assert.strictEqual(EditorRegistry.getEditor(new MyOtherInput()), d2); + assert.strictEqual(EditorRegistry.getEditor(new TestInput()), d2); + assert.strictEqual(EditorRegistry.getEditor(new OtherTestInput()), d2); assert.strictEqual(EditorRegistry.getEditorById('id1'), d1); assert.strictEqual(EditorRegistry.getEditorById('id2'), d2); @@ -153,17 +153,17 @@ suite('Workbench EditorPane', () => { }); test('Editor Lookup favors specific class over superclass (match on specific class)', function () { - let d1 = EditorDescriptor.create(MyEditor, 'id1', 'name'); + let d1 = EditorDescriptor.create(TestEditor, 'id1', 'name'); const disposables = new DisposableStore(); disposables.add(registerTestResourceEditor()); - disposables.add(EditorRegistry.registerEditor(d1, [new SyncDescriptor(MyResourceEditorInput)])); + disposables.add(EditorRegistry.registerEditor(d1, [new SyncDescriptor(TestResourceEditorInput)])); let inst = workbenchInstantiationService(); - const editor = EditorRegistry.getEditor(inst.createInstance(MyResourceEditorInput, URI.file('/fake'), 'fake', '', undefined))!.instantiate(inst); - assert.strictEqual(editor.getId(), 'myEditor'); + const editor = EditorRegistry.getEditor(inst.createInstance(TestResourceEditorInput, URI.file('/fake'), 'fake', '', undefined))!.instantiate(inst); + assert.strictEqual(editor.getId(), 'testEditor'); const otherEditor = EditorRegistry.getEditor(inst.createInstance(ResourceEditorInput, URI.file('/fake'), 'fake', '', undefined))!.instantiate(inst); assert.strictEqual(otherEditor.getId(), 'workbench.editors.textResourceEditor'); @@ -177,20 +177,27 @@ suite('Workbench EditorPane', () => { const disposables = new DisposableStore(); disposables.add(registerTestResourceEditor()); - const editor = EditorRegistry.getEditor(inst.createInstance(MyResourceEditorInput, URI.file('/fake'), 'fake', '', undefined))!.instantiate(inst); + const editor = EditorRegistry.getEditor(inst.createInstance(TestResourceEditorInput, URI.file('/fake'), 'fake', '', undefined))!.instantiate(inst); assert.strictEqual('workbench.editors.textResourceEditor', editor.getId()); disposables.dispose(); }); - test('Editor Input Factory', function () { + test('Editor Input Serializer', function () { + const testInput = new TestEditorInput(URI.file('/fake'), 'testTypeId'); workbenchInstantiationService().invokeFunction(accessor => EditorInputRegistry.start(accessor)); - const disposable = EditorInputRegistry.registerEditorInputFactory('myInputId', MyInputFactory); + const disposable = EditorInputRegistry.registerEditorInputSerializer(testInput.typeId, TestInputSerializer); - let factory = EditorInputRegistry.getEditorInputFactory('myInputId'); + let factory = EditorInputRegistry.getEditorInputSerializer('testTypeId'); assert(factory); + factory = EditorInputRegistry.getEditorInputSerializer(testInput); + assert(factory); + + // throws when registering serializer for same type + assert.throws(() => EditorInputRegistry.registerEditorInputSerializer(testInput.typeId, TestInputSerializer)); + disposable.dispose(); }); @@ -304,10 +311,10 @@ suite('Workbench EditorPane', () => { constructor(public resource: URI, private id = 'testEditorInputForMementoTest') { super(); } - getTypeId() { return 'testEditorInputForMementoTest'; } - async resolve(): Promise { return null; } + override get typeId() { return 'testEditorInputForMementoTest'; } + override async resolve(): Promise { return null; } - matches(other: TestEditorInput): boolean { + override matches(other: TestEditorInput): boolean { return other && this.id === other.id && other instanceof TestEditorInput; } } @@ -342,10 +349,10 @@ suite('Workbench EditorPane', () => { constructor(public resource: URI, private id = 'testEditorInputForMementoTest') { super(); } - getTypeId() { return 'testEditorInputForMementoTest'; } - async resolve(): Promise { return null; } + override get typeId() { return 'testEditorInputForMementoTest'; } + override async resolve(): Promise { return null; } - matches(other: TestEditorInput): boolean { + override matches(other: TestEditorInput): boolean { return other && this.id === other.id && other instanceof TestEditorInput; } } @@ -386,9 +393,4 @@ suite('Workbench EditorPane', () => { res = memento.loadEditorState(testGroup0, testInputB); assert.ok(!res); }); - - return { - MyEditor: MyEditor, - MyOtherEditor: MyOtherEditor - }; }); diff --git a/src/vs/workbench/test/browser/parts/editor/resourceEditorInput.test.ts b/src/vs/workbench/test/browser/parts/editor/resourceEditorInput.test.ts index e8608159..09baace3 100644 --- a/src/vs/workbench/test/browser/parts/editor/resourceEditorInput.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/resourceEditorInput.test.ts @@ -46,9 +46,9 @@ suite('Resource text editors', () => { const model = await input.resolve(); assert.ok(model); - assert.strictEqual(model.textEditorModel.getModeId(), 'resource-input-test'); + assert.strictEqual(model.textEditorModel?.getModeId(), 'resource-input-test'); input.setMode('text'); - assert.strictEqual(model.textEditorModel.getModeId(), PLAINTEXT_MODE_ID); + assert.strictEqual(model.textEditorModel?.getModeId(), PLAINTEXT_MODE_ID); }); }); diff --git a/src/vs/workbench/test/browser/viewlet.test.ts b/src/vs/workbench/test/browser/viewlet.test.ts index d3b57ab5..1ef7b53d 100644 --- a/src/vs/workbench/test/browser/viewlet.test.ts +++ b/src/vs/workbench/test/browser/viewlet.test.ts @@ -16,7 +16,7 @@ suite('Viewlets', () => { super('id', null!, null!, null!, null!, null!, null!, null!, null!, null!); } - layout(dimension: any): void { + override layout(dimension: any): void { throw new Error('Method not implemented.'); } diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 6dd37cb4..01808693 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -9,15 +9,15 @@ import { basename } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; -import { IEditorInputWithOptions, IEditorIdentifier, IUntitledTextResourceEditorInput, IResourceDiffEditorInput, IEditorInput, IEditorPane, IEditorCloseEvent, IEditorPartOptions, IRevertOptions, GroupIdentifier, EditorInput, EditorOptions, EditorsOrder, IFileEditorInput, IEditorInputFactoryRegistry, IEditorInputFactory, Extensions as EditorExtensions, ISaveOptions, IMoveResult, ITextEditorPane, ITextDiffEditorPane, IVisibleEditorPane, IEditorOpenContext, SideBySideEditorInput } from 'vs/workbench/common/editor'; -import { IEditorOpeningEvent, EditorServiceImpl, IEditorGroupView, IEditorGroupsAccessor, IEditorGroupTitleHeight } from 'vs/workbench/browser/parts/editor/editor'; +import { IEditorInputWithOptions, IEditorIdentifier, IUntitledTextResourceEditorInput, IResourceDiffEditorInput, IEditorInput, IEditorPane, IEditorCloseEvent, IEditorPartOptions, IRevertOptions, GroupIdentifier, EditorInput, EditorOptions, EditorsOrder, IFileEditorInput, IEditorInputFactoryRegistry, IEditorInputSerializer, EditorExtensions, ISaveOptions, IMoveResult, ITextEditorPane, ITextDiffEditorPane, IVisibleEditorPane, IEditorOpenContext, SideBySideEditorInput, IEditorMoveEvent, EditorExtensions as Extensions } from 'vs/workbench/common/editor'; +import { EditorServiceImpl, IEditorGroupView, IEditorGroupsAccessor, IEditorGroupTitleHeight } from 'vs/workbench/browser/parts/editor/editor'; import { Event, Emitter } from 'vs/base/common/event'; -import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; +import { IWorkingCopyBackupService } from 'vs/workbench/services/workingCopy/common/workingCopyBackup'; import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { IWorkbenchLayoutService, Parts, Position as PartPosition } from 'vs/workbench/services/layout/browser/layoutService'; import { TextModelResolverService } from 'vs/workbench/services/textmodelResolver/common/textModelResolverService'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; -import { IEditorOptions, IResourceEditorInput, IEditorModel, ITextEditorOptions } from 'vs/platform/editor/common/editor'; +import { IEditorOptions, IResourceEditorInput, IEditorModel, ITextEditorOptions, IResourceEditorInputIdentifier } from 'vs/platform/editor/common/editor'; import { IUntitledTextEditorService, UntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { ILifecycleService, BeforeShutdownEvent, ShutdownReason, StartupKind, LifecyclePhase, WillShutdownEvent } from 'vs/workbench/services/lifecycle/common/lifecycle'; @@ -42,7 +42,7 @@ import { IMenuService, MenuId, IMenu } from 'vs/platform/actions/common/actions' import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { MockContextKeyService, MockKeybindingService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; import { ITextBufferFactory, DefaultEndOfLine, EndOfLinePreference, ITextSnapshot } from 'vs/editor/common/model'; -import { Range } from 'vs/editor/common/core/range'; +import { IRange, Range } from 'vs/editor/common/core/range'; import { IDialogService, IPickAndOpenOptions, ISaveDialogOptions, IOpenDialogOptions, IFileDialogService, ConfirmResult } from 'vs/platform/dialogs/common/dialogs'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; @@ -50,11 +50,10 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IDecorationsService, IResourceDecorationChangeEvent, IDecoration, IDecorationData, IDecorationsProvider } from 'vs/workbench/services/decorations/browser/decorations'; import { IDisposable, toDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { IEditorGroupsService, IEditorGroup, GroupsOrder, GroupsArrangement, GroupDirection, IAddGroupOptions, IMergeGroupOptions, IMoveEditorOptions, ICopyEditorOptions, IEditorReplacement, IGroupChangeEvent, IFindGroupScope, EditorGroupLayout, ICloseEditorOptions, GroupOrientation, ICloseAllEditorsOptions, ICloseEditorsFilter } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IEditorGroupsService, IEditorGroup, GroupsOrder, GroupsArrangement, GroupDirection, IAddGroupOptions, IMergeGroupOptions, IEditorReplacement, IGroupChangeEvent, IFindGroupScope, EditorGroupLayout, ICloseEditorOptions, GroupOrientation, ICloseAllEditorsOptions, ICloseEditorsFilter } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService, IOpenEditorOverrideHandler, ISaveEditorsOptions, IRevertAllEditorsOptions, IResourceEditorInputType, SIDE_GROUP_TYPE, ACTIVE_GROUP_TYPE, IOpenEditorOverrideEntry } from 'vs/workbench/services/editor/common/editorService'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { IEditorRegistry, EditorDescriptor, Extensions } from 'vs/workbench/browser/editor'; -import { EditorGroup } from 'vs/workbench/common/editor/editorGroup'; +import { IEditorRegistry, EditorDescriptor } from 'vs/workbench/browser/editor'; import { Dimension, IDimension } from 'vs/base/browser/dom'; import { ILogService, NullLogService } from 'vs/platform/log/common/log'; import { ILabelService } from 'vs/platform/label/common/label'; @@ -63,18 +62,19 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { ViewletDescriptor, Viewlet } from 'vs/workbench/browser/viewlet'; import { IViewlet } from 'vs/workbench/common/viewlet'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; -import { IProcessEnvironment, isLinux, isWindows } from 'vs/base/common/platform'; +import { IProcessEnvironment, isLinux, isWindows, OperatingSystem } from 'vs/base/common/platform'; import { LabelService } from 'vs/workbench/services/label/common/labelService'; import { Part } from 'vs/workbench/browser/part'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IPanel } from 'vs/workbench/common/panel'; import { IBadge } from 'vs/workbench/services/activity/common/activity'; -import { bufferToStream, VSBuffer, VSBufferReadable } from 'vs/base/common/buffer'; +import { bufferToStream, VSBuffer, VSBufferReadable, VSBufferReadableStream } from 'vs/base/common/buffer'; import { Schemas } from 'vs/base/common/network'; import { IProductService } from 'vs/platform/product/common/productService'; import product from 'vs/platform/product/common/product'; import { IHostService } from 'vs/workbench/services/host/browser/host'; -import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { IWorkingCopyService, WorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { IWorkingCopyIdentifier } from 'vs/workbench/services/workingCopy/common/workingCopy'; import { IFilesConfigurationService, FilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; import { BrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; @@ -96,12 +96,12 @@ import { TestDialogService } from 'vs/platform/dialogs/test/common/testDialogSer import { CodeEditorService } from 'vs/workbench/services/editor/browser/codeEditorService'; import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { IDiffEditor, IEditor } from 'vs/editor/common/editorCommon'; +import { IChange, IDiffEditor, IEditor } from 'vs/editor/common/editorCommon'; import { IInputBox, IInputOptions, IPickOptions, IQuickInputButton, IQuickInputService, IQuickNavigateConfiguration, IQuickPick, IQuickPickItem, QuickPickInput } from 'vs/platform/quickinput/common/quickInput'; import { QuickInputService } from 'vs/workbench/services/quickinput/browser/quickInputService'; import { IListService } from 'vs/platform/list/browser/listService'; import { win32, posix } from 'vs/base/common/path'; -import { TestWorkingCopyService, TestContextService, TestStorageService, TestTextResourcePropertiesService, TestExtensionService } from 'vs/workbench/test/common/workbenchTestServices'; +import { TestContextService, TestStorageService, TestTextResourcePropertiesService, TestExtensionService } from 'vs/workbench/test/common/workbenchTestServices'; import { IViewsService, IView, ViewContainer, ViewContainerLocation } from 'vs/workbench/common/views'; import { IPaneComposite } from 'vs/workbench/common/panecomposite'; import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; @@ -113,9 +113,8 @@ import { EncodingOracle, IEncodingOverride } from 'vs/workbench/services/textfil import { UTF16le, UTF16be, UTF8_with_bom } from 'vs/workbench/services/textfile/common/encoding'; import { ColorScheme } from 'vs/platform/theme/common/theme'; import { Iterable } from 'vs/base/common/iterator'; -import { InMemoryBackupFileService } from 'vs/workbench/services/backup/common/backupFileService'; -import { hash } from 'vs/base/common/hash'; -import { BrowserBackupFileService } from 'vs/workbench/services/backup/browser/backupFileService'; +import { InMemoryWorkingCopyBackupService } from 'vs/workbench/services/workingCopy/common/workingCopyBackupService'; +import { BrowserWorkingCopyBackupService } from 'vs/workbench/services/workingCopy/browser/workingCopyBackupService'; import { FileService } from 'vs/platform/files/common/fileService'; import { TextResourceEditor } from 'vs/workbench/browser/parts/editor/textResourceEditor'; import { TestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; @@ -124,12 +123,23 @@ import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorIn import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; import { SideBySideEditor } from 'vs/workbench/browser/parts/editor/sideBySideEditor'; import { IEnterWorkspaceResult, IRecent, IRecentlyOpened, IWorkspaceFolderCreationData, IWorkspaceIdentifier, IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; -import { IWorkspaceTrustService } from 'vs/platform/workspace/common/workspaceTrust'; -import { TestWorkspaceTrustService } from 'vs/workbench/services/workspaces/test/common/testWorkspaceTrustService'; +import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust'; +import { TestWorkspaceTrustManagementService } from 'vs/workbench/services/workspaces/test/common/testWorkspaceTrustService'; import { ILocalTerminalService, IShellLaunchConfig, ITerminalChildProcess, ITerminalsLayoutInfo, ITerminalsLayoutInfoById } from 'vs/platform/terminal/common/terminal'; import { IProcessDetails, ISetTerminalLayoutInfoArgs } from 'vs/platform/terminal/common/terminalProcess'; import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { isArray } from 'vs/base/common/types'; +import { IShellLaunchConfigResolveOptions, ITerminalProfile, ITerminalProfileResolverService } from 'vs/workbench/contrib/terminal/common/terminal'; +import { EditorOverrideService } from 'vs/workbench/services/editor/browser/editorOverrideService'; +import { FILE_EDITOR_INPUT_ID } from 'vs/workbench/contrib/files/common/files'; +import { IEditorOverrideService } from 'vs/workbench/services/editor/common/editorOverrideService'; +import { IWorkingCopyEditorService, WorkingCopyEditorService } from 'vs/workbench/services/workingCopy/common/workingCopyEditorService'; +import { IElevatedFileService } from 'vs/workbench/services/files/common/elevatedFileService'; +import { BrowserElevatedFileService } from 'vs/workbench/services/files/browser/elevatedFileService'; +import { TextDiffEditor } from 'vs/workbench/browser/parts/editor/textDiffEditor'; +import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; +import { IDiffComputationResult, IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService'; +import { TextEdit, IInplaceReplaceSupportResult } from 'vs/editor/common/modes'; export function createFileEditorInput(instantiationService: IInstantiationService, resource: URI): FileEditorInput { return instantiationService.createInstance(FileEditorInput, resource, undefined, undefined, undefined, undefined, undefined); @@ -137,6 +147,8 @@ export function createFileEditorInput(instantiationService: IInstantiationServic Registry.as(EditorExtensions.EditorInputFactories).registerFileEditorInputFactory({ + typeId: FILE_EDITOR_INPUT_ID, + createFileEditorInput: (resource, preferredResource, preferredName, preferredDescription, preferredEncoding, preferredMode, instantiationService): IFileEditorInput => { return instantiationService.createInstance(FileEditorInput, resource, preferredResource, preferredName, preferredDescription, preferredEncoding, preferredMode); }, @@ -148,14 +160,14 @@ Registry.as(EditorExtensions.EditorInputFactories). export class TestTextResourceEditor extends TextResourceEditor { - protected createEditorControl(parent: HTMLElement, configuration: any): IEditor { + protected override createEditorControl(parent: HTMLElement, configuration: any): IEditor { return this.instantiationService.createInstance(TestCodeEditor, parent, configuration, {}); } } export class TestTextFileEditor extends TextFileEditor { - protected createEditorControl(parent: HTMLElement, configuration: any): IEditor { + protected override createEditorControl(parent: HTMLElement, configuration: any): IEditor { return this.instantiationService.createInstance(TestCodeEditor, parent, configuration, {}); } } @@ -175,7 +187,8 @@ export function workbenchInstantiationService( ): ITestInstantiationService { const instantiationService = new TestInstantiationService(new ServiceCollection([ILifecycleService, new TestLifecycleService()])); - instantiationService.stub(IWorkingCopyService, disposables.add(new TestWorkingCopyService())); + instantiationService.stub(IEditorWorkerService, new TestEditorWorkerService()); + instantiationService.stub(IWorkingCopyService, disposables.add(new WorkingCopyService())); instantiationService.stub(IEnvironmentService, TestEnvironmentService); instantiationService.stub(IWorkbenchEnvironmentService, TestEnvironmentService); const contextKeyService = overrides?.contextKeyService ? overrides.contextKeyService(instantiationService) : instantiationService.createInstance(MockContextKeyService); @@ -206,7 +219,7 @@ export function workbenchInstantiationService( const fileService = new TestFileService(); instantiationService.stub(IFileService, fileService); instantiationService.stub(IUriIdentityService, new UriIdentityService(fileService)); - instantiationService.stub(IBackupFileService, new TestBackupFileService()); + instantiationService.stub(IWorkingCopyBackupService, new TestWorkingCopyBackupService()); instantiationService.stub(ITelemetryService, NullTelemetryService); instantiationService.stub(INotificationService, new TestNotificationService()); instantiationService.stub(IUntitledTextEditorService, disposables.add(instantiationService.createInstance(UntitledTextEditorService))); @@ -215,6 +228,7 @@ export function workbenchInstantiationService( instantiationService.stub(IKeybindingService, keybindingService); instantiationService.stub(IDecorationsService, new TestDecorationsService()); instantiationService.stub(IExtensionService, new TestExtensionService()); + instantiationService.stub(IWorkingCopyEditorService, disposables.add(instantiationService.createInstance(WorkingCopyEditorService))); instantiationService.stub(IWorkingCopyFileService, disposables.add(instantiationService.createInstance(WorkingCopyFileService))); instantiationService.stub(ITextFileService, overrides?.textFileService ? overrides.textFileService(instantiationService) : disposables.add(instantiationService.createInstance(TestTextFileService))); instantiationService.stub(IHostService, instantiationService.createInstance(TestHostService)); @@ -225,14 +239,16 @@ export function workbenchInstantiationService( instantiationService.stub(ILabelService, disposables.add(instantiationService.createInstance(LabelService))); const editorService = overrides?.editorService ? overrides.editorService(instantiationService) : new TestEditorService(editorGroupService); instantiationService.stub(IEditorService, editorService); + instantiationService.stub(IEditorOverrideService, disposables.add(instantiationService.createInstance(EditorOverrideService))); instantiationService.stub(ICodeEditorService, disposables.add(new CodeEditorService(editorService, themeService, configService))); instantiationService.stub(IViewletService, new TestViewletService()); instantiationService.stub(IListService, new TestListService()); instantiationService.stub(IQuickInputService, disposables.add(new QuickInputService(configService, instantiationService, keybindingService, contextKeyService, themeService, accessibilityService, layoutService))); instantiationService.stub(IWorkspacesService, new TestWorkspacesService()); - instantiationService.stub(IWorkspaceTrustService, new TestWorkspaceTrustService()); + instantiationService.stub(IWorkspaceTrustManagementService, new TestWorkspaceTrustManagementService()); instantiationService.stub(ITerminalInstanceService, new TestTerminalInstanceService()); instantiationService.stub(ILocalTerminalService, new TestLocalTerminalService()); + instantiationService.stub(IElevatedFileService, new BrowserElevatedFileService()); return instantiationService; } @@ -254,12 +270,17 @@ export class TestServiceAccessor { @ITextModelService public textModelResolverService: ITextModelService, @IUntitledTextEditorService public untitledTextEditorService: UntitledTextEditorService, @IConfigurationService public testConfigurationService: TestConfigurationService, - @IBackupFileService public backupFileService: TestBackupFileService, + @IWorkingCopyBackupService public workingCopyBackupService: TestWorkingCopyBackupService, @IHostService public hostService: TestHostService, @IQuickInputService public quickInputService: IQuickInputService, @ILabelService public labelService: ILabelService, @ILogService public logService: ILogService, - @IUriIdentityService public uriIdentityService: IUriIdentityService + @IUriIdentityService public uriIdentityService: IUriIdentityService, + @IInstantiationService public instantitionService: IInstantiationService, + @INotificationService public notificationService: INotificationService, + @IWorkingCopyEditorService public workingCopyEditorService: IWorkingCopyEditorService, + @IInstantiationService public instantiationService: IInstantiationService, + @IElevatedFileService public elevatedFileService: IElevatedFileService ) { } } @@ -268,7 +289,7 @@ export class TestTextFileService extends BrowserTextFileService { private writeError: FileOperationError | undefined = undefined; constructor( - @IFileService protected fileService: IFileService, + @IFileService fileService: IFileService, @IUntitledTextEditorService untitledTextEditorService: IUntitledTextEditorService, @ILifecycleService lifecycleService: ILifecycleService, @IInstantiationService instantiationService: IInstantiationService, @@ -285,7 +306,8 @@ export class TestTextFileService extends BrowserTextFileService { @IWorkingCopyFileService workingCopyFileService: IWorkingCopyFileService, @IUriIdentityService uriIdentityService: IUriIdentityService, @IModeService modeService: IModeService, - @ILogService logService: ILogService + @ILogService logService: ILogService, + @IElevatedFileService elevatedFileService: IElevatedFileService ) { super( fileService, @@ -304,7 +326,8 @@ export class TestTextFileService extends BrowserTextFileService { workingCopyFileService, uriIdentityService, modeService, - logService + logService, + elevatedFileService ); } @@ -312,7 +335,7 @@ export class TestTextFileService extends BrowserTextFileService { this.readStreamError = error; } - async readStream(resource: URI, options?: IReadTextFileOptions): Promise { + override async readStream(resource: URI, options?: IReadTextFileOptions): Promise { if (this.readStreamError) { const error = this.readStreamError; this.readStreamError = undefined; @@ -337,7 +360,7 @@ export class TestTextFileService extends BrowserTextFileService { this.writeError = error; } - async write(resource: URI, value: string | ITextSnapshot, options?: IWriteTextFileOptions): Promise { + override async write(resource: URI, value: string | ITextSnapshot, options?: IWriteTextFileOptions): Promise { if (this.writeError) { const error = this.writeError; this.writeError = undefined; @@ -352,7 +375,7 @@ export class TestTextFileService extends BrowserTextFileService { export class TestBrowserTextFileServiceWithEncodingOverrides extends BrowserTextFileService { private _testEncoding: TestEncodingOracle | undefined; - get encoding(): TestEncodingOracle { + override get encoding(): TestEncodingOracle { if (!this._testEncoding) { this._testEncoding = this._register(this.instantiationService.createInstance(TestEncodingOracle)); } @@ -363,7 +386,7 @@ export class TestBrowserTextFileServiceWithEncodingOverrides extends BrowserText export class TestEncodingOracle extends EncodingOracle { - protected get encodingOverrides(): IEncodingOverride[] { + protected override get encodingOverrides(): IEncodingOverride[] { return [ { extension: 'utf16le', encoding: UTF16le }, { extension: 'utf16be', encoding: UTF16be }, @@ -371,7 +394,7 @@ export class TestEncodingOracle extends EncodingOracle { ]; } - protected set encodingOverrides(overrides: IEncodingOverride[]) { } + protected override set encodingOverrides(overrides: IEncodingOverride[]) { } } class TestEnvironmentServiceWithArgs extends BrowserWorkbenchEnvironmentService { @@ -443,7 +466,7 @@ export class TestHistoryService implements IHistoryService { removeFromHistory(_input: IEditorInput | IResourceEditorInput): void { } clear(): void { } clearRecentlyOpened(): void { } - getHistory(): ReadonlyArray { return []; } + getHistory(): readonly (IEditorInput | IResourceEditorInput)[] { return []; } openNextRecentlyUsedEditor(group?: GroupIdentifier): void { } openPreviouslyUsedEditor(group?: GroupIdentifier): void { } getLastActiveWorkspaceRoot(_schemeFilter: string): URI | undefined { return this.root; } @@ -500,6 +523,8 @@ export class TestLayoutService implements IWorkbenchLayoutService { layout(): void { } isRestored(): boolean { return true; } + whenReady: Promise = Promise.resolve(undefined); + whenRestored: Promise = Promise.resolve(undefined); hasFocus(_part: Parts): boolean { return false; } focusPart(_part: Parts): void { } hasWindowBorder(): boolean { return false; } @@ -621,15 +646,17 @@ export class TestEditorGroupsService implements IEditorGroupsService { onDidChangeEditorPartOptions = Event.None; orientation = GroupOrientation.HORIZONTAL; + whenReady: Promise = Promise.resolve(undefined); whenRestored: Promise = Promise.resolve(undefined); - willRestoreEditors = false; + hasRestorableState = false; contentDimension = { width: 800, height: 600 }; get activeGroup(): IEditorGroup { return this.groups[0]; } get count(): number { return this.groups.length; } - getGroups(_order?: GroupsOrder): ReadonlyArray { return this.groups; } + isRestored(): boolean { return true; } + getGroups(_order?: GroupsOrder): readonly IEditorGroup[] { return this.groups; } getGroup(identifier: number): IEditorGroup | undefined { return this.groups.find(group => group.id === identifier); } getLabel(_identifier: number): string { return 'Group 1'; } findGroup(_scope: IFindGroupScope, _source?: number | IEditorGroup, _wrap?: boolean): IEditorGroup { throw new Error('not implemented'); } @@ -657,14 +684,13 @@ export class TestEditorGroupView implements IEditorGroupView { constructor(public id: number) { } - get group(): EditorGroup { throw new Error('not implemented'); } activeEditorPane!: IVisibleEditorPane; activeEditor!: IEditorInput; previewEditor!: IEditorInput; count!: number; stickyCount!: number; disposed!: boolean; - editors: ReadonlyArray = []; + editors: readonly IEditorInput[] = []; label!: string; ariaLabel!: string; index!: number; @@ -684,22 +710,23 @@ export class TestEditorGroupView implements IEditorGroupView { onDidGroupChange: Event = Event.None; onWillCloseEditor: Event = Event.None; onDidCloseEditor: Event = Event.None; - onWillOpenEditor: Event = Event.None; onDidOpenEditorFail: Event = Event.None; onDidFocus: Event = Event.None; onDidChange: Event<{ width: number; height: number; }> = Event.None; + onWillMoveEditor: Event = Event.None; - getEditors(_order?: EditorsOrder): ReadonlyArray { return []; } + getEditors(_order?: EditorsOrder): readonly IEditorInput[] { return []; } + findEditors(_resource: URI): readonly IEditorInput[] { return []; } getEditorByIndex(_index: number): IEditorInput { throw new Error('not implemented'); } getIndexOfEditor(_editor: IEditorInput): number { return -1; } openEditor(_editor: IEditorInput, _options?: IEditorOptions): Promise { throw new Error('not implemented'); } openEditors(_editors: IEditorInputWithOptions[]): Promise { throw new Error('not implemented'); } - isOpened(_editor: IEditorInput | IResourceEditorInput): boolean { return false; } isPinned(_editor: IEditorInput): boolean { return false; } isSticky(_editor: IEditorInput): boolean { return false; } isActive(_editor: IEditorInput): boolean { return false; } - moveEditor(_editor: IEditorInput, _target: IEditorGroup, _options?: IMoveEditorOptions): void { } - copyEditor(_editor: IEditorInput, _target: IEditorGroup, _options?: ICopyEditorOptions): void { } + contains(candidate: IEditorInput): boolean { return false; } + moveEditor(_editor: IEditorInput, _target: IEditorGroup, _options?: IEditorOptions | ITextEditorOptions): void { } + copyEditor(_editor: IEditorInput, _target: IEditorGroup, _options?: IEditorOptions | ITextEditorOptions): void { } async closeEditor(_editor?: IEditorInput, options?: ICloseEditorOptions): Promise { } async closeEditors(_editors: IEditorInput[] | ICloseEditorsFilter, options?: ICloseEditorOptions): Promise { } async closeAllEditors(options?: ICloseAllEditorsOptions): Promise { } @@ -760,16 +787,16 @@ export class TestEditorService implements EditorServiceImpl { public get activeEditor(): IEditorInput | undefined { return this._activeEditor; } public set activeEditor(value: IEditorInput | undefined) { this._activeEditor = value; } - editors: ReadonlyArray = []; - mostRecentlyActiveEditors: ReadonlyArray = []; - visibleEditorPanes: ReadonlyArray = []; + editors: readonly IEditorInput[] = []; + mostRecentlyActiveEditors: readonly IEditorIdentifier[] = []; + visibleEditorPanes: readonly IVisibleEditorPane[] = []; visibleTextEditorControls = []; - visibleEditors: ReadonlyArray = []; + visibleEditors: readonly IEditorInput[] = []; count = this.editors.length; constructor(private editorGroupService?: IEditorGroupsService) { } getEditors() { return []; } - findEditors() { return []; } + findEditors() { return [] as any; } getEditorOverrides(resource: URI, options: IEditorOptions | undefined, group: IEditorGroup | undefined): [IOpenEditorOverrideHandler, IOpenEditorOverrideEntry][] { return []; } overrideOpenEditor(_handler: IOpenEditorOverrideHandler): IDisposable { return toDisposable(() => undefined); } openEditor(editor: IEditorInput, options?: IEditorOptions | ITextEditorOptions, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; @@ -786,14 +813,13 @@ export class TestEditorService implements EditorServiceImpl { return [this.editorGroupService.activeGroup, editor as EditorInput, undefined]; } openEditors(_editors: any, _group?: any): Promise { throw new Error('not implemented'); } - isOpen(_editor: IEditorInput | IResourceEditorInput): boolean { return false; } + isOpened(_editor: IResourceEditorInputIdentifier): boolean { return false; } replaceEditors(_editors: any, _group: any) { return Promise.resolve(undefined); } createEditorInput(_input: IResourceEditorInput | IUntitledTextResourceEditorInput | IResourceDiffEditorInput): EditorInput { throw new Error('not implemented'); } save(editors: IEditorIdentifier[], options?: ISaveEditorsOptions): Promise { throw new Error('Method not implemented.'); } saveAll(options?: ISaveEditorsOptions): Promise { throw new Error('Method not implemented.'); } revert(editors: IEditorIdentifier[], options?: IRevertOptions): Promise { throw new Error('Method not implemented.'); } revertAll(options?: IRevertAllEditorsOptions): Promise { throw new Error('Method not implemented.'); } - whenClosed(editors: IResourceEditorInput[], options?: { waitForSaved: boolean; }): Promise { throw new Error('Method not implemented.'); } } export class TestFileService implements IFileService { @@ -920,6 +946,10 @@ export class TestFileService implements IFileService { return toDisposable(() => this.providers.delete(scheme)); } + getProvider(scheme: string) { + return this.providers.get(scheme); + } + activateProvider(_scheme: string): Promise { throw new Error('not implemented'); } canHandleResource(resource: URI): boolean { return resource.scheme === Schemas.file || this.providers.has(resource.scheme); } listCapabilities() { @@ -954,28 +984,37 @@ export class TestFileService implements IFileService { async canDelete(resource: URI, options?: { useTrash?: boolean | undefined; recursive?: boolean | undefined; } | undefined): Promise { return true; } } -export class TestBackupFileService extends InMemoryBackupFileService { +export class TestWorkingCopyBackupService extends InMemoryWorkingCopyBackupService { constructor() { - super(resource => String(hash(resource.path))); + super(); } parseBackupContent(textBufferFactory: ITextBufferFactory): string { const textBuffer = textBufferFactory.create(DefaultEndOfLine.LF).textBuffer; const lineCount = textBuffer.getLineCount(); const range = new Range(1, 1, lineCount, textBuffer.getLineLength(lineCount) + 1); + return textBuffer.getValueInRange(range, EndOfLinePreference.TextDefined); } } -export class InMemoryTestBackupFileService extends BrowserBackupFileService { +export function toUntypedWorkingCopyId(resource: URI): IWorkingCopyIdentifier { + return toTypedWorkingCopyId(resource, ''); +} - readonly fileService: IFileService; +export function toTypedWorkingCopyId(resource: URI, typeId = 'testBackupTypeId'): IWorkingCopyIdentifier { + return { typeId, resource }; +} + +export class InMemoryTestWorkingCopyBackupService extends BrowserWorkingCopyBackupService { + + override readonly fileService: IFileService; private backupResourceJoiners: Function[]; private discardBackupJoiners: Function[]; - discardedBackups: URI[]; + discardedBackups: IWorkingCopyIdentifier[]; constructor() { const environmentService = TestEnvironmentService; @@ -1000,25 +1039,25 @@ export class InMemoryTestBackupFileService extends BrowserBackupFileService { return new Promise(resolve => this.discardBackupJoiners.push(resolve)); } - async backup(resource: URI, content?: ITextSnapshot, versionId?: number, meta?: any, token?: CancellationToken): Promise { - await super.backup(resource, content, versionId, meta, token); + override async backup(identifier: IWorkingCopyIdentifier, content?: VSBufferReadableStream | VSBufferReadable, versionId?: number, meta?: any, token?: CancellationToken): Promise { + await super.backup(identifier, content, versionId, meta, token); while (this.backupResourceJoiners.length) { this.backupResourceJoiners.pop()!(); } } - async discardBackup(resource: URI): Promise { - await super.discardBackup(resource); - this.discardedBackups.push(resource); + override async discardBackup(identifier: IWorkingCopyIdentifier): Promise { + await super.discardBackup(identifier); + this.discardedBackups.push(identifier); while (this.discardBackupJoiners.length) { this.discardBackupJoiners.pop()!(); } } - async getBackupContents(resource: URI): Promise { - const backupResource = this.toBackupResource(resource); + async getBackupContents(identifier: IWorkingCopyIdentifier): Promise { + const backupResource = this.toBackupResource(identifier); const fileContents = await this.fileService.readFile(backupResource); @@ -1040,7 +1079,7 @@ export class TestLifecycleService implements ILifecycleService { get onWillShutdown(): Event { return this._onWillShutdown.event; } private readonly _onShutdown = new Emitter(); - get onShutdown(): Event { return this._onShutdown.event; } + get onDidShutdown(): Event { return this._onShutdown.event; } async when(): Promise { } @@ -1057,13 +1096,35 @@ export class TestLifecycleService implements ILifecycleService { }); } - fireWillShutdown(event: BeforeShutdownEvent): void { this._onBeforeShutdown.fire(event); } + fireBeforeShutdown(event: BeforeShutdownEvent): void { this._onBeforeShutdown.fire(event); } + + fireWillShutdown(event: WillShutdownEvent): void { this._onWillShutdown.fire(event); } shutdown(): void { this.fireShutdown(); } } +export class TestBeforeShutdownEvent implements BeforeShutdownEvent { + + value: boolean | Promise | undefined; + reason = ShutdownReason.CLOSE; + + veto(value: boolean | Promise): void { + this.value = value; + } +} + +export class TestWillShutdownEvent implements WillShutdownEvent { + + value: Promise[] = []; + reason = ShutdownReason.CLOSE; + + join(promise: Promise, id: string): void { + this.value.push(promise); + } +} + export class TestTextResourceConfigurationService implements ITextResourceConfigurationService { declare readonly _serviceBrand: undefined; @@ -1122,7 +1183,7 @@ export class RemoteFileSystemProvider implements IFileSystemProvider { } export class TestInMemoryFileSystemProvider extends InMemoryFileSystemProvider implements IFileSystemProviderWithFileReadStreamCapability { - readonly capabilities: FileSystemProviderCapabilities = + override readonly capabilities: FileSystemProviderCapabilities = FileSystemProviderCapabilities.FileReadWrite | FileSystemProviderCapabilities.PathCaseSensitive | FileSystemProviderCapabilities.FileReadStream; @@ -1188,34 +1249,34 @@ export class TestHostService implements IHostService { export class TestFilesConfigurationService extends FilesConfigurationService { - onFilesConfigurationChange(configuration: any): void { + override onFilesConfigurationChange(configuration: any): void { super.onFilesConfigurationChange(configuration); } } export class TestReadonlyTextFileEditorModel extends TextFileEditorModel { - isReadonly(): boolean { + override isReadonly(): boolean { return true; } } export class TestEditorInput extends EditorInput { - constructor(public resource: URI, private typeId: string) { + constructor(public resource: URI, private readonly _typeId: string) { super(); } - getTypeId(): string { - return this.typeId; + override get typeId(): string { + return this._typeId; } - resolve(): Promise { + override resolve(): Promise { return Promise.resolve(null); } } -export function registerTestEditor(id: string, inputs: SyncDescriptor[], factoryInputId?: string): IDisposable { +export function registerTestEditor(id: string, inputs: SyncDescriptor[], serializerInputId?: string): IDisposable { class TestEditor extends EditorPane { private _scopedContextKeyService: IContextKeyService; @@ -1225,17 +1286,17 @@ export function registerTestEditor(id: string, inputs: SyncDescriptor { + override async setInput(input: EditorInput, options: EditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise { super.setInput(input, options, context, token); await input.resolve(); } - getId(): string { return id; } + override getId(): string { return id; } layout(): void { } createEditor(): void { } - get scopedContextKeyService() { + override get scopedContextKeyService() { return this._scopedContextKeyService; } } @@ -1244,13 +1305,13 @@ export function registerTestEditor(id: string, inputs: SyncDescriptor(Extensions.Editors).registerEditor(EditorDescriptor.create(TestEditor, id, 'Test Editor Control'), inputs)); - if (factoryInputId) { + if (serializerInputId) { interface ISerializedTestInput { resource: string; } - class EditorsObserverTestEditorInputFactory implements IEditorInputFactory { + class EditorsObserverTestEditorInputSerializer implements IEditorInputSerializer { canSerialize(editorInput: EditorInput): boolean { return true; @@ -1268,11 +1329,11 @@ export function registerTestEditor(id: string, inputs: SyncDescriptor(EditorExtensions.EditorInputFactories).registerEditorInputFactory(factoryInputId, EditorsObserverTestEditorInputFactory)); + disposables.add(Registry.as(EditorExtensions.EditorInputFactories).registerEditorInputSerializer(serializerInputId, EditorsObserverTestEditorInputSerializer)); } return disposables; @@ -1328,6 +1389,23 @@ export function registerTestSideBySideEditor(): IDisposable { return disposables; } +export function registerTestDiffEditor(): IDisposable { + const disposables = new DisposableStore(); + + disposables.add(Registry.as(Extensions.Editors).registerEditor( + EditorDescriptor.create( + TextDiffEditor, + TextDiffEditor.ID, + 'Text Diff Editor' + ), + [ + new SyncDescriptor(DiffEditorInput) + ] + )); + + return disposables; +} + export class TestFileEditorInput extends EditorInput implements IFileEditorInput { readonly preferredResource = this.resource; @@ -1341,16 +1419,16 @@ export class TestFileEditorInput extends EditorInput implements IFileEditorInput constructor( public resource: URI, - private typeId: string + private _typeId: string ) { super(); } - getTypeId() { return this.typeId; } - resolve(): Promise { return !this.fails ? Promise.resolve(null) : Promise.reject(new Error('fails')); } - matches(other: EditorInput): boolean { return !!(other?.resource && this.resource.toString() === other.resource.toString() && other instanceof TestFileEditorInput && other.getTypeId() === this.typeId); } + override get typeId() { return this._typeId; } + override resolve(): Promise { return !this.fails ? Promise.resolve(null) : Promise.reject(new Error('fails')); } + override matches(other: EditorInput): boolean { return !!(other?.resource && this.resource.toString() === other.resource.toString() && other instanceof TestFileEditorInput && other.typeId === this.typeId); } setPreferredResource(resource: URI): void { } - setEncoding(encoding: string) { } + async setEncoding(encoding: string) { } getEncoding() { return undefined; } setPreferredName(name: string): void { } setPreferredDescription(description: string): void { } @@ -1361,40 +1439,40 @@ export class TestFileEditorInput extends EditorInput implements IFileEditorInput setFailToOpen(): void { this.fails = true; } - async save(groupId: GroupIdentifier, options?: ISaveOptions): Promise { + override async save(groupId: GroupIdentifier, options?: ISaveOptions): Promise { this.gotSaved = true; this.dirty = false; return this; } - async saveAs(groupId: GroupIdentifier, options?: ISaveOptions): Promise { + override async saveAs(groupId: GroupIdentifier, options?: ISaveOptions): Promise { this.gotSavedAs = true; return this; } - async revert(group: GroupIdentifier, options?: IRevertOptions): Promise { + override async revert(group: GroupIdentifier, options?: IRevertOptions): Promise { this.gotReverted = true; this.gotSaved = false; this.gotSavedAs = false; this.dirty = false; } setDirty(): void { this.dirty = true; } - isDirty(): boolean { + override isDirty(): boolean { return this.dirty; } - isReadonly(): boolean { + override isReadonly(): boolean { return false; } isResolved(): boolean { return false; } - dispose(): void { + override dispose(): void { super.dispose(); this.gotDisposed = true; } movedEditor: IMoveResult | undefined = undefined; - rename(): IMoveResult | undefined { return this.movedEditor; } + override rename(): IMoveResult | undefined { return this.movedEditor; } } export class TestEditorPart extends EditorPart { - saveState(): void { + override saveState(): void { return super.saveState(); } @@ -1411,11 +1489,13 @@ export class TestEditorPart extends EditorPart { } } -export function createEditorPart(instantiationService: IInstantiationService, disposables: DisposableStore): TestEditorPart { +export async function createEditorPart(instantiationService: IInstantiationService, disposables: DisposableStore): Promise { const part = disposables.add(instantiationService.createInstance(TestEditorPart)); part.create(document.createElement('div')); part.layout(1080, 800); + await part.whenReady; + return part; } @@ -1449,11 +1529,11 @@ export class TestPathService implements IPathService { export class TestTextFileEditorModelManager extends TextFileEditorModelManager { - add(resource: URI, model: TextFileEditorModel): void { + override add(resource: URI, model: TextFileEditorModel): void { return super.add(resource, model); } - remove(resource: URI): void { + override remove(resource: URI): void { return super.remove(resource); } } @@ -1504,6 +1584,18 @@ export class TestTerminalInstanceService implements ITerminalInstanceService { createWindowsShellHelper(shellProcessId: number, xterm: any): any { throw new Error('Method not implemented.'); } } +export class TestTerminalProfileResolverService implements ITerminalProfileResolverService { + _serviceBrand: undefined; + resolveIcon(shellLaunchConfig: IShellLaunchConfig): void { } + async resolveShellLaunchConfig(shellLaunchConfig: IShellLaunchConfig, options: IShellLaunchConfigResolveOptions): Promise { } + async getDefaultProfile(options: IShellLaunchConfigResolveOptions): Promise { return { path: '/default', profileName: 'Default' }; } + async getDefaultShell(options: IShellLaunchConfigResolveOptions): Promise { return '/default'; } + async getDefaultShellArgs(options: IShellLaunchConfigResolveOptions): Promise { return []; } + async getShellEnvironment(): Promise { return process.env; } + getSafeConfigValue(key: string, os: OperatingSystem): unknown | undefined { return undefined; } + getSafeConfigValueFullKey(key: string): unknown | undefined { return undefined; } +} + export class TestLocalTerminalService implements ILocalTerminalService { declare readonly _serviceBrand: undefined; @@ -1516,9 +1608,13 @@ export class TestLocalTerminalService implements ILocalTerminalService { return new TestTerminalChildProcess(shouldPersist); } async attachToProcess(id: number): Promise { throw new Error('Method not implemented.'); } - async listProcesses(reduceGraceTime: boolean): Promise { throw new Error('Method not implemented.'); } + async listProcesses(): Promise { throw new Error('Method not implemented.'); } + getDefaultSystemShell(osOverride?: OperatingSystem): Promise { throw new Error('Method not implemented.'); } + getShellEnvironment(): Promise { throw new Error('Method not implemented.'); } async setTerminalLayoutInfo(argsOrLayout?: ISetTerminalLayoutInfoArgs | ITerminalsLayoutInfoById) { throw new Error('Method not implemented.'); } async getTerminalLayoutInfo(): Promise { throw new Error('Method not implemented.'); } + async reduceConnectionGraceTime(): Promise { throw new Error('Method not implemented.'); } + processBinary(id: number, data: string): Promise { throw new Error('Method not implemented.'); } } class TestTerminalChildProcess implements ITerminalChildProcess { @@ -1542,6 +1638,7 @@ class TestTerminalChildProcess implements ITerminalChildProcess { async getInitialCwd(): Promise { return ''; } async getCwd(): Promise { return ''; } async getLatency(): Promise { return 0; } + async processBinary(data: string): Promise { } } export class TestQuickInputService implements IQuickInputService { @@ -1575,3 +1672,18 @@ export class TestQuickInputService implements IQuickInputService { back(): Promise { throw new Error('not implemented.'); } cancel(): Promise { throw new Error('not implemented.'); } } + +export class TestEditorWorkerService implements IEditorWorkerService { + + declare readonly _serviceBrand: undefined; + + canComputeDiff(original: URI, modified: URI): boolean { return false; } + async computeDiff(original: URI, modified: URI, ignoreTrimWhitespace: boolean, maxComputationTime: number): Promise { return null; } + canComputeDirtyDiff(original: URI, modified: URI): boolean { return false; } + async computeDirtyDiff(original: URI, modified: URI, ignoreTrimWhitespace: boolean): Promise { return null; } + async computeMoreMinimalEdits(resource: URI, edits: TextEdit[] | null | undefined): Promise { return undefined; } + canComputeWordRanges(resource: URI): boolean { return false; } + async computeWordRanges(resource: URI, range: IRange): Promise<{ [word: string]: IRange[]; } | null> { return null; } + canNavigateValueSet(resource: URI): boolean { return false; } + async navigateValueSet(resource: URI, range: IRange, up: boolean): Promise { return null; } +} diff --git a/src/vs/workbench/test/common/notifications.test.ts b/src/vs/workbench/test/common/notifications.test.ts index 742e4073..600c0e81 100644 --- a/src/vs/workbench/test/common/notifications.test.ts +++ b/src/vs/workbench/test/common/notifications.test.ts @@ -38,6 +38,12 @@ suite('Notifications', () => { assert.strictEqual(item1.equals(item4), false); assert.strictEqual(item1.equals(item5), false); + let itemId1 = NotificationViewItem.create({ id: 'same', message: 'Info Message', severity: Severity.Info })!; + let itemId2 = NotificationViewItem.create({ id: 'same', message: 'Error Message', severity: Severity.Error })!; + + assert.strictEqual(itemId1.equals(itemId2), true); + assert.strictEqual(itemId1.equals(item3), false); + // Progress assert.strictEqual(item1.hasProgress, false); assert.strictEqual(item6.hasProgress, true); diff --git a/src/vs/workbench/test/common/workbenchTestServices.ts b/src/vs/workbench/test/common/workbenchTestServices.ts index 43bb87a2..d43993d9 100644 --- a/src/vs/workbench/test/common/workbenchTestServices.ts +++ b/src/vs/workbench/test/common/workbenchTestServices.ts @@ -8,19 +8,20 @@ import { basename, isEqual, isEqualOrParent } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { Event, Emitter } from 'vs/base/common/event'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IWorkspaceContextService, IWorkspace, WorkbenchState, IWorkspaceFolder, IWorkspaceFoldersChangeEvent, Workspace } from 'vs/platform/workspace/common/workspace'; +import { IWorkspaceContextService, IWorkspace, WorkbenchState, IWorkspaceFolder, IWorkspaceFoldersChangeEvent, Workspace, IWorkspaceFoldersWillChangeEvent } from 'vs/platform/workspace/common/workspace'; import { TestWorkspace } from 'vs/platform/workspace/test/common/testWorkspace'; import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { isLinux, isMacintosh } from 'vs/base/common/platform'; import { InMemoryStorageService, WillSaveStateReason } from 'vs/platform/storage/common/storage'; -import { WorkingCopyService, IWorkingCopy, IWorkingCopyBackup, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { IWorkingCopy, IWorkingCopyBackup, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopy'; import { NullExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IWorkingCopyFileService, IWorkingCopyFileOperationParticipant, WorkingCopyFileEvent, IDeleteOperation, ICopyOperation, IMoveOperation, IFileOperationUndoRedoInfo, ICreateFileOperation, ICreateOperation } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; import { IFileStatWithMetadata } from 'vs/platform/files/common/files'; import { ISaveOptions, IRevertOptions } from 'vs/workbench/common/editor'; import { CancellationToken } from 'vs/base/common/cancellation'; +import product from 'vs/platform/product/common/product'; export class TestTextResourcePropertiesService implements ITextResourcePropertiesService { @@ -50,6 +51,9 @@ export class TestContextService implements IWorkspaceContextService { private readonly _onDidChangeWorkspaceName: Emitter; get onDidChangeWorkspaceName(): Event { return this._onDidChangeWorkspaceName.event; } + private readonly _onWillChangeWorkspaceFolders: Emitter; + get onWillChangeWorkspaceFolders(): Event { return this._onWillChangeWorkspaceFolders.event; } + private readonly _onDidChangeWorkspaceFolders: Emitter; get onDidChangeWorkspaceFolders(): Event { return this._onDidChangeWorkspaceFolders.event; } @@ -60,6 +64,7 @@ export class TestContextService implements IWorkspaceContextService { this.workspace = workspace; this.options = options || Object.create(null); this._onDidChangeWorkspaceName = new Emitter(); + this._onWillChangeWorkspaceFolders = new Emitter(); this._onDidChangeWorkspaceFolders = new Emitter(); this._onDidChangeWorkbenchState = new Emitter(); } @@ -121,13 +126,11 @@ export class TestContextService implements IWorkspaceContextService { export class TestStorageService extends InMemoryStorageService { - emitWillSaveState(reason: WillSaveStateReason): void { + override emitWillSaveState(reason: WillSaveStateReason): void { super.emitWillSaveState(reason); } } -export class TestWorkingCopyService extends WorkingCopyService { } - export class TestWorkingCopy extends Disposable implements IWorkingCopy { private readonly _onDidChangeDirty = this._register(new Emitter()); @@ -136,16 +139,13 @@ export class TestWorkingCopy extends Disposable implements IWorkingCopy { private readonly _onDidChangeContent = this._register(new Emitter()); readonly onDidChangeContent = this._onDidChangeContent.event; - private readonly _onDispose = this._register(new Emitter()); - readonly onDispose = this._onDispose.event; - readonly capabilities = WorkingCopyCapabilities.None; readonly name = basename(this.resource); private dirty = false; - constructor(public readonly resource: URI, isDirty = false) { + constructor(public readonly resource: URI, isDirty = false, public readonly typeId = 'testWorkingCopyType') { super(); this.dirty = isDirty; @@ -177,12 +177,6 @@ export class TestWorkingCopy extends Disposable implements IWorkingCopy { async backup(token: CancellationToken): Promise { return {}; } - - dispose(): void { - this._onDispose.fire(); - - super.dispose(); - } } export class TestWorkingCopyFileService implements IWorkingCopyFileService { @@ -195,18 +189,18 @@ export class TestWorkingCopyFileService implements IWorkingCopyFileService { addFileOperationParticipant(participant: IWorkingCopyFileOperationParticipant): IDisposable { return Disposable.None; } - async delete(operations: IDeleteOperation[], undoInfo?: IFileOperationUndoRedoInfo, token?: CancellationToken): Promise { } + async delete(operations: IDeleteOperation[], token: CancellationToken, undoInfo?: IFileOperationUndoRedoInfo): Promise { } registerWorkingCopyProvider(provider: (resourceOrFolder: URI) => IWorkingCopy[]): IDisposable { return Disposable.None; } getDirty(resource: URI): IWorkingCopy[] { return []; } - create(operations: ICreateFileOperation[], undoInfo?: IFileOperationUndoRedoInfo, token?: CancellationToken): Promise { throw new Error('Method not implemented.'); } - createFolder(operations: ICreateOperation[], undoInfo?: IFileOperationUndoRedoInfo, token?: CancellationToken): Promise { throw new Error('Method not implemented.'); } + create(operations: ICreateFileOperation[], token: CancellationToken, undoInfo?: IFileOperationUndoRedoInfo): Promise { throw new Error('Method not implemented.'); } + createFolder(operations: ICreateOperation[], token: CancellationToken, undoInfo?: IFileOperationUndoRedoInfo): Promise { throw new Error('Method not implemented.'); } - move(operations: IMoveOperation[], undoInfo?: IFileOperationUndoRedoInfo): Promise { throw new Error('Method not implemented.'); } + move(operations: IMoveOperation[], token: CancellationToken, undoInfo?: IFileOperationUndoRedoInfo): Promise { throw new Error('Method not implemented.'); } - copy(operations: ICopyOperation[], undoInfo?: IFileOperationUndoRedoInfo, token?: CancellationToken): Promise { throw new Error('Method not implemented.'); } + copy(operations: ICopyOperation[], token: CancellationToken, undoInfo?: IFileOperationUndoRedoInfo): Promise { throw new Error('Method not implemented.'); } } export function mock(): Ctor { @@ -218,3 +212,5 @@ export interface Ctor { } export class TestExtensionService extends NullExtensionService { } + +export const TestProductService = { _serviceBrand: undefined, ...product }; diff --git a/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts b/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts index e5c69a38..f5b55c70 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts @@ -23,6 +23,7 @@ import { mock } from 'vs/base/test/common/mock'; import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; import { TextSearchManager } from 'vs/workbench/services/search/common/textSearchManager'; import { NativeTextSearchManager } from 'vs/workbench/services/search/node/textSearchManager'; +import { timeout } from 'vs/base/common/async'; let rpcProtocol: TestRPCProtocol; let extHostSearch: NativeExtHostSearch; @@ -83,7 +84,7 @@ suite('ExtHostSearch', () => { const cancellation = new CancellationTokenSource(); const p = extHostSearch.$provideFileSearchResults(mockMainThreadSearch.lastHandle, 0, query, cancellation.token); if (cancel) { - await new Promise(resolve => process.nextTick(resolve)); + await timeout(0); cancellation.cancel(); } @@ -102,15 +103,11 @@ suite('ExtHostSearch', () => { }; } - async function runTextSearch(query: ITextQuery, cancel = false): Promise<{ results: IFileMatch[], stats: ISearchCompleteStats }> { + async function runTextSearch(query: ITextQuery): Promise<{ results: IFileMatch[], stats: ISearchCompleteStats }> { let stats: ISearchCompleteStats; try { const cancellation = new CancellationTokenSource(); const p = extHostSearch.$provideTextSearchResults(mockMainThreadSearch.lastHandle, 0, query, cancellation.token); - if (cancel) { - await new Promise(resolve => process.nextTick(resolve)); - cancellation.cancel(); - } stats = await p; } catch (err) { @@ -151,7 +148,7 @@ suite('ExtHostSearch', () => { this._pfs = mockPFS as any; } - protected createTextSearchManager(query: ITextQuery, provider: vscode.TextSearchProvider): TextSearchManager { + protected override createTextSearchManager(query: ITextQuery, provider: vscode.TextSearchProvider): TextSearchManager { return new NativeTextSearchManager(query, provider, this._pfs); } }; @@ -183,7 +180,7 @@ suite('ExtHostSearch', () => { function compareURIs(actual: URI[], expected: URI[]) { const sortAndStringify = (arr: URI[]) => arr.sort().map(u => u.toString()); - assert.deepEqual( + assert.deepStrictEqual( sortAndStringify(actual), sortAndStringify(expected)); } @@ -215,7 +212,7 @@ suite('ExtHostSearch', () => { const { results, stats } = await runFileSearch(getSimpleQuery()); assert(!stats.limitHit); - assert.equal(results.length, 3); + assert.strictEqual(results.length, 3); compareURIs(results, reportedResults); }); @@ -223,12 +220,19 @@ suite('ExtHostSearch', () => { let cancelRequested = false; await registerTestFileSearchProvider({ provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, token: vscode.CancellationToken): Promise { + return new Promise((resolve, reject) => { - token.onCancellationRequested(() => { + function onCancel() { cancelRequested = true; resolve([joinPath(options.folder, 'file1.ts')]); // or reject or nothing? - }); + } + + if (token.isCancellationRequested) { + onCancel(); + } else { + token.onCancellationRequested(() => onCancel()); + } }); } }); @@ -286,11 +290,11 @@ suite('ExtHostSearch', () => { await registerTestFileSearchProvider({ provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, token: vscode.CancellationToken): Promise { if (options.folder.toString() === rootFolderA.toString()) { - assert.deepEqual(options.includes.sort(), ['*.ts', 'foo']); - assert.deepEqual(options.excludes.sort(), ['*.js', 'bar']); + assert.deepStrictEqual(options.includes.sort(), ['*.ts', 'foo']); + assert.deepStrictEqual(options.excludes.sort(), ['*.js', 'bar']); } else { - assert.deepEqual(options.includes.sort(), ['*.ts']); - assert.deepEqual(options.excludes.sort(), ['*.js']); + assert.deepStrictEqual(options.includes.sort(), ['*.ts']); + assert.deepStrictEqual(options.excludes.sort(), ['*.js']); } return Promise.resolve(null!); @@ -327,8 +331,8 @@ suite('ExtHostSearch', () => { test('include/excludes resolved correctly', async () => { await registerTestFileSearchProvider({ provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, token: vscode.CancellationToken): Promise { - assert.deepEqual(options.includes.sort(), ['*.jsx', '*.ts']); - assert.deepEqual(options.excludes.sort(), []); + assert.deepStrictEqual(options.includes.sort(), ['*.jsx', '*.ts']); + assert.deepStrictEqual(options.excludes.sort(), []); return Promise.resolve(null!); } @@ -492,7 +496,7 @@ suite('ExtHostSearch', () => { const { results, stats } = await runFileSearch(query); assert(stats.limitHit, 'Expected to return limitHit'); - assert.equal(results.length, 1); + assert.strictEqual(results.length, 1); compareURIs(results, reportedResults.slice(0, 1)); assert(wasCanceled, 'Expected to be canceled when hitting limit'); }); @@ -528,7 +532,7 @@ suite('ExtHostSearch', () => { const { results, stats } = await runFileSearch(query); assert(stats.limitHit, 'Expected to return limitHit'); - assert.equal(results.length, 2); + assert.strictEqual(results.length, 2); compareURIs(results, reportedResults.slice(0, 2)); assert(wasCanceled, 'Expected to be canceled when hitting limit'); }); @@ -563,7 +567,7 @@ suite('ExtHostSearch', () => { const { results, stats } = await runFileSearch(query); assert(!stats.limitHit, 'Expected not to return limitHit'); - assert.equal(results.length, 2); + assert.strictEqual(results.length, 2); compareURIs(results, reportedResults); assert(!wasCanceled, 'Expected not to be canceled when just reaching limit'); }); @@ -601,8 +605,8 @@ suite('ExtHostSearch', () => { }; const { results } = await runFileSearch(query); - assert.equal(results.length, 2); // Don't care which 2 we got - assert.equal(cancels, 2, 'Expected all invocations to be canceled when hitting limit'); + assert.strictEqual(results.length, 2); // Don't care which 2 we got + assert.strictEqual(cancels, 2, 'Expected all invocations to be canceled when hitting limit'); }); test('works with non-file schemes', async () => { @@ -718,7 +722,7 @@ suite('ExtHostSearch', () => { lineNumber: r.lineNumber }); - return assert.deepEqual( + return assert.deepStrictEqual( makeComparable(actualTextSearchResults), makeComparable(expected)); } @@ -756,8 +760,8 @@ suite('ExtHostSearch', () => { test('all provider calls get global include/excludes', async () => { await registerTestTextSearchProvider({ provideTextSearchResults(query: vscode.TextSearchQuery, options: vscode.TextSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Promise { - assert.equal(options.includes.length, 1); - assert.equal(options.excludes.length, 1); + assert.strictEqual(options.includes.length, 1); + assert.strictEqual(options.excludes.length, 1); return Promise.resolve(null!); } }); @@ -787,11 +791,11 @@ suite('ExtHostSearch', () => { await registerTestTextSearchProvider({ provideTextSearchResults(query: vscode.TextSearchQuery, options: vscode.TextSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Promise { if (options.folder.toString() === rootFolderA.toString()) { - assert.deepEqual(options.includes.sort(), ['*.ts', 'foo']); - assert.deepEqual(options.excludes.sort(), ['*.js', 'bar']); + assert.deepStrictEqual(options.includes.sort(), ['*.ts', 'foo']); + assert.deepStrictEqual(options.excludes.sort(), ['*.js', 'bar']); } else { - assert.deepEqual(options.includes.sort(), ['*.ts']); - assert.deepEqual(options.excludes.sort(), ['*.js']); + assert.deepStrictEqual(options.includes.sort(), ['*.ts']); + assert.deepStrictEqual(options.excludes.sort(), ['*.js']); } return Promise.resolve(null!); @@ -828,8 +832,8 @@ suite('ExtHostSearch', () => { test('include/excludes resolved correctly', async () => { await registerTestTextSearchProvider({ provideTextSearchResults(query: vscode.TextSearchQuery, options: vscode.TextSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Promise { - assert.deepEqual(options.includes.sort(), ['*.jsx', '*.ts']); - assert.deepEqual(options.excludes.sort(), []); + assert.deepStrictEqual(options.includes.sort(), ['*.jsx', '*.ts']); + assert.deepStrictEqual(options.excludes.sort(), []); return Promise.resolve(null!); } @@ -1184,8 +1188,8 @@ suite('ExtHostSearch', () => { }; const { results } = await runTextSearch(query); - assert.equal(results.length, 2); - assert.equal(cancels, 2); + assert.strictEqual(results.length, 2); + assert.strictEqual(cancels, 2); }); test('works with non-file schemes', async () => { diff --git a/src/vs/workbench/test/electron-browser/api/mainThreadWorkspace.test.ts b/src/vs/workbench/test/electron-browser/api/mainThreadWorkspace.test.ts index 39a0ace8..7336a33b 100644 --- a/src/vs/workbench/test/electron-browser/api/mainThreadWorkspace.test.ts +++ b/src/vs/workbench/test/electron-browser/api/mainThreadWorkspace.test.ts @@ -28,13 +28,13 @@ suite('MainThreadWorkspace', () => { test('simple', () => { instantiationService.stub(ISearchService, { fileSearch(query: IFileQuery) { - assert.equal(query.folderQueries.length, 1); - assert.equal(query.folderQueries[0].disregardIgnoreFiles, true); + assert.strictEqual(query.folderQueries.length, 1); + assert.strictEqual(query.folderQueries[0].disregardIgnoreFiles, true); assert.deepEqual(query.includePattern, { 'foo': true }); - assert.equal(query.maxResults, 10); + assert.strictEqual(query.maxResults, 10); - return Promise.resolve({ results: [] }); + return Promise.resolve({ results: [], messages: [] }); } }); @@ -52,11 +52,11 @@ suite('MainThreadWorkspace', () => { instantiationService.stub(ISearchService, { fileSearch(query: IFileQuery) { - assert.equal(query.folderQueries.length, 1); - assert.equal(query.folderQueries[0].disregardIgnoreFiles, true); - assert.deepEqual(query.folderQueries[0].excludePattern, { 'filesExclude': true }); + assert.strictEqual(query.folderQueries.length, 1); + assert.strictEqual(query.folderQueries[0].disregardIgnoreFiles, true); + assert.deepStrictEqual(query.folderQueries[0].excludePattern, { 'filesExclude': true }); - return Promise.resolve({ results: [] }); + return Promise.resolve({ results: [], messages: [] }); } }); @@ -74,10 +74,10 @@ suite('MainThreadWorkspace', () => { instantiationService.stub(ISearchService, { fileSearch(query: IFileQuery) { - assert.equal(query.folderQueries[0].excludePattern, undefined); - assert.deepEqual(query.excludePattern, undefined); + assert.strictEqual(query.folderQueries[0].excludePattern, undefined); + assert.deepStrictEqual(query.excludePattern, undefined); - return Promise.resolve({ results: [] }); + return Promise.resolve({ results: [], messages: [] }); } }); @@ -88,10 +88,10 @@ suite('MainThreadWorkspace', () => { test('exclude string', () => { instantiationService.stub(ISearchService, { fileSearch(query: IFileQuery) { - assert.equal(query.folderQueries[0].excludePattern, undefined); + assert.strictEqual(query.folderQueries[0].excludePattern, undefined); assert.deepEqual(query.excludePattern, { 'exclude/**': true }); - return Promise.resolve({ results: [] }); + return Promise.resolve({ results: [], messages: [] }); } }); diff --git a/src/vs/workbench/test/electron-browser/colorRegistry.releaseTest.ts b/src/vs/workbench/test/electron-browser/colorRegistry.releaseTest.ts index c6e4d3a5..b0c5af9b 100644 --- a/src/vs/workbench/test/electron-browser/colorRegistry.releaseTest.ts +++ b/src/vs/workbench/test/electron-browser/colorRegistry.releaseTest.ts @@ -84,10 +84,10 @@ suite('Color Registry', function () { } let undocumentedKeys = Object.keys(missing).map(k => `\`${k}\`: ${missing[k]}`); - assert.deepEqual(undocumentedKeys, [], 'Undocumented colors ids'); + assert.deepStrictEqual(undocumentedKeys, [], 'Undocumented colors ids'); let superfluousKeys = Object.keys(colorsInDoc); - assert.deepEqual(superfluousKeys, [], 'Colors ids in doc that do not exist'); + assert.deepStrictEqual(superfluousKeys, [], 'Colors ids in doc that do not exist'); }); }); diff --git a/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts b/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts index 6337ae53..b2097608 100644 --- a/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts +++ b/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts @@ -105,12 +105,12 @@ suite.skip('TextSearch performance (integration)', () => { function onComplete(): void { try { const allEvents = telemetryService.events.map(e => JSON.stringify(e)).join('\n'); - assert.equal(telemetryService.events.length, 3, 'Expected 3 telemetry events, got:\n' + allEvents); + assert.strictEqual(telemetryService.events.length, 3, 'Expected 3 telemetry events, got:\n' + allEvents); const [firstRenderEvent, resultsShownEvent, resultsFinishedEvent] = telemetryService.events; - assert.equal(firstRenderEvent.name, 'searchResultsFirstRender'); - assert.equal(resultsShownEvent.name, 'searchResultsShown'); - assert.equal(resultsFinishedEvent.name, 'searchResultsFinished'); + assert.strictEqual(firstRenderEvent.name, 'searchResultsFirstRender'); + assert.strictEqual(resultsShownEvent.name, 'searchResultsShown'); + assert.strictEqual(resultsFinishedEvent.name, 'searchResultsFinished'); telemetryService.events = []; diff --git a/src/vs/workbench/test/electron-browser/workbenchTestServices.ts b/src/vs/workbench/test/electron-browser/workbenchTestServices.ts index 29b0815e..cda9f06f 100644 --- a/src/vs/workbench/test/electron-browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/electron-browser/workbenchTestServices.ts @@ -30,8 +30,8 @@ import { IPathService } from 'vs/workbench/services/path/common/pathService'; import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; -import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; -import { NodeTestBackupFileService } from 'vs/workbench/services/backup/test/electron-browser/backupFileService.test'; +import { IWorkingCopyBackupService } from 'vs/workbench/services/workingCopy/common/workingCopyBackup'; +import { NodeTestWorkingCopyBackupService } from 'vs/workbench/services/workingCopy/test/electron-browser/workingCopyBackupService.test'; import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices'; @@ -39,16 +39,18 @@ 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, tmpdir } from 'os'; +import { homedir, release, tmpdir, hostname } 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'; +import product from 'vs/platform/product/common/product'; +import { IElevatedFileService } from 'vs/workbench/services/files/common/elevatedFileService'; + +const args = parseArgs(process.argv, OPTIONS); export const TestWorkbenchConfiguration: INativeWorkbenchConfiguration = { windowId: 0, machineId: 'testMachineId', - sessionId: 'testSessionId', logLevel: LogLevel.Error, mainPid: 0, partsSplashPath: '', @@ -57,19 +59,21 @@ export const TestWorkbenchConfiguration: INativeWorkbenchConfiguration = { execPath: process.execPath, perfMarks: [], colorScheme: { dark: true, highContrast: false }, - os: { release: release() }, - ...parseArgs(process.argv, OPTIONS) + os: { release: release(), hostname: hostname() }, + product, + homeDir: homedir(), + tmpDir: tmpdir(), + userDataDir: getUserDataPath(args), + ...args }; -export const TestEnvironmentPaths: INativeEnvironmentPaths = { homeDir: homedir(), tmpDir: tmpdir(), userDataDir: getUserDataPath(TestWorkbenchConfiguration) }; - -export const TestEnvironmentService = new NativeWorkbenchEnvironmentService(TestWorkbenchConfiguration, TestEnvironmentPaths, TestProductService); +export const TestEnvironmentService = new NativeWorkbenchEnvironmentService(TestWorkbenchConfiguration, TestProductService); export class TestTextFileService extends NativeTextFileService { private resolveTextContentError!: FileOperationError | null; constructor( - @IFileService protected fileService: IFileService, + @IFileService fileService: IFileService, @IUntitledTextEditorService untitledTextEditorService: IUntitledTextEditorService, @ILifecycleService lifecycleService: ILifecycleService, @IInstantiationService instantiationService: IInstantiationService, @@ -87,7 +91,7 @@ export class TestTextFileService extends NativeTextFileService { @ILogService logService: ILogService, @IUriIdentityService uriIdentityService: IUriIdentityService, @IModeService modeService: IModeService, - @INativeHostService nativeHostService: INativeHostService + @IElevatedFileService elevatedFileService: IElevatedFileService ) { super( fileService, @@ -106,7 +110,7 @@ export class TestTextFileService extends NativeTextFileService { workingCopyFileService, uriIdentityService, modeService, - nativeHostService, + elevatedFileService, logService ); } @@ -115,7 +119,7 @@ export class TestTextFileService extends NativeTextFileService { this.resolveTextContentError = error; } - async readStream(resource: URI, options?: IReadTextFileOptions): Promise { + override async readStream(resource: URI, options?: IReadTextFileOptions): Promise { if (this.resolveTextContentError) { const error = this.resolveTextContentError; this.resolveTextContentError = null; @@ -140,7 +144,7 @@ export class TestTextFileService extends NativeTextFileService { export class TestNativeTextFileServiceWithEncodingOverrides extends NativeTextFileService { private _testEncoding: TestEncodingOracle | undefined; - get encoding(): TestEncodingOracle { + override get encoding(): TestEncodingOracle { if (!this._testEncoding) { this._testEncoding = this._register(this.instantiationService.createInstance(TestEncodingOracle)); } @@ -271,7 +275,7 @@ export class TestServiceAccessor { @IFileService public fileService: TestFileService, @INativeHostService public nativeHostService: TestNativeHostService, @IFileDialogService public fileDialogService: TestFileDialogService, - @IBackupFileService public backupFileService: NodeTestBackupFileService, + @IWorkingCopyBackupService public workingCopyBackupService: NodeTestWorkingCopyBackupService, @IWorkingCopyService public workingCopyService: IWorkingCopyService, @IEditorService public editorService: IEditorService ) { @@ -280,8 +284,6 @@ export class TestServiceAccessor { export class TestNativePathService extends TestPathService { - declare readonly _serviceBrand: undefined; - constructor() { super(URI.file(homedir())); } diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index 350b2c9e..8aaf7217 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -74,6 +74,7 @@ import 'vs/workbench/services/mode/common/workbenchModeService'; import 'vs/workbench/services/commands/common/commandService'; import 'vs/workbench/services/themes/browser/workbenchThemeService'; import 'vs/workbench/services/label/common/labelService'; +import 'vs/workbench/services/extensions/common/extensionManifestPropertiesService'; import 'vs/workbench/services/extensionManagement/common/webExtensionsScannerService'; import 'vs/workbench/services/extensionManagement/browser/extensionEnablementService'; import 'vs/workbench/services/extensionManagement/browser/builtinExtensionsScannerService'; @@ -85,6 +86,7 @@ import 'vs/workbench/services/userDataSync/common/userDataSyncUtil'; import 'vs/workbench/services/remote/common/remoteExplorerService'; import 'vs/workbench/services/workingCopy/common/workingCopyService'; import 'vs/workbench/services/workingCopy/common/workingCopyFileService'; +import 'vs/workbench/services/workingCopy/common/workingCopyEditorService'; import 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import 'vs/workbench/services/views/browser/viewDescriptorService'; import 'vs/workbench/services/quickinput/browser/quickInputService'; @@ -143,6 +145,9 @@ registerSingleton(IOpenerService, OpenerService, true); //#region --- workbench contributions +// Editor Override +import 'vs/workbench/services/editor/browser/editorOverrideService'; + // Telemetry import 'vs/workbench/contrib/telemetry/browser/telemetry.contribution'; @@ -171,10 +176,7 @@ import 'vs/workbench/contrib/files/browser/explorerViewlet'; import 'vs/workbench/contrib/files/browser/fileActions.contribution'; import 'vs/workbench/contrib/files/browser/files.contribution'; -// Backup -import 'vs/workbench/contrib/backup/common/backup.contribution'; - -// bulkEdit +// Bulk Edit import 'vs/workbench/contrib/bulkEdit/browser/bulkEditService'; import 'vs/workbench/contrib/bulkEdit/browser/preview/bulkEdit.contribution'; @@ -281,7 +283,6 @@ import 'vs/workbench/contrib/surveys/browser/languageSurveys.contribution'; import 'vs/workbench/contrib/welcome/overlay/browser/welcomeOverlay'; import 'vs/workbench/contrib/welcome/page/browser/welcomePage.contribution'; import 'vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.contribution'; -import 'vs/workbench/contrib/welcome/walkthroughs/browser/walkthroughs.contribution'; import 'vs/workbench/contrib/welcome/walkThrough/browser/walkThrough.contribution'; // Call Hierarchy diff --git a/src/vs/workbench/workbench.desktop.main.ts b/src/vs/workbench/workbench.desktop.main.ts index f40d776f..7cf060c4 100644 --- a/src/vs/workbench/workbench.desktop.main.ts +++ b/src/vs/workbench/workbench.desktop.main.ts @@ -110,8 +110,6 @@ import 'vs/workbench/contrib/webview/electron-browser/webview.contribution'; // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -// Notebook -import 'vs/workbench/contrib/notebook/electron-browser/notebook.contribution'; // Extensions Management import 'vs/workbench/contrib/extensions/electron-browser/extensions.contribution'; @@ -138,10 +136,6 @@ import 'vs/workbench/contrib/externalTerminal/node/externalTerminal.contribution // CLI import 'vs/workbench/contrib/cli/node/cli.contribution'; -// Tasks -import 'vs/workbench/contrib/tasks/electron-browser/taskService'; - - // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // diff --git a/src/vs/workbench/workbench.sandbox.main.ts b/src/vs/workbench/workbench.sandbox.main.ts index b4e7e1c8..45db92ae 100644 --- a/src/vs/workbench/workbench.sandbox.main.ts +++ b/src/vs/workbench/workbench.sandbox.main.ts @@ -63,10 +63,11 @@ import 'vs/workbench/services/ipc/electron-sandbox/sharedProcessService'; import 'vs/workbench/services/timer/electron-sandbox/timerService'; import 'vs/workbench/services/environment/electron-sandbox/shellEnvironmentService'; import 'vs/workbench/services/integrity/electron-sandbox/integrityService'; -import 'vs/workbench/services/backup/electron-sandbox/backupFileService'; +import 'vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupService'; import 'vs/platform/diagnostics/electron-sandbox/diagnosticsService'; import 'vs/platform/checksum/electron-sandbox/checksumService'; import 'vs/platform/telemetry/electron-sandbox/customEndpointTelemetryService'; +import 'vs/workbench/services/files/electron-sandbox/elevatedFileService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IUserDataInitializationService, UserDataInitializationService } from 'vs/workbench/services/userData/browser/userDataInit'; @@ -94,9 +95,6 @@ import 'vs/workbench/electron-sandbox/desktop.contribution'; import 'vs/workbench/contrib/files/electron-sandbox/files.contribution'; import 'vs/workbench/contrib/files/electron-sandbox/fileActions.contribution'; -// Backup -import 'vs/workbench/contrib/backup/electron-sandbox/backup.contribution'; - // CodeEditor Contributions import 'vs/workbench/contrib/codeEditor/electron-sandbox/codeEditor.contribution'; @@ -134,4 +132,7 @@ import 'vs/workbench/contrib/tags/electron-sandbox/tags.contribution'; // Performance import 'vs/workbench/contrib/performance/electron-sandbox/performance.contribution'; +// Tasks +import 'vs/workbench/contrib/tasks/electron-sandbox/taskService'; + //#endregion diff --git a/src/vs/workbench/workbench.web.api.ts b/src/vs/workbench/workbench.web.api.ts index 7d3a06dd..84d1dd88 100644 --- a/src/vs/workbench/workbench.web.api.ts +++ b/src/vs/workbench/workbench.web.api.ts @@ -6,7 +6,6 @@ import 'vs/workbench/workbench.web.main'; import { main } from 'vs/workbench/browser/web.main'; import { UriComponents, URI } from 'vs/base/common/uri'; -import { IFileSystemProvider, FileSystemProviderCapabilities, IFileChange, FileChangeType } from 'vs/platform/files/common/files'; import { IWebSocketFactory, IWebSocket } from 'vs/platform/remote/browser/browserSocketFactory'; import { IExtensionManifest } from 'vs/platform/extensions/common/extensions'; import { IURLCallbackProvider } from 'vs/workbench/services/url/browser/urlService'; @@ -16,7 +15,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { IWorkspaceProvider, IWorkspace } from 'vs/workbench/services/host/browser/browserHostService'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; -import { IProductConfiguration } from 'vs/platform/product/common/productService'; +import { IProductConfiguration } from 'vs/base/common/product'; import { mark } from 'vs/base/common/performance'; import { ICredentialsProvider } from 'vs/workbench/services/credentials/common/credentials'; import { TunnelProviderFeatures } from 'vs/platform/remote/common/tunnel'; @@ -27,7 +26,7 @@ interface IResourceUriProvider { interface IStaticExtension { packageJSON: IExtensionManifest; - extensionLocation: URI; + extensionLocation: UriComponents; isBuiltin?: boolean; } @@ -320,12 +319,12 @@ interface IWorkbenchConstructionOptions { /** * Add static extensions that cannot be uninstalled but only be disabled. */ - readonly staticExtensions?: ReadonlyArray; + readonly staticExtensions?: readonly IStaticExtension[]; /** * [TEMPORARY]: This will be removed soon. * Enable inlined extensions. - * Defaults to false on serverful and true on serverless. + * Defaults to true. */ readonly _enableBuiltinExtensions?: boolean; @@ -404,19 +403,35 @@ interface IWorkbenchConstructionOptions { //#endregion - //#region Diagnostics + //#region Development options + + readonly developmentOptions?: IDevelopmentOptions; + + //#endregion + +} + +interface IDevelopmentOptions { /** * Current logging level. Default is `LogLevel.Info`. */ readonly logLevel?: LogLevel; + /** + * Location of a module containing extension tests to run once the workbench is open. + */ + readonly extensionTestsPath?: UriComponents; + + /** + * Add extensions under development. + */ + readonly extensions?: readonly IStaticExtension[]; + /** * Whether to enable the smoke test driver. */ - readonly driver?: boolean; - - //#endregion + readonly enableSmokeTestDriver?: boolean; } interface IPerformanceMark { @@ -566,12 +581,6 @@ export { IWorkspace, IWorkspaceProvider, - // FileSystem - IFileSystemProvider, - FileSystemProviderCapabilities, - IFileChange, - FileChangeType, - // WebSockets IWebSocketFactory, IWebSocket, @@ -632,7 +641,10 @@ export { // Env IPerformanceMark, - env + env, + + // Development + IDevelopmentOptions }; //#endregion diff --git a/src/vs/workbench/workbench.web.main.ts b/src/vs/workbench/workbench.web.main.ts index a322e384..054db752 100644 --- a/src/vs/workbench/workbench.web.main.ts +++ b/src/vs/workbench/workbench.web.main.ts @@ -56,9 +56,10 @@ import 'vs/workbench/services/extensionResourceLoader/browser/extensionResourceL import 'vs/workbench/services/path/browser/pathService'; import 'vs/workbench/services/themes/browser/browserHostColorSchemeService'; import 'vs/workbench/services/encryption/browser/encryptionService'; -import 'vs/workbench/services/backup/browser/backupFileService'; +import 'vs/workbench/services/workingCopy/browser/workingCopyBackupService'; import 'vs/workbench/services/remote/browser/tunnelServiceImpl'; import 'vs/workbench/services/userDataSync/browser/userDataAutoSyncEnablementService'; +import 'vs/workbench/services/files/browser/elevatedFileService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; @@ -115,9 +116,6 @@ import 'vs/workbench/contrib/output/common/outputChannelModelService'; // Explorer import 'vs/workbench/contrib/files/browser/files.web.contribution'; -// Backup -import 'vs/workbench/contrib/backup/browser/backup.web.contribution'; - // Preferences import 'vs/workbench/contrib/preferences/browser/keyboardLayoutPicker'; diff --git a/test/automation/package.json b/test/automation/package.json index 94006ad9..edad2f73 100644 --- a/test/automation/package.json +++ b/test/automation/package.json @@ -21,12 +21,12 @@ }, "devDependencies": { "@types/debug": "4.1.5", - "@types/mkdirp": "0.5.1", + "@types/mkdirp": "^1.0.1", "@types/ncp": "2.0.1", "@types/node": "^12.19.9", "@types/tmp": "0.1.0", - "cpx": "^1.5.0", - "mkdirp": "^0.5.1", + "cpx2": "3.0.0", + "mkdirp": "^1.0.4", "ncp": "^2.0.0", "npm-run-all": "^4.1.5", "tmp": "0.1.0", diff --git a/test/automation/yarn.lock b/test/automation/yarn.lock index 4e37fb4c..f3458ada 100644 --- a/test/automation/yarn.lock +++ b/test/automation/yarn.lock @@ -7,10 +7,10 @@ resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.5.tgz#b14efa8852b7768d898906613c23f688713e02cd" integrity sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ== -"@types/mkdirp@0.5.1": - version "0.5.1" - resolved "https://registry.yarnpkg.com/@types/mkdirp/-/mkdirp-0.5.1.tgz#ea887cd024f691c1ca67cce20b7606b053e43b0f" - integrity sha512-XA4vNO6GCBz8Smq0hqSRo4yRWMqr4FPQrWjhJt6nKskzly4/p87SfuJMFYGRyYb6jo2WNIQU2FDBsY5r1BibUA== +"@types/mkdirp@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@types/mkdirp/-/mkdirp-1.0.1.tgz#0930b948914a78587de35458b86c907b6e98bbf6" + integrity sha512-HkGSK7CGAXncr8Qn/0VqNtExEE+PHMWb+qlR1faHMao7ng6P3tAaoWWBMdva0gL5h4zprjIO89GJOLXsMcDm1Q== dependencies: "@types/node" "*" @@ -36,21 +36,6 @@ resolved "https://registry.yarnpkg.com/@types/tmp/-/tmp-0.1.0.tgz#19cf73a7bcf641965485119726397a096f0049bd" integrity sha512-6IwZ9HzWbCq6XoQWhxLpDjuADodH/MKXRUIDFudvgjcVdjFknvmR+DNsoUeer4XPrEnrZs04Jj+kfV9pFsrhmA== -abbrev@1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" - integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== - -ansi-regex@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" - integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= - -ansi-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" - integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= - ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" @@ -58,105 +43,16 @@ ansi-styles@^3.2.1: dependencies: color-convert "^1.9.0" -anymatch@^1.3.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-1.3.2.tgz#553dcb8f91e3c889845dfdba34c77721b90b9d7a" - integrity sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA== - dependencies: - micromatch "^2.1.5" - normalize-path "^2.0.0" - -aproba@^1.0.3: - version "1.2.0" - resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" - integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== - -are-we-there-yet@~1.1.2: - version "1.1.5" - resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" - integrity sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w== - dependencies: - delegates "^1.0.0" - readable-stream "^2.0.6" - -arr-diff@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-2.0.0.tgz#8f3b827f955a8bd669697e4a4256ac3ceae356cf" - integrity sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8= - dependencies: - arr-flatten "^1.0.1" - -arr-diff@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" - integrity sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA= - -arr-flatten@^1.0.1, arr-flatten@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" - integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== - -arr-union@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" - integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= - -array-unique@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.2.1.tgz#a1d97ccafcbc2625cc70fadceb36a50c58b01a53" - integrity sha1-odl8yvy8JiXMcPrc6zalDFiwGlM= - -array-unique@^0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" - integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= - -assign-symbols@^1.0.0: +at-least-node@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" - integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= - -async-each@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" - integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ== - -atob@^2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" - integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== - -babel-runtime@^6.9.2: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" - integrity sha1-llxwWGaOgrVde/4E/yM3vItWR/4= - dependencies: - core-js "^2.4.0" - regenerator-runtime "^0.11.0" + resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" + integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== balanced-match@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= -base@^0.11.1: - version "0.11.2" - resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" - integrity sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg== - dependencies: - cache-base "^1.0.1" - class-utils "^0.3.5" - component-emitter "^1.2.1" - define-property "^1.0.0" - isobject "^3.0.1" - mixin-deep "^1.2.0" - pascalcase "^0.1.1" - -binary-extensions@^1.0.0: - version "1.13.1" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65" - integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw== - brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -165,46 +61,6 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" -braces@^1.8.2: - version "1.8.5" - resolved "https://registry.yarnpkg.com/braces/-/braces-1.8.5.tgz#ba77962e12dff969d6b76711e914b737857bf6a7" - integrity sha1-uneWLhLf+WnWt2cR6RS3N4V79qc= - dependencies: - expand-range "^1.8.1" - preserve "^0.2.0" - repeat-element "^1.1.2" - -braces@^2.3.1: - version "2.3.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" - integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w== - dependencies: - arr-flatten "^1.1.0" - array-unique "^0.3.2" - extend-shallow "^2.0.1" - fill-range "^4.0.0" - isobject "^3.0.1" - repeat-element "^1.1.2" - snapdragon "^0.8.1" - snapdragon-node "^2.0.1" - split-string "^3.0.2" - to-regex "^3.0.1" - -cache-base@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" - integrity sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ== - dependencies: - collection-visit "^1.0.0" - component-emitter "^1.2.1" - get-value "^2.0.6" - has-value "^1.0.0" - isobject "^3.0.1" - set-value "^2.0.0" - to-object-path "^0.3.0" - union-value "^1.0.0" - unset-value "^1.0.0" - call-bind@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.0.tgz#24127054bb3f9bdcb4b1fb82418186072f77b8ce" @@ -222,49 +78,10 @@ chalk@^2.4.1: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chokidar@^1.6.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.7.0.tgz#798e689778151c8076b4b360e5edd28cda2bb468" - integrity sha1-eY5ol3gVHIB2tLNg5e3SjNortGg= - dependencies: - anymatch "^1.3.0" - async-each "^1.0.0" - glob-parent "^2.0.0" - inherits "^2.0.1" - is-binary-path "^1.0.0" - is-glob "^2.0.0" - path-is-absolute "^1.0.0" - readdirp "^2.0.0" - optionalDependencies: - fsevents "^1.0.0" - -chownr@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.2.tgz#a18f1e0b269c8a6a5d3c86eb298beb14c3dd7bf6" - integrity sha512-GkfeAQh+QNy3wquu9oIZr6SS5x7wGdSgNQvD10X3r+AZr1Oys22HW8kAmDMvNg2+Dm0TeGaEuO8gFwdBXxwO8A== - -class-utils@^0.3.5: - version "0.3.6" - resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" - integrity sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg== - dependencies: - arr-union "^3.1.0" - define-property "^0.2.5" - isobject "^3.0.0" - static-extend "^0.1.1" - -code-point-at@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" - integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= - -collection-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" - integrity sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA= - dependencies: - map-visit "^1.0.0" - object-visit "^1.0.0" +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ= color-convert@^1.9.0: version "1.9.3" @@ -278,51 +95,27 @@ color-name@1.1.3: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= -component-emitter@^1.2.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" - integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== - concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= -console-control-strings@^1.0.0, console-control-strings@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" - integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= - -copy-descriptor@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" - integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= - -core-js@^2.4.0: - version "2.6.9" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.9.tgz#6b4b214620c834152e179323727fc19741b084f2" - integrity sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A== - -core-util-is@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" - integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= - -cpx@^1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/cpx/-/cpx-1.5.0.tgz#185be018511d87270dedccc293171e37655ab88f" - integrity sha1-GFvgGFEdhycN7czCkxceN2VauI8= +cpx2@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cpx2/-/cpx2-3.0.0.tgz#d02d2526f0d6fd967405ab20b687668aca1f55ea" + integrity sha512-WVI69l0qqlDboGngiggQitRyto20og3YNNZp6ySva9dRMYpy9OQd5ep7mQvkvuBeUkIluOKR6jBOek7FRS7X0w== dependencies: - babel-runtime "^6.9.2" - chokidar "^1.6.0" + co "^4.6.0" + debounce "^1.2.0" + debug "^4.1.1" duplexer "^0.1.1" - glob "^7.0.5" - glob2base "^0.0.12" - minimatch "^3.0.2" - mkdirp "^0.5.1" - resolve "^1.1.7" - safe-buffer "^5.0.1" - shell-quote "^1.6.1" + fs-extra "^9.0.1" + glob "^7.1.4" + glob2base "0.0.12" + minimatch "^3.0.4" + resolve "^1.12.0" + safe-buffer "^5.2.0" + shell-quote "^1.7.1" subarg "^1.0.0" cross-spawn@^6.0.5: @@ -336,29 +129,17 @@ cross-spawn@^6.0.5: shebang-command "^1.2.0" which "^1.2.9" -debug@^2.2.0, debug@^2.3.3: - version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== +debounce@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.1.tgz#38881d8f4166a5c5848020c11827b834bcb3e0a5" + integrity sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug== + +debug@^4.1.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" + integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== dependencies: - ms "2.0.0" - -debug@^3.2.6: - version "3.2.6" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" - integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== - dependencies: - ms "^2.1.1" - -decode-uri-component@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" - integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= - -deep-extend@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" - integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== + ms "2.1.2" define-properties@^1.1.3: version "1.1.3" @@ -367,38 +148,6 @@ define-properties@^1.1.3: dependencies: object-keys "^1.0.12" -define-property@^0.2.5: - version "0.2.5" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" - integrity sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY= - dependencies: - is-descriptor "^0.1.0" - -define-property@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" - integrity sha1-dp66rz9KY6rTr56NMEybvnm/sOY= - dependencies: - is-descriptor "^1.0.0" - -define-property@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" - integrity sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ== - dependencies: - is-descriptor "^1.0.2" - isobject "^3.0.1" - -delegates@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" - integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= - -detect-libc@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" - integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= - duplexer@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1" @@ -450,158 +199,31 @@ exec-sh@^0.2.0: dependencies: merge "^1.2.0" -expand-brackets@^0.1.4: - version "0.1.5" - resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-0.1.5.tgz#df07284e342a807cd733ac5af72411e581d1177b" - integrity sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s= - dependencies: - is-posix-bracket "^0.1.0" - -expand-brackets@^2.1.4: - version "2.1.4" - resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" - integrity sha1-t3c14xXOMPa27/D4OwQVGiJEliI= - dependencies: - debug "^2.3.3" - define-property "^0.2.5" - extend-shallow "^2.0.1" - posix-character-classes "^0.1.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -expand-range@^1.8.1: - version "1.8.2" - resolved "https://registry.yarnpkg.com/expand-range/-/expand-range-1.8.2.tgz#a299effd335fe2721ebae8e257ec79644fc85337" - integrity sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc= - dependencies: - fill-range "^2.1.0" - -extend-shallow@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" - integrity sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8= - dependencies: - is-extendable "^0.1.0" - -extend-shallow@^3.0.0, extend-shallow@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" - integrity sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg= - dependencies: - assign-symbols "^1.0.0" - is-extendable "^1.0.1" - -extglob@^0.3.1: - version "0.3.2" - resolved "https://registry.yarnpkg.com/extglob/-/extglob-0.3.2.tgz#2e18ff3d2f49ab2765cec9023f011daa8d8349a1" - integrity sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE= - dependencies: - is-extglob "^1.0.0" - -extglob@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" - integrity sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw== - dependencies: - array-unique "^0.3.2" - define-property "^1.0.0" - expand-brackets "^2.1.4" - extend-shallow "^2.0.1" - fragment-cache "^0.2.1" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -filename-regex@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26" - integrity sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY= - -fill-range@^2.1.0: - version "2.2.4" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.4.tgz#eb1e773abb056dcd8df2bfdf6af59b8b3a936565" - integrity sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q== - dependencies: - is-number "^2.1.0" - isobject "^2.0.0" - randomatic "^3.0.0" - repeat-element "^1.1.2" - repeat-string "^1.5.2" - -fill-range@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" - integrity sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc= - dependencies: - extend-shallow "^2.0.1" - is-number "^3.0.0" - repeat-string "^1.6.1" - to-regex-range "^2.1.0" - find-index@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/find-index/-/find-index-0.1.1.tgz#675d358b2ca3892d795a1ab47232f8b6e2e0dde4" integrity sha1-Z101iyyjiS15Whq0cjL4tuLg3eQ= -for-in@^1.0.1, for-in@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" - integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= - -for-own@^0.1.4: - version "0.1.5" - resolved "https://registry.yarnpkg.com/for-own/-/for-own-0.1.5.tgz#5265c681a4f294dabbf17c9509b6763aa84510ce" - integrity sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4= +fs-extra@^9.0.1: + version "9.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" + integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== dependencies: - for-in "^1.0.1" - -fragment-cache@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" - integrity sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk= - dependencies: - map-cache "^0.2.2" - -fs-minipass@^1.2.5: - version "1.2.6" - resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.6.tgz#2c5cc30ded81282bfe8a0d7c7c1853ddeb102c07" - integrity sha512-crhvyXcMejjv3Z5d2Fa9sf5xLYVCF5O1c71QxbVnbLsmYMBEvDAftewesN/HhY03YRoA7zOMxjNGrF5svGaaeQ== - dependencies: - minipass "^2.2.1" + at-least-node "^1.0.0" + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= -fsevents@^1.0.0: - version "1.2.9" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.9.tgz#3f5ed66583ccd6f400b5a00db6f7e861363e388f" - integrity sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw== - dependencies: - nan "^2.12.1" - node-pre-gyp "^0.12.0" - function-bind@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== -gauge@~2.7.3: - version "2.7.4" - resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" - integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c= - dependencies: - aproba "^1.0.3" - console-control-strings "^1.0.0" - has-unicode "^2.0.0" - object-assign "^4.1.0" - signal-exit "^3.0.0" - string-width "^1.0.1" - strip-ansi "^3.0.1" - wide-align "^1.1.0" - get-intrinsic@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.0.1.tgz#94a9768fcbdd0595a1c9273aacf4c89d075631be" @@ -611,34 +233,14 @@ get-intrinsic@^1.0.0: has "^1.0.3" has-symbols "^1.0.1" -get-value@^2.0.3, get-value@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" - integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= - -glob-base@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4" - integrity sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q= - dependencies: - glob-parent "^2.0.0" - is-glob "^2.0.0" - -glob-parent@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-2.0.0.tgz#81383d72db054fcccf5336daa902f182f6edbb28" - integrity sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg= - dependencies: - is-glob "^2.0.0" - -glob2base@^0.0.12: +glob2base@0.0.12: version "0.0.12" resolved "https://registry.yarnpkg.com/glob2base/-/glob2base-0.0.12.tgz#9d419b3e28f12e83a362164a277055922c9c0d56" integrity sha1-nUGbPijxLoOjYhZKJ3BVkiycDVY= dependencies: find-index "^0.1.1" -glob@^7.0.5, glob@^7.1.3: +glob@^7.1.3: version "7.1.4" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255" integrity sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A== @@ -650,16 +252,28 @@ glob@^7.0.5, glob@^7.1.3: once "^1.3.0" path-is-absolute "^1.0.0" -graceful-fs@^4.1.11: - version "4.2.2" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.2.tgz#6f0952605d0140c1cfdb138ed005775b92d67b02" - integrity sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q== +glob@^7.1.4: + version "7.1.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" + integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" graceful-fs@^4.1.2: version "4.2.4" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== +graceful-fs@^4.1.6, graceful-fs@^4.2.0: + version "4.2.6" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee" + integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ== + has-flag@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" @@ -670,42 +284,6 @@ has-symbols@^1.0.1: resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== -has-unicode@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" - integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= - -has-value@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" - integrity sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8= - dependencies: - get-value "^2.0.3" - has-values "^0.1.4" - isobject "^2.0.0" - -has-value@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" - integrity sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc= - dependencies: - get-value "^2.0.6" - has-values "^1.0.0" - isobject "^3.0.0" - -has-values@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" - integrity sha1-bWHeldkd/Km5oCCJrThL/49it3E= - -has-values@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" - integrity sha1-lbC2P+whRmGab+V/51Yo1aOe/k8= - dependencies: - is-number "^3.0.0" - kind-of "^4.0.0" - has@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" @@ -718,20 +296,6 @@ hosted-git-info@^2.1.4: resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488" integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg== -iconv-lite@^0.4.4: - version "0.4.24" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" - integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== - dependencies: - safer-buffer ">= 2.1.2 < 3" - -ignore-walk@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.1.tgz#a83e62e7d272ac0e3b551aaa82831a19b69f82f8" - integrity sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ== - dependencies: - minimatch "^3.0.4" - inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" @@ -740,185 +304,38 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@^2.0.1, inherits@~2.0.3: +inherits@2: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== -ini@~1.3.0: - version "1.3.5" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" - integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== - -is-accessor-descriptor@^0.1.6: - version "0.1.6" - resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" - integrity sha1-qeEss66Nh2cn7u84Q/igiXtcmNY= - dependencies: - kind-of "^3.0.2" - -is-accessor-descriptor@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656" - integrity sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ== - dependencies: - kind-of "^6.0.0" - is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= -is-binary-path@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" - integrity sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg= - dependencies: - binary-extensions "^1.0.0" - -is-buffer@^1.1.5: - version "1.1.6" - resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" - integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== - is-callable@^1.1.4, is-callable@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.2.tgz#c7c6715cd22d4ddb48d3e19970223aceabb080d9" integrity sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA== -is-core-module@^2.1.0: +is-core-module@^2.1.0, is-core-module@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.2.0.tgz#97037ef3d52224d85163f5597b2b63d9afed981a" integrity sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ== dependencies: has "^1.0.3" -is-data-descriptor@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" - integrity sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y= - dependencies: - kind-of "^3.0.2" - -is-data-descriptor@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7" - integrity sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ== - dependencies: - kind-of "^6.0.0" - is-date-object@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g== -is-descriptor@^0.1.0: - version "0.1.6" - resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" - integrity sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg== - dependencies: - is-accessor-descriptor "^0.1.6" - is-data-descriptor "^0.1.4" - kind-of "^5.0.0" - -is-descriptor@^1.0.0, is-descriptor@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" - integrity sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg== - dependencies: - is-accessor-descriptor "^1.0.0" - is-data-descriptor "^1.0.0" - kind-of "^6.0.2" - -is-dotfile@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.3.tgz#a6a2f32ffd2dfb04f5ca25ecd0f6b83cf798a1e1" - integrity sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE= - -is-equal-shallow@^0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz#2238098fc221de0bcfa5d9eac4c45d638aa1c534" - integrity sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ= - dependencies: - is-primitive "^2.0.0" - -is-extendable@^0.1.0, is-extendable@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" - integrity sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik= - -is-extendable@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" - integrity sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA== - dependencies: - is-plain-object "^2.0.4" - -is-extglob@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0" - integrity sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA= - -is-fullwidth-code-point@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" - integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs= - dependencies: - number-is-nan "^1.0.0" - -is-fullwidth-code-point@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" - integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= - -is-glob@^2.0.0, is-glob@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863" - integrity sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM= - dependencies: - is-extglob "^1.0.0" - is-negative-zero@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24" integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w== -is-number@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f" - integrity sha1-Afy7s5NGOlSPL0ZszhbezknbkI8= - dependencies: - kind-of "^3.0.2" - -is-number@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" - integrity sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU= - dependencies: - kind-of "^3.0.2" - -is-number@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-4.0.0.tgz#0026e37f5454d73e356dfe6564699867c6a7f0ff" - integrity sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ== - -is-plain-object@^2.0.3, is-plain-object@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" - integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== - dependencies: - isobject "^3.0.1" - -is-posix-bracket@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz#3334dc79774368e92f016e6fbc0a88f5cd6e6bc4" - integrity sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q= - -is-primitive@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-2.0.0.tgz#207bab91638499c07b2adf240a41a87210034575" - integrity sha1-IHurkWOEmcB7Kt8kCkGochADRXU= - is-regex@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.1.tgz#c6f98aacc546f6cec5468a07b7b153ab564a57b9" @@ -933,61 +350,24 @@ is-symbol@^1.0.2: dependencies: has-symbols "^1.0.1" -is-windows@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" - integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== - -isarray@1.0.0, isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= - isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= -isobject@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" - integrity sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk= - dependencies: - isarray "1.0.0" - -isobject@^3.0.0, isobject@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" - integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= - json-parse-better-errors@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== -kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: - version "3.2.2" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" - integrity sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ= +jsonfile@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" + integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== dependencies: - is-buffer "^1.1.5" - -kind-of@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" - integrity sha1-IIE989cSkosgc3hpGkUGb65y3Vc= - dependencies: - is-buffer "^1.1.5" - -kind-of@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" - integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== - -kind-of@^6.0.0, kind-of@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051" - integrity sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA== + universalify "^2.0.0" + optionalDependencies: + graceful-fs "^4.1.6" load-json-file@^4.0.0: version "4.0.0" @@ -999,23 +379,6 @@ load-json-file@^4.0.0: pify "^3.0.0" strip-bom "^3.0.0" -map-cache@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" - integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= - -map-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" - integrity sha1-7Nyo8TFE5mDxtb1B8S80edmN+48= - dependencies: - object-visit "^1.0.0" - -math-random@^1.0.1: - version "1.0.4" - resolved "https://registry.yarnpkg.com/math-random/-/math-random-1.0.4.tgz#5dd6943c938548267016d4e34f057583080c514c" - integrity sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A== - memorystream@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/memorystream/-/memorystream-0.3.1.tgz#86d7090b30ce455d63fbae12dda51a47ddcaf9b2" @@ -1026,166 +389,38 @@ merge@^1.2.0: resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.1.tgz#38bebf80c3220a8a487b6fcfb3941bb11720c145" integrity sha512-VjFo4P5Whtj4vsLzsYBu5ayHhoHJ0UqNm7ibvShmbmoz7tGi0vXaoJbGdB+GmDMLUdg8DpQXEIeVDAe8MaABvQ== -micromatch@^2.1.5: - version "2.3.11" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565" - integrity sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU= - dependencies: - arr-diff "^2.0.0" - array-unique "^0.2.1" - braces "^1.8.2" - expand-brackets "^0.1.4" - extglob "^0.3.1" - filename-regex "^2.0.0" - is-extglob "^1.0.0" - is-glob "^2.0.1" - kind-of "^3.0.2" - normalize-path "^2.0.1" - object.omit "^2.0.0" - parse-glob "^3.0.4" - regex-cache "^0.4.2" - -micromatch@^3.1.10: - version "3.1.10" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" - integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - braces "^2.3.1" - define-property "^2.0.2" - extend-shallow "^3.0.2" - extglob "^2.0.4" - fragment-cache "^0.2.1" - kind-of "^6.0.2" - nanomatch "^1.2.9" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.2" - -minimatch@^3.0.2, minimatch@^3.0.4: +minimatch@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== dependencies: brace-expansion "^1.1.7" -minimist@0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" - integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= - minimist@^1.1.0, minimist@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= -minipass@^2.2.1, minipass@^2.3.5: - version "2.3.5" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.5.tgz#cacebe492022497f656b0f0f51e2682a9ed2d848" - integrity sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA== - dependencies: - safe-buffer "^5.1.2" - yallist "^3.0.0" +mkdirp@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== -minizlib@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.2.1.tgz#dd27ea6136243c7c880684e8672bb3a45fd9b614" - integrity sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA== - dependencies: - minipass "^2.2.1" - -mixin-deep@^1.2.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" - integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA== - dependencies: - for-in "^1.0.2" - is-extendable "^1.0.1" - -mkdirp@^0.5.0, mkdirp@^0.5.1: - version "0.5.1" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" - integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= - dependencies: - minimist "0.0.8" - -ms@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= - -ms@^2.1.1: +ms@2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -nan@^2.12.1: - version "2.14.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" - integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg== - -nanomatch@^1.2.9: - version "1.2.13" - resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" - integrity sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA== - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - define-property "^2.0.2" - extend-shallow "^3.0.2" - fragment-cache "^0.2.1" - is-windows "^1.0.2" - kind-of "^6.0.2" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - ncp@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ncp/-/ncp-2.0.0.tgz#195a21d6c46e361d2fb1281ba38b91e9df7bdbb3" integrity sha1-GVoh1sRuNh0vsSgbo4uR6d9727M= -needle@^2.2.1: - version "2.4.0" - resolved "https://registry.yarnpkg.com/needle/-/needle-2.4.0.tgz#6833e74975c444642590e15a750288c5f939b57c" - integrity sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg== - dependencies: - debug "^3.2.6" - iconv-lite "^0.4.4" - sax "^1.2.4" - nice-try@^1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== -node-pre-gyp@^0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.12.0.tgz#39ba4bb1439da030295f899e3b520b7785766149" - integrity sha512-4KghwV8vH5k+g2ylT+sLTjy5wmUOb9vPhnM8NHvRf9dHmnW/CndrFXy2aRPaPST6dugXSdHXfeaHQm77PIz/1A== - dependencies: - detect-libc "^1.0.2" - mkdirp "^0.5.1" - needle "^2.2.1" - nopt "^4.0.1" - npm-packlist "^1.1.6" - npmlog "^4.0.2" - rc "^1.2.7" - rimraf "^2.6.1" - semver "^5.3.0" - tar "^4" - -nopt@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d" - integrity sha1-0NRoWv1UFRk8jHUFYC0NF81kR00= - dependencies: - abbrev "1" - osenv "^0.1.4" - normalize-package-data@^2.3.2: version "2.5.0" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" @@ -1196,26 +431,6 @@ normalize-package-data@^2.3.2: semver "2 || 3 || 4 || 5" validate-npm-package-license "^3.0.1" -normalize-path@^2.0.0, normalize-path@^2.0.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" - integrity sha1-GrKLVW4Zg2Oowab35vogE3/mrtk= - dependencies: - remove-trailing-separator "^1.0.1" - -npm-bundled@^1.0.1: - version "1.0.6" - resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.6.tgz#e7ba9aadcef962bb61248f91721cd932b3fe6bdd" - integrity sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g== - -npm-packlist@^1.1.6: - version "1.4.4" - resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.4.tgz#866224233850ac534b63d1a6e76050092b5d2f44" - integrity sha512-zTLo8UcVYtDU3gdeaFu2Xu0n0EvelfHDGuqtNIn5RO7yQj4H1TqNdBc/yZjxnWA0PVB8D3Woyp0i5B43JwQ6Vw== - dependencies: - ignore-walk "^3.0.1" - npm-bundled "^1.0.1" - npm-run-all@^4.1.5: version "4.1.5" resolved "https://registry.yarnpkg.com/npm-run-all/-/npm-run-all-4.1.5.tgz#04476202a15ee0e2e214080861bff12a51d98fba" @@ -1231,35 +446,6 @@ npm-run-all@^4.1.5: shell-quote "^1.6.1" string.prototype.padend "^3.0.0" -npmlog@^4.0.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" - integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== - dependencies: - are-we-there-yet "~1.1.2" - console-control-strings "~1.1.0" - gauge "~2.7.3" - set-blocking "~2.0.0" - -number-is-nan@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" - integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= - -object-assign@^4.1.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" - integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= - -object-copy@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" - integrity sha1-fn2Fi3gb18mRpBupde04EnVOmYw= - dependencies: - copy-descriptor "^0.1.0" - define-property "^0.2.5" - kind-of "^3.0.3" - object-inspect@^1.8.0: version "1.9.0" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.9.0.tgz#c90521d74e1127b67266ded3394ad6116986533a" @@ -1270,13 +456,6 @@ object-keys@^1.0.12, object-keys@^1.1.1: resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== -object-visit@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" - integrity sha1-95xEk68MU3e1n+OdOV5BBC3QRbs= - dependencies: - isobject "^3.0.0" - object.assign@^4.1.1: version "4.1.2" resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" @@ -1287,21 +466,6 @@ object.assign@^4.1.1: has-symbols "^1.0.1" object-keys "^1.1.1" -object.omit@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/object.omit/-/object.omit-2.0.1.tgz#1a9c744829f39dbb858c76ca3579ae2a54ebd1fa" - integrity sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo= - dependencies: - for-own "^0.1.4" - is-extendable "^0.1.1" - -object.pick@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" - integrity sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c= - dependencies: - isobject "^3.0.1" - once@^1.3.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" @@ -1309,34 +473,6 @@ once@^1.3.0: dependencies: wrappy "1" -os-homedir@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" - integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= - -os-tmpdir@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" - integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= - -osenv@^0.1.4: - version "0.1.5" - resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" - integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g== - dependencies: - os-homedir "^1.0.0" - os-tmpdir "^1.0.0" - -parse-glob@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/parse-glob/-/parse-glob-3.0.4.tgz#b2c376cfb11f35513badd173ef0bb6e3a388391c" - integrity sha1-ssN2z7EfNVE7rdFz7wu246OIORw= - dependencies: - glob-base "^0.3.0" - is-dotfile "^1.0.0" - is-extglob "^1.0.0" - is-glob "^2.0.0" - parse-json@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" @@ -1345,11 +481,6 @@ parse-json@^4.0.0: error-ex "^1.3.1" json-parse-better-errors "^1.0.1" -pascalcase@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" - integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= - path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" @@ -1382,40 +513,6 @@ pify@^3.0.0: resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY= -posix-character-classes@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" - integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= - -preserve@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" - integrity sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks= - -process-nextick-args@~2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" - integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== - -randomatic@^3.0.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-3.1.1.tgz#b776efc59375984e36c537b2f51a1f0aff0da1ed" - integrity sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw== - dependencies: - is-number "^4.0.0" - kind-of "^6.0.0" - math-random "^1.0.1" - -rc@^1.2.7: - version "1.2.8" - resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" - integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== - dependencies: - deep-extend "^0.6.0" - ini "~1.3.0" - minimist "^1.2.0" - strip-json-comments "~2.0.1" - read-pkg@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389" @@ -1425,75 +522,6 @@ read-pkg@^3.0.0: normalize-package-data "^2.3.2" path-type "^3.0.0" -readable-stream@^2.0.2, readable-stream@^2.0.6: - version "2.3.6" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" - integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw== - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~2.0.0" - safe-buffer "~5.1.1" - string_decoder "~1.1.1" - util-deprecate "~1.0.1" - -readdirp@^2.0.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525" - integrity sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ== - dependencies: - graceful-fs "^4.1.11" - micromatch "^3.1.10" - readable-stream "^2.0.2" - -regenerator-runtime@^0.11.0: - version "0.11.1" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" - integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== - -regex-cache@^0.4.2: - version "0.4.4" - resolved "https://registry.yarnpkg.com/regex-cache/-/regex-cache-0.4.4.tgz#75bdc58a2a1496cec48a12835bc54c8d562336dd" - integrity sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ== - dependencies: - is-equal-shallow "^0.1.3" - -regex-not@^1.0.0, regex-not@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" - integrity sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A== - dependencies: - extend-shallow "^3.0.2" - safe-regex "^1.1.0" - -remove-trailing-separator@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" - integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8= - -repeat-element@^1.1.2: - version "1.1.3" - resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce" - integrity sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g== - -repeat-string@^1.5.2, repeat-string@^1.6.1: - version "1.6.1" - resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" - integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= - -resolve-url@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" - integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= - -resolve@^1.1.7: - version "1.12.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.12.0.tgz#3fc644a35c84a48554609ff26ec52b66fa577df6" - integrity sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w== - dependencies: - path-parse "^1.0.6" - resolve@^1.10.0: version "1.19.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.19.0.tgz#1af5bf630409734a067cae29318aac7fa29a267c" @@ -1502,65 +530,31 @@ resolve@^1.10.0: is-core-module "^2.1.0" path-parse "^1.0.6" -ret@~0.1.10: - version "0.1.15" - resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" - integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== +resolve@^1.12.0: + version "1.20.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" + integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== + dependencies: + is-core-module "^2.2.0" + path-parse "^1.0.6" -rimraf@^2.6.1, rimraf@^2.6.3: +rimraf@^2.6.3: version "2.7.0" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.0.tgz#eb43198c5e2fb83b9323abee63bd87836f9a7c85" integrity sha512-4Liqw7ccABzsWV5BzeZeGRSq7KWIgQYzOcmRDEwSX4WAawlQpcAFXZ1Kid72XYrjSnK5yxOS6Gez/iGusYE/Pw== dependencies: glob "^7.1.3" -safe-buffer@^5.0.1, safe-buffer@^5.1.2: - version "5.2.0" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" - integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== +safe-buffer@^5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== -safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - -safe-regex@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" - integrity sha1-QKNmnzsHfR6UPURinhV91IAjvy4= - dependencies: - ret "~0.1.10" - -"safer-buffer@>= 2.1.2 < 3": - version "2.1.2" - resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" - integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== - -sax@^1.2.4: - version "1.2.4" - resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" - integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== - -"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.5.0: +"semver@2 || 3 || 4 || 5", semver@^5.5.0: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== -set-blocking@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" - integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= - -set-value@^2.0.0, set-value@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" - integrity sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw== - dependencies: - extend-shallow "^2.0.1" - is-extendable "^0.1.1" - is-plain-object "^2.0.3" - split-string "^3.0.1" - shebang-command@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" @@ -1578,61 +572,10 @@ shell-quote@^1.6.1: resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.1.tgz#3161d969886fb14f9140c65245a5dd19b6f0b06b" integrity sha512-2kUqeAGnMAu6YrTPX4E3LfxacH9gKljzVjlkUeSqY0soGwK4KLl7TURXCem712tkhBCeeaFP9QK4dKn88s3Icg== -signal-exit@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" - integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= - -snapdragon-node@^2.0.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" - integrity sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw== - dependencies: - define-property "^1.0.0" - isobject "^3.0.0" - snapdragon-util "^3.0.1" - -snapdragon-util@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" - integrity sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ== - dependencies: - kind-of "^3.2.0" - -snapdragon@^0.8.1: - version "0.8.2" - resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" - integrity sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg== - dependencies: - base "^0.11.1" - debug "^2.2.0" - define-property "^0.2.5" - extend-shallow "^2.0.1" - map-cache "^0.2.2" - source-map "^0.5.6" - source-map-resolve "^0.5.0" - use "^3.1.0" - -source-map-resolve@^0.5.0: - version "0.5.2" - resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.2.tgz#72e2cc34095543e43b2c62b2c4c10d4a9054f259" - integrity sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA== - dependencies: - atob "^2.1.1" - decode-uri-component "^0.2.0" - resolve-url "^0.2.1" - source-map-url "^0.4.0" - urix "^0.1.0" - -source-map-url@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" - integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM= - -source-map@^0.5.6: - version "0.5.7" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" - integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= +shell-quote@^1.7.1: + version "1.7.2" + resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.2.tgz#67a7d02c76c9da24f99d20808fcaded0e0e04be2" + integrity sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg== spdx-correct@^3.0.0: version "3.1.1" @@ -1660,38 +603,6 @@ spdx-license-ids@^3.0.0: resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.7.tgz#e9c18a410e5ed7e12442a549fbd8afa767038d65" integrity sha512-U+MTEOO0AiDzxwFvoa4JVnMV6mZlJKk2sBLt90s7G0Gd0Mlknc7kxEn3nuDPNZRta7O2uy8oLcZLVT+4sqNZHQ== -split-string@^3.0.1, split-string@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" - integrity sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw== - dependencies: - extend-shallow "^3.0.0" - -static-extend@^0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" - integrity sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY= - dependencies: - define-property "^0.2.5" - object-copy "^0.1.0" - -string-width@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" - integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= - dependencies: - code-point-at "^1.0.0" - is-fullwidth-code-point "^1.0.0" - strip-ansi "^3.0.0" - -"string-width@^1.0.2 || 2": - version "2.1.1" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" - integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== - dependencies: - is-fullwidth-code-point "^2.0.0" - strip-ansi "^4.0.0" - string.prototype.padend@^3.0.0: version "3.1.1" resolved "https://registry.yarnpkg.com/string.prototype.padend/-/string.prototype.padend-3.1.1.tgz#824c84265dbac46cade2b957b38b6a5d8d1683c5" @@ -1717,37 +628,11 @@ string.prototype.trimstart@^1.0.1: call-bind "^1.0.0" define-properties "^1.1.3" -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== - dependencies: - safe-buffer "~5.1.0" - -strip-ansi@^3.0.0, strip-ansi@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" - integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= - dependencies: - ansi-regex "^2.0.0" - -strip-ansi@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" - integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= - dependencies: - ansi-regex "^3.0.0" - strip-bom@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= -strip-json-comments@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" - integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= - subarg@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/subarg/-/subarg-1.0.0.tgz#f62cf17581e996b48fc965699f54c06ae268b8d2" @@ -1762,19 +647,6 @@ supports-color@^5.3.0: dependencies: has-flag "^3.0.0" -tar@^4: - version "4.4.10" - resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.10.tgz#946b2810b9a5e0b26140cf78bea6b0b0d689eba1" - integrity sha512-g2SVs5QIxvo6OLp0GudTqEf05maawKUxXru104iaayWA09551tFCTI8f1Asb4lPfkBr91k07iL4c11XO3/b0tA== - dependencies: - chownr "^1.1.1" - fs-minipass "^1.2.5" - minipass "^2.3.5" - minizlib "^1.2.1" - mkdirp "^0.5.0" - safe-buffer "^5.1.2" - yallist "^3.0.3" - tmp@0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.1.0.tgz#ee434a4e22543082e294ba6201dcc6eafefa2877" @@ -1782,31 +654,6 @@ tmp@0.1.0: dependencies: rimraf "^2.6.3" -to-object-path@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" - integrity sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68= - dependencies: - kind-of "^3.0.2" - -to-regex-range@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" - integrity sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg= - dependencies: - is-number "^3.0.0" - repeat-string "^1.6.1" - -to-regex@^3.0.1, to-regex@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" - integrity sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw== - dependencies: - define-property "^2.0.2" - extend-shallow "^3.0.2" - regex-not "^1.0.2" - safe-regex "^1.1.0" - tree-kill@1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" @@ -1817,38 +664,10 @@ typescript@3.7.5: resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.5.tgz#0692e21f65fd4108b9330238aac11dd2e177a1ae" integrity sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw== -union-value@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" - integrity sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg== - dependencies: - arr-union "^3.1.0" - get-value "^2.0.6" - is-extendable "^0.1.1" - set-value "^2.0.1" - -unset-value@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" - integrity sha1-g3aHP30jNRef+x5vw6jtDfyKtVk= - dependencies: - has-value "^0.3.1" - isobject "^3.0.0" - -urix@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" - integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= - -use@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" - integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== - -util-deprecate@~1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= +universalify@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" + integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== validate-npm-package-license@^3.0.1: version "3.0.4" @@ -1878,19 +697,7 @@ which@^1.2.9: dependencies: isexe "^2.0.0" -wide-align@^1.1.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" - integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== - dependencies: - string-width "^1.0.2 || 2" - wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= - -yallist@^3.0.0, yallist@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.3.tgz#b4b049e314be545e3ce802236d6cd22cd91c3de9" - integrity sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A== diff --git a/test/integration/browser/package.json b/test/integration/browser/package.json index 1b56f4ae..d29777d3 100644 --- a/test/integration/browser/package.json +++ b/test/integration/browser/package.json @@ -7,7 +7,7 @@ "compile": "tsc" }, "devDependencies": { - "@types/mkdirp": "0.5.1", + "@types/mkdirp": "^1.0.1", "@types/node": "^12.19.9", "@types/optimist": "0.0.29", "@types/rimraf": "^2.0.4", diff --git a/test/integration/browser/yarn.lock b/test/integration/browser/yarn.lock index d66658e6..8cdd9ff7 100644 --- a/test/integration/browser/yarn.lock +++ b/test/integration/browser/yarn.lock @@ -21,10 +21,10 @@ resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== -"@types/mkdirp@0.5.1": - version "0.5.1" - resolved "https://registry.yarnpkg.com/@types/mkdirp/-/mkdirp-0.5.1.tgz#ea887cd024f691c1ca67cce20b7606b053e43b0f" - integrity sha512-XA4vNO6GCBz8Smq0hqSRo4yRWMqr4FPQrWjhJt6nKskzly4/p87SfuJMFYGRyYb6jo2WNIQU2FDBsY5r1BibUA== +"@types/mkdirp@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@types/mkdirp/-/mkdirp-1.0.1.tgz#0930b948914a78587de35458b86c907b6e98bbf6" + integrity sha512-HkGSK7CGAXncr8Qn/0VqNtExEE+PHMWb+qlR1faHMao7ng6P3tAaoWWBMdva0gL5h4zprjIO89GJOLXsMcDm1Q== dependencies: "@types/node" "*" diff --git a/test/monaco/monaco.test.ts b/test/monaco/monaco.test.ts index 092145c8..65517721 100644 --- a/test/monaco/monaco.test.ts +++ b/test/monaco/monaco.test.ts @@ -76,7 +76,7 @@ describe('API Integration Tests', function (): void { }); it('`monaco` is not exposed as global', async function (): Promise { - assert.equal(await page.evaluate(`typeof monaco`), 'undefined'); + assert.strictEqual(await page.evaluate(`typeof monaco`), 'undefined'); }); it('Focus and Type', async function (): Promise { @@ -89,7 +89,7 @@ describe('API Integration Tests', function (): void { }); })() `); - assert.equal(await page.evaluate(`instance.getModel().getLineContent(1)`), 'afrom banana import *'); + assert.strictEqual(await page.evaluate(`instance.getModel().getLineContent(1)`), 'afrom banana import *'); }); it('Type and Undo', async function (): Promise { @@ -103,7 +103,7 @@ describe('API Integration Tests', function (): void { instance.getModel().undo(); })() `); - assert.equal(await page.evaluate(`instance.getModel().getLineContent(1)`), 'from banana import *'); + assert.strictEqual(await page.evaluate(`instance.getModel().getLineContent(1)`), 'from banana import *'); }); it('Multi Cursor', async function (): Promise { @@ -124,7 +124,7 @@ describe('API Integration Tests', function (): void { await page.waitForTimeout(1000); - assert.deepEqual(await page.evaluate(` + assert.deepStrictEqual(await page.evaluate(` [ instance.getModel().getLineContent(1), instance.getModel().getLineContent(2), diff --git a/test/smoke/package.json b/test/smoke/package.json index cb37f8db..910080d8 100644 --- a/test/smoke/package.json +++ b/test/smoke/package.json @@ -12,14 +12,13 @@ }, "devDependencies": { "@types/htmlparser2": "3.7.29", - "@types/mkdirp": "0.5.1", + "@types/mkdirp": "^1.0.1", "@types/mocha": "^8.2.0", "@types/ncp": "2.0.1", "@types/node": "^12.19.9", "@types/rimraf": "^2.0.4", - "cpx": "^1.5.0", "htmlparser2": "^3.9.2", - "mkdirp": "^0.5.1", + "mkdirp": "^1.0.4", "ncp": "^2.0.0", "npm-run-all": "^4.1.5", "portastic": "^1.0.1", diff --git a/test/smoke/yarn.lock b/test/smoke/yarn.lock index a5050d6a..979bf3b2 100644 --- a/test/smoke/yarn.lock +++ b/test/smoke/yarn.lock @@ -26,10 +26,10 @@ resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== -"@types/mkdirp@0.5.1": - version "0.5.1" - resolved "https://registry.yarnpkg.com/@types/mkdirp/-/mkdirp-0.5.1.tgz#ea887cd024f691c1ca67cce20b7606b053e43b0f" - integrity sha512-XA4vNO6GCBz8Smq0hqSRo4yRWMqr4FPQrWjhJt6nKskzly4/p87SfuJMFYGRyYb6jo2WNIQU2FDBsY5r1BibUA== +"@types/mkdirp@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@types/mkdirp/-/mkdirp-1.0.1.tgz#0930b948914a78587de35458b86c907b6e98bbf6" + integrity sha512-HkGSK7CGAXncr8Qn/0VqNtExEE+PHMWb+qlR1faHMao7ng6P3tAaoWWBMdva0gL5h4zprjIO89GJOLXsMcDm1Q== dependencies: "@types/node" "*" @@ -70,99 +70,11 @@ ansi-styles@^3.2.1: dependencies: color-convert "^1.9.0" -anymatch@^1.3.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-1.3.2.tgz#553dcb8f91e3c889845dfdba34c77721b90b9d7a" - integrity sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA== - dependencies: - micromatch "^2.1.5" - normalize-path "^2.0.0" - -arr-diff@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-2.0.0.tgz#8f3b827f955a8bd669697e4a4256ac3ceae356cf" - integrity sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8= - dependencies: - arr-flatten "^1.0.1" - -arr-diff@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" - integrity sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA= - -arr-flatten@^1.0.1, arr-flatten@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" - integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== - -arr-union@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" - integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= - -array-unique@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.2.1.tgz#a1d97ccafcbc2625cc70fadceb36a50c58b01a53" - integrity sha1-odl8yvy8JiXMcPrc6zalDFiwGlM= - -array-unique@^0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" - integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= - -assign-symbols@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" - integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= - -async-each@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" - integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ== - -atob@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" - integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== - -babel-runtime@^6.9.2: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" - integrity sha1-llxwWGaOgrVde/4E/yM3vItWR/4= - dependencies: - core-js "^2.4.0" - regenerator-runtime "^0.11.0" - balanced-match@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= -base@^0.11.1: - version "0.11.2" - resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" - integrity sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg== - dependencies: - cache-base "^1.0.1" - class-utils "^0.3.5" - component-emitter "^1.2.1" - define-property "^1.0.0" - isobject "^3.0.1" - mixin-deep "^1.2.0" - pascalcase "^0.1.1" - -binary-extensions@^1.0.0: - version "1.13.1" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65" - integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw== - -bindings@^1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" - integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== - dependencies: - file-uri-to-path "1.0.0" - bluebird@^2.9.34: version "2.11.0" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-2.11.0.tgz#534b9033c022c9579c56ba3b3e5a5caafbb650e1" @@ -176,46 +88,6 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" -braces@^1.8.2: - version "1.8.5" - resolved "https://registry.yarnpkg.com/braces/-/braces-1.8.5.tgz#ba77962e12dff969d6b76711e914b737857bf6a7" - integrity sha1-uneWLhLf+WnWt2cR6RS3N4V79qc= - dependencies: - expand-range "^1.8.1" - preserve "^0.2.0" - repeat-element "^1.1.2" - -braces@^2.3.1: - version "2.3.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" - integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w== - dependencies: - arr-flatten "^1.1.0" - array-unique "^0.3.2" - extend-shallow "^2.0.1" - fill-range "^4.0.0" - isobject "^3.0.1" - repeat-element "^1.1.2" - snapdragon "^0.8.1" - snapdragon-node "^2.0.1" - split-string "^3.0.2" - to-regex "^3.0.1" - -cache-base@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" - integrity sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ== - dependencies: - collection-visit "^1.0.0" - component-emitter "^1.2.1" - get-value "^2.0.6" - has-value "^1.0.0" - isobject "^3.0.1" - set-value "^2.0.0" - to-object-path "^0.3.0" - union-value "^1.0.0" - unset-value "^1.0.0" - call-bind@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.0.tgz#24127054bb3f9bdcb4b1fb82418186072f77b8ce" @@ -233,40 +105,6 @@ chalk@^2.4.1: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chokidar@^1.6.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.7.0.tgz#798e689778151c8076b4b360e5edd28cda2bb468" - integrity sha1-eY5ol3gVHIB2tLNg5e3SjNortGg= - dependencies: - anymatch "^1.3.0" - async-each "^1.0.0" - glob-parent "^2.0.0" - inherits "^2.0.1" - is-binary-path "^1.0.0" - is-glob "^2.0.0" - path-is-absolute "^1.0.0" - readdirp "^2.0.0" - optionalDependencies: - fsevents "^1.0.0" - -class-utils@^0.3.5: - version "0.3.6" - resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" - integrity sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg== - dependencies: - arr-union "^3.1.0" - define-property "^0.2.5" - isobject "^3.0.0" - static-extend "^0.1.1" - -collection-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" - integrity sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA= - dependencies: - map-visit "^1.0.0" - object-visit "^1.0.0" - color-convert@^1.9.0: version "1.9.3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" @@ -284,48 +122,11 @@ commander@^2.8.1: resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== -component-emitter@^1.2.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" - integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== - concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= -copy-descriptor@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" - integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= - -core-js@^2.4.0: - version "2.6.11" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.11.tgz#38831469f9922bded8ee21c9dc46985e0399308c" - integrity sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg== - -core-util-is@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" - integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= - -cpx@^1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/cpx/-/cpx-1.5.0.tgz#185be018511d87270dedccc293171e37655ab88f" - integrity sha1-GFvgGFEdhycN7czCkxceN2VauI8= - dependencies: - babel-runtime "^6.9.2" - chokidar "^1.6.0" - duplexer "^0.1.1" - glob "^7.0.5" - glob2base "^0.0.12" - minimatch "^3.0.2" - mkdirp "^0.5.1" - resolve "^1.1.7" - safe-buffer "^5.0.1" - shell-quote "^1.6.1" - subarg "^1.0.0" - cross-spawn@^6.0.5: version "6.0.5" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" @@ -337,18 +138,13 @@ cross-spawn@^6.0.5: shebang-command "^1.2.0" which "^1.2.9" -debug@^2.2.0, debug@^2.3.3: +debug@^2.2.0: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== dependencies: ms "2.0.0" -decode-uri-component@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" - integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= - define-properties@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" @@ -356,28 +152,6 @@ define-properties@^1.1.3: dependencies: object-keys "^1.0.12" -define-property@^0.2.5: - version "0.2.5" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" - integrity sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY= - dependencies: - is-descriptor "^0.1.0" - -define-property@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" - integrity sha1-dp66rz9KY6rTr56NMEybvnm/sOY= - dependencies: - is-descriptor "^1.0.0" - -define-property@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" - integrity sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ== - dependencies: - is-descriptor "^1.0.2" - isobject "^3.0.1" - dom-serializer@0: version "0.2.2" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51" @@ -411,11 +185,6 @@ domutils@^1.5.1: dom-serializer "0" domelementtype "1" -duplexer@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1" - integrity sha1-rOb/gIwc5mtX0ev5eXessCM0z8E= - entities@^1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" @@ -472,137 +241,11 @@ exec-sh@^0.2.0: dependencies: merge "^1.2.0" -expand-brackets@^0.1.4: - version "0.1.5" - resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-0.1.5.tgz#df07284e342a807cd733ac5af72411e581d1177b" - integrity sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s= - dependencies: - is-posix-bracket "^0.1.0" - -expand-brackets@^2.1.4: - version "2.1.4" - resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" - integrity sha1-t3c14xXOMPa27/D4OwQVGiJEliI= - dependencies: - debug "^2.3.3" - define-property "^0.2.5" - extend-shallow "^2.0.1" - posix-character-classes "^0.1.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -expand-range@^1.8.1: - version "1.8.2" - resolved "https://registry.yarnpkg.com/expand-range/-/expand-range-1.8.2.tgz#a299effd335fe2721ebae8e257ec79644fc85337" - integrity sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc= - dependencies: - fill-range "^2.1.0" - -extend-shallow@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" - integrity sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8= - dependencies: - is-extendable "^0.1.0" - -extend-shallow@^3.0.0, extend-shallow@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" - integrity sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg= - dependencies: - assign-symbols "^1.0.0" - is-extendable "^1.0.1" - -extglob@^0.3.1: - version "0.3.2" - resolved "https://registry.yarnpkg.com/extglob/-/extglob-0.3.2.tgz#2e18ff3d2f49ab2765cec9023f011daa8d8349a1" - integrity sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE= - dependencies: - is-extglob "^1.0.0" - -extglob@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" - integrity sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw== - dependencies: - array-unique "^0.3.2" - define-property "^1.0.0" - expand-brackets "^2.1.4" - extend-shallow "^2.0.1" - fragment-cache "^0.2.1" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -file-uri-to-path@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" - integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== - -filename-regex@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26" - integrity sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY= - -fill-range@^2.1.0: - version "2.2.4" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.4.tgz#eb1e773abb056dcd8df2bfdf6af59b8b3a936565" - integrity sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q== - dependencies: - is-number "^2.1.0" - isobject "^2.0.0" - randomatic "^3.0.0" - repeat-element "^1.1.2" - repeat-string "^1.5.2" - -fill-range@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" - integrity sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc= - dependencies: - extend-shallow "^2.0.1" - is-number "^3.0.0" - repeat-string "^1.6.1" - to-regex-range "^2.1.0" - -find-index@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/find-index/-/find-index-0.1.1.tgz#675d358b2ca3892d795a1ab47232f8b6e2e0dde4" - integrity sha1-Z101iyyjiS15Whq0cjL4tuLg3eQ= - -for-in@^1.0.1, for-in@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" - integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= - -for-own@^0.1.4: - version "0.1.5" - resolved "https://registry.yarnpkg.com/for-own/-/for-own-0.1.5.tgz#5265c681a4f294dabbf17c9509b6763aa84510ce" - integrity sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4= - dependencies: - for-in "^1.0.1" - -fragment-cache@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" - integrity sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk= - dependencies: - map-cache "^0.2.2" - fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= -fsevents@^1.0.0: - version "1.2.12" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.12.tgz#db7e0d8ec3b0b45724fd4d83d43554a8f1f0de5c" - integrity sha512-Ggd/Ktt7E7I8pxZRbGIs7vwqAPscSESMrCSkx2FtWeqmheJgCo2R74fTsZFCifr0VTPwqRpPv17+6b8Zp7th0Q== - dependencies: - bindings "^1.5.0" - nan "^2.12.1" - function-bind@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" @@ -617,34 +260,7 @@ get-intrinsic@^1.0.0: has "^1.0.3" has-symbols "^1.0.1" -get-value@^2.0.3, get-value@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" - integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= - -glob-base@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4" - integrity sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q= - dependencies: - glob-parent "^2.0.0" - is-glob "^2.0.0" - -glob-parent@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-2.0.0.tgz#81383d72db054fcccf5336daa902f182f6edbb28" - integrity sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg= - dependencies: - is-glob "^2.0.0" - -glob2base@^0.0.12: - version "0.0.12" - resolved "https://registry.yarnpkg.com/glob2base/-/glob2base-0.0.12.tgz#9d419b3e28f12e83a362164a277055922c9c0d56" - integrity sha1-nUGbPijxLoOjYhZKJ3BVkiycDVY= - dependencies: - find-index "^0.1.1" - -glob@^7.0.5, glob@^7.1.3: +glob@^7.1.3: version "7.1.6" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== @@ -656,11 +272,6 @@ glob@^7.0.5, glob@^7.1.3: once "^1.3.0" path-is-absolute "^1.0.0" -graceful-fs@^4.1.11: - version "4.2.3" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" - integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ== - graceful-fs@^4.1.2: version "4.2.4" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" @@ -676,37 +287,6 @@ has-symbols@^1.0.1: resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== -has-value@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" - integrity sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8= - dependencies: - get-value "^2.0.3" - has-values "^0.1.4" - isobject "^2.0.0" - -has-value@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" - integrity sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc= - dependencies: - get-value "^2.0.6" - has-values "^1.0.0" - isobject "^3.0.0" - -has-values@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" - integrity sha1-bWHeldkd/Km5oCCJrThL/49it3E= - -has-values@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" - integrity sha1-lbC2P+whRmGab+V/51Yo1aOe/k8= - dependencies: - is-number "^3.0.0" - kind-of "^4.0.0" - has@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" @@ -739,42 +319,16 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3: +inherits@2, inherits@^2.0.1, inherits@^2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== -is-accessor-descriptor@^0.1.6: - version "0.1.6" - resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" - integrity sha1-qeEss66Nh2cn7u84Q/igiXtcmNY= - dependencies: - kind-of "^3.0.2" - -is-accessor-descriptor@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656" - integrity sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ== - dependencies: - kind-of "^6.0.0" - is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= -is-binary-path@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" - integrity sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg= - dependencies: - binary-extensions "^1.0.0" - -is-buffer@^1.1.5: - version "1.1.6" - resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" - integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== - is-callable@^1.1.4: version "1.1.5" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.5.tgz#f7e46b596890456db74e7f6e976cb3273d06faab" @@ -792,120 +346,16 @@ is-core-module@^2.1.0: dependencies: has "^1.0.3" -is-data-descriptor@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" - integrity sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y= - dependencies: - kind-of "^3.0.2" - -is-data-descriptor@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7" - integrity sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ== - dependencies: - kind-of "^6.0.0" - is-date-object@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g== -is-descriptor@^0.1.0: - version "0.1.6" - resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" - integrity sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg== - dependencies: - is-accessor-descriptor "^0.1.6" - is-data-descriptor "^0.1.4" - kind-of "^5.0.0" - -is-descriptor@^1.0.0, is-descriptor@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" - integrity sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg== - dependencies: - is-accessor-descriptor "^1.0.0" - is-data-descriptor "^1.0.0" - kind-of "^6.0.2" - -is-dotfile@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.3.tgz#a6a2f32ffd2dfb04f5ca25ecd0f6b83cf798a1e1" - integrity sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE= - -is-equal-shallow@^0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz#2238098fc221de0bcfa5d9eac4c45d638aa1c534" - integrity sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ= - dependencies: - is-primitive "^2.0.0" - -is-extendable@^0.1.0, is-extendable@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" - integrity sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik= - -is-extendable@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" - integrity sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA== - dependencies: - is-plain-object "^2.0.4" - -is-extglob@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0" - integrity sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA= - -is-glob@^2.0.0, is-glob@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863" - integrity sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM= - dependencies: - is-extglob "^1.0.0" - is-negative-zero@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24" integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w== -is-number@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f" - integrity sha1-Afy7s5NGOlSPL0ZszhbezknbkI8= - dependencies: - kind-of "^3.0.2" - -is-number@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" - integrity sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU= - dependencies: - kind-of "^3.0.2" - -is-number@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-4.0.0.tgz#0026e37f5454d73e356dfe6564699867c6a7f0ff" - integrity sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ== - -is-plain-object@^2.0.3, is-plain-object@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" - integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== - dependencies: - isobject "^3.0.1" - -is-posix-bracket@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz#3334dc79774368e92f016e6fbc0a88f5cd6e6bc4" - integrity sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q= - -is-primitive@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-2.0.0.tgz#207bab91638499c07b2adf240a41a87210034575" - integrity sha1-IHurkWOEmcB7Kt8kCkGochADRXU= - is-regex@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.1.tgz#c6f98aacc546f6cec5468a07b7b153ab564a57b9" @@ -920,62 +370,16 @@ is-symbol@^1.0.2: dependencies: has-symbols "^1.0.1" -is-windows@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" - integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== - -isarray@1.0.0, isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= - isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= -isobject@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" - integrity sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk= - dependencies: - isarray "1.0.0" - -isobject@^3.0.0, isobject@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" - integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= - json-parse-better-errors@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== -kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: - version "3.2.2" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" - integrity sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ= - dependencies: - is-buffer "^1.1.5" - -kind-of@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" - integrity sha1-IIE989cSkosgc3hpGkUGb65y3Vc= - dependencies: - is-buffer "^1.1.5" - -kind-of@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" - integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== - -kind-of@^6.0.0, kind-of@^6.0.2: - version "6.0.3" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" - integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== - load-json-file@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b" @@ -986,23 +390,6 @@ load-json-file@^4.0.0: pify "^3.0.0" strip-bom "^3.0.0" -map-cache@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" - integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= - -map-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" - integrity sha1-7Nyo8TFE5mDxtb1B8S80edmN+48= - dependencies: - object-visit "^1.0.0" - -math-random@^1.0.1: - version "1.0.4" - resolved "https://registry.yarnpkg.com/math-random/-/math-random-1.0.4.tgz#5dd6943c938548267016d4e34f057583080c514c" - integrity sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A== - memorystream@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/memorystream/-/memorystream-0.3.1.tgz#86d7090b30ce455d63fbae12dda51a47ddcaf9b2" @@ -1013,98 +400,28 @@ merge@^1.2.0: resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.1.tgz#38bebf80c3220a8a487b6fcfb3941bb11720c145" integrity sha512-VjFo4P5Whtj4vsLzsYBu5ayHhoHJ0UqNm7ibvShmbmoz7tGi0vXaoJbGdB+GmDMLUdg8DpQXEIeVDAe8MaABvQ== -micromatch@^2.1.5: - version "2.3.11" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565" - integrity sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU= - dependencies: - arr-diff "^2.0.0" - array-unique "^0.2.1" - braces "^1.8.2" - expand-brackets "^0.1.4" - extglob "^0.3.1" - filename-regex "^2.0.0" - is-extglob "^1.0.0" - is-glob "^2.0.1" - kind-of "^3.0.2" - normalize-path "^2.0.1" - object.omit "^2.0.0" - parse-glob "^3.0.4" - regex-cache "^0.4.2" - -micromatch@^3.1.10: - version "3.1.10" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" - integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - braces "^2.3.1" - define-property "^2.0.2" - extend-shallow "^3.0.2" - extglob "^2.0.4" - fragment-cache "^0.2.1" - kind-of "^6.0.2" - nanomatch "^1.2.9" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.2" - -minimatch@^3.0.2, minimatch@^3.0.4: +minimatch@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== dependencies: brace-expansion "^1.1.7" -minimist@^1.1.0, minimist@^1.2.0, minimist@^1.2.5: +minimist@^1.2.0: version "1.2.5" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== -mixin-deep@^1.2.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" - integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA== - dependencies: - for-in "^1.0.2" - is-extendable "^1.0.1" - -mkdirp@^0.5.1: - version "0.5.4" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.4.tgz#fd01504a6797ec5c9be81ff43d204961ed64a512" - integrity sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw== - dependencies: - minimist "^1.2.5" +mkdirp@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= -nan@^2.12.1: - version "2.14.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" - integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg== - -nanomatch@^1.2.9: - version "1.2.13" - resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" - integrity sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA== - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - define-property "^2.0.2" - extend-shallow "^3.0.2" - fragment-cache "^0.2.1" - is-windows "^1.0.2" - kind-of "^6.0.2" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - ncp@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ncp/-/ncp-2.0.0.tgz#195a21d6c46e361d2fb1281ba38b91e9df7bdbb3" @@ -1125,13 +442,6 @@ normalize-package-data@^2.3.2: semver "2 || 3 || 4 || 5" validate-npm-package-license "^3.0.1" -normalize-path@^2.0.0, normalize-path@^2.0.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" - integrity sha1-GrKLVW4Zg2Oowab35vogE3/mrtk= - dependencies: - remove-trailing-separator "^1.0.1" - npm-run-all@^4.1.5: version "4.1.5" resolved "https://registry.yarnpkg.com/npm-run-all/-/npm-run-all-4.1.5.tgz#04476202a15ee0e2e214080861bff12a51d98fba" @@ -1147,15 +457,6 @@ npm-run-all@^4.1.5: shell-quote "^1.6.1" string.prototype.padend "^3.0.0" -object-copy@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" - integrity sha1-fn2Fi3gb18mRpBupde04EnVOmYw= - dependencies: - copy-descriptor "^0.1.0" - define-property "^0.2.5" - kind-of "^3.0.3" - object-inspect@^1.8.0: version "1.9.0" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.9.0.tgz#c90521d74e1127b67266ded3394ad6116986533a" @@ -1166,13 +467,6 @@ object-keys@^1.0.12, object-keys@^1.1.1: resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== -object-visit@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" - integrity sha1-95xEk68MU3e1n+OdOV5BBC3QRbs= - dependencies: - isobject "^3.0.0" - object.assign@^4.1.1: version "4.1.2" resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" @@ -1183,21 +477,6 @@ object.assign@^4.1.1: has-symbols "^1.0.1" object-keys "^1.1.1" -object.omit@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/object.omit/-/object.omit-2.0.1.tgz#1a9c744829f39dbb858c76ca3579ae2a54ebd1fa" - integrity sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo= - dependencies: - for-own "^0.1.4" - is-extendable "^0.1.1" - -object.pick@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" - integrity sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c= - dependencies: - isobject "^3.0.1" - once@^1.3.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" @@ -1210,16 +489,6 @@ os-tmpdir@~1.0.2: resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= -parse-glob@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/parse-glob/-/parse-glob-3.0.4.tgz#b2c376cfb11f35513badd173ef0bb6e3a388391c" - integrity sha1-ssN2z7EfNVE7rdFz7wu246OIORw= - dependencies: - glob-base "^0.3.0" - is-dotfile "^1.0.0" - is-extglob "^1.0.0" - is-glob "^2.0.0" - parse-json@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" @@ -1228,11 +497,6 @@ parse-json@^4.0.0: error-ex "^1.3.1" json-parse-better-errors "^1.0.1" -pascalcase@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" - integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= - path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" @@ -1274,30 +538,6 @@ portastic@^1.0.1: commander "^2.8.1" debug "^2.2.0" -posix-character-classes@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" - integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= - -preserve@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" - integrity sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks= - -process-nextick-args@~2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" - integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== - -randomatic@^3.0.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-3.1.1.tgz#b776efc59375984e36c537b2f51a1f0aff0da1ed" - integrity sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw== - dependencies: - is-number "^4.0.0" - kind-of "^6.0.0" - math-random "^1.0.1" - read-pkg@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389" @@ -1307,19 +547,6 @@ read-pkg@^3.0.0: normalize-package-data "^2.3.2" path-type "^3.0.0" -readable-stream@^2.0.2: - version "2.3.7" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" - integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~2.0.0" - safe-buffer "~5.1.1" - string_decoder "~1.1.1" - util-deprecate "~1.0.1" - readable-stream@^3.1.1: version "3.6.0" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" @@ -1329,62 +556,6 @@ readable-stream@^3.1.1: string_decoder "^1.1.1" util-deprecate "^1.0.1" -readdirp@^2.0.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525" - integrity sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ== - dependencies: - graceful-fs "^4.1.11" - micromatch "^3.1.10" - readable-stream "^2.0.2" - -regenerator-runtime@^0.11.0: - version "0.11.1" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" - integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== - -regex-cache@^0.4.2: - version "0.4.4" - resolved "https://registry.yarnpkg.com/regex-cache/-/regex-cache-0.4.4.tgz#75bdc58a2a1496cec48a12835bc54c8d562336dd" - integrity sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ== - dependencies: - is-equal-shallow "^0.1.3" - -regex-not@^1.0.0, regex-not@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" - integrity sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A== - dependencies: - extend-shallow "^3.0.2" - safe-regex "^1.1.0" - -remove-trailing-separator@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" - integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8= - -repeat-element@^1.1.2: - version "1.1.3" - resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce" - integrity sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g== - -repeat-string@^1.5.2, repeat-string@^1.6.1: - version "1.6.1" - resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" - integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= - -resolve-url@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" - integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= - -resolve@^1.1.7: - version "1.15.1" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.15.1.tgz#27bdcdeffeaf2d6244b95bb0f9f4b4653451f3e8" - integrity sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w== - dependencies: - path-parse "^1.0.6" - resolve@^1.10.0: version "1.19.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.19.0.tgz#1af5bf630409734a067cae29318aac7fa29a267c" @@ -1393,11 +564,6 @@ resolve@^1.10.0: is-core-module "^2.1.0" path-parse "^1.0.6" -ret@~0.1.10: - version "0.1.15" - resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" - integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== - rimraf@^2.6.1: version "2.7.1" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" @@ -1405,38 +571,16 @@ rimraf@^2.6.1: dependencies: glob "^7.1.3" -safe-buffer@^5.0.1, safe-buffer@~5.2.0: +safe-buffer@~5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== -safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - -safe-regex@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" - integrity sha1-QKNmnzsHfR6UPURinhV91IAjvy4= - dependencies: - ret "~0.1.10" - "semver@2 || 3 || 4 || 5", semver@^5.5.0: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== -set-value@^2.0.0, set-value@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" - integrity sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw== - dependencies: - extend-shallow "^2.0.1" - is-extendable "^0.1.1" - is-plain-object "^2.0.3" - split-string "^3.0.1" - shebang-command@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" @@ -1454,57 +598,6 @@ shell-quote@^1.6.1: resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.2.tgz#67a7d02c76c9da24f99d20808fcaded0e0e04be2" integrity sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg== -snapdragon-node@^2.0.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" - integrity sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw== - dependencies: - define-property "^1.0.0" - isobject "^3.0.0" - snapdragon-util "^3.0.1" - -snapdragon-util@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" - integrity sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ== - dependencies: - kind-of "^3.2.0" - -snapdragon@^0.8.1: - version "0.8.2" - resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" - integrity sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg== - dependencies: - base "^0.11.1" - debug "^2.2.0" - define-property "^0.2.5" - extend-shallow "^2.0.1" - map-cache "^0.2.2" - source-map "^0.5.6" - source-map-resolve "^0.5.0" - use "^3.1.0" - -source-map-resolve@^0.5.0: - version "0.5.3" - resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" - integrity sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw== - dependencies: - atob "^2.1.2" - decode-uri-component "^0.2.0" - resolve-url "^0.2.1" - source-map-url "^0.4.0" - urix "^0.1.0" - -source-map-url@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" - integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM= - -source-map@^0.5.6: - version "0.5.7" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" - integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= - spdx-correct@^3.0.0: version "3.1.1" resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9" @@ -1531,21 +624,6 @@ spdx-license-ids@^3.0.0: resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.7.tgz#e9c18a410e5ed7e12442a549fbd8afa767038d65" integrity sha512-U+MTEOO0AiDzxwFvoa4JVnMV6mZlJKk2sBLt90s7G0Gd0Mlknc7kxEn3nuDPNZRta7O2uy8oLcZLVT+4sqNZHQ== -split-string@^3.0.1, split-string@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" - integrity sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw== - dependencies: - extend-shallow "^3.0.0" - -static-extend@^0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" - integrity sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY= - dependencies: - define-property "^0.2.5" - object-copy "^0.1.0" - string.prototype.padend@^3.0.0: version "3.1.1" resolved "https://registry.yarnpkg.com/string.prototype.padend/-/string.prototype.padend-3.1.1.tgz#824c84265dbac46cade2b957b38b6a5d8d1683c5" @@ -1578,13 +656,6 @@ string_decoder@^1.1.1: dependencies: safe-buffer "~5.2.0" -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== - dependencies: - safe-buffer "~5.1.0" - strip-bom@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" @@ -1595,13 +666,6 @@ strip-json-comments@^2.0.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= -subarg@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/subarg/-/subarg-1.0.0.tgz#f62cf17581e996b48fc965699f54c06ae268b8d2" - integrity sha1-9izxdYHplrSPyWVpn1TAauJouNI= - dependencies: - minimist "^1.1.0" - supports-color@^5.3.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" @@ -1616,65 +680,12 @@ tmp@0.0.33: dependencies: os-tmpdir "~1.0.2" -to-object-path@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" - integrity sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68= - dependencies: - kind-of "^3.0.2" - -to-regex-range@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" - integrity sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg= - dependencies: - is-number "^3.0.0" - repeat-string "^1.6.1" - -to-regex@^3.0.1, to-regex@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" - integrity sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw== - dependencies: - define-property "^2.0.2" - extend-shallow "^3.0.2" - regex-not "^1.0.2" - safe-regex "^1.1.0" - typescript@3.7.5: version "3.7.5" resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.5.tgz#0692e21f65fd4108b9330238aac11dd2e177a1ae" integrity sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw== -union-value@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" - integrity sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg== - dependencies: - arr-union "^3.1.0" - get-value "^2.0.6" - is-extendable "^0.1.1" - set-value "^2.0.1" - -unset-value@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" - integrity sha1-g3aHP30jNRef+x5vw6jtDfyKtVk= - dependencies: - has-value "^0.3.1" - isobject "^3.0.0" - -urix@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" - integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= - -use@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" - integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== - -util-deprecate@^1.0.1, util-deprecate@~1.0.1: +util-deprecate@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= diff --git a/test/unit/electron/index.js b/test/unit/electron/index.js index 33ff8c53..7d85f302 100644 --- a/test/unit/electron/index.js +++ b/test/unit/electron/index.js @@ -15,6 +15,7 @@ const mocha = require('mocha'); const events = require('events'); const MochaJUnitReporter = require('mocha-junit-reporter'); const url = require('url'); +const net = require('net'); const createStatsCollector = require('mocha/lib/stats-collector'); const FullJsonStreamReporter = require('../fullJsonStreamReporter'); @@ -31,6 +32,8 @@ const optimist = require('optimist') .describe('debug', 'open dev tools, keep window open, reuse app data').string('debug') .describe('reporter', 'the mocha reporter').string('reporter').default('reporter', 'spec') .describe('reporter-options', 'the mocha reporter options').string('reporter-options').default('reporter-options', '') + .describe('wait-server', 'port to connect to and wait before running tests') + .describe('timeout', 'timeout for tests') .describe('tfs').string('tfs') .describe('help', 'show the help').alias('help', 'h'); @@ -85,6 +88,12 @@ function importMochaReporter(name) { function deserializeError(err) { const inspect = err.inspect; err.inspect = () => inspect; + if (err.actual) { + err.actual = JSON.parse(err.actual).value; + } + if (err.expected) { + err.expected = JSON.parse(err.expected).value; + } return err; } @@ -94,9 +103,13 @@ class IPCRunner extends events.EventEmitter { super(); this.didFail = false; + this.didEnd = false; ipcMain.on('start', () => this.emit('start')); - ipcMain.on('end', () => this.emit('end')); + ipcMain.on('end', () => { + this.didEnd = true; + this.emit('end'); + }); ipcMain.on('suite', (e, suite) => this.emit('suite', deserializeSuite(suite))); ipcMain.on('suite end', (e, suite) => this.emit('suite end', deserializeSuite(suite))); ipcMain.on('test', (e, test) => this.emit('test', deserializeRunnable(test))); @@ -126,13 +139,34 @@ app.on('ready', () => { } }); + // We need to provide a basic `ISandboxConfiguration` + // for our preload script to function properly because + // some of our types depend on it (e.g. product.ts). + ipcMain.handle('vscode:test-vscode-window-config', async () => { + return { + product: { + version: '1.x.y', + nameShort: 'Code - OSS Dev', + nameLong: 'Code - OSS Dev', + applicationName: 'code-oss', + dataFolderName: '.vscode-oss', + urlProtocol: 'code-oss', + } + }; + }); + + // No-op since invoke the IPC as part of IIFE in the preload. + ipcMain.handle('vscode:fetchShellEnv', event => { }); + const win = new BrowserWindow({ height: 600, width: 800, show: false, webPreferences: { preload: path.join(__dirname, '..', '..', '..', 'src', 'vs', 'base', 'parts', 'sandbox', 'electron-browser', 'preload.js'), // ensure similar environment as VSCode as tests may depend on this + additionalArguments: [`--vscode-window-config=vscode:test-vscode-window-config`], nodeIntegration: true, + contextIsolation: false, enableWebSQL: false, enableRemoteModule: false, spellcheck: false, @@ -146,14 +180,58 @@ app.on('ready', () => { win.show(); win.webContents.openDevTools(); } - win.webContents.send('run', argv); + + if (argv.waitServer) { + waitForServer(Number(argv.waitServer)).then(sendRun); + } else { + sendRun(); + } }); + async function waitForServer(port) { + let timeout; + let socket; + + return new Promise(resolve => { + socket = net.connect(port, '127.0.0.1'); + socket.on('error', e => { + console.error('error connecting to waitServer', e); + resolve(); + }); + + socket.on('close', () => { + resolve(); + }); + + timeout = setTimeout(() => { + console.error('timed out waiting for before starting tests debugger'); + resolve(); + }, 7000); + }).finally(() => { + if (socket) { + socket.end(); + } + clearTimeout(timeout); + }); + } + + function sendRun() { + win.webContents.send('run', argv); + } + win.loadURL(url.format({ pathname: path.join(__dirname, 'renderer.html'), protocol: 'file:', slashes: true })); const runner = new IPCRunner(); createStatsCollector(runner); + // Handle renderer crashes, #117068 + win.webContents.on('render-process-gone', (evt, details) => { + if (!runner.didEnd) { + console.error(`Renderer process crashed with: ${JSON.stringify(details)}`); + app.exit(1); + } + }); + if (argv.tfs) { new mocha.reporters.Spec(runner); new MochaJUnitReporter(runner, { diff --git a/test/unit/electron/renderer.js b/test/unit/electron/renderer.js index b67ff471..afc02f20 100644 --- a/test/unit/electron/renderer.js +++ b/test/unit/electron/renderer.js @@ -5,7 +5,7 @@ /*eslint-env mocha*/ -(function() { +(function () { const fs = require('fs'); const originals = {}; let logging = false; @@ -21,7 +21,7 @@ }; function createSpy(element, cnt) { - return function(...args) { + return function (...args) { if (logging) { console.log(`calling ${element}: ` + args.slice(0, cnt).join(',') + (withStacks ? (`\n` + new Error().stack.split('\n').slice(2).join('\n')) : '')); } @@ -213,14 +213,39 @@ function serializeError(err) { return { message: err.message, stack: err.stack, - actual: err.actual, - expected: err.expected, + actual: safeStringify({ value: err.actual }), + expected: safeStringify({ value: err.expected }), uncaught: err.uncaught, showDiff: err.showDiff, inspect: typeof err.inspect === 'function' ? err.inspect() : '' }; } +function safeStringify(obj) { + const seen = new Set(); + return JSON.stringify(obj, (key, value) => { + if (isObject(value) || Array.isArray(value)) { + if (seen.has(value)) { + return '[Circular]'; + } else { + seen.add(value); + } + } + return value; + }); +} + +function isObject(obj) { + // The method can't do a type cast since there are type (like strings) which + // are subclasses of any put not positvely matched by the function. Hence type + // narrowing results in wrong results. + return typeof obj === 'object' + && obj !== null + && !Array.isArray(obj) + && !(obj instanceof RegExp) + && !(obj instanceof Date); +} + class IPCReporter { constructor(runner) { @@ -239,6 +264,10 @@ class IPCReporter { } function runTests(opts) { + // this *must* come before loadTests, or it doesn't work. + if (opts.timeout !== undefined) { + mocha.timeout(opts.timeout); + } return loadTests(opts).then(() => { diff --git a/yarn.lock b/yarn.lock index 24975122..b475eec7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -145,14 +145,6 @@ lodash "^4.17.13" to-fast-properties "^2.0.0" -"@dsherret/to-absolute-glob@^2.0.2": - version "2.0.2" - resolved "https://registry.yarnpkg.com/@dsherret/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz#1f6475dc8bd974cea07a2daf3864b317b1dd332c" - integrity sha1-H2R13IvZdM6gei2vOGSzF7HdMyw= - dependencies: - is-absolute "^1.0.0" - is-negated-glob "^1.0.0" - "@electron/get@^1.0.1": version "1.12.3" resolved "https://registry.yarnpkg.com/@electron/get/-/get-1.12.3.tgz#fa2723385c4b565a34c4c82f46087aa2a5fbf6d0" @@ -210,13 +202,6 @@ resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.2.tgz#26520bf09abe4a5644cd5414e37125a8954241dd" integrity sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw== -"@malept/cross-spawn-promise@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@malept/cross-spawn-promise/-/cross-spawn-promise-1.1.0.tgz#258fde4098f5004a56db67c35f33033af64810f6" - integrity sha512-GeIK5rfU1Yd7BZJQPTGZMMmcZy5nhRToPXZcjaDwQDRSewdhp648GT2E4dh+L7+Io7AOW6WQ+GR44QSzja4qxg== - dependencies: - cross-spawn "^7.0.1" - "@nodelib/fs.scandir@2.1.3": version "2.1.3" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz#3a582bdb53804c6ba6d146579c46e52130cf4a3b" @@ -360,6 +345,20 @@ dependencies: defer-to-connect "^1.0.1" +"@tootallnate/once@1", "@tootallnate/once@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" + integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== + +"@ts-morph/common@~0.9.0": + version "0.9.0" + resolved "https://registry.yarnpkg.com/@ts-morph/common/-/common-0.9.0.tgz#a306355bad82cff22a1881f7f2f2c710bbb4d69d" + integrity sha512-yPcW6koNVK1hVKUu+KhPzhfgMb0uwzr2FewF+q8kxLerl0b+YZwmjvFMU2qbIawytIHT2VBI4bi+C09EFPB4aw== + dependencies: + fast-glob "^3.2.5" + minimatch "^3.0.4" + mkdirp "^1.0.4" + "@types/anymatch@*": version "1.3.1" resolved "https://registry.yarnpkg.com/@types/anymatch/-/anymatch-1.3.1.tgz#336badc1beecb9dacc38bea2cf32adf627a8421a" @@ -470,7 +469,7 @@ resolved "https://registry.yarnpkg.com/@types/keytar/-/keytar-4.4.0.tgz#ca24e6ee6d0df10c003aafe26e93113b8faf0d8e" integrity sha512-cq/NkUUy6rpWD8n7PweNQQBpw2o0cf5v6fbkUVEpOB9VzzIvyPvSEId1/goIj+MciW2v1Lw5mRimKO01XgE9EA== -"@types/minimatch@*", "@types/minimatch@^3.0.3": +"@types/minimatch@*": version "3.0.3" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== @@ -500,15 +499,10 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.21.tgz#7e8a0c34cf29f4e17a36e9bd0ea72d45ba03908e" integrity sha512-CBgLNk4o3XMnqMc0rhb6lc77IwShMEglz05deDcn2lQxyXEZivfwgYJu7SMha9V5XcrP6qZuevTHV/QrN2vjKQ== -"@types/node@^12.0.12": - version "12.19.15" - resolved "https://registry.yarnpkg.com/@types/node/-/node-12.19.15.tgz#0de7e978fb43db62da369db18ea088a63673c182" - integrity sha512-lowukE3GUI+VSYSu6VcBXl14d61Rp5hA1D+61r16qnwC0lYNSqdxcvRh0pswejorHfS+HgwBasM8jLXz0/aOsw== - -"@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/node@^14.14.37", "@types/node@^14.6.2": + version "14.14.37" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.37.tgz#a3dd8da4eb84a996c36e331df98d82abd76b516e" + integrity sha512-XYmBiy+ohOR4Lh5jE379fV2IU+6Jn4g5qASinhitfyO71b/sCo6MKsMLF5tc7Zf2CE8hViVQyYSobJNke8OvUw== "@types/q@^1.5.1": version "1.5.4" @@ -581,6 +575,11 @@ "@types/webpack-sources" "*" source-map "^0.6.0" +"@types/wicg-file-system-access@^2020.9.1": + version "2020.9.1" + resolved "https://registry.yarnpkg.com/@types/wicg-file-system-access/-/wicg-file-system-access-2020.9.1.tgz#ae1f420b0ca70f545c8621a9b63ed29270ef724a" + integrity sha512-hEN/YpLwvDjhRJrKoBiyiKtIh2zNkmJ/GY9VWIXNgjy7TBZNM9upfb/rnWDGpOoLomnEQtlTBjFBFCDra1oxOQ== + "@types/windows-foreground-love@^0.3.0": version "0.3.0" resolved "https://registry.yarnpkg.com/@types/windows-foreground-love/-/windows-foreground-love-0.3.0.tgz#26bc230b2568aa7ab7c56d35bb5653c0a6965a42" @@ -888,12 +887,12 @@ agent-base@^4.3.0: dependencies: es6-promisify "^5.0.0" -agent-base@~4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9" - integrity sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg== +agent-base@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== dependencies: - es6-promisify "^5.0.0" + debug "4" aggregate-error@^3.0.0: version "3.1.0" @@ -1159,11 +1158,6 @@ array-differ@^1.0.0: resolved "https://registry.yarnpkg.com/array-differ/-/array-differ-1.0.0.tgz#eff52e3758249d33be402b8bb8e564bb2b5d4031" integrity sha1-7/UuN1gknTO+QCuLuOVkuytdQDE= -array-differ@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/array-differ/-/array-differ-3.0.0.tgz#3cbb3d0f316810eafcc47624734237d6aee4ae6b" - integrity sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg== - array-each@^1.0.0, array-each@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/array-each/-/array-each-1.0.1.tgz#a794af0c05ab1752846ee753a1f211a05ba0c44f" @@ -1235,11 +1229,6 @@ arrify@^1.0.0: resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0= -arrify@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa" - integrity sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug== - asar@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/asar/-/asar-3.0.3.tgz#1fef03c2d6d2de0cbad138788e4f7ae03b129c7b" @@ -1346,11 +1335,6 @@ asynckit@^0.4.0: resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= -at-least-node@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" - integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== - atob@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" @@ -1429,9 +1413,9 @@ bcrypt-pbkdf@^1.0.0: tweetnacl "^0.14.3" before-after-hook@^2.1.0: - version "2.1.0" - resolved "https://pkgs.dev.azure.com/terrapin-prod/Terrapin/_packaging/open-source-packages-2/npm/registry/before-after-hook/-/before-after-hook-2.1.0.tgz#0bb562539c98af6479c3e423df1880d022bd9c76" - integrity sha1-C7ViU5yYr2R5w+Qj3xiA0CK9nHY= + version "2.2.1" + resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.2.1.tgz#73540563558687586b52ed217dad6a802ab1549c" + integrity sha512-/6FKxSTWoJdbsLDF8tdIjaRiFXiE6UHsEHE3OPI/cwPURCVi1ukP0gmLn7XWEiFk5TcwQjjY5PWsU+j+tgXgmw== big.js@^3.1.3: version "3.2.0" @@ -2012,11 +1996,6 @@ cli-cursor@^3.1.0: dependencies: restore-cursor "^3.1.0" -cli-spinners@^2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.4.0.tgz#c6256db216b878cfba4720e719cec7cf72685d7f" - integrity sha512-sJAofoarcm76ZGpuooaO0eDy8saEy+YoZBLjC4h8srt4jeBnkYeOgqxgsJQTpyt2LjI5PTfLJHSL+41Yu4fEJA== - cli-width@^2.0.0: version "2.2.1" resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.1.tgz#b0433d0b4e9c847ef18868a4ef16fd5fc8271c48" @@ -2054,15 +2033,6 @@ cliui@^6.0.0: strip-ansi "^6.0.0" wrap-ansi "^6.2.0" -cliui@^7.0.0: - version "7.0.1" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.1.tgz#a4cb67aad45cd83d8d05128fc9f4d8fbb887e6b3" - integrity sha512-rcvHOWyGyid6I1WjT/3NatKj2kDt9OdSHSXpyLXaMWFbKpGACNW8pRhhdPUq9MWUOdwn8Rz9AVETjF4105rZZQ== - dependencies: - string-width "^4.2.0" - strip-ansi "^6.0.0" - wrap-ansi "^7.0.0" - clone-buffer@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/clone-buffer/-/clone-buffer-1.0.0.tgz#e3e25b207ac4e701af721e2cb5a16792cac3dc58" @@ -2085,7 +2055,7 @@ clone-stats@^1.0.0: resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-1.0.0.tgz#b3782dff8bb5474e18b9b6bf0fdfe782f8777680" integrity sha1-s3gt/4u1R04Yuba/D9/ngvh3doA= -clone@^1.0.0, clone@^1.0.2: +clone@^1.0.0: version "1.0.4" resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4= @@ -2122,10 +2092,10 @@ coa@^2.0.2: chalk "^2.4.1" q "^1.1.2" -code-block-writer@9.4.1: - version "9.4.1" - resolved "https://registry.yarnpkg.com/code-block-writer/-/code-block-writer-9.4.1.tgz#1448fca79dfc7a3649000f4c85be6bc770604c4c" - integrity sha512-LHAB+DL4YZDcwK8y/kAxZ0Lf/ncwLh/Ux4cTVWbPwIdrf1gPxXiPcwpz8r8/KqXu1aD+Raz46EOxDjFlbyO6bA== +code-block-writer@^10.1.1: + version "10.1.1" + resolved "https://registry.yarnpkg.com/code-block-writer/-/code-block-writer-10.1.1.tgz#ad5684ed4bfb2b0783c8b131281ae84ee640a42f" + integrity sha512-67ueh2IRGst/51p0n6FvPrnRjAGHY5F8xdjkgrYE7DDzpJe6qA07RYQ9VcoUeo5ATOjSOiWpSL3SWBRRbempMw== code-point-at@^1.0.0: version "1.1.0" @@ -2204,11 +2174,6 @@ colorette@^1.2.1: resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.1.tgz#4d0b921325c14faf92633086a536db6e89564b1b" integrity sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw== -colors@^1.3.3: - version "1.4.0" - resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" - integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== - combined-stream@^1.0.6, combined-stream@~1.0.6: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" @@ -2440,15 +2405,6 @@ cross-spawn@^6.0.0, cross-spawn@^6.0.5: shebang-command "^1.2.0" which "^1.2.9" -cross-spawn@^7.0.1: - version "7.0.3" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" - integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - crypt@~0.0.1: version "0.0.2" resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" @@ -2559,10 +2515,10 @@ cssesc@^3.0.0: resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== -cssnano-preset-default@^4.0.7: - version "4.0.7" - resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-4.0.7.tgz#51ec662ccfca0f88b396dcd9679cdb931be17f76" - integrity sha512-x0YHHx2h6p0fCl1zY9L9roD7rnlltugGu7zXSKQx6k2rYw0Hi3IqxcoAGF7u9Q5w1nt7vK0ulxV8Lo+EvllGsA== +cssnano-preset-default@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-4.0.8.tgz#920622b1fc1e95a34e8838203f1397a504f2d3ff" + integrity sha512-LdAyHuq+VRyeVREFmuxUZR1TXjQm8QQU/ktoo/x7bz+SdOge1YKc5eMN6pRW7YWBmyq59CqYba1dJ5cUukEjLQ== dependencies: css-declaration-sorter "^4.0.1" cssnano-util-raw-cache "^4.0.1" @@ -2592,7 +2548,7 @@ cssnano-preset-default@^4.0.7: postcss-ordered-values "^4.1.2" postcss-reduce-initial "^4.0.3" postcss-reduce-transforms "^4.0.2" - postcss-svgo "^4.0.2" + postcss-svgo "^4.0.3" postcss-unique-selectors "^4.0.1" cssnano-util-get-arguments@^4.0.0: @@ -2617,13 +2573,13 @@ cssnano-util-same-parent@^4.0.0: resolved "https://registry.yarnpkg.com/cssnano-util-same-parent/-/cssnano-util-same-parent-4.0.1.tgz#574082fb2859d2db433855835d9a8456ea18bbf3" integrity sha512-WcKx5OY+KoSIAxBW6UBBRay1U6vkYheCdjyVNDm85zt5K9mHoGOfsOsqIszfAqrQQFIIKgjh2+FDgIj/zsl21Q== -cssnano@^4.1.10: - version "4.1.10" - resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-4.1.10.tgz#0ac41f0b13d13d465487e111b778d42da631b8b2" - integrity sha512-5wny+F6H4/8RgNlaqab4ktc3e0/blKutmq8yNlBFXA//nSFFAqAngjNVRzUvCgYROULmZZUoosL/KSoZo5aUaQ== +cssnano@^4.1.11: + version "4.1.11" + resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-4.1.11.tgz#c7b5f5b81da269cb1fd982cb960c1200910c9a99" + integrity sha512-6gZm2htn7xIPJOHY824ERgj8cNPgPxyCSnkXc4v7YvNW+TdVfzgngHcEhy/8D11kUWRUMbke+tC+AUcUsnMz2g== dependencies: cosmiconfig "^5.0.0" - cssnano-preset-default "^4.0.7" + cssnano-preset-default "^4.0.8" is-resolvable "^1.0.0" postcss "^7.0.0" @@ -2666,6 +2622,11 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" +data-uri-to-buffer@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz#594b8973938c5bc2c33046535785341abc4f3636" + integrity sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og== + debounce@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.1.0.tgz#6a1a4ee2a9dc4b7c24bb012558dbcdb05b37f408" @@ -2722,7 +2683,7 @@ debug@^3.1.0: dependencies: ms "^2.1.1" -debug@^4.0.1, debug@^4.1.0, debug@^4.1.1: +debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1: version "4.3.1" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== @@ -2793,13 +2754,6 @@ default-resolution@^2.0.0: resolved "https://registry.yarnpkg.com/default-resolution/-/default-resolution-2.0.0.tgz#bcb82baa72ad79b426a76732f1a81ad6df26d684" integrity sha1-vLgrqnKtebQmp2cy8aga1t8m1oQ= -defaults@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d" - integrity sha1-xlYFHpgX2f8I7YgUd/P+QBnz730= - dependencies: - clone "^1.0.2" - defer-to-connect@^1.0.1: version "1.1.3" resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.3.tgz#331ae050c08dcf789f8c83a7b81f0ed94f4ac591" @@ -3037,33 +2991,18 @@ editorconfig@^0.15.2: semver "^5.6.0" sigmund "^1.0.1" -electron-rebuild@2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/electron-rebuild/-/electron-rebuild-2.0.3.tgz#fbcf34d35bf6795a0ded39bfe2aee24526a152c8" - integrity sha512-I8Oeey9afU+trFLd8/qRRiHC083CCoBnmw3q0qQaRFsg0OzMaeJQn7Nl6EYKPpntuQ/3yOqZQ7b3ObNuETN/Ig== - dependencies: - "@malept/cross-spawn-promise" "^1.1.0" - colors "^1.3.3" - debug "^4.1.1" - detect-libc "^1.0.3" - fs-extra "^9.0.1" - node-abi "^2.19.1" - node-gyp "^7.1.0" - ora "^5.1.0" - yargs "^16.0.0" - electron-to-chromium@^1.3.634: version "1.3.642" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.642.tgz#8b884f50296c2ae2a9997f024d0e3e57facc2b94" integrity sha512-cev+jOrz/Zm1i+Yh334Hed6lQVOkkemk2wRozfMF4MtTR7pxf3r3L5Rbd7uX1zMcEqVJ7alJBnJL7+JffkC6FQ== -electron@11.3.0: - version "11.3.0" - resolved "https://registry.yarnpkg.com/electron/-/electron-11.3.0.tgz#87e8528fd23ae53b0eeb3a738f1fe0a3ad27c2db" - integrity sha512-MhdS0gok3wZBTscLBbYrOhLaQybCSAfkupazbK1dMP5c+84eVMxJE/QGohiWQkzs0tVFIJsAHyN19YKPbelNrQ== +electron@12.0.4: + version "12.0.4" + resolved "https://registry.yarnpkg.com/electron/-/electron-12.0.4.tgz#c2ca4710d0e4da7db6d31c4f55777b08bfcb08e5" + integrity sha512-A8Lq3YMZ1CaO1z5z5nsyFxIwkgwXLHUwL2pf9MVUHpq7fv3XUewCMD98EnLL3DdtiyCvw5KMkeT1WGsZh8qFug== dependencies: "@electron/get" "^1.0.1" - "@types/node" "^12.0.12" + "@types/node" "^14.6.2" extract-zip "^1.0.3" elliptic@^6.5.3: @@ -3262,11 +3201,6 @@ es6-weak-map@^2.0.1, es6-weak-map@^2.0.3: es6-iterator "^2.0.3" es6-symbol "^3.1.1" -escalade@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.0.2.tgz#6a580d70edb87880f22b4c91d0d56078df6962c4" - integrity sha512-gPYAU37hYCUhW5euPeR+Y74F7BL+IBsV93j5cvGriSaD1aG6MGsqsV1yamRdrWrb2j3aiZvb0X+UBOWpx3JWtQ== - escalade@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" @@ -3677,7 +3611,7 @@ fast-deep-equal@^3.1.1: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== -fast-glob@^3.0.3, fast-glob@^3.1.1, fast-glob@^3.2.4: +fast-glob@^3.1.1, fast-glob@^3.2.4: version "3.2.4" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.4.tgz#d20aefbf99579383e7f3cc66529158c9b98554d3" integrity sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ== @@ -3689,6 +3623,18 @@ fast-glob@^3.0.3, fast-glob@^3.1.1, fast-glob@^3.2.4: micromatch "^4.0.2" picomatch "^2.2.1" +fast-glob@^3.2.5: + version "3.2.5" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.5.tgz#7939af2a656de79a4f1901903ee8adcaa7cb9661" + integrity sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.0" + merge2 "^1.3.0" + micromatch "^4.0.2" + picomatch "^2.2.1" + fast-json-stable-stringify@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" @@ -3769,6 +3715,11 @@ file-uri-to-path@1.0.0: resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== +file-uri-to-path@2: + version "2.0.0" + resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-2.0.0.tgz#7b415aeba227d575851e0a5b0c640d7656403fba" + integrity sha512-hjPFI8oE/2iQPVe4gbrJ73Pp+Xfub2+WI2LlXDbsaJBwT5wuMh35WNWVYYTpnz895shtwfyutMFLFywpQAFdLg== + filename-regex@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26" @@ -4019,16 +3970,6 @@ fs-extra@^8.1.0: jsonfile "^4.0.0" universalify "^0.1.0" -fs-extra@^9.0.1: - version "9.0.1" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.0.1.tgz#910da0062437ba4c39fedd863f1675ccfefcb9fc" - integrity sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ== - dependencies: - at-least-node "^1.0.0" - graceful-fs "^4.2.0" - jsonfile "^6.0.1" - universalify "^1.0.0" - fs-minipass@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" @@ -4092,6 +4033,14 @@ fstream@^1.0.2: mkdirp ">=0.5 0" rimraf "2" +ftp@^0.3.10: + version "0.3.10" + resolved "https://registry.yarnpkg.com/ftp/-/ftp-0.3.10.tgz#9197d861ad8142f3e63d5a83bfe4c59f7330885d" + integrity sha1-kZfYYa2BQvPmPVqDv+TFn3MwiF0= + dependencies: + readable-stream "1.1.x" + xregexp "2.0.0" + function-bind@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" @@ -4126,7 +4075,7 @@ get-caller-file@^1.0.1: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a" integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w== -get-caller-file@^2.0.1, get-caller-file@^2.0.5: +get-caller-file@^2.0.1: version "2.0.5" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== @@ -4154,6 +4103,18 @@ get-stream@^5.1.0: dependencies: pump "^3.0.0" +get-uri@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/get-uri/-/get-uri-3.0.2.tgz#f0ef1356faabc70e1f9404fa3b66b2ba9bfc725c" + integrity sha512-+5s0SJbGoyiJTZZ2JTpFPLMPSch72KEqGOTvQsBqg0RBWvwhWUSYZFAtz3TPW0GXJuLBJPts1E241iHg+VRfhg== + dependencies: + "@tootallnate/once" "1" + data-uri-to-buffer "3" + debug "4" + file-uri-to-path "2" + fs-extra "^8.1.0" + ftp "^0.3.10" + get-value@^2.0.3, get-value@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" @@ -4345,20 +4306,6 @@ globalthis@^1.0.1: dependencies: define-properties "^1.1.3" -globby@^10.0.1: - version "10.0.2" - resolved "https://registry.yarnpkg.com/globby/-/globby-10.0.2.tgz#277593e745acaa4646c3ab411289ec47a0392543" - integrity sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg== - dependencies: - "@types/glob" "^7.1.1" - array-union "^2.1.0" - dir-glob "^3.0.1" - fast-glob "^3.0.3" - glob "^7.1.3" - ignore "^5.1.1" - merge2 "^1.2.3" - slash "^3.0.0" - globby@^11.0.1: version "11.0.1" resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.1.tgz#9a2bf107a068f3ffeabc49ad702c79ede8cfd357" @@ -4400,7 +4347,7 @@ graceful-fs@4.2.3: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ== -graceful-fs@^4.0.0, graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.3: +graceful-fs@^4.0.0, graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0: version "4.2.4" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== @@ -4837,11 +4784,6 @@ hsla-regex@^1.0.0: resolved "https://registry.yarnpkg.com/hsla-regex/-/hsla-regex-1.0.0.tgz#c1ce7a3168c8c6614033a4b5f7877f3b225f9c38" integrity sha1-wc56MWjIxmFAM6S194d/OyJfnDg= -html-comment-regex@^1.1.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.2.tgz#97d4688aeb5c81886a364faa0cad1dda14d433a7" - integrity sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ== - html-escaper@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.0.tgz#71e87f931de3fe09e56661ab9a29aadec707b491" @@ -4872,6 +4814,15 @@ http-proxy-agent@^2.1.0: agent-base "4" debug "3.1.0" +http-proxy-agent@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a" + integrity sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg== + dependencies: + "@tootallnate/once" "1" + agent-base "6" + debug "4" + http-signature@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" @@ -4959,7 +4910,7 @@ ignore@^4.0.6: resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== -ignore@^5.1.1, ignore@^5.1.4: +ignore@^5.1.4: version "5.1.8" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== @@ -5314,11 +5265,6 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1: dependencies: is-extglob "^2.1.1" -is-interactive@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e" - integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w== - is-negated-glob@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-negated-glob/-/is-negated-glob-1.0.0.tgz#6910bca5da8c95e784b5751b976cf5a10fee36d2" @@ -5419,13 +5365,6 @@ is-stream@^1.0.1, is-stream@^1.1.0: resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= -is-svg@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-svg/-/is-svg-3.0.0.tgz#9321dbd29c212e5ca99c4fa9794c714bcafa2f75" - integrity sha512-gi4iHK53LR2ujhLVVj+37Ykh9GLqYHX6JOVXbLAucaG/Cqw9xwdFOjDM2qeifLs1sF1npXXFvDu0r5HNgCMrzQ== - dependencies: - html-comment-regex "^1.1.0" - is-symbol@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937" @@ -5601,10 +5540,10 @@ jsbn@~0.1.0: resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= -jschardet@2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/jschardet/-/jschardet-2.2.1.tgz#03b0264669a90c7a5c436a68c5a7d4e4cb0c9823" - integrity sha512-Ks2JNuUJoc7PGaZ7bVFtSEvOcr0rBq6Q1J5/7+zKWLT+g+4zziL63O0jg7y2jxhzIa1LVsHUbPXrbaWmz9iwDw== +jschardet@2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/jschardet/-/jschardet-2.3.0.tgz#06e2636e16c8ada36feebbdc08aa34e6a9b3ff75" + integrity sha512-6I6xT7XN/7sBB7q8ObzKbmv5vN+blzLcboDE1BNEsEfmRXJValMxO6OIRT69ylPBRemS3rw6US+CMCar0OBc9g== jsdoctypeparser@^6.1.0: version "6.1.0" @@ -5701,15 +5640,6 @@ jsonfile@^4.0.0: optionalDependencies: graceful-fs "^4.1.6" -jsonfile@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.0.1.tgz#98966cba214378c8c84b82e085907b40bf614179" - integrity sha512-jR2b5v7d2vIOust+w3wtFKZIfpC2pnRmFAhAC/BuweZFQR8qZzxH1OyrQ10HmdVYiXWkYUqPVsz91cG7EL2FBg== - dependencies: - universalify "^1.0.0" - optionalDependencies: - graceful-fs "^4.1.6" - jsonparse@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.2.0.tgz#5c0c5685107160e72fe7489bddea0b44c2bc67bd" @@ -5972,7 +5902,7 @@ lodash@^4.17.13: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b" integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ== -log-symbols@4.0.0, log-symbols@^4.0.0: +log-symbols@4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.0.0.tgz#69b3cc46d20f448eccdb75ea1fa733d9e821c920" integrity sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA== @@ -6205,7 +6135,7 @@ merge-stream@^1.0.0: dependencies: readable-stream "^2.0.1" -merge2@^1.2.3, merge2@^1.3.0: +merge2@^1.3.0: version "1.4.1" resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== @@ -6497,17 +6427,6 @@ multimatch@^2.0.0: arrify "^1.0.0" minimatch "^3.0.0" -multimatch@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/multimatch/-/multimatch-4.0.0.tgz#8c3c0f6e3e8449ada0af3dd29efb491a375191b3" - integrity sha512-lDmx79y1z6i7RNx0ZGCPq1bzJ6ZoDDKbvh7jxr9SJcWLkShMzXrHbYVpTdnhNM5MXpDUxCQ4DgqVttVXlBgiBQ== - dependencies: - "@types/minimatch" "^3.0.3" - array-differ "^3.0.0" - array-union "^2.1.0" - arrify "^2.0.1" - minimatch "^3.0.4" - mute-stdout@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/mute-stdout/-/mute-stdout-1.0.1.tgz#acb0300eb4de23a7ddeec014e3e96044b3472331" @@ -6523,12 +6442,12 @@ mute-stream@0.0.8: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== -nan@^2.12.1: +nan@^2.12.1, nan@^2.13.2: version "2.14.2" resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19" integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ== -nan@^2.13.2, nan@^2.14.0: +nan@^2.14.0: version "2.14.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg== @@ -6560,10 +6479,10 @@ napi-build-utils@^1.0.1: resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.2.tgz#b1fddc0b2c46e380a0b7a76f984dd47c41a13806" integrity sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg== -native-is-elevated@0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/native-is-elevated/-/native-is-elevated-0.4.1.tgz#f6391aafb13441f5b949b39ae0b466b06e7f3986" - integrity sha512-2vBXCXCXYKLDjP0WzrXs/AFjDb2njPR31EbGiZ1mR2fMJg211xClK1Xm19RXve35kvAL4dBKOFGCMIyc2+pPsw== +native-is-elevated@0.4.3: + version "0.4.3" + resolved "https://registry.yarnpkg.com/native-is-elevated/-/native-is-elevated-0.4.3.tgz#f1071c4a821acc71d43f36ff8051d3816d832e1c" + integrity sha512-bHS3sCoh+raqFGIxmL/plER3eBQ+IEBy4RH/4uahhToZneTvqNKQrL0PgOTtnpL55XjBd3dy0pNtZMkCk0J48g== native-keymap@2.2.1: version "2.2.1" @@ -6600,13 +6519,6 @@ nice-try@^1.0.4: resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== -node-abi@^2.19.1: - version "2.19.3" - resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-2.19.3.tgz#252f5dcab12dad1b5503b2d27eddd4733930282d" - integrity sha512-9xZrlyfvKhWme2EXFKQhZRp1yNWT/uI1luYPr3sFl+H4keYY4xR+1jO7mvTTijIsHf1M+QDe9uWuKeEpLInIlg== - dependencies: - semver "^5.4.1" - node-abi@^2.21.0: version "2.21.0" resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-2.21.0.tgz#c2dc9ebad6f4f53d6ea9b531e7b8faad81041d48" @@ -6614,36 +6526,15 @@ node-abi@^2.21.0: dependencies: semver "^5.4.1" -node-addon-api@*, node-addon-api@^3.0.0: +node-addon-api@*, node-addon-api@^3.0.0, node-addon-api@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.1.0.tgz#98b21931557466c6729e51cb77cd39c965f42239" integrity sha512-flmrDNB06LIl5lywUz7YlNGZH/5p0M7W28k8hzd9Lshtdh1wshD2Y+U4h9LD6KObOy1f+fEVdgprPrEymjM5uw== -node-addon-api@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.0.2.tgz#04bc7b83fd845ba785bb6eae25bc857e1ef75681" - integrity sha512-+D4s2HCnxPd5PjjI0STKwncjXTUKKqm74MDMz9OPXavjsGmjkvwgLtA5yoxJUdmpj52+2u+RrXgPipahKczMKg== - node-fetch@^2.6.1: version "2.6.1" - resolved "https://pkgs.dev.azure.com/terrapin-prod/Terrapin/_packaging/open-source-packages-2/npm/registry/node-fetch/-/node-fetch-2.6.1.tgz#71da3def292fc21adc5670825461ea36c316643c" - integrity sha1-cdo97ykvwhrcVnCCVGHqNsMWZDw= - -node-gyp@^7.1.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-7.1.0.tgz#cb8aed7ab772e73ad592ae0c71b0e3741099fe39" - integrity sha512-rjlHQlnl1dqiDZxZYiKqQdrjias7V+81OVR5PTzZioCBtWkNdrKy06M05HLKxy/pcKikKRCabeDRoZaEc6nIjw== - dependencies: - env-paths "^2.2.0" - glob "^7.1.4" - graceful-fs "^4.2.3" - nopt "^4.0.3" - npmlog "^4.1.2" - request "^2.88.2" - rimraf "^2.6.3" - semver "^7.3.2" - tar "^6.0.1" - which "^2.0.2" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" + integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== node-libs-browser@^2.2.1: version "2.2.1" @@ -6674,10 +6565,10 @@ node-libs-browser@^2.2.1: util "^0.11.0" vm-browserify "^1.0.1" -node-pty@0.10.0-beta19: - version "0.10.0-beta19" - resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-0.10.0-beta19.tgz#b7cbfba53f7b2a816efe8c9302dd083cc5874458" - integrity sha512-4UIOGMvpofUbe+ZniBUtY8zc/psMURSzbMonQgIhK7JlMQsUwcbkDIrKzStVLJX0FkeZpUNlsVtK7qqzHvrUZA== +node-pty@0.10.1: + version "0.10.1" + resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-0.10.1.tgz#cd05d03a2710315ec40221232ec04186f6ac2c6d" + integrity sha512-JTdtUS0Im/yRsWJSx7yiW9rtpfmxqxolrtnyKwPLI+6XqTAPW/O2MjS8FYL4I5TsMbH2lVgDb2VMjp+9LoQGNg== dependencies: nan "^2.14.0" @@ -6699,14 +6590,6 @@ noop-logger@^0.1.1: resolved "https://registry.yarnpkg.com/noop-logger/-/noop-logger-0.1.1.tgz#94a2b1633c4f1317553007d8966fd0e841b6a4c2" integrity sha1-lKKxYzxPExdVMAfYlm/Q6EG2pMI= -nopt@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.3.tgz#a375cad9d02fd921278d954c2254d5aa57e15e48" - integrity sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg== - dependencies: - abbrev "1" - osenv "^0.1.4" - nopt@~4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d" @@ -6789,7 +6672,7 @@ npm-run-path@^2.0.0: dependencies: path-key "^2.0.0" -npmlog@^4.0.1, npmlog@^4.1.2: +npmlog@^4.0.1: version "4.1.2" resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== @@ -6799,10 +6682,10 @@ npmlog@^4.0.1, npmlog@^4.1.2: gauge "~2.7.3" set-blocking "~2.0.0" -nsfw@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/nsfw/-/nsfw-2.1.1.tgz#3cc8cc9ebf567c0121364cd42bb26167242b4be0" - integrity sha512-DvkPpsIkw/DogPxI4vQugtrSc0AtuCoxiprLxTLVZrZuEe8H++0DHpV/081q0LOtUc+96yW3FBl0pEKiyMOfJg== +nsfw@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/nsfw/-/nsfw-2.1.2.tgz#4fa841e7f7122b60b2e1f61187d1b57ad3403428" + integrity sha512-zGPdt32aJ5b1laK9rvgXQmXGAagrx3VkcMt0JePtu6wBfzC1o4xLCM3kq7FxZxUnxyxYhODyBYzpt3H16FhaGA== dependencies: node-addon-api "*" @@ -6986,20 +6869,6 @@ optionator@^0.8.2, optionator@^0.8.3: type-check "~0.3.2" word-wrap "~1.2.3" -ora@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/ora/-/ora-5.1.0.tgz#b188cf8cd2d4d9b13fd25383bc3e5cba352c94f8" - integrity sha512-9tXIMPvjZ7hPTbk8DFq1f7Kow/HU/pQYB60JbNq+QnGwcyhWVZaQ4hM9zQDEsPxw/muLpgiHSaumUZxCAmod/w== - dependencies: - chalk "^4.1.0" - cli-cursor "^3.1.0" - cli-spinners "^2.4.0" - is-interactive "^1.0.0" - log-symbols "^4.0.0" - mute-stream "0.0.8" - strip-ansi "^6.0.0" - wcwidth "^1.0.1" - ordered-read-streams@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/ordered-read-streams/-/ordered-read-streams-0.3.0.tgz#7137e69b3298bb342247a1bbee3881c80e2fd78b" @@ -7261,11 +7130,6 @@ path-key@^2.0.0, path-key@^2.0.1: resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= -path-key@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" - integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== - path-parse@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" @@ -7728,12 +7592,11 @@ postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.2: indexes-of "^1.0.1" uniq "^1.0.1" -postcss-svgo@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-4.0.2.tgz#17b997bc711b333bab143aaed3b8d3d6e3d38258" - integrity sha512-C6wyjo3VwFm0QgBy+Fu7gCYOkCmgmClghO+pjcxvrcBKtiKt0uCF+hvbMO1fyv5BMImRK90SMb+dwUnfbGd+jw== +postcss-svgo@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-4.0.3.tgz#343a2cdbac9505d416243d496f724f38894c941e" + integrity sha512-NoRbrcMWTtUghzuKSoIm6XV+sJdvZ7GZSc3wdBN0W19FTtp2ko8NqLsgoh/m9CzNhU3KLPvQmjIwtaNFkaFTvw== dependencies: - is-svg "^3.0.0" postcss "^7.0.0" postcss-value-parser "^3.0.0" svgo "^1.0.0" @@ -8068,6 +7931,16 @@ read-pkg@^3.0.0: string_decoder "~1.1.1" util-deprecate "~1.0.1" +readable-stream@1.1.x: + version "1.1.14" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" + integrity sha1-fPTFTvZI44EwhMY23SB54WbAgdk= + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.1" + isarray "0.0.1" + string_decoder "~0.10.x" + "readable-stream@2 || 3": version "3.1.1" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.1.1.tgz#ed6bbc6c5ba58b090039ff18ce670515795aeb06" @@ -8228,7 +8101,7 @@ replacestream@^4.0.0: object-assign "^4.0.1" readable-stream "^2.0.2" -"request@>= 2.44.0 < 3.0.0", request@^2.85.0, request@^2.88.0, request@^2.88.2: +"request@>= 2.44.0 < 3.0.0", request@^2.85.0, request@^2.88.0: version "2.88.2" resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== @@ -8630,23 +8503,11 @@ shebang-command@^1.2.0: dependencies: shebang-regex "^1.0.0" -shebang-command@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" - integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== - dependencies: - shebang-regex "^3.0.0" - shebang-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= -shebang-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" - integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== - shell-quote@^1.6.1: version "1.7.2" resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.2.tgz#67a7d02c76c9da24f99d20808fcaded0e0e04be2" @@ -8712,10 +8573,10 @@ slice-ansi@^2.1.0: astral-regex "^1.0.0" is-fullwidth-code-point "^2.0.0" -smart-buffer@4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.0.2.tgz#5207858c3815cc69110703c6b94e46c15634395d" - integrity sha512-JDhEpTKzXusOqXZ0BUIdH+CjFdO/CR3tLlf5CN34IypI+xMmXW1uB16OOY8z3cICbJlDAVJzNbwBhNO0wt9OAw== +smart-buffer@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.1.0.tgz#91605c25d91652f4661ea69ccf45f1b331ca21ba" + integrity sha512-iVICrxOzCynf/SNaBQCw34eM9jROU/s5rzIhpOvzhzuYHfJR/DhZfDkXiZSgKXfgv26HT3Yni3AV/DGw0cGnnw== snapdragon-node@^2.0.1: version "2.1.1" @@ -8747,21 +8608,22 @@ snapdragon@^0.8.1: source-map-resolve "^0.5.0" use "^3.1.0" -socks-proxy-agent@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-4.0.2.tgz#3c8991f3145b2799e70e11bd5fbc8b1963116386" - integrity sha512-NT6syHhI9LmuEMSK6Kd2V7gNv5KFZoLE7V5udWmn0de+3Mkj3UMA/AJPLyeNUVmElCurSHtUdM3ETpR3z770Wg== +socks-proxy-agent@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-5.0.0.tgz#7c0f364e7b1cf4a7a437e71253bed72e9004be60" + integrity sha512-lEpa1zsWCChxiynk+lCycKuC502RxDWLKJZoIhnxrWNjLSDGYRFflHA1/228VkRcnv9TIb8w98derGbpKxJRgA== dependencies: - agent-base "~4.2.1" - socks "~2.3.2" + agent-base "6" + debug "4" + socks "^2.3.3" -socks@~2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/socks/-/socks-2.3.2.tgz#ade388e9e6d87fdb11649c15746c578922a5883e" - integrity sha512-pCpjxQgOByDHLlNqlnh/mNSAxIUkyBBuwwhTcV+enZGbDaClPvHdvm6uvOwZfFJkam7cGhBNbb4JxiP8UZkRvQ== +socks@^2.3.3: + version "2.6.1" + resolved "https://registry.yarnpkg.com/socks/-/socks-2.6.1.tgz#989e6534a07cf337deb1b1c94aaa44296520d30e" + integrity sha512-kLQ9N5ucj8uIcxrDwjm0Jsqk06xdpBjGNQtpXy4Q8/QY2k+fY7nZH8CARy+hkbG+SGAovmzzuauCpBlb8FrnBA== dependencies: ip "^1.1.5" - smart-buffer "4.0.2" + smart-buffer "^4.1.0" source-list-map@^2.0.0: version "2.0.1" @@ -8916,16 +8778,16 @@ sshpk@^1.7.0: tweetnacl "~0.14.0" ssri@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.1.tgz#2a3c41b28dd45b62b63676ecb74001265ae9edd8" - integrity sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA== + version "6.0.2" + resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.2.tgz#157939134f20464e7301ddba3e90ffa8f7728ac5" + integrity sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q== dependencies: figgy-pudding "^3.5.1" ssri@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/ssri/-/ssri-8.0.0.tgz#79ca74e21f8ceaeddfcb4b90143c458b8d988808" - integrity sha512-aq/pz989nxVYwn16Tsbj1TqFpD5LLrQxHf5zaHuieFV+R0Bbr4y8qUsOA45hXT/N4/9UNXTarBjnjVmjSOVaAA== + version "8.0.1" + resolved "https://registry.yarnpkg.com/ssri/-/ssri-8.0.1.tgz#638e4e439e2ffbd2cd289776d5ca457c4f51a2af" + integrity sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ== dependencies: minipass "^3.1.1" @@ -9194,10 +9056,10 @@ stylehacks@^4.0.0: postcss "^7.0.0" postcss-selector-parser "^3.0.0" -sudo-prompt@9.1.1: - version "9.1.1" - resolved "https://registry.yarnpkg.com/sudo-prompt/-/sudo-prompt-9.1.1.tgz#73853d729770392caec029e2470db9c221754db0" - integrity sha512-es33J1g2HjMpyAhz8lOR+ICmXXAqTuKbuXuUWLhOLew20oN9oUCgCJx615U/v7aioZg7IX5lIh9x34vwneu4pA== +sudo-prompt@9.2.1: + version "9.2.1" + resolved "https://registry.yarnpkg.com/sudo-prompt/-/sudo-prompt-9.2.1.tgz#77efb84309c9ca489527a4e749f287e6bdd52afd" + integrity sha512-Mu7R0g4ig9TUuGSxJavny5Rv0egCEtpZRNMrZaYS1vxkiIxGiGUwoezU3LazIQ+KE04hTrTfNPgxU5gzi7F5Pw== sumchecker@^3.0.1: version "3.0.1" @@ -9311,7 +9173,7 @@ tar@^2.2.1: fstream "^1.0.2" inherits "2" -tar@^6.0.1, tar@^6.0.2: +tar@^6.0.2: version "6.0.5" resolved "https://registry.yarnpkg.com/tar/-/tar-6.0.5.tgz#bde815086e10b39f1dcd298e89d596e1535e200f" integrity sha512-0b4HOimQHj9nXNEAA7zWwMM91Zhhba3pspja6sQbgTpynOJf+bkjBnfybNYzbpLbnwXnbyB4LOREvlyXLkCHSg== @@ -9578,19 +9440,13 @@ ts-loader@^6.2.1: micromatch "^4.0.0" semver "^6.0.0" -ts-morph@^3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/ts-morph/-/ts-morph-3.1.3.tgz#bbfa1d14481ee23bdd1c030340ccf4a243cfc844" - integrity sha512-CwjgyJTtd3f8vBi7Vr0IOgdOY6Wi/Tq0MhieXOE2B5ns5WWRD7BwMNHtv+ZufKI/S2U/lMrh+Q3bOauE4tsv2g== +ts-morph@^10.0.2: + version "10.0.2" + resolved "https://registry.yarnpkg.com/ts-morph/-/ts-morph-10.0.2.tgz#292418207db467326231b2be92828b5e295e7946" + integrity sha512-TVuIfEqtr9dW25K3Jajqpqx7t/zLRFxKu2rXQZSDjTm4MO4lfmuj1hn8WEryjeDDBFcNOCi+yOmYUYR4HucrAg== dependencies: - "@dsherret/to-absolute-glob" "^2.0.2" - code-block-writer "9.4.1" - fs-extra "^8.1.0" - glob-parent "^5.0.0" - globby "^10.0.1" - is-negated-glob "^1.0.0" - multimatch "^4.0.0" - typescript "^3.0.1" + "@ts-morph/common" "~0.9.0" + code-block-writer "^10.1.1" tsec@0.1.4: version "0.1.4" @@ -9689,15 +9545,10 @@ typescript@^2.6.2: resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.6.2.tgz#3c5b6fd7f6de0914269027f03c0946758f7673a4" integrity sha1-PFtv1/beCRQmkCfwPAlGdY92c6Q= -typescript@^3.0.1: - version "3.9.7" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.7.tgz#98d600a5ebdc38f40cb277522f12dc800e9e25fa" - integrity sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw== - -typescript@^4.3.0-dev.20210305: - version "4.3.0-dev.20210305" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.0-dev.20210305.tgz#5e354b303c435f84a25fa82f45e9c412bfd5bd8f" - integrity sha512-OTALeeen7kl6FU1tcXRk3h+WY1NnE5lwyTGAZUCt9hw6tdaifgLXqEkfw9NHJc0xKV6PnU8GgnYFFVVyHLPSHg== +typescript@^4.3.0-dev.20210426: + version "4.3.0-dev.20210426" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.0-dev.20210426.tgz#00198cb8828f6a04b4e0ae32554a486bf7137a53" + integrity sha512-8YTqlzf3w8O8XwnnRlwRV2rswu7V7WEPUnAnH1BPPMrr06thNByMjIadA5SDW3tUJc1MG8Uj3NgZYocU5fWTVg== typical@^4.0.0: version "4.0.0" @@ -9792,11 +9643,6 @@ universalify@^0.1.0: resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== -universalify@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-1.0.0.tgz#b61a1da173e8435b2fe3c67d29b9adf8594bd16d" - integrity sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug== - unquote@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/unquote/-/unquote-1.1.1.tgz#8fded7324ec6e88a0ff8b905e7c098cdc086d544" @@ -10071,10 +9917,10 @@ vm-browserify@^1.0.1: resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ== -vscode-debugprotocol@1.46.0: - version "1.46.0" - resolved "https://registry.yarnpkg.com/vscode-debugprotocol/-/vscode-debugprotocol-1.46.0.tgz#1f6d0420fb91c158babba2eeb988a4b00d062815" - integrity sha512-V10u1L679DJZfOtQXhKylJPMqNbhazav4mRxPrBE8/Jpznow1b1j1EGDDvJ4prQ623CLAnvpFfVkVQ+CX3xdtg== +vscode-debugprotocol@1.47.0: + version "1.47.0" + resolved "https://registry.yarnpkg.com/vscode-debugprotocol/-/vscode-debugprotocol-1.47.0.tgz#700055bea38633a9530a5a552fb3ea314d76b73f" + integrity sha512-ii7oCz3Wfr/SGtFr5AYop5dJm0dUmpg0hq2lTzTBdaht8nSheYMMjPntxULBR+2TUxXLcCKFZkF2UEJQduYsIQ== vscode-nls-dev@^3.3.1: version "3.3.1" @@ -10099,44 +9945,57 @@ 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.8.2: - version "0.8.2" - resolved "https://registry.yarnpkg.com/vscode-proxy-agent/-/vscode-proxy-agent-0.8.2.tgz#5125e7f1efedd84e0114abe9f38cef3f7b33eb50" - integrity sha512-pRNhgAqrgMB4k1rZTHthCLVH+CtJ3TFlKKhFlt4IMxDRZnMEAbPiAGthvEt/Ku6qS4Vuca8b2PqT+rJlbmnznQ== +vscode-proxy-agent@^0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/vscode-proxy-agent/-/vscode-proxy-agent-0.11.0.tgz#9dc8d2bb9d448f1e33bb1caef97a741289660f2f" + integrity sha512-Y5mHjDGq/OKOvKG0IwCYfj25cvQ2cLEil8ce8n55IZHRAP9RF3e1sKU4ZUNDB8X2NIpKwyltrWpK9tFFE/kc3g== dependencies: - debug "^3.1.0" - http-proxy-agent "^2.1.0" - https-proxy-agent "^2.2.3" - socks-proxy-agent "^4.0.1" + "@tootallnate/once" "^1.1.2" + agent-base "^6.0.2" + debug "^4.3.1" + get-uri "^3.0.2" + http-proxy-agent "^4.0.1" + https-proxy-agent "^5.0.0" + socks-proxy-agent "^5.0.0" + optionalDependencies: + vscode-windows-ca-certs "^0.3.0" vscode-regexpp@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/vscode-regexpp/-/vscode-regexpp-3.1.0.tgz#42d059b6fffe99bd42939c0d013f632f0cad823f" integrity sha512-pqtN65VC1jRLawfluX4Y80MMG0DHJydWhe5ZwMHewZD6sys4LbU6lHwFAHxeuaVE6Y6+xZOtAw+9hvq7/0ejkg== -vscode-ripgrep@^1.11.1, vscode-ripgrep@^1.6.2: - version "1.11.1" - resolved "https://registry.yarnpkg.com/vscode-ripgrep/-/vscode-ripgrep-1.11.1.tgz#9fa3c0a96c2939d5a2389f71218bd1bb6eaa8679" - integrity sha512-oHJfpqeXuTQhOO+szqIObYOddwQ9o+lzd4PQLlTQN+sQ7ex8D1qqFip207O2iJyFc5oWE8Bekf4YHTibdbW66w== +vscode-ripgrep@^1.11.2: + version "1.11.2" + resolved "https://registry.yarnpkg.com/vscode-ripgrep/-/vscode-ripgrep-1.11.2.tgz#a1d9c717a20f625b7e14680cc7db25ffafd132d4" + integrity sha512-qMARNpPh/m6h9NbAQs4unGUnlAP2vrxt3a3nzbscrJcd5X9onoSdAYKG9vCkcxFJtOcQQm44a2Vf369mrrz8Sw== dependencies: https-proxy-agent "^4.0.0" proxy-from-env "^1.1.0" -vscode-sqlite3@4.0.10: - version "4.0.10" - resolved "https://registry.yarnpkg.com/vscode-sqlite3/-/vscode-sqlite3-4.0.10.tgz#cf34cd98e5a49b24d3bb5ff5f2058744931f7cee" - integrity sha512-oYH3Nff3AMfbZpDiOhUh+hVtxClwrXBt9SGHGed+RcKf1Z4/fkLpfaldZeWgn4YBHFjNJjJ8HHRRfVvB8fXj1w== +vscode-ripgrep@^1.11.3: + version "1.11.3" + resolved "https://registry.yarnpkg.com/vscode-ripgrep/-/vscode-ripgrep-1.11.3.tgz#a997f4f4535dfeb9d775f04053c1247454d7a37a" + integrity sha512-fdD+BciXiEO1iWTrV/S3sAthlK/tHRBjHF+aJIZDxUMD/q9wpNq+YPFEiLCrW+8epahfR19241DeVHHgX/I4Ww== + dependencies: + https-proxy-agent "^4.0.0" + proxy-from-env "^1.1.0" + +vscode-sqlite3@4.0.11: + version "4.0.11" + resolved "https://registry.yarnpkg.com/vscode-sqlite3/-/vscode-sqlite3-4.0.11.tgz#8f41ec8f4c03e24a284a74c5fad322ea39141f15" + integrity sha512-45oylZEr8sWJFWRoVE9iXI8RQhk7XDeIpwWYdtOAXFnCfmI74n0CwSqZOLfVsMLgtpU9/JZ1CA6s45tgKktSVQ== dependencies: nan "^2.14.0" -vscode-telemetry-extractor@^1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/vscode-telemetry-extractor/-/vscode-telemetry-extractor-1.6.0.tgz#e9d9c1d24863cce8d3d715f0287de3b31eb90c56" - integrity sha512-zSxvkbyAMa1lTRGIHfGg7gW2e9Sey+2zGYD19uNWCsVEfoXAr2NB6uzb0sNHtbZ2SSqxSePmFXzBAavsudT5fw== +vscode-telemetry-extractor@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/vscode-telemetry-extractor/-/vscode-telemetry-extractor-1.7.0.tgz#f99b1a90a4cad0f75454f2f57615a155e55eb960" + integrity sha512-UC/N/uqPuQIuNnXg52XJnejeId2+Nuq04rj4H1rSZsqj9a56pigs6ogLPdZSi+OVLI21LU9PnJ/ZKrBrLm1roA== dependencies: command-line-args "^5.1.1" - ts-morph "^3.1.3" - vscode-ripgrep "^1.6.2" + ts-morph "^10.0.2" + vscode-ripgrep "^1.11.2" vscode-textmate@5.2.0: version "5.2.0" @@ -10173,13 +10032,6 @@ watchpack@^1.7.4: chokidar "^3.4.1" watchpack-chokidar2 "^2.0.1" -wcwidth@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8" - integrity sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g= - dependencies: - defaults "^1.0.3" - webpack-cli@^3.3.12: version "3.3.12" resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-3.3.12.tgz#94e9ada081453cd0aa609c99e500012fd3ad2d4a" @@ -10264,7 +10116,7 @@ which-module@^2.0.0: resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= -which@2.0.2, which@^2.0.1, which@^2.0.2: +which@2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== @@ -10298,10 +10150,10 @@ windows-mutex@0.3.0: bindings "^1.2.1" nan "^2.14.0" -windows-process-tree@0.2.4: - version "0.2.4" - resolved "https://registry.yarnpkg.com/windows-process-tree/-/windows-process-tree-0.2.4.tgz#747af587b54cc6c996f2be0836cc8a8fd0dc038f" - integrity sha512-9gag9AHm3Iin/4YC1EwoIfZlqW/rG2eV7rJZ4Fy5NnAMGdewmnwsie5Rz+CJo2vSolqzzfw7hPeu3oOdniNejg== +windows-process-tree@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/windows-process-tree/-/windows-process-tree-0.3.0.tgz#cf0d9291b22fba2a7f5a687c8272866e28fbcafd" + integrity sha512-0bKI4gcd5MOsOpn2TdStCSlnjThtH6BdHrocekY9qCgTqgEtdaUs0B5BaqyzF9jXoTSwz38NMdE1F55o4fgv9Q== dependencies: nan "^2.13.2" @@ -10353,15 +10205,6 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" @@ -10446,6 +10289,11 @@ xmldom@0.1.x: resolved "https://registry.yarnpkg.com/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz#67fe075c5c24fef39f9d65f5f7b7fe75171968fc" integrity sha1-Z/4HXFwk/vOfnWX197f+dRcZaPw= +xregexp@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-2.0.0.tgz#52a63e56ca0b84a7f3a5f3d61872f126ad7a5943" + integrity sha1-UqY+VsoLhKfzpfPWGHLxJq16WUM= + "xtend@>=4.0.0 <4.1.0-0", xtend@^4.0.0, xtend@~4.0.0, xtend@~4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" @@ -10458,25 +10306,25 @@ xtend@~2.1.1: dependencies: object-keys "~0.4.0" -xterm-addon-search@0.8.0: - version "0.8.0" - resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.8.0.tgz#e33eab918df7eac7e7baf95dd2b3d14133754881" - integrity sha512-MPJGPVPpHRUw9cLIuqQbrVepmENMOybVUSxIALz5h1ryyQBrVqVujq2hL5aroX5/dZJoHx9lGHQTVLQ07SKgKA== +xterm-addon-search@0.9.0-beta.2: + version "0.9.0-beta.2" + resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.9.0-beta.2.tgz#46de7c7d5f1d0ae546b84552c08182916d83025d" + integrity sha512-Ljg+O8HcGx1z2RjpV+nX070zpSjmefU09SFPBVWAHjGT983y6b5yoqG7AVVZdirsJ0zGiccAZL9Kila1CQN6nQ== -xterm-addon-unicode11@0.3.0-beta.3: - version "0.3.0-beta.3" - resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.3.0-beta.3.tgz#70af2dfb67809258edb62c19e2861f7ce5ccf5cd" - integrity sha512-vaYopnOjn19wCLDCyIWPWLwKR7CvLPxB5YZ3CAxt9qL05o3symxIJJJC0DuCa4GaGKVjNc7EmjRCs5bsJ2O1tw== +xterm-addon-unicode11@0.3.0-beta.5: + version "0.3.0-beta.5" + resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.3.0-beta.5.tgz#7e490799d530d3b301125c7a4e92317c161761c4" + integrity sha512-SgDDL3PoMH1G48JO6T45whKAex4NPxi80UzUVitnrqyd8dFQP+oF6cxqUutULgm9HSGk62qy3mrZvIMGO5VXog== -xterm-addon-webgl@0.10.0: - version "0.10.0" - resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.10.0.tgz#e99366fdc4cbd46b798a5e2fc114ecc19f9fd4b7" - integrity sha512-MJzyWie5yw+PH6p//fXlXzmsULLtpBo992EWEKl2uv5M5Zj9etTwfuutCUK7o98mr6itDl+vS/CYIMP68jCf8w== +xterm-addon-webgl@0.11.0-beta.8: + version "0.11.0-beta.8" + resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.11.0-beta.8.tgz#8cb4925d67c31beb8144275daf46358f42eff9fe" + integrity sha512-udRmQ/jgH8cL8VQOZweytkToIROevVeiA7WY0tIe878Wt2zKY+AYHZV8js3c1W9wHDu5G90BhmzTidJ5UwZK3Q== -xterm@4.12.0-beta.7: - version "4.12.0-beta.7" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.12.0-beta.7.tgz#8ee0a6e180dc57ea63a59682f2ccaf41e2cefd1f" - integrity sha512-qUKJg/aOVy2pOORiwuxd2Qp0MHJyQ/gcK7OMyjMEFLdS5L+KBbUkXtpOO9LWbW+B1DvS4b1k1MzvB4GF+qXXtg== +xterm@4.12.0-beta.26: + version "4.12.0-beta.26" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.12.0-beta.26.tgz#57c75b732808795398a66bc1a3e06d09eaff2ada" + integrity sha512-yZB1kMBXQu2G0G1ch7TUi6f893iTZC+tmfjw/PQNZTmN46b4oX1l7rplc3sFcdrICHtmQ0Q5n1u0d6WUAdq1Kw== y18n@^3.2.1: version "3.2.2" @@ -10488,11 +10336,6 @@ y18n@^4.0.0: resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.1.tgz#8db2b83c31c5d75099bb890b23f3094891e247d4" integrity sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ== -y18n@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.1.tgz#1ad2a7eddfa8bce7caa2e1f6b5da96c39d99d571" - integrity sha512-/jJ831jEs4vGDbYPQp4yGKDYPSCCEQ45uZWJHE1AoYBzqdZi8+LDWas0z4HrmJXmKdpFsTiowSHXdxyFhpmdMg== - yallist@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" @@ -10540,11 +10383,6 @@ yargs-parser@^18.1.2: camelcase "^5.0.0" decamelize "^1.2.0" -yargs-parser@^20.0.0: - version "20.0.0" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.0.0.tgz#c65a1daaa977ad63cebdd52159147b789a4e19a9" - integrity sha512-8eblPHTL7ZWRkyjIZJjnGf+TijiKJSwA24svzLRVvtgoi/RZiKa9fFQTrlx0OKLnyHSdt/enrdadji6WFfESVA== - yargs-unparser@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-2.0.0.tgz#f131f9226911ae5d9ad38c432fe809366c2325eb" @@ -10605,19 +10443,6 @@ yargs@^15.3.0: y18n "^4.0.0" yargs-parser "^18.1.2" -yargs@^16.0.0: - version "16.0.3" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.0.3.tgz#7a919b9e43c90f80d4a142a89795e85399a7e54c" - integrity sha512-6+nLw8xa9uK1BOEOykaiYAJVh6/CjxWXK/q9b5FpRgNslt8s22F2xMBqVIKgCRjNgGvGPBy8Vog7WN7yh4amtA== - dependencies: - cliui "^7.0.0" - escalade "^3.0.2" - get-caller-file "^2.0.5" - require-directory "^2.1.1" - string-width "^4.2.0" - y18n "^5.0.1" - yargs-parser "^20.0.0" - yargs@^7.1.0: version "7.1.1" resolved "https://registry.yarnpkg.com/yargs/-/yargs-7.1.1.tgz#67f0ef52e228d4ee0d6311acede8850f53464df6"